Compare commits
51 Commits
pf/fix-win
...
exv/mutabl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84cecd334b | ||
|
|
bb6ebd3aea | ||
|
|
1aab0585da | ||
|
|
1d1e15d915 | ||
|
|
ff394f7c1b | ||
|
|
161b75b9b7 | ||
|
|
bc12a56920 | ||
|
|
15bb295ec6 | ||
|
|
cea178a40c | ||
|
|
f34bb3d775 | ||
|
|
30112b5b5b | ||
|
|
9aaa1bf413 | ||
|
|
0471c5de2f | ||
|
|
65cca71abf | ||
|
|
e3384feee0 | ||
|
|
3650556de4 | ||
|
|
679b08b5db | ||
|
|
5fd5cd270c | ||
|
|
275ffb409f | ||
|
|
0261af22b0 | ||
|
|
60bdbb3d1f | ||
|
|
84c68f7080 | ||
|
|
d9b3535be9 | ||
|
|
fe3c804c73 | ||
|
|
d76ba8fb19 | ||
|
|
5daa0cfe4b | ||
|
|
a7653cf773 | ||
|
|
1b3a22876c | ||
|
|
663a451031 | ||
|
|
96b26b85cc | ||
|
|
69fe317052 | ||
|
|
9e16263876 | ||
|
|
cb43e53b71 | ||
|
|
45fcea101f | ||
|
|
a150fabace | ||
|
|
95db13a544 | ||
|
|
a8732caa1f | ||
|
|
0d6995babc | ||
|
|
c9a1e446c8 | ||
|
|
39aef4b430 | ||
|
|
cc382fd571 | ||
|
|
55c65fb8a2 | ||
|
|
bcea4ef75d | ||
|
|
0ef7507464 | ||
|
|
39268a6ad0 | ||
|
|
01f7744025 | ||
|
|
6b2804985b | ||
|
|
320ee183c6 | ||
|
|
8145a5dd25 | ||
|
|
ba0793ac18 | ||
|
|
2c154be0b3 |
23
.github/actions/get-gltf-assets/action.yml
vendored
Normal file
23
.github/actions/get-gltf-assets/action.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: 'Get and cache glTF Assets'
|
||||
description: 'Downloads and caches glTF assets by calling the get-gltf-sample-assets.sh script.'
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: ./.github/actions/dep-versions
|
||||
- name: Hash models file
|
||||
id: hash-models
|
||||
shell: bash
|
||||
run: echo "hash=$(cat test/renderdiff/tests/gltf_models.txt | md5sum | sed 's/ -//g')" >> $GITHUB_OUTPUT
|
||||
- name: Cache glTF assets
|
||||
id: cache-gltf
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: gltf
|
||||
key: gltf-assets-${{ env.GITHUB_GLTF_SAMPLE_ASSETS_COMMIT }}-${{ steps.hash-models.outputs.hash }}
|
||||
- name: Download assets via script if cache not found
|
||||
if: steps.cache-gltf.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Cache miss for commit ${{ env.GITHUB_GLTF_SAMPLE_ASSETS_COMMIT }}. Running download script..."
|
||||
xargs bash build/common/get-gltf-sample-assets.sh < test/renderdiff/tests/gltf_models.txt
|
||||
5
.github/actions/get-mesa/action.yml
vendored
5
.github/actions/get-mesa/action.yml
vendored
@@ -1,5 +1,6 @@
|
||||
name: 'Get Mesa'
|
||||
description: 'Caches and installs Mesa'
|
||||
name: 'Get and cache Mesa'
|
||||
description: 'Get and cache Mesa'
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
|
||||
8
.github/actions/get-vulkan-sdk/action.yml
vendored
8
.github/actions/get-vulkan-sdk/action.yml
vendored
@@ -8,8 +8,8 @@ runs:
|
||||
uses: actions/cache@v3
|
||||
id: cache-vulkan-sdk
|
||||
with:
|
||||
path: vulkansdk
|
||||
key: vulkansdk-${{ env.GITHUB_VULKANSDK_VERSION }}-${{ runner.os }}
|
||||
path: ${{ runner.homedir }}/VulkanSDK
|
||||
key: vulkansdk-${{ env.GITHUB_VULKANSDK_VERSION }}-2-${{ runner.os }}
|
||||
- name: Download Vulkan SDK
|
||||
if: steps.cache-vulkan-sdk.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
@@ -21,4 +21,8 @@ runs:
|
||||
run: |
|
||||
source ${{ github.workspace }}/build/common/get-vulkan-sdk.sh
|
||||
unpack_vulkan_installer
|
||||
pushd .
|
||||
cd ~/VulkanSDK/${GITHUB_VULKANSDK_VERSION}
|
||||
sudo ./install_vulkan.py
|
||||
popd
|
||||
shell: bash
|
||||
2
.github/actions/mac-prereq/action.yml
vendored
2
.github/actions/mac-prereq/action.yml
vendored
@@ -19,5 +19,7 @@ runs:
|
||||
- name: Install Mac Prerequisites
|
||||
shell: bash
|
||||
run: |
|
||||
# Install brew prereqs
|
||||
brew install coreutils
|
||||
# Install ninja
|
||||
source ./build/common/get-ninja.sh
|
||||
|
||||
353
.github/workflows/presubmit.yml
vendored
353
.github/workflows/presubmit.yml
vendored
@@ -9,6 +9,36 @@ 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
|
||||
@@ -18,194 +48,157 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- name: Run build script
|
||||
run: |
|
||||
build\windows\build-github.bat continuous
|
||||
build\windows\build-github.bat presubmit
|
||||
shell: cmd
|
||||
|
||||
# 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-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-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-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-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
|
||||
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-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
|
||||
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-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
|
||||
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-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
|
||||
# Note that we need to upload the output even if comparison fails, so we undo `set -ex`
|
||||
end_
|
||||
|
||||
# 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}
|
||||
python3 ${TEST_DIR}/src/compare.py \
|
||||
--src=${GOLDEN_OUTPUT_DIR} \
|
||||
--dest=${RENDER_OUTPUT_DIR} \
|
||||
--out=${DIFF_OUTPUT_DIR} 2>&1 | tee compare_output.txt
|
||||
|
||||
# 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}
|
||||
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
|
||||
|
||||
# # Note that we need to upload the output even if comparison fails, so we undo `set -ex`
|
||||
# end_
|
||||
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*
|
||||
|
||||
# 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
|
||||
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
|
||||
|
||||
@@ -544,7 +544,6 @@ if (FILAMENT_SUPPORTS_VULKAN)
|
||||
endif()
|
||||
|
||||
if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
message(AUTHOR_WARNING "WebGPU is in development stage and broken at this stage. Its support is very limited.")
|
||||
add_definitions(-DFILAMENT_SUPPORTS_WEBGPU)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -8,5 +8,5 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||
|
||||
## Release notes for next branch cut
|
||||
|
||||
- Update CMake minimum version to 3.22.1
|
||||
- material: Add a material parameter to control shadow far attenuation (b/436680157)
|
||||
- Rename `sampler` parameter `unfilterable` to `filterable` [⚠️ **New Material Version**]
|
||||
- materials: new mutable specialization constants feature. See the [materials documentation](https://google.github.io/filament/Materials.html) for details. [⚠️ **New Material Version**]
|
||||
|
||||
12
README.md
12
README.md
@@ -18,7 +18,7 @@ Filament release archives contains host-side tools that are required to generate
|
||||
Make sure you always use tools from the same release as the runtime library. This is particularly
|
||||
important for `matc` (material compiler).
|
||||
|
||||
If you'd rather build Filament yourself, please refer to our [build manual](BUILDING.md).
|
||||
If you'd rather build Filament yourself, please refer to our [build manual](/BUILDING.md).
|
||||
|
||||
### Android
|
||||
|
||||
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.63.1'
|
||||
implementation 'com.google.android.filament:filament-android:1.64.0'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
||||
iOS projects can use CocoaPods to install the latest release:
|
||||
|
||||
```shell
|
||||
pod 'Filament', '~> 1.63.1'
|
||||
pod 'Filament', '~> 1.64.0'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
@@ -222,7 +222,7 @@ MaterialInstance* materialInstance = material->createInstance();
|
||||
```
|
||||
|
||||
To learn more about materials and `matc`, please refer to the
|
||||
[materials documentation](./docs/Materials.md.html).
|
||||
[materials documentation](https://google.github.io/filament/Materials.html).
|
||||
|
||||
To render, simply pass the `View` to the `Renderer`:
|
||||
|
||||
@@ -240,7 +240,7 @@ in the `samples/` directory. These samples are all based on `libs/filamentapp/`
|
||||
code that creates a native window with SDL2 and initializes the Filament engine, renderer and views.
|
||||
|
||||
For more information on how to prepare environment maps for image-based lighting please refer to
|
||||
[BUILDING.md](https://github.com/google/filament/blob/main/BUILDING.md#running-the-native-samples).
|
||||
[BUILDING.md](/BUILDING.md#running-the-native-samples).
|
||||
|
||||
### Android
|
||||
|
||||
@@ -272,7 +272,7 @@ To get started you can use the textures and environment maps found respectively
|
||||
refer to their respective `URL.txt` files to know more about the original authors.
|
||||
|
||||
Environments must be pre-processed using
|
||||
[`cmgen`](https://github.com/google/filament/blob/main/BUILDING.md#running-the-native-samples) or
|
||||
[`cmgen`](/BUILDING.md#running-the-native-samples) or
|
||||
using the `libiblprefilter` library.
|
||||
|
||||
## How to make contributions
|
||||
|
||||
@@ -7,6 +7,11 @@ A new header is inserted each time a *tag* is created.
|
||||
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||
|
||||
## v1.64.1
|
||||
|
||||
- Update CMake minimum version to 3.22.1
|
||||
- material: Add a material parameter to control shadow far attenuation (b/436680157)
|
||||
|
||||
## v1.64.0
|
||||
|
||||
- engine: add a `linearFog` material parameter. [⚠️ **New Material Version**]
|
||||
|
||||
@@ -60,6 +60,14 @@ static void setParameter(JNIEnv* env, jlong nativeMaterialInstance, jstring name
|
||||
env->ReleaseStringUTFChars(name_, name);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void setConstant(JNIEnv* env, jlong nativeMaterialInstance, jstring name_, T v) {
|
||||
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
|
||||
const char *name = env->GetStringUTFChars(name_, 0);
|
||||
instance->setConstant(name, v);
|
||||
env->ReleaseStringUTFChars(name_, name);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_MaterialInstance_nSetParameterBool(JNIEnv *env, jclass,
|
||||
@@ -264,6 +272,13 @@ Java_com_google_android_filament_MaterialInstance_nSetParameterTexture(
|
||||
env->ReleaseStringUTFChars(name_, name);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_MaterialInstance_nSetConstantBool(JNIEnv *env, jclass,
|
||||
jlong nativeMaterialInstance, jstring name_, jboolean x) {
|
||||
setConstant(env, nativeMaterialInstance, name_, bool(x));
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_MaterialInstance_nSetScissor(
|
||||
|
||||
@@ -402,6 +402,16 @@ public class MaterialInstance {
|
||||
nSetParameterFloat4(getNativeObject(), name, color[0], color[1], color[2], color[3]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of a bool constant.
|
||||
*
|
||||
* @param name the name of the material constant
|
||||
* @param x the value of the material constant
|
||||
*/
|
||||
public void setConstant(@NonNull String name, boolean x) {
|
||||
nSetConstantBool(getNativeObject(), name, x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set-up a custom scissor rectangle; by default it is disabled.
|
||||
*
|
||||
@@ -921,6 +931,9 @@ public class MaterialInstance {
|
||||
@NonNull String name, int element, @NonNull @Size(min = 1) float[] v,
|
||||
@IntRange(from = 0) int offset, @IntRange(from = 1) int count);
|
||||
|
||||
private static native void nSetConstantBool(long nativeMaterialInstance,
|
||||
@NonNull String name, boolean x);
|
||||
|
||||
private static native void nSetParameterTexture(long nativeMaterialInstance,
|
||||
@NonNull String name, long nativeTexture, long sampler);
|
||||
|
||||
|
||||
@@ -1949,6 +1949,18 @@ public class View {
|
||||
* Ground Truth-base Ambient Occlusion (GTAO) options
|
||||
*/
|
||||
public float gtaoThicknessHeuristic = 0.004f;
|
||||
/**
|
||||
* Ground Truth-base Ambient Occlusion (GTAO) options
|
||||
*/
|
||||
public boolean gtaoUseVisibilityBitmasks = false;
|
||||
/**
|
||||
* Ground Truth-base Ambient Occlusion (GTAO) options
|
||||
*/
|
||||
public float gtaoConstThickness = 0.5f;
|
||||
/**
|
||||
* Ground Truth-base Ambient Occlusion (GTAO) options
|
||||
*/
|
||||
public boolean gtaoLinearThickness = false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.63.1
|
||||
VERSION_NAME=1.64.0
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
60
build/common/get-gltf-sample-assets.sh
Normal file
60
build/common/get-gltf-sample-assets.sh
Normal file
@@ -0,0 +1,60 @@
|
||||
# Copyright (C) 2025 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
#!/usr/bin/bash
|
||||
set -e
|
||||
|
||||
GLTF_SAMPLE_ASSETS_COMMIT=${GITHUB_GLTF_SAMPLE_ASSETS_COMMIT:-d441dfdb87413ff412c620849a649d61789a470f}
|
||||
COMMIT_HASH="${GLTF_SAMPLE_ASSETS_COMMIT}"
|
||||
REPO_URL="https://github.com/KhronosGroup/glTF-Sample-Assets.git"
|
||||
TARGET_DIR="gltf"
|
||||
|
||||
# The default directories to check out if none are specified
|
||||
DEFAULT_SPARSE_PATHS=(
|
||||
"Models/Box/"
|
||||
"Models/Triangle/"
|
||||
"Models/AnimatedCube/"
|
||||
)
|
||||
|
||||
# Check if command-line arguments are provided
|
||||
if [ "$#" -gt 0 ]; then
|
||||
# If arguments are provided, use them as the paths
|
||||
SPARSE_PATHS=()
|
||||
for model_name in "$@"; do
|
||||
SPARSE_PATHS+=("Models/${model_name}/")
|
||||
done
|
||||
echo "Downloading specified models: $@"
|
||||
else
|
||||
# Otherwise, use the default list
|
||||
SPARSE_PATHS=("${DEFAULT_SPARSE_PATHS[@]}")
|
||||
echo "No models specified, downloading default set."
|
||||
fi
|
||||
|
||||
echo "Removing old directory..."
|
||||
rm -rf "${TARGET_DIR}"
|
||||
|
||||
# Clone the repository using a "treeless" clone, which is highly efficient.
|
||||
# --filter=tree:0: Clones only the repository structure without file content (no historical directory listings), making the initial clone very small.
|
||||
# --no-checkout: Prevents automatically checking out the main branch. We will check out a specific commit later.
|
||||
# --sparse: Initializes the repository for sparse checkout, allowing us to fetch only specific directories.
|
||||
git clone --filter=tree:0 --no-checkout --sparse "${REPO_URL}" "${TARGET_DIR}"
|
||||
|
||||
cd "${TARGET_DIR}"
|
||||
|
||||
git sparse-checkout set "${SPARSE_PATHS[@]}"
|
||||
|
||||
echo "Checking out commit ${COMMIT_HASH}..."
|
||||
git checkout "${COMMIT_HASH}"
|
||||
|
||||
echo "Successfully checked out the specified models into the '${TARGET_DIR}' directory."
|
||||
@@ -137,6 +137,9 @@ if [[ "$OS_NAME" == "Darwin" ]]; then
|
||||
|
||||
# This is necessary to be able to build vk (lavapipe) on macOS. Doesn't seem like a real dependency.
|
||||
sed -I '' "s/error('Vulkan drivers require dri3 for X11 support')//g" meson.build
|
||||
# This is to properly link lib-xcb-present on the mac build (though we won't be drawing to any
|
||||
# real hardware surface).
|
||||
sed -I '' "s/dep_xcb_present = null_dep/dep_xcb_present = dependency('xcb-present')/g" meson.build
|
||||
fi
|
||||
|
||||
# -Dosmesa=true => builds OSMesa, which is an offscreen GL context
|
||||
|
||||
@@ -109,13 +109,8 @@ function install_mac() {
|
||||
unzip -t vulkan_sdk.zip
|
||||
fi
|
||||
echo "recognized zip layout 'vulkan_sdk.zip' ${InstallVulkan}.app/Contents" >&2
|
||||
local sdk_temp=${VULKAN_SDK_DIR}.tmp
|
||||
sudo ${InstallVulkan}.app/Contents/MacOS/${InstallVulkan} --root "$sdk_temp" --accept-licenses --default-answer --confirm-command install
|
||||
du -hs $sdk_temp
|
||||
test -d $sdk_temp/macOS || { echo "unrecognized dmg folder layout: $sdk_temp" ; ls -l $sdk_temp ; }
|
||||
cp -r $sdk_temp/macOS/* $VULKAN_SDK_DIR/
|
||||
${InstallVulkan}.app/Contents/MacOS/${InstallVulkan} --accept-licenses --default-answer --confirm-command install
|
||||
if [[ -d ${InstallVulkan}.app/Contents ]] ; then
|
||||
sudo rm -rf "$sdk_temp"
|
||||
rm -rf ${InstallVulkan}.app
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -5,4 +5,5 @@ GITHUB_MESA_VERSION=24.2.1
|
||||
GITHUB_LLVM_VERSION=16
|
||||
GITHUB_NDK_VERSION=27.0.11718014
|
||||
GITHUB_EMSDK_VERSION=3.1.60
|
||||
GITHUB_VULKANSDK_VERSION=1.4.321.0
|
||||
GITHUB_VULKANSDK_VERSION=1.4.321.0
|
||||
GITHUB_GLTF_SAMPLE_ASSETS_COMMIT=d441dfdb87413ff412c620849a649d61789a470f
|
||||
@@ -135,9 +135,7 @@ 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 . --target sample-resources --config %config% %build_flags% -- /m || exit /b
|
||||
|
||||
:: cmake --build . %INSTALL% --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%
|
||||
|
||||
@@ -221,6 +221,8 @@ export is an html with the output captured in a <code><pre></code> tag).</
|
||||
<li>Replace css styling in the exported output as needed (so they don't interfere with the book's css.</li>
|
||||
<li>Replace resource urls to refer to locations relative to the mdbook structure.</li>
|
||||
</ul>
|
||||
<p>Any <code>markdeep</code> doc can be placed in <code>docs_src/src_markdeep/</code> and they will be parsed to html and included
|
||||
in the book as above.</p>
|
||||
<h3 id="readmes"><a class="header" href="#readmes">READMEs</a></h3>
|
||||
<p>Filament depends on a number of libraries, which reside in the directory <code>libs</code>. These individual
|
||||
libaries often have README.md in their root to describe itself. We collect these descriptions into our
|
||||
@@ -229,7 +231,7 @@ located in <code>tools</code>. Some of tools also have README.md as description.
|
||||
<p>The process for copying and processing these READMEs is outlined in <a href="#introductory-doc">Introductory docs</a>.</p>
|
||||
<h3 id="other-technical-notes"><a class="header" href="#other-technical-notes">Other technical notes</a></h3>
|
||||
<p>These are technical documents that do not fit into a library, tool, or directory of the
|
||||
Filament source tree. We collect them into the <code>docs_src/src/notes</code> directory. No additional
|
||||
Filament source tree. We collect them into the <code>docs_src/src_mdbook/src/notes</code> directory. No additional
|
||||
processing is needed for these documents.</p>
|
||||
<h3 id="raw-source-files"><a class="header" href="#raw-source-files">Raw source files</a></h3>
|
||||
<p>These are files that are not part of the <code>mdbook</code> generation, but should be included output in <code>/docs</code>
|
||||
@@ -242,7 +244,7 @@ add a link in <code>SUMMARY.md</code>, and perform the steps outlined in
|
||||
<a href="#how-to-create">how-to create section</a>.</p>
|
||||
<p>For example, if you are adding a general technical note, then you would</p>
|
||||
<ul>
|
||||
<li>Place the document (file with extension <code>.md</code>) in <code>docs_src/src/notes</code></li>
|
||||
<li>Place the document (file with extension <code>.md</code>) in <code>docs_src/src_mdbook/src/notes</code></li>
|
||||
<li>Add a link in <a href="https://github.com/google/filament/blob/main/docs_src/src_mdbook/src/SUMMARY.md"><code>docs_src/src_mdbook/src/SUMMARY.md</code></a></li>
|
||||
<li>Run the commands in the <a href="#how-to-generate">Generate</a> section</li>
|
||||
</ul>
|
||||
|
||||
@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.63.1'
|
||||
implementation 'com.google.android.filament:filament-android:1.64.0'
|
||||
}
|
||||
</code></pre>
|
||||
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
|
||||
@@ -196,18 +196,18 @@ dependencies {
|
||||
</div>
|
||||
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
|
||||
<p>iOS projects can use CocoaPods to install the latest release:</p>
|
||||
<pre><code class="language-shell">pod 'Filament', '~> 1.63.1'
|
||||
<pre><code class="language-shell">pod 'Filament', '~> 1.64.0'
|
||||
</code></pre>
|
||||
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
|
||||
<ul>
|
||||
<li><a href="https://google.github.io/filament/Filament.html">Filament</a>, an in-depth explanation of
|
||||
<li><a href="../main/filament.html">Filament</a>, an in-depth explanation of
|
||||
real-time physically based rendering, the graphics capabilities and implementation of Filament.
|
||||
This document explains the math and reasoning behind most of our decisions. This document is a
|
||||
good introduction to PBR for graphics programmers.</li>
|
||||
<li><a href="https://google.github.io/filament/Materials.html">Materials</a>, the full reference
|
||||
<li><a href="../main/materials.html">Materials</a>, the full reference
|
||||
documentation for our material system. This document explains our different material models, how
|
||||
to use the material compiler <code>matc</code> and how to write custom materials.</li>
|
||||
<li><a href="https://google.github.io/filament/notes/material_properties.html">Material Properties</a>, a reference
|
||||
<li><a href="../notes/material_properties.html">Material Properties</a>, a reference
|
||||
sheet for the standard material model.</li>
|
||||
</ul>
|
||||
<h2 id="examples"><a class="header" href="#examples">Examples</a></h2>
|
||||
@@ -395,7 +395,7 @@ by <code>matc</code>:</p>
|
||||
MaterialInstance* materialInstance = material->createInstance();
|
||||
</code></pre>
|
||||
<p>To learn more about materials and <code>matc</code>, please refer to the
|
||||
<a href="./docs/Materials.html">materials documentation</a>.</p>
|
||||
<a href="../main/materials.html">materials documentation</a>.</p>
|
||||
<p>To render, simply pass the <code>View</code> to the <code>Renderer</code>:</p>
|
||||
<pre><code class="language-c++">// beginFrame() returns false if we need to skip a frame
|
||||
if (renderer->beginFrame(swapChain)) {
|
||||
@@ -408,7 +408,7 @@ if (renderer->beginFrame(swapChain)) {
|
||||
in the <code>samples/</code> directory. These samples are all based on <code>libs/filamentapp/</code> which contains the
|
||||
code that creates a native window with SDL2 and initializes the Filament engine, renderer and views.</p>
|
||||
<p>For more information on how to prepare environment maps for image-based lighting please refer to
|
||||
<a href="https://github.com/google/filament/blob/main/BUILDING.md#running-the-native-samples">BUILDING.md</a>.</p>
|
||||
<a href="building.html#running-the-native-samples">BUILDING.md</a>.</p>
|
||||
<h3 id="android-1"><a class="header" href="#android-1">Android</a></h3>
|
||||
<p>See <code>android/samples</code> for examples of how to use Filament on Android.</p>
|
||||
<p>You must always first initialize Filament by calling <code>Filament.init()</code>.</p>
|
||||
@@ -430,7 +430,7 @@ OpenGL ES.</p>
|
||||
<code>third_party/textures</code> and <code>third_party/environments</code>. These assets are under CC0 license. Please
|
||||
refer to their respective <code>URL.txt</code> files to know more about the original authors.</p>
|
||||
<p>Environments must be pre-processed using
|
||||
<a href="https://github.com/google/filament/blob/main/BUILDING.md#running-the-native-samples"><code>cmgen</code></a> or
|
||||
<a href="building.html#running-the-native-samples"><code>cmgen</code></a> or
|
||||
using the <code>libiblprefilter</code> library.</p>
|
||||
<h2 id="how-to-make-contributions"><a class="header" href="#how-to-make-contributions">How to make contributions</a></h2>
|
||||
<p>Please read and follow the steps in <a href="contributing.html">CONTRIBUTING.md</a>. Make sure you are
|
||||
@@ -513,7 +513,7 @@ and tools.</p>
|
||||
<li><code>web</code>: JavaScript bindings, documentation, and samples</li>
|
||||
</ul>
|
||||
<h2 id="license"><a class="header" href="#license">License</a></h2>
|
||||
<p>Please see <a href="/LICENSE">LICENSE</a>.</p>
|
||||
<p>Please see <a href="https://github.com/google/filament/blob/main/LICENSE">LICENSE</a>.</p>
|
||||
<h2 id="disclaimer"><a class="header" href="#disclaimer">Disclaimer</a></h2>
|
||||
<p>This is not an officially supported Google product.</p>
|
||||
|
||||
|
||||
573
docs/index.html
573
docs/index.html
@@ -1,564 +1,11 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="light sidebar-visible" dir="ltr">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>Introduction - Filament</title>
|
||||
|
||||
|
||||
<!-- Custom HTML head -->
|
||||
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
<link rel="shortcut icon" href="favicon.png">
|
||||
<link rel="stylesheet" href="css/variables.css">
|
||||
<link rel="stylesheet" href="css/general.css">
|
||||
<link rel="stylesheet" href="css/chrome.css">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
|
||||
<link rel="stylesheet" href="fonts/fonts.css">
|
||||
|
||||
<!-- Highlight.js Stylesheets -->
|
||||
<link rel="stylesheet" href="highlight.css">
|
||||
<link rel="stylesheet" href="tomorrow-night.css">
|
||||
<link rel="stylesheet" href="ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme stylesheets -->
|
||||
|
||||
<!-- MathJax -->
|
||||
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
||||
|
||||
<!-- Provide site root to javascript -->
|
||||
<script>
|
||||
var path_to_root = "";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
|
||||
</script>
|
||||
<!-- Start loading toc.js asap -->
|
||||
<script src="toc.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="body-container">
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script>
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
|
||||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||||
}
|
||||
|
||||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||||
}
|
||||
} catch (e) { }
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script>
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
const html = document.documentElement;
|
||||
html.classList.remove('light')
|
||||
html.classList.add(theme);
|
||||
html.classList.add("js");
|
||||
</script>
|
||||
|
||||
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script>
|
||||
var sidebar = null;
|
||||
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||||
sidebar = sidebar || 'visible';
|
||||
} else {
|
||||
sidebar = 'hidden';
|
||||
}
|
||||
sidebar_toggle.checked = sidebar === 'visible';
|
||||
html.classList.remove('sidebar-visible');
|
||||
html.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<div style="display:flex;align-items:center;justify-content:center">
|
||||
<img class="flogo" src="images/filament_logo_small.png"></img>
|
||||
</div>
|
||||
<!-- populated by js -->
|
||||
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
|
||||
<noscript>
|
||||
<iframe class="sidebar-iframe-outer" src="toc.html"></iframe>
|
||||
</noscript>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
|
||||
<div class="sidebar-resize-indicator"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
<div id="menu-bar-hover-placeholder"></div>
|
||||
<div id="menu-bar" class="menu-bar sticky">
|
||||
<div class="left-buttons">
|
||||
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</label>
|
||||
<!-- Filament: disable themes because the markdeep part does not look good for dark themes -->
|
||||
<!--
|
||||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
</ul>
|
||||
-->
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h1 class="menu-title">Filament</h1>
|
||||
|
||||
<div class="right-buttons">
|
||||
<a href="https://github.com/google/filament" title="Git repository" aria-label="Git repository">
|
||||
<i id="git-repository-button" class="fa fa-github"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||||
<div id="searchresults-header" class="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script>
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1 id="filament"><a class="header" href="#filament">Filament</a></h1>
|
||||
<p><a href="https://github.com/google/filament/actions?query=workflow%3AAndroid"><img src="https://github.com/google/filament/workflows/Android/badge.svg" alt="Android Build Status" /></a>
|
||||
<a href="https://github.com/google/filament/actions?query=workflow%3AiOS"><img src="https://github.com/google/filament/workflows/iOS/badge.svg" alt="iOS Build Status" /></a>
|
||||
<a href="https://github.com/google/filament/actions?query=workflow%3ALinux"><img src="https://github.com/google/filament/workflows/Linux/badge.svg" alt="Linux Build Status" /></a>
|
||||
<a href="https://github.com/google/filament/actions?query=workflow%3AmacOS"><img src="https://github.com/google/filament/workflows/macOS/badge.svg" alt="macOS Build Status" /></a>
|
||||
<a href="https://github.com/google/filament/actions?query=workflow%3AWindows"><img src="https://github.com/google/filament/workflows/Windows/badge.svg" alt="Windows Build Status" /></a>
|
||||
<a href="https://github.com/google/filament/actions?query=workflow%3AWeb"><img src="https://github.com/google/filament/workflows/Web/badge.svg" alt="Web Build Status" /></a></p>
|
||||
<p>Filament is a real-time physically based rendering engine for Android, iOS, Linux, macOS, Windows,
|
||||
and WebGL. It is designed to be as small as possible and as efficient as possible on Android.</p>
|
||||
<h2 id="download"><a class="header" href="#download">Download</a></h2>
|
||||
<p><a href="https://github.com/google/filament/releases">Download Filament releases</a> to access stable builds.
|
||||
Filament release archives contains host-side tools that are required to generate assets.</p>
|
||||
<p>Make sure you always use tools from the same release as the runtime library. This is particularly
|
||||
important for <code>matc</code> (material compiler).</p>
|
||||
<p>If you'd rather build Filament yourself, please refer to our <a href="building.html">build manual</a>.</p>
|
||||
<h3 id="android"><a class="header" href="#android">Android</a></h3>
|
||||
<p>Android projects can simply declare Filament libraries as Maven dependencies:</p>
|
||||
<pre><code class="language-gradle">repositories {
|
||||
// ...
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.63.1'
|
||||
}
|
||||
</code></pre>
|
||||
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
|
||||
<div class="table-wrapper"><table><thead><tr><th>Artifact</th><th>Description</th></tr></thead><tbody>
|
||||
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android/badge.svg?subject=filament-android" alt="filament-android" /></a></td><td>The Filament rendering engine itself.</td></tr>
|
||||
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android-debug"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android-debug/badge.svg?subject=filament-android-debug" alt="filament-android-debug" /></a></td><td>Debug version of <code>filament-android</code>.</td></tr>
|
||||
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/gltfio-android"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/gltfio-android/badge.svg?subject=gltfio-android" alt="gltfio-android" /></a></td><td>A glTF 2.0 loader for Filament, depends on <code>filament-android</code>.</td></tr>
|
||||
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-utils-android"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-utils-android/badge.svg?subject=filament-utils-android" alt="filament-utils-android" /></a></td><td>KTX loading, Kotlin math, and camera utilities, depends on <code>gltfio-android</code>.</td></tr>
|
||||
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android/badge.svg?subject=filamat-android" alt="filamat-android" /></a></td><td>A runtime material builder/compiler. This library is large but contains a full shader compiler/validator/optimizer and supports both OpenGL and Vulkan.</td></tr>
|
||||
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android-lite"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android-lite/badge.svg?subject=filamat-android-lite" alt="filamat-android-lite" /></a></td><td>A much smaller alternative to <code>filamat-android</code> that can only generate OpenGL shaders. It does not provide validation or optimizations.</td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
|
||||
<p>iOS projects can use CocoaPods to install the latest release:</p>
|
||||
<pre><code class="language-shell">pod 'Filament', '~> 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 & 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->createSwapChain(nativeWindow);
|
||||
Renderer* renderer = engine->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->createCamera(EntityManager::get().create());
|
||||
View* view = engine->createView();
|
||||
Scene* scene = engine->createScene();
|
||||
|
||||
view->setCamera(camera);
|
||||
view->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->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->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->beginFrame(swapChain)) {
|
||||
// for each View
|
||||
renderer->render(view);
|
||||
renderer->endFrame();
|
||||
}
|
||||
</code></pre>
|
||||
<p>For complete examples of Linux, macOS and Windows Filament applications, look at the source files
|
||||
in the <code>samples/</code> directory. These samples are all based on <code>libs/filamentapp/</code> which contains the
|
||||
code that creates a native window with SDL2 and initializes the Filament engine, renderer and views.</p>
|
||||
<p>For more information on how to prepare environment maps for image-based lighting please refer to
|
||||
<a href="https://github.com/google/filament/blob/main/BUILDING.md#running-the-native-samples">BUILDING.md</a>.</p>
|
||||
<h3 id="android-1"><a class="header" href="#android-1">Android</a></h3>
|
||||
<p>See <code>android/samples</code> for examples of how to use Filament on Android.</p>
|
||||
<p>You must always first initialize Filament by calling <code>Filament.init()</code>.</p>
|
||||
<p>Rendering with Filament on Android is similar to rendering from native code (the APIs are largely
|
||||
the same across languages). You can render into a <code>Surface</code> by passing a <code>Surface</code> to the
|
||||
<code>createSwapChain</code> method. This allows you to render to a <code>SurfaceTexture</code>, a <code>TextureView</code> or
|
||||
a <code>SurfaceView</code>. To make things easier we provide an Android specific API called <code>UiHelper</code> in the
|
||||
package <code>com.google.android.filament.android</code>. All you need to do is set a render callback on the
|
||||
helper and attach your <code>SurfaceView</code> or <code>TextureView</code> to it. You are still responsible for
|
||||
creating the swap chain in the <code>onNativeWindowChanged()</code> callback.</p>
|
||||
<h3 id="ios-1"><a class="header" href="#ios-1">iOS</a></h3>
|
||||
<p>Filament is supported on iOS 11.0 and above. See <code>ios/samples</code> for examples of using Filament on
|
||||
iOS.</p>
|
||||
<p>Filament on iOS is largely the same as native rendering with C++. A <code>CAEAGLLayer</code> or <code>CAMetalLayer</code>
|
||||
is passed to the <code>createSwapChain</code> method. Filament for iOS supports both Metal (preferred) and
|
||||
OpenGL ES.</p>
|
||||
<h2 id="assets"><a class="header" href="#assets">Assets</a></h2>
|
||||
<p>To get started you can use the textures and environment maps found respectively in
|
||||
<code>third_party/textures</code> and <code>third_party/environments</code>. These assets are under CC0 license. Please
|
||||
refer to their respective <code>URL.txt</code> files to know more about the original authors.</p>
|
||||
<p>Environments must be pre-processed using
|
||||
<a href="https://github.com/google/filament/blob/main/BUILDING.md#running-the-native-samples"><code>cmgen</code></a> or
|
||||
using the <code>libiblprefilter</code> library.</p>
|
||||
<h2 id="how-to-make-contributions"><a class="header" href="#how-to-make-contributions">How to make contributions</a></h2>
|
||||
<p>Please read and follow the steps in <a href="contributing.html">CONTRIBUTING.md</a>. Make sure you are
|
||||
familiar with the <a href="code_style.html">code style</a>.</p>
|
||||
<h2 id="directory-structure"><a class="header" href="#directory-structure">Directory structure</a></h2>
|
||||
<p>This repository not only contains the core Filament engine, but also its supporting libraries
|
||||
and tools.</p>
|
||||
<ul>
|
||||
<li><code>android</code>: Android libraries and projects
|
||||
<ul>
|
||||
<li><code>filamat-android</code>: Filament material generation library (AAR) for Android</li>
|
||||
<li><code>filament-android</code>: Filament library (AAR) for Android</li>
|
||||
<li><code>filament-utils-android</code>: Extra utilities (KTX loader, math types, etc.)</li>
|
||||
<li><code>gltfio-android</code>: Filament glTF loading library (AAR) for Android</li>
|
||||
<li><code>samples</code>: Android-specific Filament samples</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>art</code>: Source for various artworks (logos, PDF manuals, etc.)</li>
|
||||
<li><code>assets</code>: 3D assets to use with sample applications</li>
|
||||
<li><code>build</code>: CMake build scripts</li>
|
||||
<li><code>docs</code>: Documentation
|
||||
<ul>
|
||||
<li><code>math</code>: Mathematica notebooks used to explore BRDFs, equations, etc.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>filament</code>: Filament rendering engine (minimal dependencies)
|
||||
<ul>
|
||||
<li><code>backend</code>: Rendering backends/drivers (Vulkan, Metal, OpenGL/ES)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>ide</code>: Configuration files for IDEs (CLion, etc.)</li>
|
||||
<li><code>ios</code>: Sample projects for iOS</li>
|
||||
<li><code>libs</code>: Libraries
|
||||
<ul>
|
||||
<li><code>bluegl</code>: OpenGL bindings for macOS, Linux and Windows</li>
|
||||
<li><code>bluevk</code>: Vulkan bindings for macOS, Linux, Windows and Android</li>
|
||||
<li><code>camutils</code>: Camera manipulation utilities</li>
|
||||
<li><code>filabridge</code>: Library shared by the Filament engine and host tools</li>
|
||||
<li><code>filaflat</code>: Serialization/deserialization library used for materials</li>
|
||||
<li><code>filagui</code>: Helper library for <a href="https://github.com/ocornut/imgui">Dear ImGui</a></li>
|
||||
<li><code>filamat</code>: Material generation library</li>
|
||||
<li><code>filamentapp</code>: SDL2 skeleton to build sample apps</li>
|
||||
<li><code>filameshio</code>: Tiny filamesh parsing library (see also <code>tools/filamesh</code>)</li>
|
||||
<li><code>geometry</code>: Mesh-related utilities</li>
|
||||
<li><code>gltfio</code>: Loader for glTF 2.0</li>
|
||||
<li><code>ibl</code>: IBL generation tools</li>
|
||||
<li><code>image</code>: Image filtering and simple transforms</li>
|
||||
<li><code>imageio</code>: Image file reading / writing, only intended for internal use</li>
|
||||
<li><code>matdbg</code>: DebugServer for inspecting shaders at run-time (debug builds only)</li>
|
||||
<li><code>math</code>: Math library</li>
|
||||
<li><code>mathio</code>: Math types support for output streams</li>
|
||||
<li><code>utils</code>: Utility library (threads, memory, data structures, etc.)</li>
|
||||
<li><code>viewer</code>: glTF viewer library (requires gltfio)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>samples</code>: Sample desktop applications</li>
|
||||
<li><code>shaders</code>: Shaders used by <code>filamat</code> and <code>matc</code></li>
|
||||
<li><code>third_party</code>: External libraries and assets
|
||||
<ul>
|
||||
<li><code>environments</code>: Environment maps under CC0 license that can be used with <code>cmgen</code></li>
|
||||
<li><code>models</code>: Models under permissive licenses</li>
|
||||
<li><code>textures</code>: Textures under CC0 license</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>tools</code>: Host tools
|
||||
<ul>
|
||||
<li><code>cmgen</code>: Image-based lighting asset generator</li>
|
||||
<li><code>filamesh</code>: Mesh converter</li>
|
||||
<li><code>glslminifier</code>: Minifies GLSL source code</li>
|
||||
<li><code>matc</code>: Material compiler</li>
|
||||
<li><code>filament-matp</code>: Material parser</li>
|
||||
<li><code>matinfo</code> Displays information about materials compiled with <code>matc</code></li>
|
||||
<li><code>mipgen</code> Generates a series of miplevels from a source image</li>
|
||||
<li><code>normal-blending</code>: Tool to blend normal maps</li>
|
||||
<li><code>resgen</code> Aggregates binary blobs into embeddable resources</li>
|
||||
<li><code>roughness-prefilter</code>: Pre-filters a roughness map from a normal map to reduce aliasing</li>
|
||||
<li><code>specular-color</code>: Computes the specular color of conductors based on spectral data</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>web</code>: JavaScript bindings, documentation, and samples</li>
|
||||
</ul>
|
||||
<h2 id="license"><a class="header" href="#license">License</a></h2>
|
||||
<p>Please see <a href="/LICENSE">LICENSE</a>.</p>
|
||||
<h2 id="disclaimer"><a class="header" href="#disclaimer">Disclaimer</a></h2>
|
||||
<p>This is not an officially supported Google product.</p>
|
||||
|
||||
</main>
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
|
||||
<a rel="next prefetch" href="dup/building.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
|
||||
<a rel="next prefetch" href="dup/building.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
window.playground_copyable = true;
|
||||
</script>
|
||||
|
||||
|
||||
<script src="elasticlunr.min.js"></script>
|
||||
<script src="mark.min.js"></script>
|
||||
<script src="searcher.js"></script>
|
||||
|
||||
<script src="clipboard.min.js"></script>
|
||||
<script src="highlight.js"></script>
|
||||
<script src="book.js"></script>
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Redirecting...</title>
|
||||
<meta http-equiv="refresh" content="0; url=./dup/intro.html">
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -160,8 +160,8 @@
|
||||
<main>
|
||||
<h1 id="core-concepts"><a class="header" href="#core-concepts">Core Concepts</a></h1>
|
||||
<ul>
|
||||
<li><a href="main/filament.html">Filament</a> - High-level designs; Filament's PBR/math assumptions; implementation details.</li>
|
||||
<li><a href="main/materials.html">Materials</a> - A guide to Filament's material definition.</li>
|
||||
<li><a href="filament.html">Filament</a> - High-level designs; Filament's PBR/math assumptions; implementation details.</li>
|
||||
<li><a href="materials.html">Materials</a> - A guide to Filament's material definition.</li>
|
||||
</ul>
|
||||
|
||||
</main>
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -65,6 +65,9 @@ We describe step 1 in detail for the sake of record:
|
||||
- Replace css styling in the exported output as needed (so they don't interfere with the book's css.
|
||||
- Replace resource urls to refer to locations relative to the mdbook structure.
|
||||
|
||||
Any `markdeep` doc can be placed in `docs_src/src_markdeep/` and they will be parsed to html and included
|
||||
in the book as above.
|
||||
|
||||
### READMEs
|
||||
Filament depends on a number of libraries, which reside in the directory `libs`. These individual
|
||||
libaries often have README.md in their root to describe itself. We collect these descriptions into our
|
||||
@@ -75,7 +78,7 @@ The process for copying and processing these READMEs is outlined in [Introductor
|
||||
|
||||
### Other technical notes
|
||||
These are technical documents that do not fit into a library, tool, or directory of the
|
||||
Filament source tree. We collect them into the `docs_src/src/notes` directory. No additional
|
||||
Filament source tree. We collect them into the `docs_src/src_mdbook/src/notes` directory. No additional
|
||||
processing is needed for these documents.
|
||||
|
||||
### Raw source files
|
||||
@@ -90,7 +93,7 @@ add a link in `SUMMARY.md`, and perform the steps outlined in
|
||||
[how-to create section](#how-to-create).
|
||||
|
||||
For example, if you are adding a general technical note, then you would
|
||||
- Place the document (file with extension `.md`) in `docs_src/src/notes`
|
||||
- Place the document (file with extension `.md`) in `docs_src/src_mdbook/src/notes`
|
||||
- Add a link in [`docs_src/src_mdbook/src/SUMMARY.md`]
|
||||
- Run the commands in the [Generate](#how-to-generate) section
|
||||
|
||||
|
||||
@@ -2,9 +2,14 @@
|
||||
"README.md": {
|
||||
"dest": "dup/intro.md",
|
||||
"link_transforms": {
|
||||
"BUILDING.md": "building.md",
|
||||
"/BUILDING.md#running-the-native-samples": "building.md#running-the-native-samples",
|
||||
"/BUILDING.md": "building.md",
|
||||
"/CONTRIBUTING.md": "contributing.md",
|
||||
"/CODE_STYLE.md": "code_style.md",
|
||||
"/LICENSE": "https://github.com/google/filament/blob/main/LICENSE",
|
||||
"https://google.github.io/filament/Filament.html": "../main/filament.md",
|
||||
"https://google.github.io/filament/Materials.html": "../main/materials.md",
|
||||
"https://google.github.io/filament/notes/material_properties.html": "../notes/material_properties.md",
|
||||
"docs/images/samples": "../images/samples"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -212,7 +212,7 @@ when using textures.
|
||||
This property can dramatically change the appearance of a surface. Non-metallic surfaces have
|
||||
chromatic diffuse reflection and achromatic specular reflection (reflected light does not change
|
||||
color). Metallic surfaces do not have any diffuse reflection and chromatic specular reflection
|
||||
(reflected light takes on the color of the surfaced as defined by `baseColor`).
|
||||
(reflected light takes on the color of the surfaced as defined by `baseColor`).
|
||||
|
||||
The effect of `metallic` is shown in figure [metallicProperty] (click on the image to see a
|
||||
larger version).
|
||||
@@ -248,7 +248,7 @@ The effect of `roughness` on metallic surfaces is shown in figure [roughnessCond
|
||||
When refraction through an object is enabled (using a `refractonType` of `thin` or `solid`), the
|
||||
`roughness` property will also affect the refractions, as shown in figure
|
||||
[roughnessRefractionProperty] (click on the image to see a larger version).
|
||||
|
||||
|
||||
![Figure [roughnessRefractionProperty]: Refractive sphere with `roughness` varying from 0.0
|
||||
(left) to 1.0 (right)](images/materials/refraction_roughness.png)
|
||||
|
||||
@@ -307,7 +307,7 @@ The sheen color controls the color appearance and strength of an optional sheen
|
||||
base layer described by the properties above. The sheen layer always sits below the clear coat layer
|
||||
if such a layer is present.
|
||||
|
||||
The sheen layer can be used to represent cloth and fabric materials. Please refer to
|
||||
The sheen layer can be used to represent cloth and fabric materials. Please refer to
|
||||
section [Cloth model] for more information about cloth and fabric materials.
|
||||
|
||||
The effect of `sheenColor` is shown in figure [materialSheenColor]
|
||||
@@ -520,14 +520,14 @@ light to bend further away from the initial path.
|
||||
|
||||
Table [commonMatIOR] describes acceptable refractive indices for various types of materials.
|
||||
|
||||
Material | IOR
|
||||
Material | IOR
|
||||
--------------------------:|:-----------------
|
||||
Air | 1.0
|
||||
Water | 1.33
|
||||
Common liquids | 1.33 to 1.5
|
||||
Common gemstones | 1.58 to 2.33
|
||||
Plastics, glass | 1.5 to 1.58
|
||||
Other dielectric materials | 1.33 to 1.58
|
||||
Water | 1.33
|
||||
Common liquids | 1.33 to 1.5
|
||||
Common gemstones | 1.58 to 2.33
|
||||
Plastics, glass | 1.5 to 1.58
|
||||
Other dielectric materials | 1.33 to 1.58
|
||||
[Table [commonMatIOR]: Index of refraction of common materials]
|
||||
|
||||
The appearance of a refractive material will greatly depend on the `refractionType` and
|
||||
@@ -1096,30 +1096,37 @@ Value
|
||||
`bool` or `number`, depending on the `type` of the constant. The type must be one of the types
|
||||
described in table [materialConstantsTypes].
|
||||
|
||||
Type | Description | Default
|
||||
:----------------------|:-----------------------------------------|:------------------
|
||||
int | A signed, 32 bit GLSL int | 0
|
||||
float | A single-precision GLSL float | 0.0
|
||||
bool | A GLSL bool | false
|
||||
Constants may also be specified as mutable by setting the `mutable` property to `true`. Only
|
||||
`bool` spec constants may be specified as mutable.
|
||||
|
||||
| Type | Description | Default | May be mutable? |
|
||||
|:------|:------------------------------|:--------|-----------------|
|
||||
| int | A signed, 32 bit GLSL int | | no |
|
||||
| float | A single-precision GLSL float | 0.0 | no |
|
||||
| bool | A GLSL bool | false | yes |
|
||||
[Table [materialConstantsTypes]: Material constants types]
|
||||
|
||||
Description
|
||||
: Lists the constant parameters accepted by your material. These constants can be set, or
|
||||
"specialized", at runtime when loading a material package. Multiple materials can be loaded from
|
||||
the same material package with differing constant parameter specializations. Once a material is
|
||||
loaded from a material package, its constant parameters cannot be changed. Compared to regular
|
||||
parameters, constant parameters allow the compiler to generate more efficient code. Access
|
||||
constant parameters from the shader by prefixing the name with `materialConstant_`. For example,
|
||||
a constant parameter named `myConstant` is accessed in the shader as
|
||||
`materialConstant_myConstant`. If a constant parameter is not set at runtime, the default is
|
||||
used.
|
||||
the same material package with differing constant parameter specializations. If a constant
|
||||
parameter is specialized as mutable, it may be changed at any time on a per-instance basis via
|
||||
`MaterialInstance::setConstant(name, value)`. Otherwise, once a material is loaded from a
|
||||
material package, its constant parameters cannot be changed.
|
||||
|
||||
Compared to regular parameters, constant parameters allow the compiler to generate more
|
||||
efficient code. Access constant parameters from the shader by prefixing the name with
|
||||
`materialConstant_`. For example, a constant parameter named `myConstant` is accessed in the
|
||||
shader as `materialConstant_myConstant`. If a constant parameter is not set at runtime, the
|
||||
default is used.
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
|
||||
material {
|
||||
constants : [
|
||||
{
|
||||
name : overrideAlpha,
|
||||
type : bool
|
||||
type : bool,
|
||||
mutable : true,
|
||||
},
|
||||
{
|
||||
name : customAlpha,
|
||||
@@ -2639,7 +2646,7 @@ standard skybox material. It produces a list of 2 parameters, named `showSun` an
|
||||
respectively a boolean and a cubemap texture.
|
||||
|
||||
```text
|
||||
$ matc --reflect parameters filament/src/materials/skybox.mat
|
||||
$ matc --reflect parameters filament/src/materials/skybox.mat
|
||||
{
|
||||
"parameters": [
|
||||
{
|
||||
@@ -2655,7 +2662,7 @@ $ matc --reflect parameters filament/src/materials/skybox.mat
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
### --variant-filter
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Core Concepts
|
||||
|
||||
|
||||
- [Filament](main/filament.md) - High-level designs; Filament's PBR/math assumptions; implementation details.
|
||||
- [Materials](main/materials.md) - A guide to Filament's material definition.
|
||||
- [Filament](filament.md) - High-level designs; Filament's PBR/math assumptions; implementation details.
|
||||
- [Materials](materials.md) - A guide to Filament's material definition.
|
||||
|
||||
77
docs_src/src_mdbook/src/notes/performance_analysis.md
Normal file
77
docs_src/src_mdbook/src/notes/performance_analysis.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# Performance Analysis
|
||||
|
||||
## Android
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Download and install Android GPU Inspector (AGI). See https://developer.android.com/agi.
|
||||
|
||||
### Android with the Vulkan Backend
|
||||
|
||||
---
|
||||
|
||||
#### Profiling
|
||||
|
||||
1. Build a release build of Filament with the applicable backend(s) enabled
|
||||
_(+ any special flags for enabling sys strace. Nothing special is needed for Vulkan or WebGPU aside from
|
||||
building a release build with no flags)_ _(debug builds for this are useless)_
|
||||
|
||||
```shell
|
||||
# the following command assumes you are in the root filament directory
|
||||
# and ANDROID_HOME is exported (and possibly also CC and CXX on linux as needed)
|
||||
#
|
||||
# NOTE: to build with WebGPU support you need to explicitly include the -W flag
|
||||
# (it doesn't get compiled in by default), e.g.:
|
||||
# ./build.sh -W -p android,desktop -i release
|
||||
#
|
||||
# Note that you can speed this up a bit (and reduce disk space usage) by limiting the target to just
|
||||
# the ABI you plan on testing with the -q flag, e.g. -q arm64-v8a. If you do this, you
|
||||
# will need to update the android/gradle.properties file to specify the ABI(s) you are targeting
|
||||
# with the com.google.android.filament.abis property.
|
||||
# Thus, a build command that would target BOTH Vulkan AND WebGPU AND only target the ARM64 ABI would look something
|
||||
# like:
|
||||
# ./build.sh -W -q arm64-v8a -p android,desktop -i release
|
||||
./build.sh -p android,desktop -i release
|
||||
```
|
||||
1. Connect your Android device to your computer via a USB cord with USB debugging enabled and configure
|
||||
the system property to default to the desired backend, e.g.
|
||||
_(to determine how these numbers map to the backends, see the `enum class Backend`
|
||||
definition in [filament/filament/backend/include/backend/DriverEnums.h](../../../../filament/backend/include/backend/DriverEnums.h))_:
|
||||
```shell
|
||||
# to set the backend to Vulkan:
|
||||
adb shell setprop debug.filament.backend 2
|
||||
# to set the backend to WebGPU:
|
||||
adb shell setprop debug.filament.backend 4
|
||||
# to view the current property:
|
||||
adb shell getprop debug.filament.backend
|
||||
```
|
||||
1. Build and run a sample, e.g. sample-gltf-viewer, on your Android device
|
||||
_([Android Studio](https://developer.android.com/studio) recommended)_.
|
||||
1. Run [AGI](https://developer.android.com/agi) and follow instructions to profile the app/system
|
||||
with trace capture(s). See https://developer.android.com/agi/start for more details to
|
||||
get started.
|
||||
- We typically only run "Capture System Profiler trace" _(not necessarily "Capture Frame Profiler trace")_
|
||||
- When configuring the trace:
|
||||
- For both the WebGPU and Vulkan backends configure the profiler for the Vulkan API
|
||||
_(since WebGPU should be using Vulkan under the hood as well)_
|
||||
- Running for ~1 seconds should suffice
|
||||
- Hit the "Configure" button in Trace objects, select "Switch to advanced mode" and add:
|
||||
```
|
||||
data_sources {
|
||||
config {
|
||||
name: "track_event"
|
||||
track_event_config {
|
||||
disabled_categories: "*"
|
||||
enabled_categories: "filament/filament"
|
||||
enabled_categories: "filament/jobsystem"
|
||||
enabled_categories: "filament/gltfio"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
- One you open the trace, zoom into a series of frames to get a sense of generally how long they typically take
|
||||
_(use `W`, `S`, `A` and `D` keys and mouse wheel for navigation)_ and find a representative one.
|
||||
We are most interested in the performance of the
|
||||
`FEngine::loop` thread, how long it takes, overlap in activities/processes/commands, reduction in queue submissions,
|
||||
etc. Similarly, we can view GPU timeline as it relates to that. We want to see overlapping
|
||||
shader invocations and non-interrupted fragment shader runs.
|
||||
11
docs_src/src_raw/index.html
Normal file
11
docs_src/src_raw/index.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Redirecting...</title>
|
||||
<meta http-equiv="refresh" content="0; url=./dup/intro.html">
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -258,6 +258,8 @@ if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
include/backend/platforms/WebGPUPlatform.h
|
||||
src/webgpu/platform/WebGPUPlatform.cpp
|
||||
src/webgpu/SpdMipmapGenerator/SpdMipmapGenerator.cpp
|
||||
src/webgpu/utils/AsyncTaskCounter.cpp
|
||||
src/webgpu/utils/AsyncTaskCounter.h
|
||||
src/webgpu/utils/StringPlaceholderTemplateProcessor.cpp
|
||||
src/webgpu/utils/StringPlaceholderTemplateProcessor.h
|
||||
src/webgpu/WebGPUBlitter.cpp
|
||||
@@ -412,6 +414,7 @@ endif()
|
||||
|
||||
if (APPLE AND NOT IOS)
|
||||
target_link_libraries(${TARGET} PRIVATE "-framework Cocoa")
|
||||
target_link_libraries(${TARGET} PRIVATE "-framework QuartzCore")
|
||||
endif()
|
||||
|
||||
target_link_libraries(${TARGET} PUBLIC math)
|
||||
@@ -527,7 +530,7 @@ target_link_libraries(${TARGET} PRIVATE
|
||||
if (LINUX AND FILAMENT_SUPPORTS_WEBGPU)
|
||||
# Unclear why this needs to be explicit while other linux libs do not, but it is necessary for
|
||||
# WebGPU to build on linux.
|
||||
target_link_libraries(${TARGET} PRIVATE -lxcb)
|
||||
target_link_libraries(${TARGET} PRIVATE -lxcb -lX11)
|
||||
endif()
|
||||
|
||||
# ==================================================================================================
|
||||
@@ -576,6 +579,9 @@ if (APPLE OR LINUX)
|
||||
test/test_CommandBufferQueue.cpp
|
||||
test/test_Template.cpp
|
||||
)
|
||||
if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
list(APPEND BACKEND_TEST_SRC test/test_WebGPUAsyncTaskCounter.cpp)
|
||||
endif()
|
||||
if (APPLE)
|
||||
# Metal-specific tests
|
||||
list(APPEND BACKEND_TEST_SRC
|
||||
@@ -593,6 +599,7 @@ if (APPLE OR LINUX)
|
||||
spirv-cross-glsl)
|
||||
# Create input/output directories for test result images.
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/actual_images)
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/diff_images)
|
||||
file(COPY test/expected_images DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/images)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ public:
|
||||
* Set to `true` to forcibly disable amortized shader compilation in the backend.
|
||||
* Currently only honored by the GL backend.
|
||||
*/
|
||||
bool disableAmortizedShaderCompile = false;
|
||||
bool disableAmortizedShaderCompile = true;
|
||||
|
||||
/**
|
||||
* Disable backend handles use-after-free checks.
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <utils/CString.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Invocable.h>
|
||||
#include <utils/bitset.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
@@ -66,6 +67,7 @@ public:
|
||||
using DescriptorBindingsInfo = utils::FixedCapacityVector<Descriptor>;
|
||||
using DescriptorSetInfo = std::array<DescriptorBindingsInfo, MAX_DESCRIPTOR_SET_COUNT>;
|
||||
using SpecializationConstantsInfo = utils::FixedCapacityVector<SpecializationConstant>;
|
||||
using MutableSpecConstantsInfo = utils::bitset8;
|
||||
using ShaderBlob = utils::FixedCapacityVector<uint8_t>;
|
||||
using ShaderSource = std::array<ShaderBlob, SHADER_TYPE_COUNT>;
|
||||
|
||||
@@ -103,7 +105,8 @@ public:
|
||||
Program& descriptorBindings(backend::descriptor_set_t set,
|
||||
DescriptorBindingsInfo descriptorBindings) noexcept;
|
||||
|
||||
Program& specializationConstants(SpecializationConstantsInfo specConstants) noexcept;
|
||||
Program& specializationConstants(SpecializationConstantsInfo specConstants,
|
||||
uint32_t firstMutableId, MutableSpecConstantsInfo mutableSpecConstants) noexcept;
|
||||
|
||||
struct PushConstant {
|
||||
utils::CString name;
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace filament::backend {
|
||||
* A Platform interface, handling the environment-specific concerns, e.g. OS, for creating a WebGPU
|
||||
* driver (backend).
|
||||
*/
|
||||
class WebGPUPlatform final : public Platform {
|
||||
class WebGPUPlatform : public Platform {
|
||||
public:
|
||||
WebGPUPlatform();
|
||||
~WebGPUPlatform() override = default;
|
||||
@@ -52,6 +52,14 @@ public:
|
||||
// either returns a valid device or panics
|
||||
[[nodiscard]] wgpu::Device requestDevice(wgpu::Adapter const& adapter);
|
||||
|
||||
struct Configuration {
|
||||
wgpu::BackendType forceBackendType = wgpu::BackendType::Undefined;
|
||||
};
|
||||
|
||||
[[nodiscard]] virtual Configuration getConfiguration() const noexcept {
|
||||
return {};
|
||||
}
|
||||
|
||||
protected:
|
||||
[[nodiscard]] Driver* createDriver(void* sharedContext,
|
||||
const Platform::DriverConfig& driverConfig) noexcept override;
|
||||
|
||||
@@ -97,11 +97,13 @@ void CommandStream::execute(void* buffer) {
|
||||
}
|
||||
}
|
||||
|
||||
mDriver.execute([this, buffer]() {
|
||||
Driver& UTILS_RESTRICT driver = mDriver;
|
||||
CommandBase* UTILS_RESTRICT base = static_cast<CommandBase*>(buffer);
|
||||
while (UTILS_LIKELY(base)) {
|
||||
base = base->execute(driver);
|
||||
Driver& UTILS_RESTRICT driver = mDriver;
|
||||
CommandBase* UTILS_RESTRICT base = static_cast<CommandBase*>(buffer);
|
||||
mDriver.execute([&driver, base] {
|
||||
auto& d = driver;
|
||||
auto p = base;
|
||||
while (UTILS_LIKELY(p)) {
|
||||
p = p->execute(d);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -82,8 +82,19 @@ Program& Program::attributes(AttributesInfo attributes) noexcept {
|
||||
return *this;
|
||||
}
|
||||
|
||||
Program& Program::specializationConstants(SpecializationConstantsInfo specConstants) noexcept {
|
||||
mSpecializationConstants = std::move(specConstants);
|
||||
Program& Program::specializationConstants(SpecializationConstantsInfo specConstants,
|
||||
uint32_t firstMutableId, MutableSpecConstantsInfo mutableSpecConstants) noexcept {
|
||||
// String the two lists together.
|
||||
mSpecializationConstants = SpecializationConstantsInfo(
|
||||
specConstants.size() + mutableSpecConstants.size());
|
||||
std::uninitialized_move(specConstants.begin(), specConstants.end(),
|
||||
mSpecializationConstants.begin());
|
||||
for (uint32_t i = 0; i < mutableSpecConstants.size(); i++) {
|
||||
mSpecializationConstants[i + specConstants.size()] = SpecializationConstant {
|
||||
.id = i + firstMutableId,
|
||||
.value = mutableSpecConstants[i],
|
||||
};
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
@@ -118,6 +118,10 @@ MetalSwapChain::MetalSwapChain(
|
||||
layerDrawableMutex(std::make_shared<std::mutex>()),
|
||||
type(SwapChainType::CAMETALLAYER) {
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION([nativeWindow isKindOfClass:[CAMetalLayer class]])
|
||||
<< "nativeWindow pointer of class "
|
||||
<< [NSStringFromClass([nativeWindow class]) UTF8String] << " is not a CAMetalLayer";
|
||||
|
||||
if (!(flags & SwapChain::CONFIG_TRANSPARENT) && !nativeWindow.opaque) {
|
||||
LOG(WARNING) << "Warning: Filament SwapChain has no CONFIG_TRANSPARENT flag, but the "
|
||||
"CAMetaLayer("
|
||||
|
||||
@@ -452,7 +452,7 @@ void VulkanDriver::updateDescriptorSetTexture(
|
||||
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
|
||||
auto texture = resource_ptr<VulkanTexture>::cast(&mResourceManager, th);
|
||||
|
||||
if (mExternalImageManager.isExternallySampledTexture(texture)) {
|
||||
if (UTILS_UNLIKELY(mExternalImageManager.isExternallySampledTexture(texture))) {
|
||||
mExternalImageManager.bindExternallySampledTexture(set, binding, texture, params);
|
||||
mAppState.hasBoundExternalImages = true;
|
||||
} else {
|
||||
@@ -1939,6 +1939,18 @@ void VulkanDriver::bindDescriptorSet(
|
||||
if (dsh) {
|
||||
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
|
||||
mDescriptorSetCache.bind(setIndex, set, std::move(offsets));
|
||||
|
||||
if (mAppState.hasExternalSamplers()) {
|
||||
auto const& bindInDrawBundle = mPipelineState.bindInDraw.second;
|
||||
// The set index being bound has already been bound or will be bound. If it's already
|
||||
// been bound and this set has external samplers, we do the doBindindraw block in
|
||||
// draw2() again. Because this set might potentially cause a new pipelineLayout
|
||||
// (therefore pipeline) to be bound.
|
||||
if (bindInDrawBundle.descriptorSetMask[setIndex] &&
|
||||
mExternalImageManager.hasExternalSampler(set)) {
|
||||
mPipelineState.bindInDraw.first = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mDescriptorSetCache.unbind(setIndex);
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ fvkutils::DescriptorSetMask VulkanExternalImageManager::prepareBindSets(LayoutAr
|
||||
}
|
||||
|
||||
bool VulkanExternalImageManager::hasExternalSampler(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set) {
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set) const {
|
||||
auto itr = std::find_if(mSetBindings.begin(), mSetBindings.end(),
|
||||
[&](SetBindingInfo const& info) { return info.set == set; });
|
||||
return itr != mSetBindings.end();
|
||||
@@ -191,9 +191,11 @@ void VulkanExternalImageManager::updateSetAndLayout(
|
||||
VkDescriptorSet const srcSet = oldSet != VK_NULL_HANDLE ? oldSet : set->getVkSet();
|
||||
copySet(mPlatform->getDevice(), srcSet, newSet, copyBindings);
|
||||
|
||||
set->setExternalSamplerVkSet(newSet, [&](VulkanDescriptorSet*) {
|
||||
mDescriptorSetCache->manualRecycle(layout->count, newLayout, newSet);
|
||||
});
|
||||
set->setExternalSamplerVkSet(newSet,
|
||||
[&descriptorSetCache = mDescriptorSetCache, layoutCount = layout->count, newLayout,
|
||||
newSet](VulkanDescriptorSet*) {
|
||||
descriptorSetCache->manualRecycle(layoutCount, newLayout, newSet);
|
||||
});
|
||||
if (oldLayout != newLayout) {
|
||||
layout->setExternalSamplerVkLayout(newLayout);
|
||||
}
|
||||
|
||||
@@ -86,9 +86,9 @@ public:
|
||||
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
private:
|
||||
bool hasExternalSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set);
|
||||
bool hasExternalSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set) const;
|
||||
|
||||
private:
|
||||
void updateSetAndLayout(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
||||
|
||||
|
||||
@@ -46,17 +46,13 @@ fvkmemory::resource_ptr<VulkanTimerQuery> VulkanQueryManager::getNextQuery(
|
||||
return {};
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
std::pair<uint32_t, uint32_t> queryIndices;
|
||||
unused.forEachSetBit([&](size_t index) {
|
||||
if (found) {
|
||||
return;
|
||||
}
|
||||
size_t const firstUnused = unused.firstSetBit();
|
||||
{
|
||||
std::unique_lock<utils::Mutex> lock(mMutex);
|
||||
mUsed.set(index);
|
||||
found = true;
|
||||
queryIndices = std::make_pair(index * 2, index * 2 + 1);
|
||||
});
|
||||
mUsed.set(firstUnused);
|
||||
queryIndices = { firstUnused * 2, firstUnused * 2 + 1 };
|
||||
}
|
||||
return fvkmemory::resource_ptr<VulkanTimerQuery>::construct(resourceManager, queryIndices.first,
|
||||
queryIndices.second);
|
||||
}
|
||||
|
||||
@@ -58,10 +58,10 @@ namespace {
|
||||
areCopyCompatible(sourceFormat, destinationFormat) &&
|
||||
(sourceExtent.width == destinationExtent.width) &&
|
||||
(sourceExtent.height == destinationExtent.height) &&
|
||||
((sourceOrigin.x & sourceBlockSize.width) == 0) &&
|
||||
((sourceOrigin.y & sourceBlockSize.height) == 0) &&
|
||||
((destinationOrigin.x & destinationBlockSize.width) == 0) &&
|
||||
((destinationOrigin.y & destinationBlockSize.height) == 0);
|
||||
((sourceOrigin.x % sourceBlockSize.width) == 0) &&
|
||||
((sourceOrigin.y % sourceBlockSize.height) == 0) &&
|
||||
((destinationOrigin.x % destinationBlockSize.width) == 0) &&
|
||||
((destinationOrigin.y % destinationBlockSize.height) == 0);
|
||||
}
|
||||
|
||||
struct BlitFragmentShaderArgs final {
|
||||
@@ -206,9 +206,20 @@ constexpr std::string_view FRAGMENT_SHADER_SNIPPET_2D_INPUT_TEMPLATE{ R"(
|
||||
|
||||
} // namespace
|
||||
|
||||
// It can perform a direct memory copy if the formats are compatible and no scaling or format
|
||||
// conversion is needed. Otherwise, it uses a render pass with a custom shader to perform
|
||||
// the blit. This allows for scaling, format conversion, and resolving multisampled textures.
|
||||
// The blitter caches pipelines, layouts, and shaders to avoid recompilation.
|
||||
WebGPUBlitter::WebGPUBlitter(wgpu::Device const& device)
|
||||
: mDevice{ device } {}
|
||||
|
||||
// Performs a blit operation from a source to a destination texture.
|
||||
// This function first checks if a direct texture-to-texture copy can be performed.
|
||||
// A direct copy is possible if the source and destination textures have the same sample count,
|
||||
// compatible formats, and the copy extents match. If a direct copy is not possible, this
|
||||
// function falls back to a render pass-based blit. The render pass uses a shader to read from
|
||||
// the source texture and write to the destination texture, allowing for format conversion,
|
||||
// scaling, and MSAA resolves.
|
||||
void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& commandEncoder,
|
||||
BlitArgs const& args) {
|
||||
// current assumptions/simplifications made in this implementation:
|
||||
@@ -267,22 +278,20 @@ void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& c
|
||||
args.destination.texture.GetUsage() & wgpu::TextureUsage::CopyDst)
|
||||
<< "destination texture usage doesn't have wgpu::TextureUsage::CopyDst";
|
||||
|
||||
copy(commandEncoder, args);
|
||||
copyByteByByte(commandEncoder, args);
|
||||
return;
|
||||
}
|
||||
// need to do a render pass blit...
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(args.destination.texture.GetSampleCount() == 1)
|
||||
<< "Blitting does not currently support writing to multisampled output textures.";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(args.source.texture.GetUsage() & wgpu::TextureUsage::TextureBinding)
|
||||
<< "source texture usage doesn't have wgpu::TextureUsage::TextureBinding";
|
||||
<< "Source texture usage doesn't have wgpu::TextureUsage::TextureBinding";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
args.destination.texture.GetUsage() & wgpu::TextureUsage::RenderAttachment)
|
||||
<< "destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
|
||||
<< "Destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
|
||||
|
||||
// create input/output texture views...
|
||||
const wgpu::TextureViewDimension sourceDimension{ args.source.texture.GetDimension() ==
|
||||
wgpu::TextureDimension::e3D
|
||||
? wgpu::TextureViewDimension::e3D
|
||||
@@ -327,7 +336,6 @@ void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& c
|
||||
<< destinationViewDescriptor.baseArrayLayer << " and mip level "
|
||||
<< destinationViewDescriptor.baseMipLevel << " for render pass blit?";
|
||||
|
||||
// create uniform buffer (for shader args)...
|
||||
const BlitFragmentShaderArgs blitFragmentShaderArgs{
|
||||
.depthPlane = args.source.layerOrDepth,
|
||||
.scale = { static_cast<float>(args.source.extent.width) / args.destination.extent.width,
|
||||
@@ -438,7 +446,7 @@ void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& c
|
||||
const wgpu::RenderPassEncoder renderPassEncoder{ commandEncoder.BeginRenderPass(
|
||||
&renderPassDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(renderPassEncoder)
|
||||
<< "Failed to create render pass encoder for blit?";
|
||||
<< "Failed to create render pass encoder for blit.";
|
||||
renderPassEncoder.SetPipeline(getOrCreateRenderPipeline(args.filter, sourceDimension,
|
||||
args.source.texture.GetSampleCount(), depthSource,
|
||||
args.destination.texture.GetFormat()));
|
||||
@@ -448,7 +456,7 @@ void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& c
|
||||
renderPassEncoder.End();
|
||||
}
|
||||
|
||||
void WebGPUBlitter::copy(wgpu::CommandEncoder const& commandEncoder, BlitArgs const& args) {
|
||||
void WebGPUBlitter::copyByteByByte(wgpu::CommandEncoder const& commandEncoder, BlitArgs const& args) {
|
||||
const wgpu::TexelCopyTextureInfo sourceCopyInfo{
|
||||
.texture = args.source.texture,
|
||||
.mipLevel = args.source.mipLevel,
|
||||
@@ -505,7 +513,7 @@ void WebGPUBlitter::createSampler(const SamplerMagFilter filter) {
|
||||
.maxAnisotropy = 1, // should not matter, just being consistently defined
|
||||
};
|
||||
const wgpu::Sampler sampler{ mDevice.CreateSampler(&descriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(sampler) << "Failed to create blit sampler?";
|
||||
FILAMENT_CHECK_POSTCONDITION(sampler) << "Failed to create blit sampler.";
|
||||
switch (filter) {
|
||||
case SamplerMagFilter::NEAREST:
|
||||
mNearestSampler = sampler;
|
||||
@@ -516,6 +524,8 @@ void WebGPUBlitter::createSampler(const SamplerMagFilter filter) {
|
||||
}
|
||||
}
|
||||
|
||||
// Caches and returns a render pipeline for a given blit configuration.
|
||||
// If a pipeline for the given configuration does not exist, it creates and caches one.
|
||||
wgpu::RenderPipeline const& WebGPUBlitter::getOrCreateRenderPipeline(
|
||||
const SamplerMagFilter filterType, const wgpu::TextureViewDimension sourceDimension,
|
||||
const uint32_t sourceSampleCount, const bool depthSource,
|
||||
@@ -529,6 +539,9 @@ wgpu::RenderPipeline const& WebGPUBlitter::getOrCreateRenderPipeline(
|
||||
return mRenderPipelines[key];
|
||||
}
|
||||
|
||||
// Creates a render pipeline for a blit operation.
|
||||
// The pipeline is configured with the appropriate vertex and fragment shaders,
|
||||
// pipeline layout, and render target state based on the blit parameters.
|
||||
wgpu::RenderPipeline WebGPUBlitter::createRenderPipeline(const SamplerMagFilter filterType,
|
||||
const wgpu::TextureViewDimension sourceDimension, const uint32_t sourceSampleCount,
|
||||
const bool depthSource, const wgpu::TextureFormat destinationTextureFormat) {
|
||||
@@ -580,7 +593,7 @@ wgpu::RenderPipeline WebGPUBlitter::createRenderPipeline(const SamplerMagFilter
|
||||
.fragment = &fragmentState,
|
||||
};
|
||||
const wgpu::RenderPipeline pipeline{ mDevice.CreateRenderPipeline(&pipelineDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline) << "Failed to create pipeline for render pass blit?";
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline) << "Failed to create pipeline for render pass blit.";
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
@@ -595,6 +608,8 @@ size_t WebGPUBlitter::hashRenderPipelineKey(const SamplerMagFilter filterType,
|
||||
return seed;
|
||||
}
|
||||
|
||||
// Caches and returns a pipeline layout for a given blit configuration.
|
||||
// If a layout for the given configuration does not exist, it creates and caches one.
|
||||
wgpu::PipelineLayout const& WebGPUBlitter::getOrCreatePipelineLayout(
|
||||
const SamplerMagFilter filterType, const wgpu::TextureViewDimension sourceDimension,
|
||||
const bool multisampledSource, const bool depthSource) {
|
||||
@@ -607,6 +622,8 @@ wgpu::PipelineLayout const& WebGPUBlitter::getOrCreatePipelineLayout(
|
||||
return mPipelineLayouts[key];
|
||||
}
|
||||
|
||||
// Creates a pipeline layout for a blit operation.
|
||||
// The layout defines the bind group layouts used by the blit pipeline.
|
||||
wgpu::PipelineLayout WebGPUBlitter::createPipelineLayout(const SamplerMagFilter filterType,
|
||||
const wgpu::TextureViewDimension sourceDimension, const bool multisampledSource,
|
||||
const bool depthSource) {
|
||||
@@ -619,7 +636,7 @@ wgpu::PipelineLayout WebGPUBlitter::createPipelineLayout(const SamplerMagFilter
|
||||
const wgpu::PipelineLayout pipelineLayout{ mDevice.CreatePipelineLayout(
|
||||
&pipelineLayoutDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipelineLayout)
|
||||
<< "Failed to create pipeline layout for render pass blit?";
|
||||
<< "Failed to create pipeline layout for render pass blit.";
|
||||
return pipelineLayout;
|
||||
}
|
||||
|
||||
@@ -633,6 +650,8 @@ size_t WebGPUBlitter::hashPipelineLayoutKey(const SamplerMagFilter filterType,
|
||||
return seed;
|
||||
}
|
||||
|
||||
// Caches and returns a bind group layout for a given blit configuration.
|
||||
// If a layout for the given configuration does not exist, it creates and caches one.
|
||||
wgpu::BindGroupLayout const& WebGPUBlitter::getOrCreateTextureBindGroupLayout(
|
||||
const SamplerMagFilter filterType, const wgpu::TextureViewDimension sourceDimension,
|
||||
const bool multisampledSource, const bool depthSource) {
|
||||
@@ -645,6 +664,9 @@ wgpu::BindGroupLayout const& WebGPUBlitter::getOrCreateTextureBindGroupLayout(
|
||||
return mTextureBindGroupLayouts[key];
|
||||
}
|
||||
|
||||
// Creates a bind group layout for the blit operation's texture resources.
|
||||
// This layout specifies the bindings for the source texture, a uniform buffer with blit parameters,
|
||||
// and optionally a sampler.
|
||||
wgpu::BindGroupLayout WebGPUBlitter::createTextureBindGroupLayout(const SamplerMagFilter filterType,
|
||||
const wgpu::TextureViewDimension sourceDimension, const bool multisampledSource,
|
||||
const bool depthSource) {
|
||||
@@ -655,11 +677,11 @@ wgpu::BindGroupLayout WebGPUBlitter::createTextureBindGroupLayout(const SamplerM
|
||||
.texture = {
|
||||
.sampleType = depthSource
|
||||
? wgpu::TextureSampleType::Depth
|
||||
: wgpu::TextureSampleType::Float, // only F32 scalar sample
|
||||
// type supported for now
|
||||
// (aside from depth)
|
||||
// see assumptions listed
|
||||
// in blit function
|
||||
: (multisampledSource
|
||||
? wgpu::TextureSampleType::UnfilterableFloat
|
||||
: wgpu::TextureSampleType::Float), // only F32 scalar sample
|
||||
// type supported for now
|
||||
// (aside from depth)
|
||||
.viewDimension = sourceDimension,
|
||||
.multisampled = multisampledSource,
|
||||
},
|
||||
@@ -693,7 +715,7 @@ wgpu::BindGroupLayout WebGPUBlitter::createTextureBindGroupLayout(const SamplerM
|
||||
const wgpu::BindGroupLayout textureBindGroupLayout{ mDevice.CreateBindGroupLayout(
|
||||
&textureBindGroupLayoutDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(textureBindGroupLayout)
|
||||
<< "Failed to create texture bind group layout for render pass blit?";
|
||||
<< "Failed to create texture bind group layout for render pass blit.";
|
||||
return textureBindGroupLayout;
|
||||
}
|
||||
|
||||
@@ -707,6 +729,8 @@ size_t WebGPUBlitter::hashTextureBindGroupLayoutKey(const SamplerMagFilter filte
|
||||
return seed;
|
||||
}
|
||||
|
||||
// Caches and returns a shader module for a given blit configuration.
|
||||
// If a shader module for the given configuration does not exist, it creates and caches one.
|
||||
wgpu::ShaderModule const& WebGPUBlitter::getOrCreateShaderModule(
|
||||
const wgpu::TextureViewDimension sourceDimension, const bool multisampledSource,
|
||||
const bool depthSource, const bool depthDestination) {
|
||||
@@ -719,6 +743,9 @@ wgpu::ShaderModule const& WebGPUBlitter::getOrCreateShaderModule(
|
||||
return mShaderModules[key];
|
||||
}
|
||||
|
||||
// Creates a shader module containing the vertex and fragment shaders for the blit operation.
|
||||
// The shader source is generated from a template, with placeholders filled in based on the
|
||||
// blit configuration (e.g., texture type, sample count).
|
||||
wgpu::ShaderModule WebGPUBlitter::createShaderModule(
|
||||
const wgpu::TextureViewDimension sourceDimension, const bool multisampledSource,
|
||||
const bool depthSource, const bool depthDestination) {
|
||||
@@ -730,9 +757,8 @@ wgpu::ShaderModule WebGPUBlitter::createShaderModule(
|
||||
textureType = "texture_depth_2d";
|
||||
}
|
||||
} else {
|
||||
// non-depth
|
||||
if (multisampledSource) {
|
||||
textureType = "texture_multisampled_2d";
|
||||
textureType = "texture_multisampled_2d<f32>";
|
||||
} else {
|
||||
if (sourceDimension == wgpu::TextureViewDimension::e3D) {
|
||||
textureType = "texture_3d<f32>";
|
||||
@@ -785,7 +811,7 @@ wgpu::ShaderModule WebGPUBlitter::createShaderModule(
|
||||
};
|
||||
const wgpu::ShaderModule shaderModule{ mDevice.CreateShaderModule(&shaderModuleDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(shaderModule)
|
||||
<< "Failed to create shader module for render pass blit?";
|
||||
<< "Failed to create shader module for render pass blit.";
|
||||
return shaderModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,17 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A blit essentially writes pixels to a region of one texture given a region of another texture.
|
||||
* Such a process can be a byte-by-byte copy when possible, while other scenarios involve various
|
||||
* transformations, such as pixel format conversion, image scaling, filtering/sampling
|
||||
* (such as down sampling), or even multi-sample resolves. Some blits can be faster than others,
|
||||
* depending on what is needed; for example, a byte-by-byte copy is generally faster than other
|
||||
* operations.
|
||||
*
|
||||
* This class is responsible for performing blits throughout the lifecycle of its owner/caller,
|
||||
* e.g. the WebGPU backend/driver, where it can leverage state to optimize such blit calls over time.
|
||||
*/
|
||||
class WebGPUBlitter final {
|
||||
public:
|
||||
struct BlitArgs final {
|
||||
@@ -58,10 +69,15 @@ private:
|
||||
/**
|
||||
* ONLY should be called if canDoDirectCopy(...) is true (see it in WebGPUBlitter.cpp)
|
||||
*/
|
||||
void copy(wgpu::CommandEncoder const&, BlitArgs const&);
|
||||
void copyByteByByte(wgpu::CommandEncoder const&, BlitArgs const&);
|
||||
|
||||
void createSampler(SamplerMagFilter);
|
||||
|
||||
// The following methods are used to create and cache WebGPU objects.
|
||||
// The pattern is to have a `getOrCreate...` method that looks up the object in a cache,
|
||||
// and if it's not found, it calls a `create...` method to create it and then stores it in the
|
||||
// cache. A `hash...` method is used to generate a key for the cache.
|
||||
|
||||
[[nodiscard]] wgpu::RenderPipeline const& getOrCreateRenderPipeline(SamplerMagFilter,
|
||||
wgpu::TextureViewDimension sourceDimension, uint32_t sourceSampleCount,
|
||||
bool depthSource, wgpu::TextureFormat destinationTextureFormat);
|
||||
|
||||
@@ -33,6 +33,9 @@ namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
// Creates a wgpu::Buffer, ensuring its size is a multiple of FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS.
|
||||
// WebGPU's WriteBuffer requires the write size to be a multiple of 4. By ensuring the buffer
|
||||
// size is also a multiple of 4, we simplify the update logic.
|
||||
[[nodiscard]] wgpu::Buffer createBuffer(wgpu::Device const& device, const wgpu::BufferUsage usage,
|
||||
uint32_t size, const char* const label) {
|
||||
// Write size must be divisible by WEBGPU_BUFFER_SIZE_MODULUS (e.g. 4).
|
||||
@@ -55,6 +58,11 @@ WebGPUBufferBase::WebGPUBufferBase(wgpu::Device const& device, const wgpu::Buffe
|
||||
const uint32_t size, char const* const label)
|
||||
: mBuffer{ createBuffer(device, usage, size, label) } {}
|
||||
|
||||
// Updates the GPU buffer with data from a BufferDescriptor.
|
||||
// WebGPU requires that the size of the data written to a buffer is a multiple of 4.
|
||||
// This function handles cases where the buffer descriptor's size is not a multiple of 4
|
||||
// by writing the bulk of the data first, and then copying the remaining bytes into a
|
||||
// padded temporary chunk which is then written to the buffer.
|
||||
void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
|
||||
const uint32_t byteOffset, wgpu::Queue const& queue) {
|
||||
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.buffer)
|
||||
|
||||
@@ -28,6 +28,10 @@ namespace filament::backend {
|
||||
|
||||
class BufferDescriptor;
|
||||
|
||||
/**
|
||||
* A base class for WebGPU buffer objects, providing common functionality for creating and
|
||||
* updating GPU buffers.
|
||||
*/
|
||||
class WebGPUBufferBase /* intended to be extended */ {
|
||||
public:
|
||||
/**
|
||||
@@ -46,7 +50,8 @@ protected:
|
||||
|
||||
private:
|
||||
const wgpu::Buffer mBuffer;
|
||||
// FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS (e.g. 4) bytes to hold any extra chunk we need.
|
||||
// WebGPU requires that the source buffer of a writeBuffer call has a size that is a multiple
|
||||
// of 4. This member is used to pad the data if the source size is not a multiple of 4.
|
||||
std::array<uint8_t, FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS> mRemainderChunk{};
|
||||
};
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
// The usage flags are determined by the binding type, and always include CopyDst to allow for
|
||||
// updating the buffer.
|
||||
WebGPUBufferObject::WebGPUBufferObject(wgpu::Device const& device,
|
||||
const BufferObjectBinding bindingType, const uint32_t byteCount)
|
||||
: HwBufferObject{ byteCount },
|
||||
|
||||
@@ -31,6 +31,12 @@ namespace filament::backend {
|
||||
|
||||
enum class BufferObjectBinding : uint8_t;
|
||||
|
||||
/**
|
||||
* A WebGPU-specific implementation of BufferObject.
|
||||
* A BufferObject is a GPU buffer that can be used for a variety of purposes, such as storing
|
||||
* uniform data, vertex attributes, or pixel data. The specific usage is determined by the
|
||||
* BufferObjectBinding.
|
||||
*/
|
||||
class WebGPUBufferObject final : public HwBufferObject, public WebGPUBufferBase {
|
||||
public:
|
||||
WebGPUBufferObject(wgpu::Device const&, BufferObjectBinding, uint32_t byteCount);
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// WebGPU requires that the source buffer of a writeBuffer call has a size that is a multiple of 4.
|
||||
constexpr size_t FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS = 4;
|
||||
|
||||
// FWGPU is short for Filament WebGPU
|
||||
@@ -48,8 +49,16 @@ constexpr size_t FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS = 4;
|
||||
// textures, samplers, and buffers,...), which would otherwise spam the logs.
|
||||
#define FWGPU_DEBUG_BIND_GROUPS 0x00000020
|
||||
|
||||
// Enables Android systrace
|
||||
#define FWGPU_DEBUG_SYSTRACE 0x00000040
|
||||
|
||||
// Enable a minimal set of traces to assess the performance of the backend.
|
||||
// All other debug features must be disabled.
|
||||
#define FWGPU_DEBUG_PROFILING 0x00000080
|
||||
|
||||
// Useful default combinations
|
||||
#define FWGPU_DEBUG_EVERYTHING 0xFFFFFFFF
|
||||
#define FWGPU_DEBUG_EVERYTHING (0xFFFFFFFF & ~FWGPU_DEBUG_PROFILING)
|
||||
#define FWGPU_DEBUG_PERFORMANCE FWGPU_DEBUG_SYSTRACE
|
||||
|
||||
#if defined(FILAMENT_BACKEND_DEBUG_FLAG)
|
||||
#define FWGPU_DEBUG_FORWARDED_FLAG (FILAMENT_BACKEND_DEBUG_FLAG & FWGPU_DEBUG_EVERYTHING)
|
||||
@@ -58,13 +67,53 @@ constexpr size_t FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS = 4;
|
||||
#endif
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define FWGPU_DEBUG_FLAGS FWGPU_DEBUG_FORWARDED_FLAG
|
||||
#define FWGPU_DEBUG_FLAGS (FWGPU_DEBUG_PERFORMANCE | FWGPU_DEBUG_FORWARDED_FLAG)
|
||||
#else
|
||||
#define FWGPU_DEBUG_FLAGS 0
|
||||
#define FWGPU_DEBUG_FLAGS FWGPU_DEBUG_SYSTRACE
|
||||
#endif
|
||||
|
||||
// Override the debug flags if we are forcing profiling mode
|
||||
#if defined(FILAMENT_FORCE_PROFILING_MODE)
|
||||
#undef FWGPU_DEBUG_FLAGS
|
||||
#define FWGPU_DEBUG_FLAGS (FWGPU_DEBUG_PROFILING)
|
||||
#endif
|
||||
|
||||
#define FWGPU_ENABLED(flags) (((FWGPU_DEBUG_FLAGS) & (flags)) == (flags))
|
||||
|
||||
#if FWGPU_ENABLED(FWGPU_DEBUG_PROFILING) && FWGPU_DEBUG_FLAGS != FWGPU_DEBUG_PROFILING
|
||||
#error PROFILING is exclusive; all other debug features must be disabled.
|
||||
#endif
|
||||
|
||||
#if FWGPU_DEBUG_FLAGS == FWGPU_DEBUG_PROFILING
|
||||
|
||||
#ifndef NDEBUG
|
||||
#error PROFILING is meaningless in DEBUG mode.
|
||||
#endif
|
||||
|
||||
#define FWGPU_SYSTRACE_CONTEXT()
|
||||
#define FWGPU_SYSTRACE_START(marker)
|
||||
#define FWGPU_SYSTRACE_END()
|
||||
#define FWGPU_SYSTRACE_SCOPE()
|
||||
#define FWGPU_PROFILE_MARKER(marker) PROFILE_SCOPE(marker)
|
||||
|
||||
#elif FWGPU_ENABLED(FWGPU_DEBUG_SYSTRACE)
|
||||
|
||||
#include <private/utils/Tracing.h>
|
||||
|
||||
#define FWGPU_SYSTRACE_CONTEXT() FILAMENT_TRACING_CONTEXT(FILAMENT_TRACING_CATEGORY_FILAMENT)
|
||||
#define FWGPU_SYSTRACE_START(marker) FILAMENT_TRACING_NAME_BEGIN(FILAMENT_TRACING_CATEGORY_FILAMENT, marker)
|
||||
#define FWGPU_SYSTRACE_END() FILAMENT_TRACING_NAME_END(FILAMENT_TRACING_CATEGORY_FILAMENT)
|
||||
#define FWGPU_SYSTRACE_SCOPE() FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT)
|
||||
#define FWGPU_PROFILE_MARKER(marker) FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT)
|
||||
|
||||
#else
|
||||
#define FWGPU_SYSTRACE_CONTEXT()
|
||||
#define FWGPU_SYSTRACE_START(marker)
|
||||
#define FWGPU_SYSTRACE_END()
|
||||
#define FWGPU_SYSTRACE_SCOPE()
|
||||
#define FWGPU_PROFILE_MARKER(marker)
|
||||
#endif
|
||||
|
||||
#if FWGPU_ENABLED(FWGPU_DEBUG_FORCE_LOG_TO_I)
|
||||
#define FWGPU_LOGI LOG(INFO)
|
||||
#define FWGPU_LOGD FWGPU_LOGI
|
||||
|
||||
@@ -42,6 +42,9 @@ constexpr uint8_t INVALID_INDEX = MAX_DESCRIPTOR_COUNT + 1;
|
||||
|
||||
} // namespace
|
||||
|
||||
// The constructor initializes the descriptor set based on a given layout.
|
||||
// It pre-allocates space for the bind group entries and sets up a mapping from binding index
|
||||
// to the internal entry array index. This prepares the object to be populated with resources.
|
||||
WebGPUDescriptorSet::WebGPUDescriptorSet(wgpu::BindGroupLayout const& layout,
|
||||
std::vector<WebGPUDescriptorSetLayout::BindGroupEntryInfo> const& bindGroupEntries)
|
||||
: mLayout{ layout },
|
||||
@@ -52,11 +55,10 @@ WebGPUDescriptorSet::WebGPUDescriptorSet(wgpu::BindGroupLayout const& layout,
|
||||
for (size_t i = 0; i < bindGroupEntries.size(); ++i) {
|
||||
mEntries[i].binding = bindGroupEntries[i].binding;
|
||||
}
|
||||
// Establish the size of entries based on the layout. This should be reliable and efficient.
|
||||
|
||||
// Create a mapping from binding index to entry index for efficient lookup.
|
||||
assert_invariant(INVALID_INDEX > mEntryIndexByBinding.size());
|
||||
for (size_t i = 0; i < mEntryIndexByBinding.size(); i++) {
|
||||
mEntryIndexByBinding[i] = INVALID_INDEX;
|
||||
}
|
||||
std::fill(mEntryIndexByBinding.begin(), mEntryIndexByBinding.end(), INVALID_INDEX);
|
||||
for (size_t index = 0; index < mEntries.size(); index++) {
|
||||
wgpu::BindGroupEntry const& entry = mEntries[index];
|
||||
assert_invariant(entry.binding < mEntryIndexByBinding.size());
|
||||
@@ -66,19 +68,20 @@ WebGPUDescriptorSet::WebGPUDescriptorSet(wgpu::BindGroupLayout const& layout,
|
||||
|
||||
void WebGPUDescriptorSet::addEntry(const unsigned int index, wgpu::BindGroupEntry&& entry) {
|
||||
if (mBindGroup) {
|
||||
// We will keep getting hits from future updates, but shouldn't do anything
|
||||
// Filament guarantees this won't change after things have locked.
|
||||
// The bind group has already been created, so we can't add new entries.
|
||||
// Filament guarantees that the descriptor set will not be modified after it has been locked.
|
||||
return;
|
||||
}
|
||||
// TODO: Putting some level of trust that Filament is not going to reuse indexes or go past the
|
||||
// layout index for efficiency. Add guards if wrong.
|
||||
FILAMENT_CHECK_POSTCONDITION(index < mEntryIndexByBinding.size())
|
||||
<< "impossible/invalid index for a descriptor/binding (our of range or >= "
|
||||
<< "impossible/invalid index for a descriptor/binding (out of range or >= "
|
||||
"MAX_DESCRIPTOR_COUNT) "
|
||||
<< index;
|
||||
uint8_t entryIndex = mEntryIndexByBinding[index];
|
||||
FILAMENT_CHECK_POSTCONDITION(entryIndex != INVALID_INDEX && entryIndex < mEntries.size())
|
||||
<< "Invalid binding " << index;
|
||||
<< "Invalid binding index: " << index;
|
||||
|
||||
entry.binding = index;
|
||||
mEntries[entryIndex] = std::move(entry);
|
||||
}
|
||||
@@ -94,8 +97,9 @@ wgpu::BindGroup WebGPUDescriptorSet::lockAndReturn(wgpu::Device const& device) {
|
||||
.entries = mEntries.data()
|
||||
};
|
||||
mBindGroup = device.CreateBindGroup(&descriptor);
|
||||
FILAMENT_CHECK_POSTCONDITION(mBindGroup) << "Failed to create bind group?";
|
||||
// once we have created the bind group itself we should no longer need any other state
|
||||
FILAMENT_CHECK_POSTCONDITION(mBindGroup) << "Failed to create bind group.";
|
||||
|
||||
// Once the bind group is created, we can release the resources used to create it.
|
||||
#if FWGPU_ENABLED(FWGPU_DEBUG_BIND_GROUPS)
|
||||
FWGPU_LOGD << "WebGPUDescriptorSet (lockAndReturn):";
|
||||
FWGPU_LOGD << " wgpu::BindGroupLayout handle: " << mLayout.Get();
|
||||
|
||||
@@ -32,23 +32,46 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A WebGPU-specific implementation of DescriptorSet.
|
||||
* This class manages a collection of resource bindings (e.g., textures, buffers) that are used by
|
||||
* a shader. It corresponds to a `wgpu::BindGroup` in WebGPU. The actual `wgpu::BindGroup` is
|
||||
* created lazily when `lockAndReturn` is called, at which point the descriptor set becomes
|
||||
* immutable.
|
||||
*/
|
||||
class WebGPUDescriptorSet final : public HwDescriptorSet {
|
||||
public:
|
||||
WebGPUDescriptorSet(wgpu::BindGroupLayout const&,
|
||||
std::vector<WebGPUDescriptorSetLayout::BindGroupEntryInfo> const&);
|
||||
~WebGPUDescriptorSet();
|
||||
|
||||
/**
|
||||
* Adds or updates a resource binding in the descriptor set.
|
||||
* This method is called to populate the descriptor set with buffers, textures, and samplers.
|
||||
* It is only valid to call this before the descriptor set is locked (i.e., before
|
||||
* `lockAndReturn` is called).
|
||||
*/
|
||||
void addEntry(unsigned int index, wgpu::BindGroupEntry&& entry);
|
||||
|
||||
/**
|
||||
* Finalizes the descriptor set by creating the wgpu::BindGroup. After this call, the
|
||||
* descriptor set is considered "locked" and cannot be modified further.
|
||||
*/
|
||||
[[nodiscard]] wgpu::BindGroup lockAndReturn(wgpu::Device const&);
|
||||
|
||||
/**
|
||||
* @return true if the descriptor set has been locked (i.e., the wgpu::BindGroup has been
|
||||
* created).
|
||||
*/
|
||||
[[nodiscard]] bool getIsLocked() const { return mBindGroup != nullptr; }
|
||||
|
||||
[[nodiscard]] size_t getEntitiesWithDynamicOffsetsCount() const {
|
||||
return mEntriesWithDynamicOffsetsCount;
|
||||
}
|
||||
|
||||
// May be nullptr. Use lockAndReturn to create the bind group when appropriate
|
||||
/**
|
||||
* May be nullptr. Use lockAndReturn to create the bind group when appropriate
|
||||
*/
|
||||
[[nodiscard]] wgpu::BindGroup const& getBindGroup() const { return mBindGroup; }
|
||||
|
||||
#if FWGPU_ENABLED(FWGPU_DEBUG_BIND_GROUPS)
|
||||
@@ -60,6 +83,7 @@ private:
|
||||
std::array<uint8_t, MAX_DESCRIPTOR_COUNT> mEntryIndexByBinding{};
|
||||
std::vector<wgpu::BindGroupEntry> mEntries;
|
||||
const size_t mEntriesWithDynamicOffsetsCount;
|
||||
// This is created lazily when lockAndReturn is called.
|
||||
wgpu::BindGroup mBindGroup = nullptr;
|
||||
};
|
||||
|
||||
|
||||
@@ -40,7 +40,9 @@ namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
// Convert Filament Shader Stage Flags bitmask to webgpu equivalent
|
||||
/**
|
||||
* Converts Filament shader stage flags to the corresponding WebGPU shader stage bitmask.
|
||||
*/
|
||||
[[nodiscard]] wgpu::ShaderStage filamentStageToWGPUStage(const ShaderStageFlags fFlags) {
|
||||
wgpu::ShaderStage retStages = wgpu::ShaderStage::None;
|
||||
if (any(ShaderStageFlags::VERTEX & fFlags)) {
|
||||
@@ -70,10 +72,6 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
|
||||
baseLabel = temp->c_str();
|
||||
}
|
||||
|
||||
// TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for
|
||||
// debugging. For now, hack an incrementing value.
|
||||
static int layoutNum = 0;
|
||||
|
||||
const unsigned int samplerCount =
|
||||
std::count_if(layout.bindings.begin(), layout.bindings.end(), [](auto& fEntry) {
|
||||
return DescriptorSetLayoutBinding::isSampler(fEntry.type);
|
||||
@@ -87,6 +85,11 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
|
||||
auto& wEntry = wEntries.emplace_back();
|
||||
auto& entryInfo = mBindGroupEntries.emplace_back();
|
||||
wEntry.visibility = filamentStageToWGPUStage(fEntry.stageFlags);
|
||||
|
||||
// In WebGPU, textures and samplers are separate bindings.
|
||||
// We map the Filament binding index to two WebGPU binding indices:
|
||||
// - texture: binding * 2
|
||||
// - sampler: binding * 2 + 1
|
||||
wEntry.binding = fEntry.binding * 2;
|
||||
entryInfo.binding = wEntry.binding;
|
||||
|
||||
@@ -178,11 +181,13 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
|
||||
} else if (isCubeArrayTypeDescriptor(fEntry.type)) {
|
||||
wEntry.texture.viewDimension = wgpu::TextureViewDimension::CubeArray;
|
||||
}
|
||||
// fEntry.count is unused currently
|
||||
}
|
||||
std::string label = "layout_" + baseLabel + std::to_string(++layoutNum) ;
|
||||
// TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for
|
||||
// debugging. For now, hack an incrementing value.
|
||||
static int layoutNum = 0;
|
||||
std::string label = "layout_" + baseLabel + "_" + std::to_string(++layoutNum) ;
|
||||
const wgpu::BindGroupLayoutDescriptor layoutDescriptor{
|
||||
.label{label.c_str()}, // Use .c_str() if label needs to be const char*
|
||||
.label{label.c_str()},
|
||||
.entryCount = wEntries.size(),
|
||||
.entries = wEntries.data()
|
||||
};
|
||||
|
||||
@@ -27,6 +27,11 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A WebGPU implementation of the HwDescriptorSetLayout.
|
||||
* This class defines the layout of a descriptor set, which specifies the types and arrangement
|
||||
* of resources that can be bound to a pipeline.
|
||||
*/
|
||||
class WebGPUDescriptorSetLayout final : public HwDescriptorSetLayout {
|
||||
public:
|
||||
struct BindGroupEntryInfo final {
|
||||
|
||||
@@ -32,10 +32,12 @@
|
||||
#include "WebGPUTextureHelpers.h"
|
||||
#include "WebGPUVertexBuffer.h"
|
||||
#include "WebGPUVertexBufferInfo.h"
|
||||
#include "webgpu/utils/AsyncTaskCounter.h"
|
||||
#include <backend/platforms/WebGPUPlatform.h>
|
||||
|
||||
#include "CommandStreamDispatcher.h"
|
||||
#include "DriverBase.h"
|
||||
#include "SystraceProfile.h"
|
||||
#include "private/backend/Dispatcher.h"
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Handle.h>
|
||||
@@ -71,7 +73,7 @@ void setDefaultTargetFlags(WebGPURenderTarget& defaultRenderTarget,
|
||||
const wgpu::TextureFormat depthFormat{ swapChain.getDepthFormat() };
|
||||
TargetBufferFlags newTargetFlags{ filament::backend::TargetBufferFlags::NONE };
|
||||
|
||||
// Assuming Color always present in default render target.
|
||||
// Color is always assumed to be present in the default render target.
|
||||
newTargetFlags |= filament::backend::TargetBufferFlags::COLOR;
|
||||
if (depthFormat != wgpu::TextureFormat::Undefined) {
|
||||
if (hasDepth(depthFormat)) {
|
||||
@@ -86,6 +88,12 @@ void setDefaultTargetFlags(WebGPURenderTarget& defaultRenderTarget,
|
||||
|
||||
} // namespace
|
||||
|
||||
// The WebGPUDriver is the main entry point for the WebGPU backend. It is responsible for
|
||||
// creating and managing all WebGPU resources, and for submitting commands to the GPU.
|
||||
// It implements the Driver interface, which is the abstraction layer used by Filament's
|
||||
// renderer. The driver uses a handle-based system to manage resources, and it operates
|
||||
// asynchronously, with `create...S` methods for synchronous handle allocation and
|
||||
// `create...R` methods for resource creation on the backend thread.
|
||||
Driver* WebGPUDriver::create(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept {
|
||||
constexpr size_t defaultSize = FILAMENT_WEBGPU_HANDLE_ARENA_SIZE_IN_MB * 1024U * 1024U;
|
||||
Platform::DriverConfig validConfig {driverConfig};
|
||||
@@ -141,6 +149,7 @@ void WebGPUDriver::tick(int) {
|
||||
|
||||
void WebGPUDriver::beginFrame(int64_t monotonic_clock_ns,
|
||||
int64_t refreshIntervalNs, uint32_t frameId) {
|
||||
FWGPU_PROFILE_MARKER(PROFILE_NAME_BEGINFRAME);
|
||||
}
|
||||
|
||||
void WebGPUDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
|
||||
@@ -157,9 +166,11 @@ void WebGPUDriver::setPresentationTime(int64_t monotonic_clock_ns) {
|
||||
}
|
||||
|
||||
void WebGPUDriver::endFrame(const uint32_t /* frameId */) {
|
||||
FWGPU_PROFILE_MARKER(PROFILE_NAME_ENDFRAME);
|
||||
mPipelineLayoutCache.onFrameEnd();
|
||||
mPipelineCache.onFrameEnd();
|
||||
|
||||
// Clear the currently bound descriptor sets at the end of the frame.
|
||||
for (size_t i = 0; i < MAX_DESCRIPTOR_SET_COUNT; i++) {
|
||||
mCurrentDescriptorSets[i] = {};
|
||||
}
|
||||
@@ -169,10 +180,11 @@ void WebGPUDriver::endFrame(const uint32_t /* frameId */) {
|
||||
// A new command encoder is created fresh after it.
|
||||
// If a command encoder is not in flight (nullptr), then this is a noop.
|
||||
void WebGPUDriver::flush(int /* dummy */) {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
if (mCommandEncoder == nullptr) {
|
||||
return;
|
||||
}
|
||||
// submit the command buffer thus far...
|
||||
// Submit the commands recorded so far.
|
||||
assert_invariant(mRenderPassEncoder == nullptr);
|
||||
wgpu::CommandBufferDescriptor commandBufferDescriptor{
|
||||
.label = "frame_command_after_flush",
|
||||
@@ -189,12 +201,14 @@ void WebGPUDriver::flush(int /* dummy */) {
|
||||
assert_invariant(mCommandEncoder);
|
||||
}
|
||||
|
||||
// Submits the currently recorded commands and waits for them to complete on the GPU.
|
||||
// This is a synchronous operation and should be used sparingly.
|
||||
void WebGPUDriver::finish(int /* dummy */) {
|
||||
if (mCommandEncoder == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
mDevice.Tick();
|
||||
mAdapter.GetInstance().ProcessEvents();
|
||||
flush();
|
||||
// Wait for all previously submitted work to finish.
|
||||
std::mutex syncPoint;
|
||||
std::condition_variable syncCondition;
|
||||
bool done = false;
|
||||
@@ -211,36 +225,47 @@ void WebGPUDriver::finish(int /* dummy */) {
|
||||
});
|
||||
std::unique_lock<std::mutex> lock(syncPoint);
|
||||
syncCondition.wait(lock, [&done] { return done; });
|
||||
mReadPixelMapsCounter.waitForAllToFinish();
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
if (rph) {
|
||||
destructHandle<WebGPURenderPrimitive>(rph);
|
||||
if (!rph) {
|
||||
return;
|
||||
}
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
destructHandle<WebGPURenderPrimitive>(rph);
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyVertexBufferInfo(Handle<HwVertexBufferInfo> vertexBufferInfoHandle) {
|
||||
if (vertexBufferInfoHandle) {
|
||||
destructHandle<WebGPUVertexBufferInfo>(vertexBufferInfoHandle);
|
||||
if (!vertexBufferInfoHandle) {
|
||||
return;
|
||||
}
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
destructHandle<WebGPUVertexBufferInfo>(vertexBufferInfoHandle);
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyVertexBuffer(Handle<HwVertexBuffer> vertexBufferHandle) {
|
||||
if (vertexBufferHandle) {
|
||||
destructHandle<WebGPUVertexBuffer>(vertexBufferHandle);
|
||||
if (!vertexBufferHandle) {
|
||||
return;
|
||||
}
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
destructHandle<WebGPUVertexBuffer>(vertexBufferHandle);
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyIndexBuffer(Handle<HwIndexBuffer> indexBufferHandle) {
|
||||
if (indexBufferHandle) {
|
||||
destructHandle<WebGPUIndexBuffer>(indexBufferHandle);
|
||||
if (!indexBufferHandle) {
|
||||
return;
|
||||
}
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
destructHandle<WebGPUIndexBuffer>(indexBufferHandle);
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyBufferObject(Handle<HwBufferObject> bufferObjectHandle) {
|
||||
if (bufferObjectHandle) {
|
||||
destructHandle<WebGPUBufferObject>(bufferObjectHandle);
|
||||
if (!bufferObjectHandle) {
|
||||
return;
|
||||
}
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
destructHandle<WebGPUBufferObject>(bufferObjectHandle);
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyTexture(Handle<HwTexture> textureHandle) {
|
||||
@@ -264,10 +289,8 @@ void WebGPUDriver::destroyRenderTarget(Handle<HwRenderTarget> renderTargetHandle
|
||||
if (renderTarget == mCurrentRenderTarget) {
|
||||
mCurrentRenderTarget = nullptr;
|
||||
}
|
||||
// WGPURenderTarget destructor is trivial.
|
||||
// The HwTexture handles stored within WGPURenderTarget (via MRT, TargetBufferInfo)
|
||||
// are not owned by WGPURenderTarget, so they are not destroyed here.
|
||||
// They are destroyed via WebGPUDriver::destroyTexture.
|
||||
// The HwTexture handles stored within WGPURenderTarget are not owned by it,
|
||||
// so they are not destroyed here. They are destroyed via WebGPUDriver::destroyTexture.
|
||||
destructHandle<WebGPURenderTarget>(renderTargetHandle);
|
||||
}
|
||||
}
|
||||
@@ -312,6 +335,14 @@ void WebGPUDriver::destroyDescriptorSet(Handle<HwDescriptorSet> descriptorSetHan
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Synchronous handle creation methods (S-methods)
|
||||
//
|
||||
// These methods are called from the frontend thread to allocate a handle for a backend resource.
|
||||
// They are lightweight and do not perform any GPU operations. The actual resource creation
|
||||
// happens in the corresponding R-method on the backend thread.
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
Handle<HwSwapChain> WebGPUDriver::createSwapChainS() noexcept {
|
||||
return allocHandle<WebGPUSwapChain>();
|
||||
}
|
||||
@@ -396,9 +427,17 @@ Handle<HwTexture> WebGPUDriver::createTextureExternalImagePlaneS() noexcept {
|
||||
return allocHandle<WebGPUTexture>();
|
||||
}
|
||||
|
||||
void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Asynchronous resource creation methods (R-methods)
|
||||
//
|
||||
// These methods are called on the backend thread to create the actual GPU resources
|
||||
// associated with a handle that was previously allocated by an S-method.
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
|
||||
const uint64_t flags) {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
mNativeWindow = nativeWindow;
|
||||
assert_invariant(!mSwapChain);
|
||||
wgpu::Surface surface = mPlatform.createSurface(nativeWindow, flags);
|
||||
|
||||
wgpu::Extent2D extent = mPlatform.getSurfaceExtent(mNativeWindow);
|
||||
@@ -433,12 +472,14 @@ void WebGPUDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t wi
|
||||
|
||||
void WebGPUDriver::createVertexBufferInfoR(Handle<HwVertexBufferInfo> vertexBufferInfoHandle,
|
||||
const uint8_t bufferCount, const uint8_t attributeCount, const AttributeArray attributes) {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
constructHandle<WebGPUVertexBufferInfo>(vertexBufferInfoHandle, bufferCount, attributeCount,
|
||||
attributes, mDeviceLimits);
|
||||
}
|
||||
|
||||
void WebGPUDriver::createVertexBufferR(Handle<HwVertexBuffer> vertexBufferHandle,
|
||||
const uint32_t vertexCount, Handle<HwVertexBufferInfo> vertexBufferInfoHandle) {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
const auto vertexBufferInfo = handleCast<WebGPUVertexBufferInfo>(vertexBufferInfoHandle);
|
||||
constructHandle<WebGPUVertexBuffer>(vertexBufferHandle, vertexCount,
|
||||
vertexBufferInfo->bufferCount, vertexBufferInfoHandle);
|
||||
@@ -446,12 +487,14 @@ void WebGPUDriver::createVertexBufferR(Handle<HwVertexBuffer> vertexBufferHandle
|
||||
|
||||
void WebGPUDriver::createIndexBufferR(Handle<HwIndexBuffer> indexBufferHandle,
|
||||
const ElementType elementType, const uint32_t indexCount, const BufferUsage usage) {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
const auto elementSize = static_cast<uint8_t>(getElementTypeSize(elementType));
|
||||
constructHandle<WebGPUIndexBuffer>(indexBufferHandle, mDevice, elementSize, indexCount);
|
||||
}
|
||||
|
||||
void WebGPUDriver::createBufferObjectR(Handle<HwBufferObject> bufferObjectHandle,
|
||||
const uint32_t byteCount, const BufferObjectBinding bindingType, const BufferUsage usage) {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
constructHandle<WebGPUBufferObject>(bufferObjectHandle, mDevice, bindingType, byteCount);
|
||||
}
|
||||
|
||||
@@ -459,6 +502,7 @@ void WebGPUDriver::createTextureR(Handle<HwTexture> textureHandle, const Sampler
|
||||
const uint8_t levels, const TextureFormat format, const uint8_t samples,
|
||||
const uint32_t width, const uint32_t height, const uint32_t depth,
|
||||
const TextureUsage usage) {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
constructHandle<WebGPUTexture>(textureHandle, target, levels, format, samples, width, height,
|
||||
depth, usage, mDevice);
|
||||
}
|
||||
@@ -474,13 +518,47 @@ void WebGPUDriver::createTextureViewSwizzleR(Handle<HwTexture> textureHandle,
|
||||
Handle<HwTexture> sourceTextureHandle, const backend::TextureSwizzle r,
|
||||
const backend::TextureSwizzle g, const backend::TextureSwizzle b,
|
||||
const backend::TextureSwizzle a) {
|
||||
PANIC_POSTCONDITION("Swizzle WebGPU Texture is not supported");
|
||||
|
||||
if (!isTextureSwizzleSupported()) {
|
||||
FWGPU_LOGW << "WebGPUDriver::createTextureViewSwizzleR called while texture swizzling is "
|
||||
"not supported by the device/driver";
|
||||
}
|
||||
|
||||
auto sourceTexture{ handleCast<WebGPUTexture>(sourceTextureHandle) };
|
||||
assert_invariant(sourceTexture);
|
||||
|
||||
wgpu::TextureComponentSwizzle swizzle
|
||||
{
|
||||
.r = toWGPUComponentSwizzle(r),
|
||||
.g = toWGPUComponentSwizzle(g),
|
||||
.b = toWGPUComponentSwizzle(b),
|
||||
.a = toWGPUComponentSwizzle(a),
|
||||
};
|
||||
|
||||
wgpu::TextureComponentSwizzleDescriptor swizzleDesc {};
|
||||
swizzleDesc.swizzle = swizzle;
|
||||
|
||||
const wgpu::TextureViewDescriptor viewDesc {
|
||||
.nextInChain = &swizzleDesc,
|
||||
.label = "swizzled_texture_view",
|
||||
.format = sourceTexture->getTexture().GetFormat(),
|
||||
.dimension = sourceTexture->getViewDimension(),
|
||||
.baseMipLevel = 0,
|
||||
.mipLevelCount = sourceTexture->getTexture().GetMipLevelCount(),
|
||||
.baseArrayLayer = 0,
|
||||
.arrayLayerCount = sourceTexture->getTexture().GetDepthOrArrayLayers(),
|
||||
};
|
||||
|
||||
wgpu::TextureView swizzledView{ sourceTexture->getTexture().CreateView(&viewDesc) };
|
||||
FILAMENT_CHECK_POSTCONDITION(swizzledView) << "Failed to create swizzled Texture view";
|
||||
constructHandle<WebGPUTexture>(textureHandle, sourceTexture, swizzledView);
|
||||
}
|
||||
|
||||
void WebGPUDriver::createTextureExternalImage2R(Handle<HwTexture> textureHandle,
|
||||
const backend::SamplerType target, const backend::TextureFormat format,
|
||||
const uint32_t width, const uint32_t height, const backend::TextureUsage usage,
|
||||
Platform::ExternalImageHandleRef externalImage) {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
PANIC_POSTCONDITION("External WebGPU Texture is not supported");
|
||||
}
|
||||
|
||||
@@ -488,6 +566,7 @@ void WebGPUDriver::createTextureExternalImageR(Handle<HwTexture> textureHandle,
|
||||
const backend::SamplerType target, const backend::TextureFormat format,
|
||||
const uint32_t width, const uint32_t height, const backend::TextureUsage usage,
|
||||
void* externalImage) {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
PANIC_POSTCONDITION("External WebGPU Texture is not supported");
|
||||
}
|
||||
|
||||
@@ -507,6 +586,7 @@ void WebGPUDriver::importTextureR(Handle<HwTexture> textureHandle, const intptr_
|
||||
void WebGPUDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> renderPrimitiveHandle,
|
||||
Handle<HwVertexBuffer> vertexBufferHandle, Handle<HwIndexBuffer> indexBufferHandle,
|
||||
const PrimitiveType primitiveType) {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
assert_invariant(mDevice);
|
||||
const auto renderPrimitive = constructHandle<WebGPURenderPrimitive>(renderPrimitiveHandle);
|
||||
const auto vertexBuffer = handleCast<WebGPUVertexBuffer>(vertexBufferHandle);
|
||||
@@ -517,6 +597,7 @@ void WebGPUDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> renderPrimit
|
||||
}
|
||||
|
||||
void WebGPUDriver::createProgramR(Handle<HwProgram> programHandle, Program&& program) {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
constructHandle<WebGPUProgram>(programHandle, mDevice, program);
|
||||
}
|
||||
|
||||
@@ -534,6 +615,7 @@ void WebGPUDriver::createRenderTargetR(Handle<HwRenderTarget> renderTargetHandle
|
||||
const TargetBufferFlags targetFlags, const uint32_t width, const uint32_t height,
|
||||
const uint8_t samples, const uint8_t layerCount, const MRT color,
|
||||
const TargetBufferInfo depth, const TargetBufferInfo stencil) {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
constructHandle<WebGPURenderTarget>(
|
||||
renderTargetHandle, width, height, samples, layerCount, color, depth, stencil,
|
||||
targetFlags,
|
||||
@@ -544,7 +626,7 @@ void WebGPUDriver::createRenderTargetR(Handle<HwRenderTarget> renderTargetHandle
|
||||
}
|
||||
|
||||
void WebGPUDriver::createFenceR(Handle<HwFence> fenceHandle, const int /* dummy */) {
|
||||
// the handle was already constructed in createFenceS
|
||||
// The handle is constructed synchronously in createFenceS.
|
||||
const auto fence = handleCast<WebGPUFence>(fenceHandle);
|
||||
assert_invariant(mQueue);
|
||||
fence->addMarkerToQueueState(mQueue);
|
||||
@@ -555,11 +637,13 @@ void WebGPUDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {}
|
||||
void WebGPUDriver::createDescriptorSetLayoutR(
|
||||
Handle<HwDescriptorSetLayout> descriptorSetLayoutHandle,
|
||||
backend::DescriptorSetLayout&& info) {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
constructHandle<WebGPUDescriptorSetLayout>(descriptorSetLayoutHandle, std::move(info), mDevice);
|
||||
}
|
||||
|
||||
void WebGPUDriver::createDescriptorSetR(Handle<HwDescriptorSet> descriptorSetHandle,
|
||||
Handle<HwDescriptorSetLayout> descriptorSetLayoutHandle) {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
auto layout = handleCast<WebGPUDescriptorSetLayout>(descriptorSetLayoutHandle);
|
||||
constructHandle<WebGPUDescriptorSet>(descriptorSetHandle, layout->getLayout(),
|
||||
layout->getBindGroupEntries());
|
||||
@@ -614,14 +698,12 @@ FenceStatus WebGPUDriver::getFenceStatus(Handle<HwFence> fenceHandle) {
|
||||
return fence->getStatus();
|
||||
}
|
||||
|
||||
// We create all textures using VK_IMAGE_TILING_OPTIMAL, so our definition of "supported" is that
|
||||
// the GPU supports the given texture format with non-zero optimal tiling features.
|
||||
bool WebGPUDriver::isTextureFormatSupported(const TextureFormat format) {
|
||||
return toWGPUTextureFormat(format) != wgpu::TextureFormat::Undefined;
|
||||
}
|
||||
|
||||
bool WebGPUDriver::isTextureSwizzleSupported() {
|
||||
return false;
|
||||
return mDevice.HasFeature(wgpu::FeatureName::TextureComponentSwizzle);
|
||||
}
|
||||
|
||||
bool WebGPUDriver::isTextureFormatMipmappable(const TextureFormat format) {
|
||||
@@ -796,6 +878,10 @@ void WebGPUDriver::setVertexBufferObject(Handle<HwVertexBuffer> vertexBufferHand
|
||||
vertexBuffer->getBuffers()[index] = bufferObject->getBuffer();
|
||||
}
|
||||
|
||||
// Updates a 3D texture region with pixel data from a buffer.
|
||||
// This function handles various pixel formats and performs a blit operation if the source
|
||||
// format needs to be converted to the destination format. For compressed textures, it ensures
|
||||
// that the offsets are aligned to the block dimensions.
|
||||
void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t level,
|
||||
const uint32_t xoffset, const uint32_t yoffset, const uint32_t zoffset,
|
||||
const uint32_t width, const uint32_t height, const uint32_t depth,
|
||||
@@ -812,15 +898,15 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
|
||||
const auto texture{ handleCast<WebGPUTexture>(textureHandle) };
|
||||
FILAMENT_CHECK_PRECONDITION((texture->getTexture().GetWidth() + xoffset) >= width)
|
||||
<< "Blitting requires the destination region to have enough width ("
|
||||
<< texture->getTexture().GetWidth() << " to accommodate the requested " << width
|
||||
<< texture->getTexture().GetWidth() << ") to accommodate the requested " << width
|
||||
<< " width to write/blit (accounting for xoffset, which is " << xoffset << ").";
|
||||
FILAMENT_CHECK_PRECONDITION((texture->getTexture().GetHeight() + yoffset) >= height)
|
||||
<< "Blitting requires the destination region to have enough height ("
|
||||
<< texture->getTexture().GetHeight() << " to accommodate the requested " << height
|
||||
<< texture->getTexture().GetHeight() << ") to accommodate the requested " << height
|
||||
<< " height to write/blit (accounting for yoffset, which is " << yoffset << ").";
|
||||
FILAMENT_CHECK_PRECONDITION((texture->getTexture().GetDepthOrArrayLayers() + zoffset) >= depth)
|
||||
<< "Blitting requires the destination region to have enough depth/arrayLayers ("
|
||||
<< texture->getTexture().GetDepthOrArrayLayers() << " to accommodate the requested "
|
||||
<< texture->getTexture().GetDepthOrArrayLayers() << ") to accommodate the requested "
|
||||
<< depth << " depth to write/blit (accounting for zoffset, which is " << zoffset << ").";
|
||||
|
||||
// TODO: Writing to a depth texture is illegal and errors. I'm not sure why Filament is trying
|
||||
@@ -833,10 +919,8 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
|
||||
inputData->type) };
|
||||
const wgpu::TextureFormat outputLinearFormat{ toLinearFormat(
|
||||
texture->getTexture().GetFormat()) };
|
||||
const bool conversionNecessary{
|
||||
inputPixelFormat != outputLinearFormat && inputData->type != PixelDataType::COMPRESSED
|
||||
}; // compressed formats should never need conversion
|
||||
const bool doBlit{ conversionNecessary };
|
||||
|
||||
const bool doBlit{ conversionNecessary(inputPixelFormat, outputLinearFormat, inputData->type) };
|
||||
#if FWGPU_ENABLED(FWGPU_DEBUG_UPDATE_IMAGE)
|
||||
if (texture->width > 1000 && texture->height > 500) {
|
||||
FWGPU_LOGD << "Update3DImage(..., level=" << level << ", xoffset=" << xoffset
|
||||
@@ -901,7 +985,7 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
|
||||
<< "Failed to create staging input texture for blit?";
|
||||
const auto copyInfo{ wgpu::TexelCopyTextureInfo{
|
||||
.texture = stagingTexture,
|
||||
.mipLevel = level,
|
||||
.mipLevel = 0,
|
||||
.origin = { .x = 0, .y = 0, .z = 0 },
|
||||
.aspect = texture->getAspect(),
|
||||
} };
|
||||
@@ -956,9 +1040,8 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
|
||||
mQueue.Submit(1, &blitCommand);
|
||||
mCommandEncoder = nullptr;
|
||||
}
|
||||
stagingTexture.Destroy();
|
||||
} else {
|
||||
// not doing blit (copy byte-by-byte)...
|
||||
// Direct copy without a blit.
|
||||
const auto copyInfo { wgpu::TexelCopyTextureInfo{
|
||||
.texture = texture->getTexture(),
|
||||
.mipLevel = level,
|
||||
@@ -1070,10 +1153,15 @@ void WebGPUDriver::compilePrograms(CompilerPriorityQueue priority,
|
||||
}
|
||||
}
|
||||
|
||||
// Begins a render pass. This sets up the render pass descriptor with the appropriate
|
||||
// color and depth/stencil attachments, based on whether the target is the default
|
||||
// swap chain render target or a custom one. It also handles MSAA by setting up
|
||||
// resolve targets if necessary.
|
||||
void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
|
||||
RenderPassParams const& params) {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
if (!mCommandEncoder) {
|
||||
wgpu::CommandEncoderDescriptor commandEncoderDescriptor = { .label = "frame_command_encoder" };
|
||||
wgpu::CommandEncoderDescriptor commandEncoderDescriptor{ .label = "frame_command_encoder" };
|
||||
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
|
||||
}
|
||||
assert_invariant(mCommandEncoder);
|
||||
@@ -1225,6 +1313,7 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
|
||||
}
|
||||
|
||||
void WebGPUDriver::endRenderPass(int /* dummy */) {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
mRenderPassEncoder.End();
|
||||
mRenderPassEncoder = nullptr;
|
||||
}
|
||||
@@ -1233,10 +1322,12 @@ void WebGPUDriver::nextSubpass(int) {
|
||||
//todo
|
||||
}
|
||||
|
||||
void WebGPUDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain> readSch) {
|
||||
ASSERT_PRECONDITION_NON_FATAL(drawSch == readSch,
|
||||
void WebGPUDriver::makeCurrent(Handle<HwSwapChain> drawSwapChain,
|
||||
Handle<HwSwapChain> readSwapChain) {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
ASSERT_PRECONDITION_NON_FATAL(drawSwapChain == readSwapChain,
|
||||
"WebGPU driver does not support distinct draw/read swap chains.");
|
||||
auto swapChain = handleCast<WebGPUSwapChain>(drawSch);
|
||||
auto swapChain = handleCast<WebGPUSwapChain>(drawSwapChain);
|
||||
mSwapChain = swapChain;
|
||||
assert_invariant(mSwapChain);
|
||||
|
||||
@@ -1249,13 +1340,16 @@ void WebGPUDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain>
|
||||
assert_invariant(mTextureView);
|
||||
|
||||
if (!mCommandEncoder) {
|
||||
wgpu::CommandEncoderDescriptor commandEncoderDescriptor = { .label = "frame_command_encoder" };
|
||||
wgpu::CommandEncoderDescriptor commandEncoderDescriptor = {
|
||||
.label = "frame_command_encoder"
|
||||
};
|
||||
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
|
||||
}
|
||||
assert_invariant(mCommandEncoder);
|
||||
}
|
||||
|
||||
void WebGPUDriver::commit(Handle<HwSwapChain> sch) {
|
||||
void WebGPUDriver::commit(Handle<HwSwapChain> swapChainHandle) {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
if (UTILS_VERY_UNLIKELY(!mCommandEncoder)) {
|
||||
// nothing to submit
|
||||
if (UTILS_LIKELY(mSwapChain)) {
|
||||
@@ -1306,34 +1400,41 @@ void WebGPUDriver::insertEventMarker(char const* string) {
|
||||
}
|
||||
|
||||
void WebGPUDriver::pushGroupMarker(char const* string) {
|
||||
FWGPU_SYSTRACE_CONTEXT();
|
||||
FWGPU_SYSTRACE_START(string);
|
||||
}
|
||||
|
||||
void WebGPUDriver::popGroupMarker(int /* dummy */) {
|
||||
FWGPU_SYSTRACE_CONTEXT();
|
||||
FWGPU_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void WebGPUDriver::startCapture(int /* dummy */) {
|
||||
//todo
|
||||
}
|
||||
|
||||
void WebGPUDriver::popGroupMarker(int) {
|
||||
//todo
|
||||
}
|
||||
|
||||
void WebGPUDriver::startCapture(int) {
|
||||
//todo
|
||||
}
|
||||
|
||||
void WebGPUDriver::stopCapture(int) {
|
||||
void WebGPUDriver::stopCapture(int /* dummy */) {
|
||||
//todo
|
||||
}
|
||||
|
||||
// Reads a block of pixels from a render target into a buffer.
|
||||
// This function is asynchronous. It copies the pixel data to a staging buffer on the GPU,
|
||||
// and then maps the buffer for reading on the CPU. The provided callback is invoked when the
|
||||
// data is ready. This function also handles the 256-byte row alignment requirement for
|
||||
// buffer-to-texture copies in WebGPU.
|
||||
void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, const uint32_t x,
|
||||
const uint32_t y, const uint32_t width, const uint32_t height,
|
||||
PixelBufferDescriptor&& pixelBufferDescriptor) {
|
||||
auto srcTarget = handleCast<WebGPURenderTarget>(sourceRenderTargetHandle);
|
||||
const auto srcTarget{ handleCast<WebGPURenderTarget>(sourceRenderTargetHandle) };
|
||||
assert_invariant(srcTarget);
|
||||
|
||||
wgpu::Texture srcTexture = nullptr;
|
||||
wgpu::Texture srcTexture{ nullptr };
|
||||
if (srcTarget->isDefaultRenderTarget()) {
|
||||
assert_invariant(mSwapChain);
|
||||
srcTexture = mSwapChain->getCurrentTexture();
|
||||
} else {
|
||||
// Handle custom render targets. Read from the first color attachment.
|
||||
const auto& colorAttachmentInfos = srcTarget->getColorAttachmentInfos();
|
||||
const auto& colorAttachmentInfos{ srcTarget->getColorAttachmentInfos() };
|
||||
// TODO we are currently assuming the first attachment is the desired texture.
|
||||
if (colorAttachmentInfos[0].handle) {
|
||||
auto texture = handleCast<WebGPUTexture>(colorAttachmentInfos[0].handle);
|
||||
@@ -1348,116 +1449,172 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, c
|
||||
scheduleDestroy(std::move(pixelBufferDescriptor));
|
||||
return;
|
||||
}
|
||||
const uint32_t srcWidth = srcTexture.GetWidth();
|
||||
const uint32_t srcHeight = srcTexture.GetHeight();
|
||||
const uint32_t srcWidth {srcTexture.GetWidth()};
|
||||
const uint32_t srcHeight{srcTexture.GetHeight()};
|
||||
|
||||
// Clamp read region to texture bounds
|
||||
if (x >= srcWidth || y >= srcHeight) {
|
||||
if (UTILS_UNLIKELY(x >= srcWidth || y >= srcHeight)) {
|
||||
scheduleDestroy(std::move(pixelBufferDescriptor));
|
||||
return;
|
||||
}
|
||||
auto actualWidth = std::min(width, srcWidth - x);
|
||||
auto actualHeight = std::min(height, srcHeight - y);
|
||||
auto actualWidth{ std::min(width, srcWidth - x) };
|
||||
auto actualHeight{ std::min(height, srcHeight - y)};
|
||||
if (UTILS_UNLIKELY(actualWidth == 0 || actualHeight == 0)) {
|
||||
scheduleDestroy(std::move(pixelBufferDescriptor));
|
||||
return;
|
||||
}
|
||||
|
||||
// Once we're ready to read the pixels, we need to flush all the previous work of this frame.
|
||||
// This ensures that the readPixels will be ordered after all the draws.
|
||||
flush();
|
||||
if (!mCommandEncoder) {
|
||||
const wgpu::CommandEncoderDescriptor commandEncoderDescriptor{
|
||||
.label = "read_pixels_command",
|
||||
};
|
||||
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
|
||||
FILAMENT_CHECK_POSTCONDITION(mCommandEncoder)
|
||||
<< "Failed to create command encoder for readPixels?";
|
||||
}
|
||||
|
||||
const size_t dstBytesPerPixel = PixelBufferDescriptor::computePixelSize(
|
||||
pixelBufferDescriptor.format, pixelBufferDescriptor.type);
|
||||
const size_t srcBytesPerPixel = getWGPUTextureFormatPixelSize(srcTexture.GetFormat());
|
||||
const wgpu::TextureFormat srcFormat{srcTexture.GetFormat()};
|
||||
const wgpu::TextureFormat dstFormat{toWebGPUFormat(pixelBufferDescriptor.format,
|
||||
pixelBufferDescriptor.type)};
|
||||
wgpu::Texture textureToReadFrom{srcTexture};
|
||||
wgpu::Texture stagingTexture{ nullptr };
|
||||
uint32_t readX{x};
|
||||
uint32_t readY{y};
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(dstBytesPerPixel == srcBytesPerPixel && dstBytesPerPixel > 0)
|
||||
<< "Source texture pixel size (" << srcBytesPerPixel
|
||||
<< ") does not match destination pixel buffer pixel size (" << dstBytesPerPixel
|
||||
<< "), or the format is not supported for readPixels.";
|
||||
// If the source format is different from the destination (e.g. BGRA vs RGBA),
|
||||
// we need to perform a conversion using an intermediate blit.
|
||||
if (conversionNecessary(srcFormat, dstFormat, pixelBufferDescriptor.type)) {
|
||||
const wgpu::TextureDescriptor stagingDescriptor{
|
||||
.label = "readpixels_staging_texture",
|
||||
.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment,
|
||||
.dimension = wgpu::TextureDimension::e2D, // are we sure we can assume this? thus far we have
|
||||
.size = {
|
||||
.width = actualWidth,
|
||||
.height = actualHeight,
|
||||
.depthOrArrayLayers = 1,
|
||||
},
|
||||
.format = dstFormat,
|
||||
.mipLevelCount = 1,
|
||||
.sampleCount = srcTexture.GetSampleCount(),
|
||||
};
|
||||
stagingTexture = mDevice.CreateTexture(&stagingDescriptor);
|
||||
assert_invariant(stagingTexture);
|
||||
textureToReadFrom = stagingTexture;
|
||||
const WebGPUBlitter::BlitArgs blitArgs{
|
||||
.source = {
|
||||
.texture = srcTexture,
|
||||
.origin = {.x = x, .y = y,},
|
||||
.extent = {
|
||||
.width = actualWidth,
|
||||
.height = actualHeight,
|
||||
},
|
||||
},
|
||||
.destination = {
|
||||
.texture = stagingTexture,
|
||||
.origin = {.x = 0, .y = 0},
|
||||
.extent = {
|
||||
.width = actualWidth,
|
||||
.height = actualHeight,
|
||||
},
|
||||
},
|
||||
.filter = SamplerMagFilter::NEAREST, // Use NEAREST for a 1:1 copy
|
||||
};
|
||||
mBlitter.blit(mQueue, mCommandEncoder, blitArgs);
|
||||
|
||||
// Create a staging buffer to copy the texture to. WebGPU requires 256 byte alignment
|
||||
const size_t bytesPerPixel = dstBytesPerPixel;
|
||||
const size_t unpaddedBytesPerRow = actualWidth * bytesPerPixel;
|
||||
const size_t alignment = 256;
|
||||
const size_t paddedBytesPerRow = (unpaddedBytesPerRow + alignment - 1) & ~(alignment - 1);
|
||||
// The subsequent read will be from the top-left of the new intermediate texture.
|
||||
readX = 0;
|
||||
readY = 0;
|
||||
}
|
||||
|
||||
size_t bufferSize = paddedBytesPerRow * height;
|
||||
wgpu::BufferDescriptor bufferDesc;
|
||||
bufferDesc.size = bufferSize;
|
||||
bufferDesc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead;
|
||||
wgpu::Buffer stagingBuffer = mDevice.CreateBuffer(&bufferDesc);
|
||||
// Create a staging buffer to copy the texture to. WebGPU requires 256 byte alignment for buffer-to-texture copies.
|
||||
const size_t bytesPerPixel{ PixelBufferDescriptor::computePixelSize(
|
||||
pixelBufferDescriptor.format, pixelBufferDescriptor.type) };
|
||||
const size_t unpaddedBytesPerRow{ actualWidth * bytesPerPixel };
|
||||
const size_t alignment{ 256 };
|
||||
const size_t paddedBytesPerRow = { (unpaddedBytesPerRow + alignment - 1) & ~(alignment - 1) };
|
||||
|
||||
const size_t bufferSize{ paddedBytesPerRow * actualHeight };
|
||||
const wgpu::BufferDescriptor bufferDesc{
|
||||
.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead,
|
||||
.size = bufferSize,
|
||||
};
|
||||
|
||||
wgpu::Buffer stagingBuffer{ mDevice.CreateBuffer(&bufferDesc) };
|
||||
assert_invariant(stagingBuffer);
|
||||
|
||||
wgpu::CommandEncoder encoder = mDevice.CreateCommandEncoder();
|
||||
assert_invariant(encoder);
|
||||
// WebGPU's texture coordinates for copies are top-left, but Filament's y-coordinate is
|
||||
// bottom-left. We must flip the y-coordinate relative to the texture we are reading from.
|
||||
const uint32_t textureHeight{ textureToReadFrom.GetHeight() };
|
||||
const uint32_t flippedY{ textureHeight - readY - actualHeight };
|
||||
|
||||
// WebGPU flips the y axis like Metal does
|
||||
const uint32_t flippedY = srcHeight - y - height;
|
||||
wgpu::TexelCopyTextureInfo source{
|
||||
.texture = srcTexture,
|
||||
.mipLevel = 0,
|
||||
.origin = { .x = x, .y = flippedY, .z = 0 },
|
||||
const wgpu::TexelCopyTextureInfo source{
|
||||
.texture = textureToReadFrom, // Read from the original or intermediate texture
|
||||
.mipLevel = 0,
|
||||
.origin = {.x = readX, .y = flippedY, .z = 0,},
|
||||
};
|
||||
wgpu::TexelCopyBufferInfo destination {
|
||||
.layout = {
|
||||
.offset = 0,
|
||||
.bytesPerRow = static_cast<uint32_t>(paddedBytesPerRow),
|
||||
.rowsPerImage = actualHeight,
|
||||
},
|
||||
.buffer = stagingBuffer
|
||||
const wgpu::TexelCopyBufferInfo destination{
|
||||
.layout = {
|
||||
.offset = 0,
|
||||
.bytesPerRow = static_cast<uint32_t>(paddedBytesPerRow),
|
||||
.rowsPerImage = actualHeight,
|
||||
},
|
||||
.buffer = stagingBuffer
|
||||
};
|
||||
wgpu::Extent3D copySize{ .width = actualWidth,
|
||||
const wgpu::Extent3D copySize{
|
||||
.width = actualWidth,
|
||||
.height = actualHeight,
|
||||
.depthOrArrayLayers = 1 };
|
||||
// TODO consider removing encoder finish + submit once we have test coverage and can
|
||||
// safely verify doing so will not result in strange errors. We don't think this
|
||||
// submit is necessary, as the encoder should get finished and the command
|
||||
// submitted in commit(), finish(), and/or flush() in this frame
|
||||
// or the next anyway. And, the read/copy bits will be encoded before other things.
|
||||
// Thus, not submitting here _seems_ ok. Nonetheless, doing the submit here likely
|
||||
// represents unnecessary inefficiency and higher bandwidth
|
||||
// between the CPU and GPU, but we are worried about functional correctness at this
|
||||
// stage.
|
||||
encoder.CopyTextureToBuffer(&source, &destination, ©Size);
|
||||
wgpu::CommandBuffer commandBuffer = encoder.Finish();
|
||||
mQueue.Submit(1, &commandBuffer);
|
||||
.depthOrArrayLayers = 1,
|
||||
};
|
||||
mCommandEncoder.CopyTextureToBuffer(&source, &destination, ©Size);
|
||||
mCommandBuffer = mCommandEncoder.Finish();
|
||||
assert_invariant(mCommandBuffer);
|
||||
mCommandEncoder = nullptr;
|
||||
mQueue.Submit(1, &mCommandBuffer);
|
||||
mCommandBuffer = nullptr;
|
||||
|
||||
// Map the buffer to read the data
|
||||
struct UserData {
|
||||
struct UserData final {
|
||||
PixelBufferDescriptor pixelBufferDescriptor;
|
||||
wgpu::Buffer buffer;
|
||||
size_t unpaddedBytesPerRow;
|
||||
size_t paddedBytesPerRow;
|
||||
uint32_t height;
|
||||
WebGPUDriver* driver;
|
||||
WebGPUDriver *driver;
|
||||
};
|
||||
auto userData = std::make_unique<UserData>(UserData{
|
||||
.pixelBufferDescriptor = std::move(pixelBufferDescriptor),
|
||||
.buffer = std::move(stagingBuffer),
|
||||
.unpaddedBytesPerRow = unpaddedBytesPerRow,
|
||||
.paddedBytesPerRow = paddedBytesPerRow,
|
||||
.height = height,
|
||||
.driver = this,
|
||||
.pixelBufferDescriptor = std::move(pixelBufferDescriptor),
|
||||
.buffer = std::move(stagingBuffer),
|
||||
.unpaddedBytesPerRow = unpaddedBytesPerRow,
|
||||
.paddedBytesPerRow = paddedBytesPerRow,
|
||||
.height = actualHeight,
|
||||
.driver = this,
|
||||
});
|
||||
|
||||
mReadPixelMapsCounter.startTask();
|
||||
userData->buffer.MapAsync(
|
||||
wgpu::MapMode::Read, 0, bufferSize, wgpu::CallbackMode::AllowSpontaneous,
|
||||
[](wgpu::MapAsyncStatus status, const char* message, UserData* userdata) {
|
||||
std::unique_ptr<UserData> data(static_cast<UserData*>(userdata));
|
||||
if (UTILS_LIKELY(status == wgpu::MapAsyncStatus::Success)) {
|
||||
const char* src = static_cast<const char*>(
|
||||
data->buffer.GetConstMappedRange(0, data->buffer.GetSize()));
|
||||
char* dst = static_cast<char*>(data->pixelBufferDescriptor.buffer);
|
||||
for (uint32_t i = 0; i < data->height; ++i) {
|
||||
memcpy(dst + i * data->unpaddedBytesPerRow,
|
||||
src + i * data->paddedBytesPerRow, data->unpaddedBytesPerRow);
|
||||
const char *src {static_cast<const char *>(
|
||||
data->buffer.GetConstMappedRange(0, data->buffer.GetSize()))};
|
||||
char* dst{ static_cast<char*>(data->pixelBufferDescriptor.buffer) };
|
||||
|
||||
// If padding was added for alignment, we need to copy row by row.
|
||||
if (data->paddedBytesPerRow == data->unpaddedBytesPerRow) {
|
||||
memcpy(dst, src, data->unpaddedBytesPerRow * data->height);
|
||||
} else {
|
||||
for (uint32_t i{ 0 }; i < data->height; ++i) {
|
||||
memcpy(dst + i * data->unpaddedBytesPerRow,
|
||||
src + i * data->paddedBytesPerRow, data->unpaddedBytesPerRow);
|
||||
}
|
||||
}
|
||||
data->buffer.Unmap();
|
||||
} else {
|
||||
FWGPU_LOGE << "Failed to map staging buffer for readPixels: " << message;
|
||||
}
|
||||
data->driver->scheduleDestroy(std::move(data->pixelBufferDescriptor));
|
||||
data->driver->mReadPixelMapsCounter.finishTask();
|
||||
},
|
||||
userData.release());
|
||||
}
|
||||
@@ -1472,14 +1629,99 @@ void WebGPUDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
Handle<HwRenderTarget> destinationRenderTargetHandle, const Viewport destinationViewport,
|
||||
Handle<HwRenderTarget> sourceRenderTargetHandle, const Viewport sourceViewport,
|
||||
const SamplerMagFilter filter) {
|
||||
PANIC_PRECONDITION("WebGPUDriver::blitDEPRECATED not supported");
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
auto const sourceTarget{ handleCast<WebGPURenderTarget>(sourceRenderTargetHandle) };
|
||||
auto const destinationTarget{ handleCast<WebGPURenderTarget>(destinationRenderTargetHandle) };
|
||||
assert_invariant(sourceTarget && destinationTarget);
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(buffers == TargetBufferFlags::COLOR0)
|
||||
<< "blitDEPRECATED only supports COLOR0";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(sourceViewport.left >= 0 && sourceViewport.bottom >= 0 &&
|
||||
destinationViewport.left >= 0 && destinationViewport.bottom >= 0)
|
||||
<< "Source and destination viewports must be positive.";
|
||||
|
||||
// We always blit from/to the COLOR0 attachment.
|
||||
auto sourceAttachment {sourceTarget->getColorAttachmentInfos()[0]};
|
||||
Handle<HwTexture> sourceTextureHandle{ sourceAttachment.handle };
|
||||
auto destinationAttachment {destinationTarget->getColorAttachmentInfos()[0]};
|
||||
Handle<HwTexture> destinationTextureHandle{
|
||||
destinationAttachment.handle
|
||||
};
|
||||
|
||||
if (UTILS_UNLIKELY(!sourceTextureHandle || !destinationTextureHandle)) {
|
||||
FWGPU_LOGE << "blitDEPRECATED could not find a valid color attachment to blit.";
|
||||
return;
|
||||
}
|
||||
|
||||
// WebGPU's texture coordinates are top-left, while Filament's are bottom-left.
|
||||
// We need to flip the y-coordinate for both source and destination.
|
||||
auto const sourceTexture{ handleCast<WebGPUTexture>(sourceTextureHandle) };
|
||||
auto const destinationTexture{ handleCast<WebGPUTexture>(destinationTextureHandle) };
|
||||
|
||||
const uint32_t sourceTextureHeight{ sourceTexture->height };
|
||||
const uint32_t flippedSourceY{ sourceTextureHeight - sourceViewport.bottom -
|
||||
sourceViewport.height };
|
||||
|
||||
const uint32_t destinationTextureHeight{ destinationTexture->height };
|
||||
const uint32_t flippedDestinationY{ destinationTextureHeight - destinationViewport.bottom -
|
||||
destinationViewport.height };
|
||||
|
||||
const wgpu::Origin2D sourceOrigin{ static_cast<uint32_t>(sourceViewport.left), flippedSourceY };
|
||||
const wgpu::Origin2D destinationOrigin{ static_cast<uint32_t>(destinationViewport.left),
|
||||
flippedDestinationY };
|
||||
|
||||
const wgpu::Extent2D sourceSize{ static_cast<uint32_t>(sourceViewport.width),
|
||||
static_cast<uint32_t>(sourceViewport.height) };
|
||||
const wgpu::Extent2D destinationSize{ static_cast<uint32_t>(destinationViewport.width),
|
||||
static_cast<uint32_t>(destinationViewport.height) };
|
||||
|
||||
bool reusedCommandEncoder{ true };
|
||||
if (mCommandEncoder) {
|
||||
flush();
|
||||
} else {
|
||||
reusedCommandEncoder = false;
|
||||
const wgpu::CommandEncoderDescriptor desc{ .label = "blit_deprecated_command" };
|
||||
mCommandEncoder = mDevice.CreateCommandEncoder(&desc);
|
||||
}
|
||||
|
||||
const WebGPUBlitter::BlitArgs blitArgs{
|
||||
.source = { .texture = sourceTexture->getTexture(),
|
||||
.aspect = sourceTexture->getAspect(),
|
||||
.origin = sourceOrigin,
|
||||
.extent = sourceSize,
|
||||
.mipLevel = sourceAttachment.level,
|
||||
.layerOrDepth = sourceAttachment.layer,
|
||||
},
|
||||
.destination = { .texture = destinationTexture->getTexture(),
|
||||
.aspect = destinationTexture->getAspect(),
|
||||
.origin = destinationOrigin,
|
||||
.extent = destinationSize,
|
||||
.mipLevel = destinationAttachment.level,
|
||||
.layerOrDepth = destinationAttachment.layer,
|
||||
},
|
||||
.filter = filter,
|
||||
};
|
||||
mBlitter.blit(mQueue, mCommandEncoder, blitArgs);
|
||||
|
||||
if (!reusedCommandEncoder) {
|
||||
const wgpu::CommandBufferDescriptor desc{ .label = "blit_deprecated_command_buffer" };
|
||||
const wgpu::CommandBuffer blitCommand{ mCommandEncoder.Finish(&desc) };
|
||||
mQueue.Submit(1, &blitCommand);
|
||||
mCommandEncoder = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void WebGPUDriver::resolve(Handle<HwTexture> destinationTextureHandle, const uint8_t sourceLevel,
|
||||
const uint8_t sourceLayer, Handle<HwTexture> sourceTextureHandle,
|
||||
const uint8_t destinationLevel, const uint8_t destinationLayer) {
|
||||
FILAMENT_CHECK_PRECONDITION(mCommandEncoder)
|
||||
<< "Resolve assumes there is a valid command encoder to piggyback on.";
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
if (!mCommandEncoder) {
|
||||
const wgpu::CommandEncoderDescriptor encoderDescriptor{
|
||||
.label = "command_created_with_resolve",
|
||||
};
|
||||
mCommandEncoder = mDevice.CreateCommandEncoder(&encoderDescriptor);
|
||||
}
|
||||
FILAMENT_CHECK_PRECONDITION(mRenderPassEncoder == nullptr)
|
||||
<< "Resolve cannot be called during an existing render pass";
|
||||
const auto sourceTexture{ handleCast<WebGPUTexture>(sourceTextureHandle) };
|
||||
@@ -1511,6 +1753,7 @@ void WebGPUDriver::blit(Handle<HwTexture> destinationTextureHandle, const uint8_
|
||||
const uint8_t sourceLayer, const math::uint2 destinationOrigin,
|
||||
Handle<HwTexture> sourceTextureHandle, const uint8_t destinationLevel,
|
||||
const uint8_t destinationLayer, const math::uint2 sourceOrigin, const math::uint2 size) {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
bool reusedCommandEncoder{ true };
|
||||
if (mCommandEncoder) {
|
||||
// make sure command elements (draws, etc.) prior to this blit are processed before the blit
|
||||
@@ -1569,6 +1812,7 @@ void WebGPUDriver::blit(Handle<HwTexture> destinationTextureHandle, const uint8_
|
||||
}
|
||||
|
||||
void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
assert_invariant(mRenderPassEncoder);
|
||||
const auto program{ handleCast<WebGPUProgram>(pipelineState.program) };
|
||||
assert_invariant(program);
|
||||
@@ -1605,13 +1849,10 @@ void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
};
|
||||
wgpu::TextureFormat depthStencilFormat{ wgpu::TextureFormat::Undefined };
|
||||
if (renderTarget->isDefaultRenderTarget()) {
|
||||
// default render target color(s) (one)...
|
||||
colorFormatCount = 1;
|
||||
colorFormats[0] = mSwapChain->getColorFormat();
|
||||
// default render target depth/stencil...
|
||||
depthStencilFormat = mSwapChain->getDepthFormat();
|
||||
} else {
|
||||
// custom render target color(s)...
|
||||
MRT const& mrtColorAttachments{ mCurrentRenderTarget->getColorAttachmentInfos() };
|
||||
for (size_t i{ 0 }; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; ++i) {
|
||||
if (mrtColorAttachments[i].handle) {
|
||||
@@ -1660,6 +1901,7 @@ void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
}
|
||||
|
||||
void WebGPUDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> renderPrimitiveHandle) {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
const auto renderPrimitive = handleCast<WebGPURenderPrimitive>(renderPrimitiveHandle);
|
||||
const auto vertexBufferInfo = handleCast<WebGPUVertexBufferInfo>(
|
||||
renderPrimitive->vertexBuffer->getVertexBufferInfoHandle());
|
||||
@@ -1677,7 +1919,8 @@ void WebGPUDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> renderPrimitive
|
||||
|
||||
void WebGPUDriver::draw2(const uint32_t indexOffset, const uint32_t indexCount,
|
||||
const uint32_t instanceCount) {
|
||||
// We defer actually binding until we actually draw
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
// Bind groups are deferred until the actual draw call.
|
||||
for (size_t i = 0; i < MAX_DESCRIPTOR_SET_COUNT; i++) {
|
||||
auto& binding = mCurrentDescriptorSets[i];
|
||||
if (binding.bindGroup) {
|
||||
@@ -1723,6 +1966,7 @@ void WebGPUDriver::resetState(int) {
|
||||
void WebGPUDriver::updateDescriptorSetBuffer(Handle<HwDescriptorSet> descriptorSetHandle,
|
||||
const backend::descriptor_binding_t binding, Handle<HwBufferObject> bufferObjectHandle,
|
||||
const uint32_t offset, const uint32_t size) {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
const auto bindGroup = handleCast<WebGPUDescriptorSet>(descriptorSetHandle);
|
||||
const auto buffer = handleCast<WebGPUBufferObject>(bufferObjectHandle);
|
||||
if (!bindGroup->getIsLocked()) {
|
||||
@@ -1743,14 +1987,13 @@ void WebGPUDriver::updateDescriptorSetBuffer(Handle<HwDescriptorSet> descriptorS
|
||||
void WebGPUDriver::updateDescriptorSetTexture(Handle<HwDescriptorSet> descriptorSetHandle,
|
||||
const backend::descriptor_binding_t binding, Handle<HwTexture> textureHandle,
|
||||
const SamplerParams params) {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
auto bindGroup = handleCast<WebGPUDescriptorSet>(descriptorSetHandle);
|
||||
auto texture = handleCast<WebGPUTexture>(textureHandle);
|
||||
|
||||
if (!bindGroup->getIsLocked()) {
|
||||
// Dawn will cache duplicate samplers, so we don't strictly need to maintain a cache.
|
||||
// Making a cache might save us minor perf by reducing param translation
|
||||
// Dawn will cache duplicate samplers, so we don't strictly need to maintain a cache here.
|
||||
const auto sampler = makeSampler(params);
|
||||
// TODO making assumptions that size and offset mean the same thing here.
|
||||
wgpu::BindGroupEntry tEntry{
|
||||
.binding = static_cast<uint32_t>(binding * 2),
|
||||
.textureView = texture->getDefaultTextureView() };
|
||||
@@ -1784,7 +2027,7 @@ void WebGPUDriver::bindDescriptorSet(Handle<HwDescriptorSet> descriptorSetHandle
|
||||
const backend::descriptor_set_t setIndex, backend::DescriptorSetOffsetArray&& offsets) {
|
||||
assert_invariant(setIndex < MAX_DESCRIPTOR_SET_COUNT);
|
||||
|
||||
// An empty handle signifies we need to release this bind point
|
||||
// An empty handle unbinds the descriptor set.
|
||||
if (!descriptorSetHandle) {
|
||||
mCurrentDescriptorSets[setIndex] = {};
|
||||
return;
|
||||
@@ -1914,4 +2157,4 @@ wgpu::AddressMode WebGPUDriver::fWrapModeToWAddressMode(const SamplerWrapMode& f
|
||||
return wgpu::AddressMode::Undefined;
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "webgpu/WebGPUPipelineCache.h"
|
||||
#include "webgpu/WebGPUPipelineLayoutCache.h"
|
||||
#include "webgpu/WebGPURenderPassMipmapGenerator.h"
|
||||
#include "webgpu/utils/AsyncTaskCounter.h"
|
||||
#include <backend/platforms/WebGPUPlatform.h>
|
||||
|
||||
#include "DriverBase.h"
|
||||
@@ -47,8 +48,12 @@
|
||||
namespace filament::backend {
|
||||
|
||||
class WebGPUSwapChain;
|
||||
|
||||
/**
|
||||
* WebGPU backend (driver) implementation
|
||||
* Implements the private backend driver API for WebGPU specifically (that API is essentially
|
||||
* expressed in private/backend/DriverAPI.inc)
|
||||
*
|
||||
* It manages all the WebGPU resources necessary to accomplish this.
|
||||
*/
|
||||
class WebGPUDriver final : public DriverBase {
|
||||
public:
|
||||
@@ -64,8 +69,8 @@ private:
|
||||
[[nodiscard]] wgpu::Sampler makeSampler(SamplerParams const& params);
|
||||
[[nodiscard]] static wgpu::AddressMode fWrapModeToWAddressMode(const filament::backend::SamplerWrapMode& fUsage);
|
||||
|
||||
// the platform (e.g. OS) specific aspects of the WebGPU backend are strictly only
|
||||
// handled in the WebGPUPlatform
|
||||
// The platform (e.g. OS) specific aspects of the WebGPU backend are strictly only
|
||||
// handled in the WebGPUPlatform.
|
||||
WebGPUPlatform& mPlatform;
|
||||
wgpu::Adapter mAdapter = nullptr;
|
||||
wgpu::Device mDevice = nullptr;
|
||||
@@ -86,6 +91,7 @@ private:
|
||||
spd::MipmapGenerator mSpdComputePassMipmapGenerator;
|
||||
WebGPUMsaaTextureResolver mMsaaTextureResolver{};
|
||||
WebGPUBlitter mBlitter;
|
||||
webgpuutils::AsyncTaskCounter mReadPixelMapsCounter{};
|
||||
|
||||
struct DescriptorSetBindingInfo{
|
||||
wgpu::BindGroup bindGroup;
|
||||
|
||||
@@ -28,12 +28,11 @@ namespace filament::backend {
|
||||
FenceStatus WebGPUFence::getStatus() { return mStatus.load(); }
|
||||
|
||||
void WebGPUFence::addMarkerToQueueState(wgpu::Queue const& queue) {
|
||||
// The lambda function is called when the work is done. It updates the fence status based on the
|
||||
// result of the work.
|
||||
queue.OnSubmittedWorkDone(
|
||||
wgpu::CallbackMode::AllowSpontaneous,
|
||||
[this](const wgpu::QueueWorkDoneStatus status, wgpu::StringView message) {
|
||||
// Note: The 'message' parameter is required by the function signature
|
||||
// but can be ignored if not needed.
|
||||
|
||||
switch (status) {
|
||||
case wgpu::QueueWorkDoneStatus::Success:
|
||||
mStatus.store(FenceStatus::CONDITION_SATISFIED);
|
||||
|
||||
@@ -28,6 +28,11 @@ class Queue;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A WebGPU-specific implementation of the HwFence.
|
||||
* This class is used to synchronize the CPU and GPU. It allows the CPU to know when the GPU has
|
||||
* finished a set of commands.
|
||||
*/
|
||||
class WebGPUFence final : public HwFence {
|
||||
public:
|
||||
[[nodiscard]] FenceStatus getStatus();
|
||||
@@ -35,6 +40,9 @@ public:
|
||||
void addMarkerToQueueState(wgpu::Queue const&);
|
||||
|
||||
private:
|
||||
// The current status of the fence.
|
||||
// This is atomic because it can be updated by a WebGPU callback thread and read from the main
|
||||
// thread.
|
||||
std::atomic<FenceStatus> mStatus{ FenceStatus::TIMEOUT_EXPIRED };
|
||||
};
|
||||
|
||||
|
||||
@@ -30,6 +30,10 @@ enum class IndexFormat : uint32_t;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A WebGPU implementation of the HwIndexBuffer.
|
||||
* This class represents a GPU buffer that stores indices for indexed drawing.
|
||||
*/
|
||||
class WebGPUIndexBuffer final : public HwIndexBuffer, public WebGPUBufferBase {
|
||||
public:
|
||||
WebGPUIndexBuffer(wgpu::Device const&, uint8_t elementSize, uint32_t indexCount);
|
||||
|
||||
@@ -48,8 +48,8 @@ void resolveColorTextures(wgpu::CommandEncoder const& commandEncoder,
|
||||
const wgpu::RenderPassEncoder renderPassEncoder{ commandEncoder.BeginRenderPass(
|
||||
&renderPassDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(renderPassEncoder)
|
||||
<< "Failed to create wgpu::RenderPassEncoder for WebGPUDriver::resolve";
|
||||
renderPassEncoder.End(); // only the implicit resolve is happening in the pass
|
||||
<< "Failed to create wgpu::RenderPassEncoder for MSAA resolve.";
|
||||
renderPassEncoder.End(); // The resolve is implicit in the pass.
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -59,30 +59,29 @@ void WebGPUMsaaTextureResolver::resolve(ResolveRequest const& request) {
|
||||
ResolveRequest::TextureInfo const& destination{ request.destination };
|
||||
FILAMENT_CHECK_PRECONDITION(destination.texture.GetWidth() == source.texture.GetWidth() &&
|
||||
destination.texture.GetHeight() == source.texture.GetHeight())
|
||||
<< "invalid resolve: source and destination sizes don't match";
|
||||
<< "Invalid resolve: source and destination sizes don't match.";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
source.texture.GetSampleCount() > 1 && destination.texture.GetSampleCount() == 1)
|
||||
<< "invalid resolve: source.samples=" << source.texture.GetSampleCount()
|
||||
<< "Invalid resolve: source.samples=" << source.texture.GetSampleCount()
|
||||
<< ", destination.samples=" << destination.texture.GetSampleCount();
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(source.texture.GetFormat() == destination.texture.GetFormat())
|
||||
<< "source and destination texture format don't match";
|
||||
<< "Source and destination texture format don't match.";
|
||||
const wgpu::TextureFormat format{ source.texture.GetFormat() };
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(!hasDepth(format)) << "can't resolve depth formats";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(!hasStencil(format)) << "can't resolve stencil formats";
|
||||
FILAMENT_CHECK_PRECONDITION(!hasDepth(format)) << "Can't resolve depth formats.";
|
||||
FILAMENT_CHECK_PRECONDITION(!hasStencil(format)) << "Can't resolve stencil formats.";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(source.texture.GetUsage() & wgpu::TextureUsage::RenderAttachment)
|
||||
<< "source texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
|
||||
<< "Source texture usage doesn't have wgpu::TextureUsage::RenderAttachment.";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
destination.texture.GetUsage() & wgpu::TextureUsage::RenderAttachment)
|
||||
<< "destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
|
||||
<< "Destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment.";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(destination.texture.GetUsage() & wgpu::TextureUsage::TextureBinding)
|
||||
<< "destination texture usage doesn't have wgpu::TextureUsage::TextureBinding";
|
||||
<< "Destination texture usage doesn't have wgpu::TextureUsage::TextureBinding.";
|
||||
|
||||
const wgpu::TextureViewDescriptor sourceTextureViewDescriptor{
|
||||
.label = "resolve_source_texture_view",
|
||||
@@ -116,7 +115,7 @@ void WebGPUMsaaTextureResolver::resolve(ResolveRequest const& request) {
|
||||
<< "Failed to create wgpu::TextureView destinationTextureView.";
|
||||
|
||||
if (hasDepth(format)) {
|
||||
PANIC_PRECONDITION("DEPTH RESOLVE NOT IMPLEMENTED YET");
|
||||
PANIC_PRECONDITION("Depth resolve not yet implemented.");
|
||||
} else {
|
||||
resolveColorTextures(request.commandEncoder, sourceTextureView, destinationTextureView);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,10 @@ namespace filament::backend {
|
||||
|
||||
class WebGPUTexture;
|
||||
|
||||
/**
|
||||
* A utility class for resolving multisampled textures in WebGPU.
|
||||
* This is necessary because WebGPU does not have an implicit resolve step like other APIs.
|
||||
*/
|
||||
class WebGPUMsaaTextureResolver final {
|
||||
public:
|
||||
struct ResolveRequest final {
|
||||
@@ -47,6 +51,9 @@ public:
|
||||
TextureInfo destination;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolves a multisampled texture into a single-sampled texture.
|
||||
*/
|
||||
void resolve(ResolveRequest const&);
|
||||
};
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace {
|
||||
case CullingMode::FRONT: return wgpu::CullMode::Front;
|
||||
case CullingMode::BACK: return wgpu::CullMode::Back;
|
||||
case CullingMode::FRONT_AND_BACK:
|
||||
// no WegGPU equivalent of front and back
|
||||
// WebGPU does not support culling both front and back faces simultaneously.
|
||||
FILAMENT_CHECK_POSTCONDITION(false)
|
||||
<< "WebGPU does not support CullingMode::FRONT_AND_BACK";
|
||||
return wgpu::CullMode::Undefined;
|
||||
@@ -220,7 +220,7 @@ wgpu::RenderPipeline WebGPUPipelineCache::createRenderPipeline(
|
||||
const bool requestedDepth{ any(request.targetRenderFlags & TargetBufferFlags::DEPTH) };
|
||||
const bool requestedStencil{ any(request.targetRenderFlags & TargetBufferFlags::STENCIL) };
|
||||
const bool depthOrStencilRequested{ requestedDepth || requestedStencil };
|
||||
// depth/stencil...
|
||||
|
||||
if (depthOrStencilRequested) {
|
||||
FILAMENT_CHECK_PRECONDITION(request.depthStencilFormat != wgpu::TextureFormat::Undefined)
|
||||
<< "Depth or Stencil requested for pipeline, but depthStencilFormat is "
|
||||
@@ -312,6 +312,13 @@ wgpu::RenderPipeline WebGPUPipelineCache::createRenderPipeline(
|
||||
},
|
||||
.fragment = nullptr // will add below if fragment module is included
|
||||
};
|
||||
// TODO:
|
||||
if (pipelineDescriptor.primitive.topology == wgpu::PrimitiveTopology::LineStrip ||
|
||||
pipelineDescriptor.primitive.topology == wgpu::PrimitiveTopology::TriangleStrip) {
|
||||
PANIC_POSTCONDITION("stripIndexFormat must be set for strip topologies. "
|
||||
"This needs to be plumbed through from the RenderPrimitive.");
|
||||
}
|
||||
|
||||
wgpu::FragmentState fragmentState = {};
|
||||
const wgpu::BlendState blendState {
|
||||
.color = {
|
||||
@@ -365,6 +372,7 @@ bool WebGPUPipelineCache::RenderPipelineKeyEqual::operator()(RenderPipelineKey c
|
||||
}
|
||||
|
||||
void WebGPUPipelineCache::removeExpiredPipelines() {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
using Iterator = decltype(mRenderPipelines)::const_iterator;
|
||||
for (Iterator iterator{ mRenderPipelines.begin() }; iterator != mRenderPipelines.end();) {
|
||||
RenderPipelineCacheEntry const& entry{ iterator.value() };
|
||||
|
||||
@@ -34,6 +34,11 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A cache for WebGPU render pipelines.
|
||||
* This class is responsible for creating and caching wgpu::RenderPipeline objects to avoid
|
||||
* expensive pipeline creation at runtime.
|
||||
*/
|
||||
class WebGPUPipelineCache final {
|
||||
public:
|
||||
struct RenderPipelineRequest final {
|
||||
@@ -63,6 +68,9 @@ public:
|
||||
[[nodiscard]] wgpu::RenderPipeline const& getOrCreateRenderPipeline(
|
||||
RenderPipelineRequest const&);
|
||||
|
||||
/**
|
||||
* Should be called at the end of each frame to perform cache maintenance.
|
||||
*/
|
||||
void onFrameEnd();
|
||||
|
||||
private:
|
||||
|
||||
@@ -77,26 +77,27 @@ wgpu::PipelineLayout WebGPUPipelineLayoutCache::createPipelineLayout(
|
||||
};
|
||||
const wgpu::PipelineLayout layout{ mDevice.CreatePipelineLayout(&descriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(layout)
|
||||
<< "Failed to create pipeline layout " << descriptor.label << "?";
|
||||
<< "Failed to create pipeline layout " << descriptor.label << ".";
|
||||
return layout;
|
||||
}
|
||||
|
||||
bool WebGPUPipelineLayoutCache::PipelineLayoutKeyEqual::operator()(PipelineLayoutKey const& key1,
|
||||
PipelineLayoutKey const& key2) const {
|
||||
// Compare the raw bytes of the keys for equality.
|
||||
return 0 == memcmp(reinterpret_cast<void const*>(&key1), reinterpret_cast<void const*>(&key2),
|
||||
sizeof(key1));
|
||||
}
|
||||
|
||||
void WebGPUPipelineLayoutCache::removeExpiredPipelineLayouts() {
|
||||
FWGPU_SYSTRACE_SCOPE();
|
||||
using Iterator = decltype(mPipelineLayouts)::const_iterator;
|
||||
for (Iterator iterator{ mPipelineLayouts.begin() }; iterator != mPipelineLayouts.end();) {
|
||||
PipelineLayoutCacheEntry const& entry{ iterator.value() };
|
||||
if (mFrameCount > (entry.lastUsedFrameCount +
|
||||
FILAMENT_WEBGPU_PIPELINE_LAYOUT_EXPIRATION_IN_FRAME_COUNT)) {
|
||||
// pipeline layout expired...
|
||||
// The pipeline layout has not been used recently, so we can remove it from the cache.
|
||||
iterator = mPipelineLayouts.erase(iterator);
|
||||
} else {
|
||||
// pipeline layout not yet expired...
|
||||
++iterator;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,11 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A cache for WebGPU pipeline layouts.
|
||||
* This class is responsible for creating and caching wgpu::PipelineLayout objects to avoid
|
||||
* expensive pipeline layout creation at runtime.
|
||||
*/
|
||||
class WebGPUPipelineLayoutCache final {
|
||||
public:
|
||||
struct PipelineLayoutRequest final {
|
||||
@@ -45,9 +50,16 @@ public:
|
||||
WebGPUPipelineLayoutCache& operator=(WebGPUPipelineLayoutCache const&) = delete;
|
||||
WebGPUPipelineLayoutCache& operator=(WebGPUPipelineLayoutCache const&&) = delete;
|
||||
|
||||
/**
|
||||
* Retrieves a pipeline layout from the cache or creates a new one if it doesn't exist.
|
||||
* @return A constant reference to the cached or newly created pipeline layout.
|
||||
*/
|
||||
[[nodiscard]] wgpu::PipelineLayout const& getOrCreatePipelineLayout(
|
||||
PipelineLayoutRequest const&);
|
||||
|
||||
/**
|
||||
* Should be called at the end of each frame to perform cache maintenance.
|
||||
*/
|
||||
void onFrameEnd();
|
||||
|
||||
private:
|
||||
@@ -59,10 +71,10 @@ private:
|
||||
* instances, single bytes for booleans etc.), trivial copying and comparison (byte by byte),
|
||||
* and a word-aligned structure with a size in bytes as a multiple of 4 (for murmer hash).
|
||||
*/
|
||||
struct PipelineLayoutKey final { // size : offset (need multiples of 4 bytes for hashing)
|
||||
WGPUBindGroupLayout bindGroupLayoutHandles[MAX_DESCRIPTOR_SET_COUNT]{ nullptr }; // 32 : 0
|
||||
uint8_t bindGroupLayoutCount{ 0 }; // 1 : 32
|
||||
uint8_t padding[7]{ 0 }; // 7 : 33
|
||||
struct PipelineLayoutKey final {
|
||||
WGPUBindGroupLayout bindGroupLayoutHandles[MAX_DESCRIPTOR_SET_COUNT]{ nullptr }; // 32 :0
|
||||
uint8_t bindGroupLayoutCount{ 0 }; // 1 :32
|
||||
uint8_t padding[7]{ 0 }; // 7 :33
|
||||
};
|
||||
static_assert(sizeof(PipelineLayoutKey) == 40,
|
||||
"PipelineLayoutKey must not have implicit padding.");
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace {
|
||||
std::string_view(sourceData + posAfterId, posEndOfStatement - posAfterId);
|
||||
size_t posOfEqual = statementSegment.find('=');
|
||||
if (posOfEqual == std::string::npos) {
|
||||
// not an assignment statement, so stream to the end of the statement and continue...
|
||||
// Not an assignment, so we don't need to replace it.
|
||||
processedShaderSource << std::string_view(sourceData + pos,
|
||||
posEndOfStatement + 1 - pos);
|
||||
pos = posEndOfStatement + 1;
|
||||
@@ -122,10 +122,7 @@ namespace {
|
||||
}
|
||||
const auto newValueItr = specConstants.find(static_cast<uint32_t>(constantId));
|
||||
if (newValueItr == specConstants.end()) {
|
||||
// not going to override the constant,
|
||||
// as the specConstants parameter doesn't specify it. So, we will keep the default
|
||||
// already in the source text
|
||||
// (stream to the end of the statement)...
|
||||
// The constant is not being overridden, so keep the default value.
|
||||
processedShaderSource << std::string_view(sourceData + pos,
|
||||
posEndOfStatement + 1 - pos);
|
||||
pos = posEndOfStatement + 1;
|
||||
@@ -174,7 +171,7 @@ namespace {
|
||||
utils::FixedCapacityVector<uint8_t> const& sourceBytes =
|
||||
shaderSource[static_cast<size_t>(stage)];
|
||||
if (sourceBytes.empty()) {
|
||||
return nullptr;// nothing to compile/create, the shader was not provided
|
||||
return nullptr; // No shader source to compile.
|
||||
}
|
||||
std::stringstream labelStream;
|
||||
labelStream << programName << " " << filamentShaderStageToString(stage) << " shader";
|
||||
@@ -192,7 +189,8 @@ namespace {
|
||||
};
|
||||
const wgpu::ShaderModule shaderModule = device.CreateShaderModule(&descriptor);
|
||||
const wgpu::Instance instance = device.GetAdapter().GetInstance();
|
||||
// synchronously creates the shader module...
|
||||
|
||||
// Synchronously compile the shader module.
|
||||
const wgpu::WaitStatus waitResult = instance.WaitAny(
|
||||
shaderModule.GetCompilationInfo(wgpu::CallbackMode::WaitAnyOnly,
|
||||
[&descriptor](auto const& status,
|
||||
@@ -282,7 +280,7 @@ WebGPUProgram::WebGPUProgram(wgpu::Device const& device, Program const& program)
|
||||
: HwProgram{ program.getName() } {
|
||||
std::unordered_map<uint32_t, std::variant<int32_t, float, bool>> specConstants;
|
||||
toMap(program.getSpecializationConstants(), specConstants);
|
||||
// TODO consider creating/compiling these shaders in parallel
|
||||
// TODO: Consider creating/compiling these shaders in parallel.
|
||||
vertexShaderModule = createShaderModule(device, program, ShaderStage::VERTEX, specConstants);
|
||||
fragmentShaderModule =
|
||||
createShaderModule(device, program, ShaderStage::FRAGMENT, specConstants);
|
||||
|
||||
@@ -25,6 +25,10 @@ namespace filament::backend {
|
||||
|
||||
class Program;
|
||||
|
||||
/**
|
||||
* A WebGPU implementation of the HwProgram.
|
||||
* This class holds the compiled shader modules for a given program.
|
||||
*/
|
||||
class WebGPUProgram final : public HwProgram {
|
||||
public:
|
||||
WebGPUProgram(wgpu::Device const&, Program const&);
|
||||
|
||||
@@ -27,9 +27,8 @@ namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
// The shaders expect a single-sampled 2D texture
|
||||
// with a format compatible with scalar sample format f32
|
||||
// (this is checked with the getCompatibilityFor function)
|
||||
// The shaders expect a single-sampled 2D texture with a format compatible with a scalar sample format of f32.
|
||||
// This is checked with the getCompatibilityFor function.
|
||||
constexpr uint32_t TEXTURE_BIND_GROUP_INDEX{ 0 };
|
||||
constexpr size_t TEXTURE_BIND_GROUP_ENTRY_SIZE{ 2 }; // sampler and texture
|
||||
constexpr uint32_t SAMPLER_BINDING_INDEX{ 0 };
|
||||
@@ -85,7 +84,7 @@ constexpr std::string_view SHADER_SOURCE{ R"(
|
||||
.maxAnisotropy = 1, // should not matter, just being consistently defined
|
||||
};
|
||||
const wgpu::Sampler sampler{ device.CreateSampler(&descriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(sampler) << "Failed to create previous mip level sampler?";
|
||||
FILAMENT_CHECK_POSTCONDITION(sampler) << "Failed to create previous mip level sampler.";
|
||||
return sampler;
|
||||
}
|
||||
|
||||
@@ -98,7 +97,7 @@ constexpr std::string_view SHADER_SOURCE{ R"(
|
||||
};
|
||||
const wgpu::ShaderModule shaderModule{ device.CreateShaderModule(&shaderModuleDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(shaderModule)
|
||||
<< "Failed to create shader module for render pass mipmap generation?";
|
||||
<< "Failed to create shader module for render pass mipmap generation.";
|
||||
return shaderModule;
|
||||
}
|
||||
|
||||
@@ -131,7 +130,7 @@ constexpr std::string_view SHADER_SOURCE{ R"(
|
||||
const wgpu::BindGroupLayout textureBindGroupLayout{ device.CreateBindGroupLayout(
|
||||
&textureBindGroupLayoutDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(textureBindGroupLayout)
|
||||
<< "Failed to create texture bind group layout for render pass mipmap generation?";
|
||||
<< "Failed to create texture bind group layout for render pass mipmap generation.";
|
||||
return textureBindGroupLayout;
|
||||
}
|
||||
|
||||
@@ -145,7 +144,7 @@ constexpr std::string_view SHADER_SOURCE{ R"(
|
||||
const wgpu::PipelineLayout pipelineLayout{ device.CreatePipelineLayout(
|
||||
&pipelineLayoutDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipelineLayout)
|
||||
<< "Failed to create pipeline layout for render pass mipmap generation?";
|
||||
<< "Failed to create pipeline layout for render pass mipmap generation.";
|
||||
return pipelineLayout;
|
||||
}
|
||||
|
||||
@@ -194,7 +193,7 @@ constexpr std::string_view SHADER_SOURCE{ R"(
|
||||
};
|
||||
const wgpu::RenderPipeline pipeline{ device.CreateRenderPipeline(&pipelineDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline)
|
||||
<< "Failed to create pipeline for render pass mipmap generation?";
|
||||
<< "Failed to create pipeline for render pass mipmap generation.";
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
@@ -210,8 +209,7 @@ WebGPURenderPassMipmapGenerator::WebGPURenderPassMipmapGenerator(wgpu::Device co
|
||||
WebGPURenderPassMipmapGenerator::FormatCompatibility
|
||||
WebGPURenderPassMipmapGenerator::getCompatibilityFor(const wgpu::TextureFormat format,
|
||||
const wgpu::TextureDimension dimension, const uint32_t sampleCount) {
|
||||
// check that the format is compatible (currently expects that the scalar sample type is
|
||||
// f32)...
|
||||
// Check that the format is compatible (currently expects that the scalar sample type is f32).
|
||||
switch (format) {
|
||||
case wgpu::TextureFormat::Depth16Unorm:
|
||||
case wgpu::TextureFormat::Depth24Plus:
|
||||
@@ -220,20 +218,16 @@ WebGPURenderPassMipmapGenerator::getCompatibilityFor(const wgpu::TextureFormat f
|
||||
case wgpu::TextureFormat::Depth32FloatStencil8:
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "A depth texture format requires special sampler treatment and does "
|
||||
"not "
|
||||
"generally support linear filtering needed for render pass based "
|
||||
"mipmap "
|
||||
"generation, thus this texture is not supported, as it is a kind of "
|
||||
"depth texture.",
|
||||
.reason = "A depth texture format requires special sampler treatment and does not "
|
||||
"generally support linear filtering needed for render pass based mipmap "
|
||||
"generation.",
|
||||
};
|
||||
case wgpu::TextureFormat::Undefined:
|
||||
case wgpu::TextureFormat::External:
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "Undefined or External textures are not supported for render pass "
|
||||
"based "
|
||||
"mipmap generation",
|
||||
.reason = "Undefined or External textures are not supported for render pass based "
|
||||
"mipmap generation.",
|
||||
};
|
||||
default:
|
||||
const ScalarSampleType scalarSampleType{ getScalarSampleTypeFrom(format) };
|
||||
@@ -242,21 +236,19 @@ WebGPURenderPassMipmapGenerator::getCompatibilityFor(const wgpu::TextureFormat f
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "The provided texture format requires an unsigned integer "
|
||||
"sampler, which does not natively support linear filtering, "
|
||||
"which is needed for render pass based mipmap generation.",
|
||||
"sampler, which does not natively support linear filtering.",
|
||||
};
|
||||
case ScalarSampleType::I32:
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "The provided texture format requires a signed integer "
|
||||
"sampler, which does not natively support linear filtering, "
|
||||
"which is needed for render pass based mipmap generation.",
|
||||
"sampler, which does not natively support linear filtering.",
|
||||
};
|
||||
case ScalarSampleType::F32:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// check that the dimensionality is compatible (currently expects 2D textures)...
|
||||
// Check that the dimensionality is compatible (currently expects 2D textures).
|
||||
switch (dimension) {
|
||||
case wgpu::TextureDimension::Undefined:
|
||||
return {
|
||||
@@ -282,7 +274,7 @@ WebGPURenderPassMipmapGenerator::getCompatibilityFor(const wgpu::TextureFormat f
|
||||
case wgpu::TextureDimension::e2D:
|
||||
break;
|
||||
}
|
||||
// check that the texture is single-sampled (for now)...
|
||||
// Check that the texture is single-sampled.
|
||||
if (sampleCount > 1) {
|
||||
return {
|
||||
.compatible = false,
|
||||
@@ -427,7 +419,7 @@ void WebGPURenderPassMipmapGenerator::generateMipmaps(wgpu::Queue const& queue,
|
||||
wgpu::Texture const& texture) {
|
||||
const uint32_t mipLevelCount{ texture.GetMipLevelCount() };
|
||||
if (mipLevelCount < 2) {
|
||||
return; // nothing to do
|
||||
return; // Nothing to do.
|
||||
}
|
||||
wgpu::RenderPipeline const& pipeline{ getOrCreatePipelineFor(texture.GetFormat()) };
|
||||
const wgpu::CommandEncoderDescriptor commandEncoderDescriptor{
|
||||
@@ -436,20 +428,19 @@ void WebGPURenderPassMipmapGenerator::generateMipmaps(wgpu::Queue const& queue,
|
||||
const wgpu::CommandEncoder commandEncoder{ mDevice.CreateCommandEncoder(
|
||||
&commandEncoderDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(commandEncoder)
|
||||
<< "Failed to create command encoder for layer for render pass mipmap generation?";
|
||||
<< "Failed to create command encoder for render pass mipmap generation.";
|
||||
const uint32_t layerCount{ texture.GetDepthOrArrayLayers() };
|
||||
for (uint32_t layer = 0; layer < layerCount; layer++) {
|
||||
for (uint32_t mipLevel = 1; mipLevel < mipLevelCount; mipLevel++) {
|
||||
generateMipmap(commandEncoder, texture, pipeline, layer, mipLevel);
|
||||
}
|
||||
}
|
||||
// submit the command buffer...
|
||||
const wgpu::CommandBufferDescriptor commandBufferDescriptor{
|
||||
.label = "mipmap_generation_render_pass_cmd_buffer",
|
||||
};
|
||||
const wgpu::CommandBuffer commandBuffer{ commandEncoder.Finish(&commandBufferDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(commandBuffer)
|
||||
<< "Failed to create command buffer for layer for render pass mipmap generation?";
|
||||
<< "Failed to create command buffer for render pass mipmap generation.";
|
||||
queue.Submit(1, &commandBuffer);
|
||||
}
|
||||
|
||||
@@ -472,7 +463,7 @@ void WebGPURenderPassMipmapGenerator::generateMipmap(wgpu::CommandEncoder const&
|
||||
.usage = wgpu::TextureUsage::TextureBinding,
|
||||
};
|
||||
const wgpu::TextureView sourceView{ texture.CreateView(&sourceViewDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline)
|
||||
FILAMENT_CHECK_POSTCONDITION(sourceView)
|
||||
<< "Failed to create source texture view for layer " << layer << " and mip level "
|
||||
<< mipLevel << " for render pass mipmap generation?";
|
||||
const wgpu::TextureViewDescriptor destinationViewDescriptor{
|
||||
@@ -487,7 +478,7 @@ void WebGPURenderPassMipmapGenerator::generateMipmap(wgpu::CommandEncoder const&
|
||||
.usage = wgpu::TextureUsage::RenderAttachment,
|
||||
};
|
||||
const wgpu::TextureView destinationView{ texture.CreateView(&destinationViewDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline)
|
||||
FILAMENT_CHECK_POSTCONDITION(destinationView)
|
||||
<< "Failed to create destination texture view for layer " << layer << " and mip level "
|
||||
<< mipLevel << " for render pass mipmap generation?";
|
||||
// create the render pass...
|
||||
@@ -508,7 +499,7 @@ void WebGPURenderPassMipmapGenerator::generateMipmap(wgpu::CommandEncoder const&
|
||||
.entries = textureBindGroupEntries,
|
||||
};
|
||||
const wgpu::BindGroup textureBindGroup{ mDevice.CreateBindGroup(&textureBindGroupDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline)
|
||||
FILAMENT_CHECK_POSTCONDITION(textureBindGroup)
|
||||
<< "Failed to create texture bind group for layer " << layer << " and mip level "
|
||||
<< mipLevel << " for render pass mipmap generation?";
|
||||
const wgpu::RenderPassColorAttachment colorAttachment{
|
||||
@@ -534,8 +525,7 @@ void WebGPURenderPassMipmapGenerator::generateMipmap(wgpu::CommandEncoder const&
|
||||
<< mipLevel << " for render pass mipmap generation?";
|
||||
renderPassEncoder.SetPipeline(pipeline);
|
||||
renderPassEncoder.SetBindGroup(TEXTURE_BIND_GROUP_INDEX, textureBindGroup);
|
||||
renderPassEncoder.Draw(3); // draw the full-screen triangle
|
||||
// with hard-coded vertices in the shader
|
||||
renderPassEncoder.Draw(3); // Draw a full-screen triangle.
|
||||
renderPassEncoder.End();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A utility class for generating mipmaps for a texture using a series of render passes.
|
||||
*/
|
||||
class WebGPURenderPassMipmapGenerator final {
|
||||
public:
|
||||
struct FormatCompatibility final {
|
||||
@@ -34,6 +37,11 @@ public:
|
||||
std::string_view reason;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a given texture format is compatible with render pass-based mipmap generation.
|
||||
* @return A FormatCompatibility struct indicating whether the format is compatible
|
||||
* and a reason if not.
|
||||
*/
|
||||
[[nodiscard]] static FormatCompatibility getCompatibilityFor(wgpu::TextureFormat,
|
||||
wgpu::TextureDimension, uint32_t sampleCount);
|
||||
|
||||
|
||||
@@ -24,6 +24,10 @@ namespace filament::backend {
|
||||
class WebGPUVertexBuffer;
|
||||
class WebGPUIndexBuffer;
|
||||
|
||||
/**
|
||||
* A WebGPU implementation of the HwRenderPrimitive.
|
||||
* This struct holds the vertex and index buffers that define a renderable primitive.
|
||||
*/
|
||||
struct WebGPURenderPrimitive : public HwRenderPrimitive {
|
||||
WebGPUVertexBuffer* vertexBuffer = nullptr;
|
||||
WebGPUIndexBuffer* indexBuffer = nullptr;
|
||||
|
||||
@@ -41,9 +41,9 @@ namespace filament::backend {
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Panics if the number of samples in the original attachment textures do not match.
|
||||
* @return The number of samples in the original attachment textures (the number/count in each one).
|
||||
* Returns 0 if there are no attachments.
|
||||
* Creates MSAA sidecar textures for attachments if necessary.
|
||||
* This function also verifies that all attachments have the same sample count.
|
||||
* @return The number of samples per attachment.
|
||||
*/
|
||||
[[nodiscard]] uint8_t createMsaaSidecarTextures(const uint8_t renderTargetSampleCount,
|
||||
const TargetBufferFlags targetFlags, MRT const& colorAttachments,
|
||||
@@ -100,9 +100,6 @@ namespace {
|
||||
sampleCountPerAttachment = texture->samples;
|
||||
firstAttachment = false;
|
||||
}
|
||||
FILAMENT_CHECK_PRECONDITION(texture->samples == sampleCountPerAttachment)
|
||||
<< target.name << " attachment texture has " << +texture->samples
|
||||
<< " but the other attachment(s) have " << +sampleCountPerAttachment;
|
||||
if (renderTargetSampleCount > 1 && sampleCountPerAttachment == 1) {
|
||||
texture->createMsaaSidecarTextureIfNotAlreadyCreated(renderTargetSampleCount,
|
||||
device);
|
||||
@@ -136,7 +133,7 @@ WebGPURenderTarget::WebGPURenderTarget(const uint32_t width, const uint32_t heig
|
||||
mColorAttachmentDesc.reserve(MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
|
||||
}
|
||||
|
||||
// Default constructor for the default render target
|
||||
// Constructor for the default render target
|
||||
WebGPURenderTarget::WebGPURenderTarget()
|
||||
: HwRenderTarget{ 0, 0 },
|
||||
mDefaultRenderTarget{ true },
|
||||
@@ -182,19 +179,19 @@ void WebGPURenderTarget::setUpRenderPassAttachments(wgpu::RenderPassDescriptor&
|
||||
FILAMENT_CHECK_PRECONDITION(std::all_of(customColorMsaaSidecarTextureViews,
|
||||
customColorMsaaSidecarTextureViews + customColorTextureViewCount,
|
||||
[](wgpu::TextureView const& msaaView) { return msaaView != nullptr; }))
|
||||
<< "A color or depth/stencil attachment texture has a MSAA sidecar but at least "
|
||||
<< "A color or depth/stencil attachment texture has an MSAA sidecar but at least "
|
||||
"one other color attachment texture does not.";
|
||||
FILAMENT_CHECK_PRECONDITION(customDepthStencilMsaaSidecarTextureView != nullptr)
|
||||
<< "The color attachment texture(s) have MSAA sidecar(s) but the depth/stencil "
|
||||
<< "The color attachment texture(s) have MSAA sidecars but the depth/stencil "
|
||||
"texture does not.";
|
||||
} else {
|
||||
FILAMENT_CHECK_PRECONDITION(std::all_of(customColorMsaaSidecarTextureViews,
|
||||
customColorMsaaSidecarTextureViews + customColorTextureViewCount,
|
||||
[](wgpu::TextureView const& msaaView) { return msaaView == nullptr; }))
|
||||
<< "A color or depth/stencil attachment texture does not have a MSAA sidecar but "
|
||||
<< "A color or depth/stencil attachment texture does not have an MSAA sidecar but "
|
||||
"at least one color attachment texture does.";
|
||||
FILAMENT_CHECK_PRECONDITION(customDepthStencilMsaaSidecarTextureView == nullptr)
|
||||
<< "Custom color textures for the render target do not have MSAA sidecar(s) but "
|
||||
<< "Custom color textures for the render target do not have MSAA sidecars but "
|
||||
"the depth/stencil texture does.";
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,10 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A WebGPU implementation of the HwRenderTarget.
|
||||
* This class represents a collection of attachments (textures) that can be rendered to.
|
||||
*/
|
||||
class WebGPURenderTarget : public HwRenderTarget {
|
||||
public:
|
||||
using Attachment = TargetBufferInfo; // Using TargetBufferInfo directly for attachments
|
||||
@@ -38,7 +42,8 @@ public:
|
||||
WebGPURenderTarget(uint32_t width, uint32_t height, uint8_t samples, uint8_t layerCount,
|
||||
MRT const& colorAttachments, Attachment const& depthAttachment,
|
||||
Attachment const& stencilAttachment, TargetBufferFlags const& targetFlags,
|
||||
std::function<WebGPUTexture*(const Handle<HwTexture>)> const&, wgpu::Device const&);
|
||||
std::function<WebGPUTexture*(const Handle<HwTexture>)> const&,
|
||||
wgpu::Device const&);
|
||||
|
||||
// Default constructor for the default render target
|
||||
WebGPURenderTarget();
|
||||
@@ -80,7 +85,7 @@ private:
|
||||
|
||||
MRT mColorAttachments{};
|
||||
// TODO WebGPU only supports a DepthStencil attachment, should this be just
|
||||
// mDepthStencilAttachment?
|
||||
// mDepthStencilAttachment?
|
||||
Attachment mDepthAttachment{};
|
||||
Attachment mStencilAttachment{};
|
||||
|
||||
|
||||
@@ -187,7 +187,8 @@ void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device const& device,
|
||||
wgpu::SurfaceCapabilities const& capabilities, wgpu::Extent2D const& extent,
|
||||
bool useSRGBColorSpace) {
|
||||
config.device = device;
|
||||
config.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
|
||||
config.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc |
|
||||
wgpu::TextureUsage::TextureBinding;
|
||||
config.width = extent.width;
|
||||
config.height = extent.height;
|
||||
config.format = selectColorFormat(capabilities.formatCount, capabilities.formats, useSRGBColorSpace);
|
||||
@@ -269,8 +270,6 @@ WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const&
|
||||
const bool useSRGBColorSpace = (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0;
|
||||
|
||||
initConfig(mConfig, device, capabilities, extent, useSRGBColorSpace);
|
||||
mDepthFormat = selectDepthFormat(device.HasFeature(wgpu::FeatureName::Depth32FloatStencil8),
|
||||
mNeedStencil);
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
printSurfaceConfiguration(mConfig, mDepthFormat);
|
||||
#endif
|
||||
@@ -297,8 +296,6 @@ WebGPUSwapChain::WebGPUSwapChain(wgpu::Extent2D const& extent,
|
||||
mConfig.presentMode = wgpu::PresentMode::Fifo;
|
||||
mConfig.alphaMode = wgpu::CompositeAlphaMode::Auto;
|
||||
|
||||
mDepthFormat = selectDepthFormat(device.HasFeature(wgpu::FeatureName::Depth32FloatStencil8),
|
||||
mNeedStencil);
|
||||
const wgpu::TextureDescriptor textureDescriptor = {
|
||||
.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc,
|
||||
.dimension = wgpu::TextureDimension::e2D,
|
||||
|
||||
@@ -107,84 +107,6 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fUsage Filament's requested texture usage
|
||||
* @param samples How many samples to use for MSAA
|
||||
* @param needsComputeStorageSupport if we need to use this texture as storage binding in something
|
||||
* like a compute shader
|
||||
* @param needsRenderAttachmentSupport if we need to use this texture as a render pass attachment
|
||||
* in something like a render pass blit (e.g. mipmap generation)
|
||||
* @return The appropriate texture usage flags for the underlying texture
|
||||
*/
|
||||
[[nodiscard]] wgpu::TextureUsage fToWGPUTextureUsage(TextureUsage const& fUsage,
|
||||
const uint8_t samples, const bool needsComputeStorageSupport,
|
||||
const bool needsRenderAttachmentSupport) {
|
||||
wgpu::TextureUsage retUsage = wgpu::TextureUsage::None;
|
||||
|
||||
// if needsComputeStorageSupport we need to read and write to the texture in a shader and thus
|
||||
// require CopySrc, CopyDst, TextureBinding, & StorageBinding
|
||||
|
||||
// Basing this mapping off of VulkanTexture.cpp's getUsage func and suggestions from Gemini
|
||||
// TODO Validate assumptions, revisit if issues.
|
||||
if (needsComputeStorageSupport || any(TextureUsage::BLIT_SRC & fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::CopySrc;
|
||||
}
|
||||
if (needsComputeStorageSupport ||
|
||||
any((TextureUsage::BLIT_DST | TextureUsage::UPLOADABLE) & fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::CopyDst;
|
||||
}
|
||||
if (needsComputeStorageSupport || any(TextureUsage::SAMPLEABLE & fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::TextureBinding;
|
||||
}
|
||||
if (needsComputeStorageSupport) {
|
||||
retUsage |= wgpu::TextureUsage::StorageBinding;
|
||||
}
|
||||
if (needsRenderAttachmentSupport) {
|
||||
retUsage |= wgpu::TextureUsage::RenderAttachment;
|
||||
}
|
||||
if (any(TextureUsage::BLIT_SRC & fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::TextureBinding;
|
||||
}
|
||||
if (any(TextureUsage::BLIT_DST & fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::RenderAttachment;
|
||||
}
|
||||
if (any(TextureUsage::GEN_MIPMAPPABLE & fUsage)) {
|
||||
retUsage |= (wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding);
|
||||
}
|
||||
// WGPU Render attachment covers either color or stencil situation dependant
|
||||
// NOTE: Depth attachment isn't used this way in Vulkan but logically maps to WGPU docs. If
|
||||
// issues, investigate here.
|
||||
if (any((TextureUsage::COLOR_ATTACHMENT | TextureUsage::STENCIL_ATTACHMENT |
|
||||
TextureUsage::DEPTH_ATTACHMENT) &
|
||||
fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::RenderAttachment;
|
||||
}
|
||||
|
||||
// This is from Vulkan logic- if there are any issues try disabling this first, allows perf
|
||||
// benefit though
|
||||
const bool useTransientAttachment =
|
||||
// Usage consists of attachment flags only.
|
||||
none(fUsage & ~TextureUsage::ALL_ATTACHMENTS) &&
|
||||
// Usage contains at least one attachment flag.
|
||||
any(fUsage & TextureUsage::ALL_ATTACHMENTS) &&
|
||||
// Depth resolve cannot use transient attachment because it uses a custom shader.
|
||||
// TODO: see VulkanDriver::isDepthStencilResolveSupported() to know when to remove this
|
||||
// restriction.
|
||||
// Note that the custom shader does not resolve stencil. We do need to move to vk 1.2
|
||||
// and above to be able to support stencil resolve (along with depth).
|
||||
!(any(fUsage & TextureUsage::DEPTH_ATTACHMENT) && samples > 1);
|
||||
if (useTransientAttachment) {
|
||||
retUsage |= wgpu::TextureUsage::TransientAttachment;
|
||||
}
|
||||
// NOTE: Unused wgpu flags:
|
||||
// StorageAttachment
|
||||
|
||||
// NOTE: Unused Filament flags:
|
||||
// SUBPASS_INPUT VK goes to input attachment which we don't support right now
|
||||
// PROTECTED
|
||||
return retUsage;
|
||||
}
|
||||
|
||||
[[nodiscard]] wgpu::TextureAspect fToWGPUTextureViewAspect(TextureUsage const& fUsage,
|
||||
TextureFormat const& fFormat) {
|
||||
|
||||
@@ -286,9 +208,11 @@ WebGPUTexture::WebGPUTexture(const SamplerType samplerType, const uint8_t levels
|
||||
mAspect{ fToWGPUTextureViewAspect(usage, format) },
|
||||
mWebGPUUsage{ fToWGPUTextureUsage(usage, samples,
|
||||
mMipmapGenerationStrategy == MipmapGenerationStrategy::SPD_COMPUTE_PASS,
|
||||
mMipmapGenerationStrategy == MipmapGenerationStrategy::RENDER_PASS) },
|
||||
mViewUsage{ fToWGPUTextureUsage(usage, samples, false, false) },
|
||||
mDimension{toWebGPUTextureViewDimension(samplerType)},
|
||||
mMipmapGenerationStrategy == MipmapGenerationStrategy::RENDER_PASS,
|
||||
device.HasFeature(wgpu::FeatureName::TransientAttachments)) },
|
||||
mViewUsage{ fToWGPUTextureUsage(usage, samples, false, false,
|
||||
device.HasFeature(wgpu::FeatureName::TransientAttachments)) },
|
||||
mDimension{ toWebGPUTextureViewDimension(samplerType) },
|
||||
mBlockWidth{ filament::backend::getBlockWidth(format) },
|
||||
mBlockHeight{ filament::backend::getBlockHeight(format) },
|
||||
mDefaultMipLevel{ 0 },
|
||||
@@ -359,6 +283,24 @@ WebGPUTexture::WebGPUTexture(WebGPUTexture const* src, const uint8_t baseLevel,
|
||||
toWebGPUTextureViewDimension(src->target)) },
|
||||
mMsaaSidecarTexture{ src->mMsaaSidecarTexture } {}
|
||||
|
||||
WebGPUTexture::WebGPUTexture(const WebGPUTexture* src, const wgpu::TextureView view) noexcept
|
||||
: HwTexture{ src->target, src->levels, src->samples, src->width, src->height, src->depth,
|
||||
src->format, src->usage},
|
||||
mViewFormat{ src->mViewFormat },
|
||||
mMipmapGenerationStrategy{ src->mMipmapGenerationStrategy },
|
||||
mWebGPUFormat{ src->mWebGPUFormat },
|
||||
mAspect{ src->mAspect },
|
||||
mWebGPUUsage{ src->mWebGPUUsage },
|
||||
mViewUsage{ src->mViewUsage },
|
||||
mBlockWidth{ src->mBlockWidth },
|
||||
mBlockHeight{ src->mBlockHeight },
|
||||
mArrayLayerCount{ src->mArrayLayerCount },
|
||||
mTexture{ src->mTexture },
|
||||
mDefaultMipLevel{ src->mDefaultMipLevel },
|
||||
mDefaultBaseArrayLayer{ 0 },
|
||||
mDefaultTextureView{ view },
|
||||
mMsaaSidecarTexture{src->mMsaaSidecarTexture}{}
|
||||
|
||||
wgpu::Texture const& WebGPUTexture::getMsaaSidecarTexture(const uint8_t sampleCount) const {
|
||||
if (mMsaaSidecarTexture == nullptr) {
|
||||
return mMsaaSidecarTexture; // nullptr (no such sidecar)
|
||||
|
||||
@@ -34,11 +34,26 @@ public:
|
||||
NONE,
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a Filament texture and a texture view
|
||||
*/
|
||||
WebGPUTexture(SamplerType, uint8_t levels, TextureFormat, uint8_t samples, uint32_t width,
|
||||
uint32_t height, uint32_t depth, TextureUsage, wgpu::Device const&) noexcept;
|
||||
|
||||
/**
|
||||
* Creates a "Filament Texture View", where the underlying texture is the same as the source,
|
||||
* but the view elements differ
|
||||
*/
|
||||
WebGPUTexture(WebGPUTexture const* src, uint8_t baseLevel, uint8_t levelCount) noexcept;
|
||||
|
||||
/**
|
||||
* @param textureView texture view to use for the respective texture. e.g. it can be a
|
||||
* swizzled Texture view for that filament texture
|
||||
*
|
||||
* Associates the underlying texture with the given texture view
|
||||
*/
|
||||
WebGPUTexture(WebGPUTexture const* source, wgpu::TextureView textureView) noexcept;
|
||||
|
||||
[[nodiscard]] wgpu::TextureAspect getAspect() const { return mAspect; }
|
||||
|
||||
[[nodiscard]] size_t getBlockWidth() const { return mBlockWidth; }
|
||||
@@ -78,9 +93,9 @@ public:
|
||||
|
||||
/**
|
||||
* @param samples The number of samples the underlying texture supports
|
||||
* @param mipLevel The mip level into the underyling texture for which this view will reference
|
||||
* @param mipLevel The mip level into the underlying texture for which this view will reference
|
||||
* (this view will only have one mip level)
|
||||
* @param arrayLayer The layer into the underyling texture for which this view will reference
|
||||
* @param arrayLayer The layer into the underlying texture for which this view will reference
|
||||
* (this view will only have one layer)
|
||||
* @return A texture view for the MSAA sidecar texture
|
||||
*/
|
||||
|
||||
@@ -198,81 +198,21 @@ namespace filament::backend {
|
||||
case TextureFormat::RGB_BPTC_SIGNED_FLOAT: return wgpu::TextureFormat::BC6HRGBFloat;
|
||||
case TextureFormat::RGBA_BPTC_UNORM: return wgpu::TextureFormat::BC7RGBAUnorm;
|
||||
case TextureFormat::SRGB_ALPHA_BPTC_UNORM: return wgpu::TextureFormat::BC7RGBAUnormSrgb;
|
||||
case TextureFormat::RGB565:
|
||||
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm
|
||||
// and discard the alpha and lower precision.
|
||||
FWGPU_LOGW << "Requested Filament texture format RGB565 but getting "
|
||||
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
|
||||
return wgpu::TextureFormat::Undefined;
|
||||
case TextureFormat::RGB9_E5: return wgpu::TextureFormat::RGB9E5Ufloat;
|
||||
case TextureFormat::RGB5_A1:
|
||||
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm
|
||||
// and handle the packing/unpacking in shaders.
|
||||
FWGPU_LOGW << "Requested Filament texture format RGB5_A1 but getting "
|
||||
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
|
||||
return wgpu::TextureFormat::Undefined;
|
||||
case TextureFormat::RGBA4:
|
||||
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm
|
||||
// and handle the packing/unpacking in shaders.
|
||||
FWGPU_LOGW << "Requested Filament texture format RGBA4 but getting "
|
||||
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
|
||||
return wgpu::TextureFormat::Undefined;
|
||||
case TextureFormat::RGB8:
|
||||
FWGPU_LOGW << "Requested Filament texture format RGB8 but getting "
|
||||
"wgpu::TextureFormat::RGBA8Unorm (no direct sRGB equivalent in wgpu "
|
||||
"without alpha)";
|
||||
return wgpu::TextureFormat::RGBA8Unorm;
|
||||
case TextureFormat::SRGB8:
|
||||
FWGPU_LOGW << "Requested Filament texture format SRGB8 but getting "
|
||||
"wgpu::TextureFormat::RGBA8UnormSrgb (no direct sRGB equivalent in wgpu "
|
||||
"without alpha)";
|
||||
return wgpu::TextureFormat::RGBA8UnormSrgb;
|
||||
case TextureFormat::RGB8_SNORM:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB8_SNORM but getting "
|
||||
"wgpu::TextureFormat::RGBA8Snorm (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA8Snorm;
|
||||
case TextureFormat::RGB8UI:
|
||||
FWGPU_LOGW << "Requested Filament texture format RGB8UI but getting "
|
||||
"wgpu::TextureFormat::RGBA8Uint (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA8Uint;
|
||||
case TextureFormat::RGB8I:
|
||||
FWGPU_LOGW << "Requested Filament texture format RGB8I but getting "
|
||||
"wgpu::TextureFormat::RGBA8Sint (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA8Sint;
|
||||
case TextureFormat::RGB9_E5: return wgpu::TextureFormat::RGB9E5Ufloat;
|
||||
case TextureFormat::RGB8: return wgpu::TextureFormat::RGBA8Unorm;
|
||||
case TextureFormat::SRGB8: return wgpu::TextureFormat::RGBA8UnormSrgb;
|
||||
case TextureFormat::RGB8_SNORM: return wgpu::TextureFormat::RGBA8Snorm;
|
||||
case TextureFormat::RGB8UI: return wgpu::TextureFormat::RGBA8Uint;
|
||||
case TextureFormat::RGB8I: return wgpu::TextureFormat::RGBA8Sint;
|
||||
case TextureFormat::R11F_G11F_B10F: return wgpu::TextureFormat::RG11B10Ufloat;
|
||||
case TextureFormat::UNUSED: return wgpu::TextureFormat::Undefined;
|
||||
case TextureFormat::RGB10_A2: return wgpu::TextureFormat::RGB10A2Unorm;
|
||||
case TextureFormat::RGB16F:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB16F but getting "
|
||||
"wgpu::TextureFormat::RGBA16Float (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA16Float;
|
||||
case TextureFormat::RGB16UI:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB16UI but getting "
|
||||
"wgpu::TextureFormat::RGBA16Uint (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA16Uint;
|
||||
case TextureFormat::RGB16I:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB16I but getting "
|
||||
"wgpu::TextureFormat::RGBA16Sint (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA16Sint;
|
||||
case TextureFormat::RGB32F:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB32F but getting "
|
||||
"wgpu::TextureFormat::RGBA32Float (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA32Float;
|
||||
case TextureFormat::RGB32UI:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB32UI but getting "
|
||||
"wgpu::TextureFormat::RGBA32Uint (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA32Uint;
|
||||
case TextureFormat::RGB32I:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB32I but getting "
|
||||
"wgpu::TextureFormat::RGBA32Sint (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA32Sint;
|
||||
case TextureFormat::RGB16F: return wgpu::TextureFormat::RGBA16Float;
|
||||
case TextureFormat::RGB16UI: return wgpu::TextureFormat::RGBA16Uint;
|
||||
case TextureFormat::RGB16I: return wgpu::TextureFormat::RGBA16Sint;
|
||||
case TextureFormat::RGB32F: return wgpu::TextureFormat::RGBA32Float;
|
||||
case TextureFormat::RGB32UI: return wgpu::TextureFormat::RGBA32Uint;
|
||||
case TextureFormat::RGB32I: return wgpu::TextureFormat::RGBA32Sint;
|
||||
case TextureFormat::DXT1_RGB: return wgpu::TextureFormat::BC1RGBAUnorm;
|
||||
case TextureFormat::DXT1_RGBA: return wgpu::TextureFormat::BC1RGBAUnorm;
|
||||
case TextureFormat::DXT3_RGBA: return wgpu::TextureFormat::BC2RGBAUnorm;
|
||||
@@ -281,6 +221,20 @@ namespace filament::backend {
|
||||
case TextureFormat::DXT1_SRGBA: return wgpu::TextureFormat::BC1RGBAUnormSrgb;
|
||||
case TextureFormat::DXT3_SRGBA: return wgpu::TextureFormat::BC2RGBAUnormSrgb;
|
||||
case TextureFormat::DXT5_SRGBA: return wgpu::TextureFormat::BC3RGBAUnormSrgb;
|
||||
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm and handle the
|
||||
// packing/unpacking in shaders.
|
||||
case TextureFormat::RGB565:
|
||||
FWGPU_LOGW << "Requested Filament texture format RGB565 but getting "
|
||||
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
|
||||
return wgpu::TextureFormat::Undefined;
|
||||
case TextureFormat::RGB5_A1:
|
||||
FWGPU_LOGW << "Requested Filament texture format RGB5_A1 but getting "
|
||||
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
|
||||
return wgpu::TextureFormat::Undefined;
|
||||
case TextureFormat::RGBA4:
|
||||
FWGPU_LOGW << "Requested Filament texture format RGBA4 but getting "
|
||||
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
|
||||
return wgpu::TextureFormat::Undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,6 +295,20 @@ namespace filament::backend {
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr wgpu::ComponentSwizzle toWGPUComponentSwizzle(const backend::TextureSwizzle swizzle) {
|
||||
switch (swizzle) {
|
||||
case backend::TextureSwizzle::CHANNEL_0: return wgpu::ComponentSwizzle::R;
|
||||
case backend::TextureSwizzle::CHANNEL_1: return wgpu::ComponentSwizzle::G;
|
||||
case backend::TextureSwizzle::CHANNEL_2: return wgpu::ComponentSwizzle::B;
|
||||
case backend::TextureSwizzle::CHANNEL_3: return wgpu::ComponentSwizzle::A;
|
||||
case backend::TextureSwizzle::SUBSTITUTE_ZERO: return wgpu::ComponentSwizzle::Zero;
|
||||
case backend::TextureSwizzle::SUBSTITUTE_ONE: return wgpu::ComponentSwizzle::One;
|
||||
default:
|
||||
FWGPU_LOGW << "invalid swizzle component returning wgpu::ComponentSwizzle::Undefined";
|
||||
return wgpu::ComponentSwizzle::Undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if https://www.w3.org/TR/webgpu/#copy-compatible which states:
|
||||
* Two GPUTextureFormats format1 and format2 are copy-compatible if:
|
||||
@@ -534,6 +502,109 @@ namespace filament::backend {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Todo: should this take into account sRGB/linear when determining if conversion is necessary?
|
||||
* For instance, if the output format is the same as the input, except the output is sRGB
|
||||
* do we really need to do a conversion? If the answer is no, we should do that check here
|
||||
* and return conversionNecessary false in that case.
|
||||
* However, doing a straight-forward comparison is the safest most conservative thing to do
|
||||
* for functional correctness and NOT doing a conversion in such cases could be considered
|
||||
* an optimization. Thus, consider the optimization when we have better test coverage to
|
||||
* experiment with such a refactor.
|
||||
* @return True if theres a format mismatch
|
||||
*/
|
||||
[[nodiscard]] constexpr bool conversionNecessary(const wgpu::TextureFormat source,
|
||||
const wgpu::TextureFormat destination, const PixelDataType pixelDataType) {
|
||||
return source != destination && pixelDataType != PixelDataType::COMPRESSED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fUsage Filament's requested texture usage
|
||||
* @param samples How many samples to use for MSAA
|
||||
* @param needsComputeStorageSupport if we need to use this texture as storage binding in something
|
||||
* like a compute shader
|
||||
* @param needsRenderAttachmentSupport if we need to use this texture as a render pass attachment
|
||||
* in something like a render pass blit (e.g. mipmap generation)
|
||||
* @param deviceSupportsTransientAttachments if the device itself supports Render Attachments
|
||||
* @return The appropriate texture usage flags for the underlying texture
|
||||
*/
|
||||
[[nodiscard]] constexpr wgpu::TextureUsage fToWGPUTextureUsage(TextureUsage const& fUsage,
|
||||
const uint8_t samples, const bool needsComputeStorageSupport,
|
||||
const bool needsRenderAttachmentSupport, const bool deviceSupportsTransientAttachments) {
|
||||
wgpu::TextureUsage retUsage{ wgpu::TextureUsage::None };
|
||||
|
||||
if (any(TextureUsage::BLIT_SRC & fUsage)) {
|
||||
retUsage |= (wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding);
|
||||
}
|
||||
if (any(TextureUsage::BLIT_DST & fUsage)) {
|
||||
retUsage |= (wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
|
||||
}
|
||||
if (any(TextureUsage::UPLOADABLE & fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::CopyDst;
|
||||
}
|
||||
if (any(TextureUsage::GEN_MIPMAPPABLE & fUsage)) {
|
||||
retUsage |= (wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding);
|
||||
}
|
||||
if (any(TextureUsage::SAMPLEABLE & fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::TextureBinding;
|
||||
}
|
||||
// if needsComputeStorageSupport we need to read and write to the texture in a shader and thus
|
||||
// require CopySrc, CopyDst, TextureBinding, & StorageBinding
|
||||
if (needsComputeStorageSupport) {
|
||||
retUsage |= (wgpu::TextureUsage::StorageBinding |
|
||||
wgpu::TextureUsage::CopySrc |
|
||||
wgpu::TextureUsage::CopyDst |
|
||||
wgpu::TextureUsage::TextureBinding);
|
||||
}
|
||||
|
||||
wgpu::TextureUsage transientAttachmentNeeded{ wgpu::TextureUsage::None };
|
||||
const bool useTransientAttachment {
|
||||
deviceSupportsTransientAttachments &&
|
||||
// Usage consists of attachment flags only.
|
||||
none(fUsage & ~TextureUsage::ALL_ATTACHMENTS) &&
|
||||
// Usage contains at least one attachment flag.
|
||||
any(fUsage & TextureUsage::ALL_ATTACHMENTS) &&
|
||||
// Depth resolve cannot use transient attachment because it uses a custom shader.
|
||||
// TODO: see VulkanDriver::isDepthStencilResolveSupported() to know when to remove this
|
||||
// restriction.
|
||||
// Note that the custom shader does not resolve stencil. We do need to move to vk 1.2
|
||||
// and above to be able to support stencil resolve (along with depth).
|
||||
!(any(fUsage & TextureUsage::DEPTH_ATTACHMENT) && samples > 1)};
|
||||
if (useTransientAttachment) {
|
||||
transientAttachmentNeeded |= wgpu::TextureUsage::TransientAttachment;
|
||||
}
|
||||
|
||||
// A texture that is a blit destination or render attachment will often need to be
|
||||
// a copy source for subsequent operations (e.g., mipmap generation, readbacks).
|
||||
// However, we dont need to add the CopySrc IF its a transientAttachment
|
||||
if (any((TextureUsage::BLIT_DST | TextureUsage::COLOR_ATTACHMENT |
|
||||
TextureUsage::DEPTH_ATTACHMENT) &
|
||||
fUsage)) {
|
||||
if (!useTransientAttachment) {
|
||||
retUsage |= wgpu::TextureUsage::CopySrc;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsRenderAttachmentSupport) {
|
||||
retUsage |= wgpu::TextureUsage::RenderAttachment;
|
||||
}
|
||||
// WGPU Render attachment covers either color or stencil situation dependant
|
||||
if (any((TextureUsage::COLOR_ATTACHMENT | TextureUsage::STENCIL_ATTACHMENT |
|
||||
TextureUsage::DEPTH_ATTACHMENT) &
|
||||
fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::RenderAttachment;
|
||||
retUsage |= transientAttachmentNeeded;
|
||||
}
|
||||
|
||||
// NOTE: Unused wgpu flags:
|
||||
// StorageAttachment
|
||||
|
||||
// NOTE: Unused Filament flags:
|
||||
// SUBPASS_INPUT: VK goes to input attachment which we don't support right now
|
||||
// PROTECTED
|
||||
return retUsage;
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_WEBGPUTEXTUREHELPERS_H
|
||||
|
||||
@@ -67,9 +67,11 @@ constexpr std::array REQUIRED_FEATURES = {
|
||||
};
|
||||
|
||||
constexpr std::array OPTIONAL_FEATURES = {
|
||||
wgpu::FeatureName::CoreFeaturesAndLimits,
|
||||
wgpu::FeatureName::DepthClipControl,
|
||||
wgpu::FeatureName::Depth32FloatStencil8,
|
||||
wgpu::FeatureName::CoreFeaturesAndLimits };
|
||||
wgpu::FeatureName::TextureComponentSwizzle,
|
||||
};
|
||||
|
||||
enum class LimitToValidate : uint8_t {
|
||||
begin = 0,// needs to be first for iterating through all possible values in the enum
|
||||
@@ -505,6 +507,7 @@ struct AdapterDetailsHash final {
|
||||
|
||||
// selects one preferred adapter or panics if none can be found
|
||||
wgpu::Adapter selectPreferredAdapter(
|
||||
WebGPUPlatform::Configuration const& configuration,
|
||||
std::unordered_set<AdapterDetails, AdapterDetailsHash> const& compatibleAdapters) {
|
||||
// for each unique adapter...
|
||||
AdapterDetails const* selectedAdapter = nullptr;
|
||||
@@ -512,6 +515,10 @@ wgpu::Adapter selectPreferredAdapter(
|
||||
|
||||
// choose the most desirable adapter that meets the minimum requirements...
|
||||
for (AdapterDetails const& details: compatibleAdapters) {
|
||||
if (configuration.forceBackendType != wgpu::BackendType::Undefined &&
|
||||
configuration.forceBackendType != details.info.backendType) {
|
||||
continue;
|
||||
}
|
||||
if (!adapterMeetsMinimumRequirements(details)) {
|
||||
continue;
|
||||
}
|
||||
@@ -587,7 +594,7 @@ wgpu::Adapter WebGPUPlatform::requestAdapter(wgpu::Surface const& surface) {
|
||||
}
|
||||
const std::unordered_set<AdapterDetails, AdapterDetailsHash> compatibleAdapters =
|
||||
requestCompatibleAdapters(mInstance, requests);
|
||||
return selectPreferredAdapter(compatibleAdapters);
|
||||
return selectPreferredAdapter(getConfiguration(), compatibleAdapters);
|
||||
}
|
||||
|
||||
wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) {
|
||||
|
||||
@@ -38,7 +38,12 @@ std::vector<wgpu::RequestAdapterOptions> WebGPUPlatform::getAdapterOptions() {
|
||||
constexpr std::array powerPreferences = {
|
||||
wgpu::PowerPreference::HighPerformance,
|
||||
wgpu::PowerPreference::LowPower };
|
||||
constexpr std::array backendTypes = { wgpu::BackendType::Metal };
|
||||
constexpr std::array backendTypes = {
|
||||
wgpu::BackendType::Metal,
|
||||
// To enable software rasterization on MacOS, we need to ensure Vulkan adapters are
|
||||
// available.
|
||||
wgpu::BackendType::Vulkan,
|
||||
};
|
||||
constexpr std::array forceFallbackAdapters = { false, true };
|
||||
constexpr size_t totalCombinations =
|
||||
powerPreferences.size() * backendTypes.size() * forceFallbackAdapters.size();
|
||||
|
||||
@@ -41,36 +41,21 @@
|
||||
uint32_t height;
|
||||
} wl;
|
||||
}// namespace
|
||||
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11)
|
||||
// TODO: we should allow for headless on Linux explicitly. Right now this is the headless path
|
||||
// (with no FILAMENT_SUPPORTS_XCB or FILAMENT_SUPPORTS_XLIB).
|
||||
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11) && defined(FILAMENT_SUPPORTS_XLIB)
|
||||
// TODO: we should allow for headless on Linux explicitly. Right now headless path doesn't work
|
||||
#include <dlfcn.h>
|
||||
#if defined(FILAMENT_SUPPORTS_XCB)
|
||||
#include <xcb/xcb.h>
|
||||
namespace {
|
||||
typedef xcb_connection_t* (*XCB_CONNECT)(const char* displayname, int* screenp);
|
||||
}// namespace
|
||||
#endif
|
||||
#if defined(FILAMENT_SUPPORTS_XLIB)
|
||||
#include <X11/Xlib.h>
|
||||
namespace {
|
||||
typedef Display* (*X11_OPEN_DISPLAY)(const char*);
|
||||
}// namespace
|
||||
#endif
|
||||
static constexpr const char* LIBRARY_X11 = "libX11.so.6";
|
||||
#include <X11/Xlib.h>
|
||||
|
||||
static constexpr const char* LIBRARY_X11 { "libX11.so.6" };
|
||||
|
||||
namespace {
|
||||
struct XEnv {
|
||||
#if defined(FILAMENT_SUPPORTS_XCB)
|
||||
XCB_CONNECT xcbConnect;
|
||||
xcb_connection_t* connection;
|
||||
#endif
|
||||
#if defined(FILAMENT_SUPPORTS_XLIB)
|
||||
X11_OPEN_DISPLAY openDisplay;
|
||||
Display* display;
|
||||
#endif
|
||||
void* library = nullptr;
|
||||
} g_x11;
|
||||
}// namespace
|
||||
typedef Display* (*X11_OPEN_DISPLAY)(const char*);
|
||||
struct XEnv final {
|
||||
X11_OPEN_DISPLAY openDisplay;
|
||||
Display* display { nullptr };
|
||||
void* library { nullptr };
|
||||
} g_x11;
|
||||
} // namespace
|
||||
#else
|
||||
#error Not a supported Linux or FeeBSD + WebGPU platform
|
||||
#endif
|
||||
@@ -116,59 +101,26 @@ wgpu::Extent2D WebGPUPlatform::getSurfaceExtent(void* nativeWindow) const {
|
||||
surfaceExtent.height = ptrval->height;
|
||||
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
|
||||
<< "Unable to get window size for Linux Wayland-backed surface.";
|
||||
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11)
|
||||
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11) && defined (FILAMENT_SUPPORTS_XLIB)
|
||||
if (g_x11.library == nullptr) {
|
||||
g_x11.library = dlopen(LIBRARY_X11, RTLD_LOCAL | RTLD_NOW);
|
||||
FILAMENT_CHECK_PRECONDITION(g_x11.library) << "Unable to open X11 library.";
|
||||
#if defined(FILAMENT_SUPPORTS_XCB)
|
||||
g_x11.xcbConnect = (XCB_CONNECT) dlsym(g_x11.library, "xcb_connect");
|
||||
int screen = 0;
|
||||
g_x11.connection = g_x11.xcbConnect(nullptr, &screen);
|
||||
#endif
|
||||
#if defined(FILAMENT_SUPPORTS_XLIB)
|
||||
g_x11.openDisplay = (X11_OPEN_DISPLAY) dlsym(g_x11.library, "XOpenDisplay");
|
||||
g_x11.display = g_x11.openDisplay(NULL);
|
||||
FILAMENT_CHECK_PRECONDITION(g_x11.display) << "Unable to open X11 display.";
|
||||
#endif
|
||||
}
|
||||
#if defined(FILAMENT_SUPPORTS_XCB) || defined(FILAMENT_SUPPORTS_XLIB)
|
||||
bool useXcb = false;
|
||||
#endif
|
||||
#if defined(FILAMENT_SUPPORTS_XCB)
|
||||
#if defined(FILAMENT_SUPPORTS_XLIB)
|
||||
useXcb = (SWAP_CHAIN_CONFIG_ENABLE_XCB) != 0;
|
||||
#else
|
||||
useXcb = true;
|
||||
#endif
|
||||
if (useXcb) {
|
||||
const xcb_setup_t* setup = xcb_get_setup(g_x11.connection);
|
||||
xcb_screen_iterator_t screen_iter = xcb_setup_roots_iterator(setup);
|
||||
xcb_screen_t* screen = screen_iter.data;
|
||||
surfaceExtent.width = static_cast<uint32_t>(screen->width_in_pixels);
|
||||
surfaceExtent.height = static_cast<uint32_t>(screen->height_in_pixels);
|
||||
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
|
||||
<< "Unable to get window surface size for Linux (or FreeBSD) "
|
||||
"XCB-backed surface.";
|
||||
}
|
||||
#endif
|
||||
#if defined(FILAMENT_SUPPORTS_XLIB)
|
||||
if (!useXcb) {
|
||||
int screenNumber = DefaultScreen(g_x11.display);
|
||||
Screen* screen = ScreenOfDisplay(g_x11.display, screenNumber);
|
||||
surfaceExtent.width = static_cast<uint32_t>(WidthOfScreen(screen));
|
||||
surfaceExtent.height = static_cast<uint32_t>(HeightOfScreen(screen));
|
||||
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
|
||||
<< "Unable to get window surface size for Linux (or FreeBSD) "
|
||||
"XLib-backed surface.";
|
||||
}
|
||||
#endif
|
||||
if (g_x11.openDisplay == nullptr) {
|
||||
g_x11.openDisplay = reinterpret_cast<X11_OPEN_DISPLAY>(dlsym(g_x11.library, "XOpenDisplay"));
|
||||
g_x11.display = g_x11.openDisplay(NULL);
|
||||
FILAMENT_CHECK_PRECONDITION(g_x11.display) << "Unable to open X11 display.";
|
||||
}
|
||||
Window window { reinterpret_cast<Window const>(nativeWindow) };
|
||||
XWindowAttributes windowAttributes;
|
||||
Status ok { XGetWindowAttributes(g_x11.display, window, &windowAttributes) };
|
||||
FILAMENT_CHECK_PRECONDITION(ok != 0) << " XGetWindowAttributes failed";
|
||||
surfaceExtent.width = static_cast<uint32_t>(windowAttributes.width);
|
||||
surfaceExtent.height = static_cast<uint32_t>(windowAttributes.height);
|
||||
|
||||
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
|
||||
<< "Cannot get window surface size for X11 surface for Linux (or FreeBSD) OS "
|
||||
"(not built with support for XCB or XLIB?)";
|
||||
#elif defined(__linux__)
|
||||
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
|
||||
<< "Cannot get window surface size for Linux (or FreeBSD) OS "
|
||||
"(not built with support for Wayland or X11?)";
|
||||
"(not built with support for XLIB?)";
|
||||
#else
|
||||
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
|
||||
<< "Not a supported (Linux) OS + WebGPU platform";
|
||||
@@ -176,80 +128,40 @@ wgpu::Extent2D WebGPUPlatform::getSurfaceExtent(void* nativeWindow) const {
|
||||
return surfaceExtent;
|
||||
}
|
||||
|
||||
wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t flags) {
|
||||
wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t /*flags*/) {
|
||||
wgpu::Surface surface = nullptr;
|
||||
#if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)
|
||||
wl* ptrval = reinterpret_cast<wl*>(nativeWindow);
|
||||
wgpu::SurfaceSourceWaylandSurface surfaceSourceWayland{};
|
||||
surfaceSourceWayland.display = ptrval->display;
|
||||
surfaceSourceWayland.surface = ptrval->surface;
|
||||
wgpu::SurfaceDescriptor surfaceDescriptor{
|
||||
wgpu::SurfaceDescriptor const surfaceDescriptor {
|
||||
.nextInChain = &surfaceSourceWayland,
|
||||
.label = "linux_wayland_surface"
|
||||
};
|
||||
surface = mInstance.CreateSurface(&surfaceDescriptor);
|
||||
FILAMENT_CHECK_POSTCONDITION(surface != nullptr)
|
||||
<< "Unable to create Linux Wayland-backed surface.";
|
||||
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11)
|
||||
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11) && defined(FILAMENT_SUPPORTS_XLIB)
|
||||
if (g_x11.library == nullptr) {
|
||||
g_x11.library = dlopen(LIBRARY_X11, RTLD_LOCAL | RTLD_NOW);
|
||||
FILAMENT_CHECK_PRECONDITION(g_x11.library) << "Unable to open X11 library.";
|
||||
#if defined(FILAMENT_SUPPORTS_XCB)
|
||||
g_x11.xcbConnect = (XCB_CONNECT) dlsym(g_x11.library, "xcb_connect");
|
||||
int screen = 0;
|
||||
g_x11.connection = g_x11.xcbConnect(nullptr, &screen);
|
||||
#endif
|
||||
#if defined(FILAMENT_SUPPORTS_XLIB)
|
||||
g_x11.openDisplay = (X11_OPEN_DISPLAY) dlsym(g_x11.library, "XOpenDisplay");
|
||||
g_x11.display = g_x11.openDisplay(NULL);
|
||||
FILAMENT_CHECK_PRECONDITION(g_x11.display) << "Unable to open X11 display.";
|
||||
#endif
|
||||
}
|
||||
#if defined(FILAMENT_SUPPORTS_XCB) || defined(FILAMENT_SUPPORTS_XLIB)
|
||||
bool useXcb = false;
|
||||
#endif
|
||||
#if defined(FILAMENT_SUPPORTS_XCB)
|
||||
#if defined(FILAMENT_SUPPORTS_XLIB)
|
||||
useXcb = (flags & SWAP_CHAIN_CONFIG_ENABLE_XCB) != 0;
|
||||
#else
|
||||
useXcb = true;
|
||||
#endif
|
||||
if (useXcb) {
|
||||
wgpu::SurfaceSourceXCBWindow surfaceSourceXcb{};
|
||||
surfaceSourceXcb.connection = g_x11.connection;
|
||||
|
||||
// TODO: this looks really wrong, please fix!!
|
||||
surfaceSourceXcb.window = *((uint32_t*) nativeWindow);
|
||||
wgpu::SurfaceDescriptor surfaceDescriptor{
|
||||
.nextInChain = &surfaceSourceXcb,
|
||||
.label = "linux_xcb_surface"
|
||||
};
|
||||
surface = mInstance.CreateSurface(&surfaceDescriptor);
|
||||
FILAMENT_CHECK_POSTCONDITION(surface != nullptr)
|
||||
<< "Unable to create Linux (or FreeBSD) XCB-backed surface.";
|
||||
}
|
||||
#endif
|
||||
#if defined(FILAMENT_SUPPORTS_XLIB)
|
||||
if (!useXcb) {
|
||||
wgpu::SurfaceSourceXlibWindow surfaceSourceXlib{};
|
||||
surfaceSourceXlib.display = g_x11.display;
|
||||
surfaceSourceXlib.window = reinterpret_cast<uint64_t>(nativeWindow);
|
||||
wgpu::SurfaceDescriptor surfaceDescriptor{
|
||||
.nextInChain = &surfaceSourceXlib,
|
||||
.label = "linux_xlib_surface"
|
||||
};
|
||||
surface = mInstance.CreateSurface(&surfaceDescriptor);
|
||||
FILAMENT_CHECK_POSTCONDITION(surface != nullptr)
|
||||
<< "Unable to create Linux (or FreeBSD) XLib-backed surface.";
|
||||
}
|
||||
#endif
|
||||
if (g_x11.openDisplay == nullptr) {
|
||||
g_x11.openDisplay = (X11_OPEN_DISPLAY) dlsym(g_x11.library, "XOpenDisplay");
|
||||
g_x11.display = g_x11.openDisplay(NULL);
|
||||
FILAMENT_CHECK_PRECONDITION(g_x11.display) << "Unable to open X11 display.";
|
||||
}
|
||||
wgpu::SurfaceSourceXlibWindow surfaceSourceXlib{};
|
||||
surfaceSourceXlib.display = g_x11.display;
|
||||
surfaceSourceXlib.window = reinterpret_cast<Window const>(nativeWindow);
|
||||
wgpu::SurfaceDescriptor const surfaceDescriptor {
|
||||
.nextInChain = &surfaceSourceXlib,
|
||||
.label = "linux_xlib_surface"
|
||||
};
|
||||
surface = mInstance.CreateSurface(&surfaceDescriptor);
|
||||
FILAMENT_CHECK_POSTCONDITION(surface != nullptr)
|
||||
<< "Cannot create WebGPU X11 surface for Linux (or FreeBSD) OS "
|
||||
"(not built with support for XCB or XLIB?)";
|
||||
#elif defined(__linux__)
|
||||
FILAMENT_CHECK_POSTCONDITION(surface != nullptr)
|
||||
<< "Cannot create WebGPU surface for Linux (or FreeBSD) OS "
|
||||
"(not built with support for Wayland or X11?)";
|
||||
<< "Unable to create Linux (or FreeBSD) XLib-backed surface.";
|
||||
#else
|
||||
FILAMENT_CHECK_POSTCONDITION(surface != nullptr)
|
||||
<< "Not a supported (Linux) OS + WebGPU platform";
|
||||
|
||||
45
filament/backend/src/webgpu/utils/AsyncTaskCounter.cpp
Normal file
45
filament/backend/src/webgpu/utils/AsyncTaskCounter.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "AsyncTaskCounter.h"
|
||||
|
||||
#include "utils/debug.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace filament::backend::webgpuutils {
|
||||
|
||||
void AsyncTaskCounter::startTask() {
|
||||
std::lock_guard<std::mutex> lock{ mMutex };
|
||||
++mTasksInProgress;
|
||||
}
|
||||
|
||||
void AsyncTaskCounter::finishTask() {
|
||||
std::lock_guard<std::mutex> lock{ mMutex };
|
||||
--mTasksInProgress;
|
||||
assert_invariant(mTasksInProgress >= 0);
|
||||
if (mTasksInProgress == 0) {
|
||||
mFinishedCondition.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncTaskCounter::waitForAllToFinish() {
|
||||
std::unique_lock<std::mutex> lock{ mMutex };
|
||||
mFinishedCondition.wait(lock, [this] { return mTasksInProgress == 0; });
|
||||
}
|
||||
|
||||
} // namespace filament::backend::webgpuutils
|
||||
|
||||
62
filament/backend/src/webgpu/utils/AsyncTaskCounter.h
Normal file
62
filament/backend/src/webgpu/utils/AsyncTaskCounter.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_UTIL_ASYNCWORKCOUNTER_H
|
||||
#define TNT_FILAMENT_BACKEND_UTIL_ASYNCWORKCOUNTER_H
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
namespace filament::backend::webgpuutils {
|
||||
|
||||
/**
|
||||
* Counts the number of asynchronous tasks of a given type.
|
||||
* This can be used to wait for such a count to decrement to 0 (wait for work to complete) in a
|
||||
* thead safe manner.
|
||||
*/
|
||||
class AsyncTaskCounter final {
|
||||
public:
|
||||
AsyncTaskCounter() = default;
|
||||
AsyncTaskCounter(AsyncTaskCounter const&) = delete;
|
||||
AsyncTaskCounter(AsyncTaskCounter const&&) = delete;
|
||||
AsyncTaskCounter& operator=(AsyncTaskCounter const&) = delete;
|
||||
AsyncTaskCounter& operator=(AsyncTaskCounter const&&) = delete;
|
||||
~AsyncTaskCounter() = default;
|
||||
|
||||
/**
|
||||
* Asynchronously (thread-safe) increase the work counter by one
|
||||
*/
|
||||
void startTask();
|
||||
|
||||
/**
|
||||
* Asynchronously (thread-safe) decrease the work counter by one
|
||||
*/
|
||||
void finishTask();
|
||||
|
||||
/**
|
||||
* Wait for all tasks to finish (for the work counter to reach 0) (thread-safe)
|
||||
*/
|
||||
void waitForAllToFinish();
|
||||
|
||||
private:
|
||||
std::mutex mMutex;
|
||||
std::condition_variable mFinishedCondition;
|
||||
int mTasksInProgress{ 0 };
|
||||
};
|
||||
|
||||
} // namespace filament::backend::webgpuutils
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_UTIL_ASYNCWORKCOUNTER_H
|
||||
@@ -49,7 +49,7 @@ Backend parseArgumentsForBackend(int argc, char* argv[]) {
|
||||
} else if (arg == "webgpu") {
|
||||
backend = Backend::WEBGPU;
|
||||
} else {
|
||||
std::cerr << "Unrecognized target API. Must be 'opengl'|'vulkan'|'metal'."
|
||||
std::cerr << "Unrecognized target API. Must be 'opengl'|'vulkan'|'metal'|'webgpu'."
|
||||
<< std::endl
|
||||
<< "Defaulting to OpenGL."
|
||||
<< std::endl;
|
||||
|
||||
@@ -85,6 +85,18 @@ std::filesystem::path ScreenshotParams::expectedFilePath() const {
|
||||
return expectedDirectoryPath().append(expectedFileName());
|
||||
}
|
||||
|
||||
std::filesystem::path ScreenshotParams::diffFilePath() const {
|
||||
return diffDirectoryPath().append(diffFileName());
|
||||
}
|
||||
|
||||
std::filesystem::path ScreenshotParams::diffDirectoryPath() {
|
||||
return BackendTest::binaryDirectory().append("images/diff_images");
|
||||
}
|
||||
|
||||
std::string ScreenshotParams::diffFileName() const {
|
||||
return absl::StrFormat("%s_diff.png", mFileName);
|
||||
}
|
||||
|
||||
const std::string ScreenshotParams::filePrefix() const {
|
||||
// TODO(b/422804941): If there are platform specific goldens, when on those platforms append a
|
||||
// unique platform identifying string to this.
|
||||
@@ -128,8 +140,6 @@ void ImageExpectation::compareImage() const {
|
||||
if (bytesFilled) {
|
||||
#ifndef FILAMENT_IOS
|
||||
LoadedPng loadedImage(mParams.expectedFilePath());
|
||||
// Bytewise compare.
|
||||
EXPECT_EQ(loadedImage.bytes().size(), mResult.bytes().size());
|
||||
if (loadedImage.bytes().size() != mResult.bytes().size()) {
|
||||
// Something is wrong with the size of the expected result, which usually means the file
|
||||
// is missing. Fail the test and early return so later steps can assume the image is
|
||||
@@ -137,18 +147,41 @@ void ImageExpectation::compareImage() const {
|
||||
BackendTest::markImageAsFailure(mParams.filePrefix());
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize the vector to full saturation on all channels, bad pixels will have
|
||||
// color channels but not alpha set to 0 to produce highly contrasting black pixels.
|
||||
std::vector<unsigned char> imageDiff( loadedImage.bytes().size(), 255 );
|
||||
int pixelDeviations = 0;
|
||||
for (int i = 0; i < mResult.bytes().size(); ++i) {
|
||||
if (std::abs( mResult.bytes()[i] - loadedImage.bytes()[i] ) >
|
||||
mParams.pixelMatchThreshold()) {
|
||||
pixelDeviations++;
|
||||
for (int i = 0; i < mResult.bytes().size(); i += 4) {
|
||||
// In order to handle color channels propoerly for the output, stride 4 bytes at a time.
|
||||
// A failure of any byte in a pixel counts as a failure of the whole pixel.
|
||||
for (int j = 0; j < 4; ++j) {
|
||||
if( std::abs( mResult.bytes()[i+j] - loadedImage.bytes()[i+j] ) >
|
||||
mParams.pixelMatchThreshold() ) {
|
||||
pixelDeviations++;
|
||||
imageDiff[i] = 0;
|
||||
imageDiff[i+1] = 0;
|
||||
imageDiff[i+2] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPECT_LE(pixelDeviations, mParams.allowedPixelDeviations());
|
||||
if (pixelDeviations > mParams.allowedPixelDeviations()) {
|
||||
BackendTest::markImageAsFailure(mParams.filePrefix());
|
||||
image::LinearImage image;
|
||||
image = image::toLinearWithAlpha<uint8_t>(
|
||||
mParams.width(), mParams.height(),
|
||||
mParams.width() * 4, (uint8_t *)imageDiff.data(),
|
||||
[](uint8_t value) -> float { return value; },
|
||||
[](filament::math::float4 rgba) -> filament::math::float4 { return rgba; });
|
||||
std::string filePath = mParams.diffFilePath();
|
||||
std::ofstream pngStream(filePath, std::ios::binary | std::ios::trunc);
|
||||
// To avoid going from linear -> sRGB -> linear save the PNG as linear.
|
||||
image::ImageEncoder::encode(pngStream, image::ImageEncoder::Format::PNG_LINEAR, image, "",
|
||||
filePath);
|
||||
}
|
||||
// TODO: Add better debug output, such as generating a diff image.
|
||||
EXPECT_LE(pixelDeviations, mParams.allowedPixelDeviations());
|
||||
#else
|
||||
// For builds that can't load PNGs (currently iOS only) use the expected hash.
|
||||
uint32_t actualHash = mResult.hash();
|
||||
|
||||
@@ -58,6 +58,9 @@ public:
|
||||
static std::filesystem::path expectedDirectoryPath();
|
||||
std::string expectedFileName() const;
|
||||
std::filesystem::path expectedFilePath() const;
|
||||
static std::filesystem::path diffDirectoryPath();
|
||||
std::string diffFileName() const;
|
||||
std::filesystem::path diffFilePath() const;
|
||||
const std::string filePrefix() const;
|
||||
int allowedPixelDeviations() const;
|
||||
int pixelMatchThreshold() const;
|
||||
|
||||
@@ -19,6 +19,8 @@ code. When cmake is run it will copy this directory to the build output creating
|
||||
The unit tests will then write their resulting images into `images/actual_images` in order to make
|
||||
the tests easier to debug.
|
||||
|
||||
If an image comparison test fails, it writes a diff image to `images/diff_images` where each pixel of the diff is white (255,255,255) if the comparison for the corresponding pixel succeeds and black (0,0,0) of the comparison for the corresponding pixel fails.
|
||||
|
||||
### Python utility for updating golden images and comparing results
|
||||
|
||||
Inside the unit test source code directory there is a python script called
|
||||
|
||||
44
filament/backend/test/Workarounds.h
Normal file
44
filament/backend/test/Workarounds.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_BACKEND_TEST_WORKAROUNDS_H
|
||||
#define TNT_BACKEND_TEST_WORKAROUNDS_H
|
||||
|
||||
#if defined(FILAMENT_SUPPORTS_WEBGPU)
|
||||
// consider, if needed, adding (as Vulkan also has transient attachments): || defined(FILAMENT_SUPPORTS_VULKAN)
|
||||
// TODO: support TextureUsage::READ_PIXELS (or something like that)
|
||||
// and search-and-replace TEXTURE_USAGE_READ_PIXELS with
|
||||
// "| TextureUsage::READ_PIXELS" when it is available.
|
||||
// Until then, this serves as a workaround for WebGPU testing, such that
|
||||
// it can easily be swapped out for the "right" thing when that is available.
|
||||
// The problem this is trying to address is that the WebGPU backend will
|
||||
// assign a texture with wgpu::TextureUsage::TransientAttachment if the Filament
|
||||
// usage flags don't indicate the pixels are needed CPU-side. If transient attachment is
|
||||
// assigned to the texture, the texture will not be available later for readPixels, which
|
||||
// is often needed for the unit tests. If we suggest that
|
||||
// the texture needs to support being a blit source, then TransientAttachment will
|
||||
// not get assigned. However, this is not quite right in the test, because a test
|
||||
// may not really want to do a blit with the texture, just read the pixels from it later for
|
||||
// image comparison (renderdiff).
|
||||
// Ideally, the test could express this with a TextureUsage::READ_PIXELS flag, but that
|
||||
// does not yet exist. So, a hack would be to explicitly set the BLIT_SRC flag instead in
|
||||
// its place.
|
||||
#define TEXTURE_USAGE_READ_PIXELS | TextureUsage::BLIT_SRC
|
||||
#else
|
||||
#define TEXTURE_USAGE_READ_PIXELS
|
||||
#endif
|
||||
|
||||
#endif // TNT_BACKEND_TEST_WORKAROUNDS_H
|
||||
@@ -144,6 +144,7 @@ static void createFaces(DriverApi& dapi, Handle<HwTexture> texture, int baseWidt
|
||||
}
|
||||
|
||||
TEST_F(BlitTest, ColorMagnify) {
|
||||
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
|
||||
auto& api = getDriverApi();
|
||||
mCleanup.addPostCall([&]() { executeCommands(); });
|
||||
|
||||
@@ -160,17 +161,18 @@ TEST_F(BlitTest, ColorMagnify) {
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a source texture.
|
||||
Handle<HwTexture> const srcTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
Handle<HwTexture> const srcTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
|
||||
kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT |
|
||||
TextureUsage::BLIT_SRC));
|
||||
const bool flipY = sBackend == Backend::OPENGL;
|
||||
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, float3(0.5, 0, 0), flipY);
|
||||
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 1, float3(0, 0, 0.5), flipY);
|
||||
|
||||
// Create a destination texture.
|
||||
Handle<HwTexture> const dstTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
Handle<HwTexture> const dstTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
|
||||
kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT | TextureUsage::BLIT_DST));
|
||||
|
||||
// Create a RenderTarget for each texture's miplevel.
|
||||
Handle<HwRenderTarget> srcRenderTargets[kNumLevels];
|
||||
@@ -206,6 +208,7 @@ TEST_F(BlitTest, ColorMagnify) {
|
||||
}
|
||||
|
||||
TEST_F(BlitTest, ColorMinify) {
|
||||
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
|
||||
auto& api = getDriverApi();
|
||||
mCleanup.addPostCall([&]() { executeCommands(); });
|
||||
|
||||
@@ -222,17 +225,18 @@ TEST_F(BlitTest, ColorMinify) {
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a source texture.
|
||||
Handle<HwTexture> const srcTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
Handle<HwTexture> const srcTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
|
||||
kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT |
|
||||
TextureUsage::BLIT_SRC));
|
||||
const bool flipY = sBackend == Backend::OPENGL;
|
||||
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, float3(0.5, 0, 0), flipY);
|
||||
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 1, float3(0, 0, 0.5), flipY);
|
||||
|
||||
// Create a destination texture.
|
||||
Handle<HwTexture> const dstTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
Handle<HwTexture> const dstTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
|
||||
kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT | TextureUsage::BLIT_DST));
|
||||
|
||||
// Create a RenderTarget for each texture's miplevel.
|
||||
Handle<HwRenderTarget> srcRenderTargets[kNumLevels];
|
||||
@@ -260,6 +264,7 @@ TEST_F(BlitTest, ColorMinify) {
|
||||
}
|
||||
|
||||
TEST_F(BlitTest, ColorResolve) {
|
||||
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
|
||||
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
|
||||
"Nothing is drawn, see b/417229577");
|
||||
auto& api = getDriverApi();
|
||||
@@ -283,13 +288,12 @@ TEST_F(BlitTest, ColorResolve) {
|
||||
// Create 4-sample texture.
|
||||
Handle<HwTexture> const srcColorTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, 1, kColorTexFormat, kSampleCount, kSrcTexWidth, kSrcTexHeight,
|
||||
1,
|
||||
TextureUsage::COLOR_ATTACHMENT));
|
||||
1, TextureUsage::COLOR_ATTACHMENT | TextureUsage::BLIT_SRC | TextureUsage::UPLOADABLE));
|
||||
|
||||
// Create 1-sample texture.
|
||||
Handle<HwTexture> const dstColorTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, 1, kColorTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT | TextureUsage::BLIT_DST));
|
||||
|
||||
// Create a 4-sample render target with the 4-sample texture.
|
||||
Handle<HwRenderTarget> const srcRenderTarget = mCleanup.add(api.createRenderTarget(
|
||||
@@ -364,18 +368,18 @@ TEST_F(BlitTest, Blit2DTextureArray) {
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a source texture.
|
||||
Handle<HwTexture> srcTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D_ARRAY, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth,
|
||||
kSrcTexHeight, kSrcTexDepth,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
Handle<HwTexture> srcTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D_ARRAY,
|
||||
kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, kSrcTexDepth,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT |
|
||||
TextureUsage::BLIT_SRC));
|
||||
const bool flipY = sBackend == Backend::OPENGL;
|
||||
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, kSrcTexLayer, float3(0.5, 0, 0),
|
||||
flipY);
|
||||
|
||||
// Create a destination texture.
|
||||
Handle<HwTexture> dstTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
Handle<HwTexture> dstTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
|
||||
kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT | TextureUsage::BLIT_DST));
|
||||
|
||||
// Create two RenderTargets.
|
||||
const int level = 0;
|
||||
@@ -429,17 +433,18 @@ TEST_F(BlitTest, BlitRegion) {
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a source texture.
|
||||
Handle<HwTexture> srcTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
Handle<HwTexture> srcTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
|
||||
kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT |
|
||||
TextureUsage::BLIT_SRC));
|
||||
const bool flipY = sBackend == Backend::OPENGL;
|
||||
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, float3(0.5, 0, 0), flipY);
|
||||
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 1, float3(0, 0, 0.5), flipY);
|
||||
|
||||
// Create a destination texture.
|
||||
Handle<HwTexture> dstTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
Handle<HwTexture> dstTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
|
||||
kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT | TextureUsage::BLIT_DST));
|
||||
|
||||
// Blit one-quarter of src level 1 to dst level 0.
|
||||
Viewport srcRect = {
|
||||
@@ -487,6 +492,7 @@ TEST_F(BlitTest, BlitRegion) {
|
||||
|
||||
TEST_F(BlitTest, BlitRegionToSwapChain) {
|
||||
FAIL_IF(Backend::VULKAN, "Crashes due to not finding color attachment, see b/417481493");
|
||||
SKIP_IF(Backend::WEBGPU, "WebGPU Crashes due to not finding color attachment");
|
||||
auto& api = getDriverApi();
|
||||
mCleanup.addPostCall([&]() { executeCommands(); });
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "SharedShaders.h"
|
||||
#include "Skip.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
#include "Workarounds.h"
|
||||
|
||||
namespace test {
|
||||
|
||||
@@ -155,8 +156,6 @@ TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
|
||||
// This test renders two triangles in two separate draw calls. Between the draw calls, a uniform
|
||||
// buffer object is partially updated.
|
||||
TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
|
||||
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
|
||||
"All values including alpha are written as 0, see b/417254943");
|
||||
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
@@ -179,9 +178,9 @@ TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
|
||||
shader.bindUniform<SimpleMaterialParams>(api, ubuffer, kBindingConfig);
|
||||
|
||||
// Create a render target.
|
||||
auto colorTexture =
|
||||
cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1,
|
||||
screenWidth(), screenHeight(), 1, TextureUsage::COLOR_ATTACHMENT));
|
||||
auto colorTexture = cleanup.add(
|
||||
api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, screenWidth(),
|
||||
screenHeight(), 1, TextureUsage::COLOR_ATTACHMENT TEXTURE_USAGE_READ_PIXELS));
|
||||
auto renderTarget = cleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR0, screenWidth(),
|
||||
screenHeight(), 1, 0, { { colorTexture } }, {}, {}));
|
||||
|
||||
|
||||
@@ -104,7 +104,6 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::OPENGL),
|
||||
"OpenGL image is upside down due to readPixels failing for texture with uploaded image "
|
||||
"data");
|
||||
FAIL_IF(Backend::WEBGPU, "BUG");
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
@@ -127,7 +126,7 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
TrianglePrimitive const triangle(getDriverApi());
|
||||
|
||||
// Create a texture.
|
||||
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE;
|
||||
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE;
|
||||
Handle<HwTexture> const texture = cleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kTexFormat, 1, kTexWidth, kTexHeight, 1, usage));
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ layout(location = 0) out vec4 fragColor;
|
||||
|
||||
// Filament's Vulkan backend requires a descriptor set index of 1 for all samplers.
|
||||
// This parameter is ignored for other backends.
|
||||
layout(location = 0, set = 0) uniform {samplerType} test_tex;
|
||||
layout(binding = 0, set = 0) uniform {samplerType} test_tex;
|
||||
|
||||
void main() {
|
||||
vec2 fbsize = vec2({texSize});
|
||||
@@ -66,7 +66,7 @@ std::string fragmentUpdateImage3DTemplate (R"(#version 450 core
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
// Filament's Vulkan backend requires a descriptor set index of 1 for all samplers.
|
||||
layout(location = 0, set = 0) uniform {samplerType} test_tex;
|
||||
layout(binding = 0, set = 0) uniform {samplerType} test_tex;
|
||||
|
||||
float getLayer(in sampler3D s) { return 2.5f / 4.0f; }
|
||||
float getLayer(in sampler2DArray s) { return 2.0f; }
|
||||
@@ -87,7 +87,7 @@ std::string fragmentUpdateImageMip (R"(#version 450 core
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
// Filament's Vulkan backend requires a descriptor set index of 1 for all samplers.
|
||||
layout(location = 0, set = 0) uniform sampler2D test_tex;
|
||||
layout(binding = 0, set = 0) uniform sampler2D test_tex;
|
||||
|
||||
void main() {
|
||||
vec2 fbsize = vec2({texSize});
|
||||
@@ -231,6 +231,7 @@ public:
|
||||
};
|
||||
|
||||
TEST_F(LoadImageTest, UpdateImage2D) {
|
||||
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
|
||||
FAIL_IF(Backend::VULKAN, "Multiple test cases crash, see b/417481434");
|
||||
|
||||
// All of these test cases should result in the same rendered image, and thus the same hash.
|
||||
@@ -271,15 +272,18 @@ TEST_F(LoadImageTest, UpdateImage2D) {
|
||||
|
||||
// Test format conversion.
|
||||
// TODO: Vulkan crashes with `Texture at colorAttachment[0] has usage (0x01) which doesn't specify MTLTextureUsageRenderTarget (0x04)'
|
||||
// TODO: WebGPU Crashes with "destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment"
|
||||
testCases.emplace_back("RGBA FLOAT to RGBA16F", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F);
|
||||
|
||||
// Test texture formats not all backends support natively.
|
||||
// TODO: Vulkan crashes with "VK_FORMAT_R32G32B32_SFLOAT is not supported"
|
||||
// TODO: WebGPU Crashes with "destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment"
|
||||
testCases.emplace_back("RGB FLOAT to RGB32F", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F);
|
||||
testCases.emplace_back("RGB FLOAT to RGB16F", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB16F);
|
||||
|
||||
// Test packed format uploads.
|
||||
// TODO: Vulkan crashes with "Texture at colorAttachment[0] has usage (0x01) which doesn't specify MTLTextureUsageRenderTarget (0x04)"
|
||||
// TODO: WebGPU Crashes with "destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment"
|
||||
testCases.emplace_back("RGBA UINT_2_10_10_10_REV to RGB10_A2", PixelDataFormat::RGBA, PixelDataType::UINT_2_10_10_10_REV, TextureFormat::RGB10_A2);
|
||||
testCases.emplace_back("RGB UINT_10F_11F_11F_REV to R11F_G11F_B10F", PixelDataFormat::RGB, PixelDataType::UINT_10F_11F_11F_REV, TextureFormat::R11F_G11F_B10F);
|
||||
testCases.emplace_back("RGB HALF to R11F_G11F_B10F", PixelDataFormat::RGB, PixelDataType::HALF, TextureFormat::R11F_G11F_B10F);
|
||||
@@ -287,9 +291,12 @@ TEST_F(LoadImageTest, UpdateImage2D) {
|
||||
// Test integer format uploads.
|
||||
// TODO: These cases fail on OpenGL and Vulkan.
|
||||
// TODO: These cases now also fail on Metal, but at some point previously worked.
|
||||
testCases.emplace_back("RGB_INTEGER UBYTE to RGB8UI", PixelDataFormat::RGB_INTEGER, PixelDataType::UBYTE, TextureFormat::RGB8UI);
|
||||
testCases.emplace_back("RGB_INTEGER USHORT to RGB16UI", PixelDataFormat::RGB_INTEGER, PixelDataType::USHORT, TextureFormat::RGB16UI);
|
||||
testCases.emplace_back("RGB_INTEGER INT to RGB32I", PixelDataFormat::RGB_INTEGER, PixelDataType::INT, TextureFormat::RGB32I);
|
||||
// TODO: These cases fail for WebGPU. However, leaving them causes
|
||||
// Tint Reader Error: error: sampled image must have float component type
|
||||
// Beginning SpirV-output dump with ret 0
|
||||
// testCases.emplace_back("RGB_INTEGER UBYTE to RGB8UI", PixelDataFormat::RGB_INTEGER, PixelDataType::UBYTE, TextureFormat::RGB8UI);
|
||||
// testCases.emplace_back("RGB_INTEGER USHORT to RGB16UI", PixelDataFormat::RGB_INTEGER, PixelDataType::USHORT, TextureFormat::RGB16UI);
|
||||
// testCases.emplace_back("RGB_INTEGER INT to RGB32I", PixelDataFormat::RGB_INTEGER, PixelDataType::INT, TextureFormat::RGB32I);
|
||||
|
||||
// Test uploads with buffer padding.
|
||||
// TODO: Vulkan crashes with "Assertion failed: (offset + size <= allocationSize)"
|
||||
@@ -563,7 +570,7 @@ TEST_F(LoadImageTest, UpdateImage3D) {
|
||||
PixelDataType pixelType = PixelDataType::FLOAT;
|
||||
TextureFormat textureFormat = TextureFormat::RGBA16F;
|
||||
SamplerType samplerType = SamplerType::SAMPLER_2D_ARRAY;
|
||||
TextureUsage usage = TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE;
|
||||
TextureUsage usage = TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT;
|
||||
|
||||
// Create a platform-specific SwapChain and make it current.
|
||||
auto swapChain = cleanup.add(createSwapChain());
|
||||
|
||||
@@ -80,6 +80,7 @@ public:
|
||||
};
|
||||
|
||||
TEST_F(ReadPixelsTest, ReadPixels) {
|
||||
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
|
||||
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
|
||||
"Two cases fail, see b/417255941 and b/417255943");
|
||||
// These test scenarios use a known hash of the result pixel buffer to decide pass / fail,
|
||||
|
||||
@@ -46,8 +46,9 @@ Shader createShader(DriverApi& api, Cleanup& cleanup, Backend backend) {
|
||||
|
||||
// Rendering an external image without setting any data should not crash.
|
||||
TEST_F(BackendTest, RenderExternalImageWithoutSet) {
|
||||
SKIP_IF(Backend::METAL, "External images aren't supported in metal");
|
||||
SKIP_IF(Backend::VULKAN, "External images aren't supported in vulkan");
|
||||
SKIP_IF(Backend::METAL, "External images aren't supported in Metal");
|
||||
SKIP_IF(Backend::VULKAN, "External images aren't supported in Vulkan");
|
||||
SKIP_IF(Backend::WEBGPU, "External images aren't supported in WebGPU");
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
@@ -109,8 +110,9 @@ TEST_F(BackendTest, RenderExternalImageWithoutSet) {
|
||||
}
|
||||
|
||||
TEST_F(BackendTest, RenderExternalImage) {
|
||||
SKIP_IF(Backend::METAL, "External images aren't supported in metal");
|
||||
SKIP_IF(Backend::VULKAN, "External images aren't supported in vulkan");
|
||||
SKIP_IF(Backend::METAL, "External images aren't supported in Metal");
|
||||
SKIP_IF(Backend::VULKAN, "External images aren't supported in Vulkan");
|
||||
SKIP_IF(Backend::WEBGPU, "External images aren't supported in WebGPU");
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ using namespace filament;
|
||||
using namespace filament::backend;
|
||||
|
||||
TEST_F(BackendTest, ScissorViewportRegion) {
|
||||
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
|
||||
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
|
||||
"Affected area in wrong corner, see b/417229118");
|
||||
auto& api = getDriverApi();
|
||||
|
||||
@@ -126,6 +126,7 @@ public:
|
||||
};
|
||||
|
||||
TEST_F(BasicStencilBufferTest, StencilBuffer) {
|
||||
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
|
||||
"Stencil not supported, see b/417230776");
|
||||
auto& api = getDriverApi();
|
||||
@@ -152,6 +153,7 @@ TEST_F(BasicStencilBufferTest, StencilBuffer) {
|
||||
}
|
||||
|
||||
TEST_F(BasicStencilBufferTest, DepthAndStencilBuffer) {
|
||||
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
|
||||
"Stencil not supported, see b/417230776");
|
||||
auto& api = getDriverApi();
|
||||
@@ -178,6 +180,7 @@ TEST_F(BasicStencilBufferTest, DepthAndStencilBuffer) {
|
||||
}
|
||||
|
||||
TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
|
||||
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
|
||||
"Stencil not supported, see b/417230776");
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::OPENGL), "Stencil isn't applied");
|
||||
|
||||
171
filament/backend/test/test_WebGPUAsyncTaskCounter.cpp
Normal file
171
filament/backend/test/test_WebGPUAsyncTaskCounter.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "webgpu/utils/AsyncTaskCounter.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <future>
|
||||
#include <thread>
|
||||
|
||||
namespace test {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int TOLERANCE_MILLISECONDS{ 5 };
|
||||
constexpr int SMALLER_JOB_MILLISECONDS{ 10 };
|
||||
static_assert(SMALLER_JOB_MILLISECONDS > TOLERANCE_MILLISECONDS);
|
||||
constexpr int LARGER_JOB_MILLISECONDS{ 20 };
|
||||
static_assert(LARGER_JOB_MILLISECONDS > (SMALLER_JOB_MILLISECONDS + TOLERANCE_MILLISECONDS));
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(AsyncTaskCounter, noWorkJobs) {
|
||||
filament::backend::webgpuutils::AsyncTaskCounter counter{};
|
||||
const auto waitFuture{ std::async(std::launch::async,
|
||||
[&counter] { counter.waitForAllToFinish(); }) };
|
||||
const auto status{ waitFuture.wait_for(std::chrono::milliseconds(TOLERANCE_MILLISECONDS)) };
|
||||
ASSERT_NE(status, std::future_status::timeout)
|
||||
<< "Timed out waiting for no work to finish. This should have returned immediately. "
|
||||
"Tolerance/timeout: "
|
||||
<< TOLERANCE_MILLISECONDS << " milliseconds.";
|
||||
}
|
||||
|
||||
TEST(AsyncTaskCounter, oneWorkJob) {
|
||||
filament::backend::webgpuutils::AsyncTaskCounter counter{};
|
||||
const auto task{ [&counter]() {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(SMALLER_JOB_MILLISECONDS));
|
||||
counter.finishTask();
|
||||
} };
|
||||
const auto startTime{ std::chrono::steady_clock::now() };
|
||||
counter.startTask();
|
||||
std::thread t{ task };
|
||||
const auto waitFuture{ std::async(std::launch::async,
|
||||
[&counter] { counter.waitForAllToFinish(); }) };
|
||||
const auto expectedDuration{ SMALLER_JOB_MILLISECONDS };
|
||||
const auto status{ waitFuture.wait_for(
|
||||
std::chrono::milliseconds(expectedDuration + TOLERANCE_MILLISECONDS)) };
|
||||
const auto endTime{ std::chrono::steady_clock::now() };
|
||||
const auto duration{ std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
endTime - startTime) };
|
||||
ASSERT_NE(status, std::future_status::timeout)
|
||||
<< "Timed out waiting for one work item to finish. Expected job duration: "
|
||||
<< expectedDuration
|
||||
<< " milliseconds. Timeout: " << (expectedDuration + TOLERANCE_MILLISECONDS)
|
||||
<< " milliseconds.";
|
||||
ASSERT_GT(duration, std::chrono::milliseconds(expectedDuration - TOLERANCE_MILLISECONDS))
|
||||
<< "The one job should have taken at least its expected duration of "
|
||||
<< expectedDuration << " milliseconds (" << TOLERANCE_MILLISECONDS
|
||||
<< " milliseconds tolerance), but took " << duration << " milliseconds";
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST(AsyncTaskCounter, multipleWorkJobs) {
|
||||
filament::backend::webgpuutils::AsyncTaskCounter counter{};
|
||||
const auto smallerTasks{ [&counter]() {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(SMALLER_JOB_MILLISECONDS));
|
||||
// validates the counter incrementing and decrementing before the work is done.
|
||||
counter.startTask();
|
||||
counter.finishTask();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(SMALLER_JOB_MILLISECONDS));
|
||||
counter.finishTask();
|
||||
} };
|
||||
const auto largerTask{ [&counter]() {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(LARGER_JOB_MILLISECONDS));
|
||||
counter.finishTask();
|
||||
} };
|
||||
const auto startTime{ std::chrono::steady_clock::now() };
|
||||
counter.startTask();
|
||||
std::thread t1{ smallerTasks };
|
||||
counter.startTask();
|
||||
std::thread t2{ largerTask };
|
||||
const auto expectedDuration{ std::max(SMALLER_JOB_MILLISECONDS * 2, LARGER_JOB_MILLISECONDS) };
|
||||
const auto waitFuture{ std::async(std::launch::async,
|
||||
[&counter] { counter.waitForAllToFinish(); }) };
|
||||
const auto status{ waitFuture.wait_for(
|
||||
std::chrono::milliseconds(expectedDuration + TOLERANCE_MILLISECONDS)) };
|
||||
const auto endTime{ std::chrono::steady_clock::now() };
|
||||
const auto duration{ std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
endTime - startTime) };
|
||||
ASSERT_NE(status, std::future_status::timeout)
|
||||
<< "Timed out waiting for 3 work items to finish. Expected job duration: "
|
||||
<< expectedDuration
|
||||
<< " milliseconds. Timeout: " << (expectedDuration + TOLERANCE_MILLISECONDS)
|
||||
<< " milliseconds.";
|
||||
ASSERT_GT(duration, std::chrono::milliseconds(expectedDuration - TOLERANCE_MILLISECONDS))
|
||||
<< "The jobs should have taken at least their expected duration of " << expectedDuration
|
||||
<< " milliseconds (" << TOLERANCE_MILLISECONDS << " milliseconds tolerance), but took "
|
||||
<< duration << " milliseconds";
|
||||
t1.join();
|
||||
t2.join();
|
||||
}
|
||||
|
||||
TEST(AsyncTaskCounter, counterReuse) {
|
||||
filament::backend::webgpuutils::AsyncTaskCounter counter{};
|
||||
const auto batchOfTasks{ [&counter]() {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(SMALLER_JOB_MILLISECONDS));
|
||||
counter.startTask();
|
||||
counter.finishTask();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(SMALLER_JOB_MILLISECONDS));
|
||||
counter.finishTask();
|
||||
} };
|
||||
// first batch of tasks...
|
||||
const auto firstStartTime{ std::chrono::steady_clock::now() };
|
||||
counter.startTask();
|
||||
std::thread firstBatch{ batchOfTasks };
|
||||
const auto firstWaitFuture{ std::async(std::launch::async,
|
||||
[&counter] { counter.waitForAllToFinish(); }) };
|
||||
const auto expectedDuration{ SMALLER_JOB_MILLISECONDS * 2 };
|
||||
const auto firstStatus{ firstWaitFuture.wait_for(
|
||||
std::chrono::milliseconds(expectedDuration + TOLERANCE_MILLISECONDS)) };
|
||||
const auto firstEndTime{ std::chrono::steady_clock::now() };
|
||||
const auto firstDuration{ std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
firstEndTime - firstStartTime) };
|
||||
ASSERT_NE(firstStatus, std::future_status::timeout)
|
||||
<< "Timed out waiting for first batch to finish. Expected job duration: "
|
||||
<< expectedDuration
|
||||
<< " milliseconds. Timeout: " << (expectedDuration + TOLERANCE_MILLISECONDS)
|
||||
<< " milliseconds.";
|
||||
ASSERT_GT(firstDuration, std::chrono::milliseconds(expectedDuration - TOLERANCE_MILLISECONDS))
|
||||
<< "The first batch of tasks should have taken at least its expected duration of "
|
||||
<< expectedDuration << " milliseconds (" << TOLERANCE_MILLISECONDS
|
||||
<< " milliseconds tolerance), but took " << firstDuration << " milliseconds";
|
||||
firstBatch.join();
|
||||
// second batch of tasks...
|
||||
const auto secondStartTime{ std::chrono::steady_clock::now() };
|
||||
counter.startTask();
|
||||
std::thread secondBatch{ batchOfTasks };
|
||||
const auto secondWaitFuture{ std::async(std::launch::async,
|
||||
[&counter] { counter.waitForAllToFinish(); }) };
|
||||
const auto secondStatus{ secondWaitFuture.wait_for(
|
||||
std::chrono::milliseconds(expectedDuration + TOLERANCE_MILLISECONDS)) };
|
||||
const auto secondEndTime{ std::chrono::steady_clock::now() };
|
||||
const auto secondDuration{ std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
secondEndTime - secondStartTime) };
|
||||
ASSERT_NE(secondStatus, std::future_status::timeout)
|
||||
<< "Timed out waiting for second batch to finish. Expected job duration: "
|
||||
<< expectedDuration
|
||||
<< " milliseconds. Timeout: " << (expectedDuration + TOLERANCE_MILLISECONDS)
|
||||
<< " milliseconds.";
|
||||
ASSERT_GT(secondDuration, std::chrono::milliseconds(expectedDuration - TOLERANCE_MILLISECONDS))
|
||||
<< "The second batch of tasks should have taken at least its expected duration of "
|
||||
<< expectedDuration << " milliseconds (" << TOLERANCE_MILLISECONDS
|
||||
<< " milliseconds tolerance), but took " << secondDuration << " milliseconds";
|
||||
secondBatch.join();
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
@@ -87,6 +87,9 @@ public:
|
||||
std::is_same_v<math::mat3f, T>
|
||||
>;
|
||||
|
||||
template<typename T>
|
||||
using is_supported_constant_parameter_t = std::enable_if_t<std::is_same_v<bool, T>>;
|
||||
|
||||
/**
|
||||
* Creates a new MaterialInstance using another MaterialInstance as a template for initialization.
|
||||
* The new MaterialInstance is an instance of the same Material of the template instance and
|
||||
@@ -238,13 +241,13 @@ public:
|
||||
|
||||
/**
|
||||
* Gets the value of a parameter by name.
|
||||
*
|
||||
*
|
||||
* Note: Only supports non-texture parameters such as numeric and math types.
|
||||
*
|
||||
*
|
||||
* @param name Name of the parameter as defined by Material. Cannot be nullptr.
|
||||
* @param nameLength Length in `char` of the name parameter.
|
||||
* @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled.
|
||||
*
|
||||
*
|
||||
* @see Material::hasParameter
|
||||
*/
|
||||
template<typename T>
|
||||
@@ -262,6 +265,53 @@ public:
|
||||
return getParameter<T>(name, strlen(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a mutable constant parameter by name.
|
||||
*
|
||||
* @param name Name of the parameter as defined by Material. Cannot be nullptr.
|
||||
* @param nameLength Length in `char` of the name parameter.
|
||||
* @param value Value of the parameter to set.
|
||||
* @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled.
|
||||
*/
|
||||
template<typename T, typename = is_supported_constant_parameter_t<T>>
|
||||
void setConstant(const char* UTILS_NONNULL name, size_t nameLength, T const& value);
|
||||
|
||||
/** inline helper to provide the name as a null-terminated string literal */
|
||||
template<typename T, typename = is_supported_constant_parameter_t<T>>
|
||||
void setConstant(StringLiteral const name, T const& value) {
|
||||
setConstant<T>(name.data, name.size, value);
|
||||
}
|
||||
|
||||
/** inline helper to provide the name as a null-terminated C string */
|
||||
template<typename T, typename = is_supported_constant_parameter_t<T>>
|
||||
void setConstant(const char* UTILS_NONNULL name, T const& value) {
|
||||
setConstant<T>(name, strlen(name), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a mutable constant parameter by name.
|
||||
*
|
||||
* @param name Name of the parameter as defined by Material. Cannot be nullptr.
|
||||
* @param nameLength Length in `char` of the name parameter.
|
||||
* @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled.
|
||||
*
|
||||
* @see Material::hasConstant
|
||||
*/
|
||||
template<typename T, typename = is_supported_constant_parameter_t<T>>
|
||||
T getConstant(const char* UTILS_NONNULL name, size_t nameLength) const;
|
||||
|
||||
/** inline helper to provide the name as a null-terminated C string */
|
||||
template<typename T, typename = is_supported_constant_parameter_t<T>>
|
||||
T getConstant(StringLiteral const name) const {
|
||||
return getConstant<T>(name.data, name.size);
|
||||
}
|
||||
|
||||
/** inline helper to provide the name as a null-terminated C string */
|
||||
template<typename T, typename = is_supported_constant_parameter_t<T>>
|
||||
T getConstant(const char* UTILS_NONNULL name) const {
|
||||
return getConstant<T>(name, strlen(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set-up a custom scissor rectangle; by default it is disabled.
|
||||
*
|
||||
|
||||
@@ -437,7 +437,20 @@ struct AmbientOcclusionOptions {
|
||||
struct Gtao {
|
||||
uint8_t sampleSliceCount = 4; //!< # of slices. Higher value makes less noise.
|
||||
uint8_t sampleStepsPerSlice = 3; //!< # of steps the radius is divided into for integration. Higher value makes less bias.
|
||||
float thicknessHeuristic = 0.004f; //!< thickness heuristic, should be closed to 0
|
||||
float thicknessHeuristic = 0.004f; //!< thickness heuristic, should be closed to 0. No effect when useVisibilityBitmasks sets to true.
|
||||
|
||||
/**
|
||||
* Enables or disables visibility bitmasks mode. Notes that bent normal doesn't work under this mode.
|
||||
* Caution: Changing this option at runtime is very expensive as it may trigger a shader re-compilation.
|
||||
*/
|
||||
bool useVisibilityBitmasks = false;
|
||||
float constThickness = 0.5f; //!< constant thickness value of objects on the screen in world space. Only take effect when useVisibilityBitmasks is set to true.
|
||||
|
||||
/**
|
||||
* Increase thickness with distance to maintain detail on distant surfaces.
|
||||
* Caution: Changing this option at runtime is very expensive as it may trigger a shader re-compilation.
|
||||
*/
|
||||
bool linearThickness = false;
|
||||
};
|
||||
Gtao gtao; // %codegen_skip_javascript% %codegen_java_flatten%
|
||||
};
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <math/mathfwd.h>
|
||||
#include <math/mat4.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
@@ -757,6 +758,29 @@ public:
|
||||
//! debugging: returns a Camera from the point of view of *the* dominant directional light used for shadowing.
|
||||
utils::FixedCapacityVector<Camera const*> getDirectionalShadowCameras() const noexcept;
|
||||
|
||||
//! debugging: enable or disable froxel visualisation for this view.
|
||||
void setFroxelVizEnabled(bool enabled) noexcept;
|
||||
|
||||
//! debugging: returns information about the froxel configuration
|
||||
struct FroxelConfigurationInfo {
|
||||
uint8_t width;
|
||||
uint8_t height;
|
||||
uint8_t depth;
|
||||
uint32_t viewportWidth;
|
||||
uint32_t viewportHeight;
|
||||
math::uint2 froxelDimension;
|
||||
float zLightFar;
|
||||
float linearizer;
|
||||
math::mat4f p;
|
||||
math::float4 clipTransform;
|
||||
};
|
||||
|
||||
struct FroxelConfigurationInfoWithAge {
|
||||
FroxelConfigurationInfo info;
|
||||
uint32_t age;
|
||||
};
|
||||
|
||||
FroxelConfigurationInfoWithAge getFroxelConfigurationInfo() const noexcept;
|
||||
|
||||
/** Result of a picking query */
|
||||
struct PickingQueryResult {
|
||||
|
||||
@@ -124,6 +124,20 @@ size_t Froxelizer::getFroxelBufferByteCount(FEngine::DriverApi& driverApi) noexc
|
||||
return std::min(FROXEL_BUFFER_MAX_ENTRY_COUNT * sizeof(FroxelEntry), targetSize);
|
||||
}
|
||||
|
||||
View::FroxelConfigurationInfo Froxelizer::getFroxelConfigurationInfo() const noexcept {
|
||||
return { uint8_t(getFroxelCountX()),
|
||||
uint8_t(getFroxelCountY()),
|
||||
uint8_t(getFroxelCountZ()),
|
||||
mViewport.width,
|
||||
mViewport.height,
|
||||
mFroxelDimension,
|
||||
mZLightFar,
|
||||
mLinearizer[0],
|
||||
mProjection,
|
||||
mClipTransform
|
||||
};
|
||||
}
|
||||
|
||||
Froxelizer::Froxelizer(FEngine& engine)
|
||||
: mArena("froxel", PER_FROXELDATA_ARENA_SIZE),
|
||||
mZLightNear(FROXEL_FIRST_SLICE_DEPTH),
|
||||
@@ -198,10 +212,14 @@ void Froxelizer::setProjection(const mat4f& projection,
|
||||
bool Froxelizer::prepare(
|
||||
FEngine::DriverApi& driverApi, RootArenaScope& rootArenaScope,
|
||||
filament::Viewport const& viewport,
|
||||
const mat4f& projection, float const projectionNear, float const projectionFar) noexcept {
|
||||
const mat4f& projection, float const projectionNear, float const projectionFar,
|
||||
float4 const& clipTransform) noexcept {
|
||||
setViewport(viewport);
|
||||
setProjection(projection, projectionNear, projectionFar);
|
||||
|
||||
// Only for debugging
|
||||
mClipTransform = clipTransform;
|
||||
|
||||
bool uniformsNeedUpdating = false;
|
||||
if (UTILS_UNLIKELY(mDirtyFlags)) {
|
||||
uniformsNeedUpdating = update();
|
||||
@@ -361,8 +379,10 @@ bool Froxelizer::update() noexcept {
|
||||
getFroxelBufferEntryCount(), viewport);
|
||||
|
||||
mFroxelDimension = froxelDimension;
|
||||
mClipToFroxelX = (0.5f * float(viewport.width)) / float(froxelDimension.x);
|
||||
mClipToFroxelY = (0.5f * float(viewport.height)) / float(froxelDimension.y);
|
||||
// note: because froxelDimension is a power-of-two and viewport is an integer, mClipFroxel
|
||||
// is an exact value (which is not true for 1/mClipToFroxelX, btw)
|
||||
mClipToFroxelX = float(viewport.width) / float(2 * froxelDimension.x);
|
||||
mClipToFroxelY = float(viewport.height) / float(2 * froxelDimension.y);
|
||||
|
||||
uniformsNeedUpdating = true;
|
||||
|
||||
@@ -408,8 +428,7 @@ bool Froxelizer::update() noexcept {
|
||||
}
|
||||
|
||||
// for the inverse-transformation (view-space z to z-slice)
|
||||
mLinearizer = 1.0f / linearizer;
|
||||
mZLightFar = zLightFar;
|
||||
mLinearizer = { linearizer, 1.0f / linearizer };
|
||||
|
||||
mParamsZ[0] = 0; // updated when camera changes
|
||||
mParamsZ[1] = 0; // updated when camera changes
|
||||
@@ -466,7 +485,7 @@ bool Froxelizer::update() noexcept {
|
||||
// ==> i = log2(z_screen * (far/near)) * (-1/linearizer) + zcount
|
||||
mParamsZ[0] = mZLightFar / Pw;
|
||||
mParamsZ[1] = 0.0f;
|
||||
mParamsZ[2] = -mLinearizer;
|
||||
mParamsZ[2] = -mLinearizer[1];
|
||||
} else {
|
||||
// orthographic projection
|
||||
// z_view = (1 - z_screen) * (near - far) - near
|
||||
@@ -476,7 +495,7 @@ bool Froxelizer::update() noexcept {
|
||||
// Pw = far / (far - near)
|
||||
mParamsZ[0] = -1.0f / (Pz * mZLightFar); // -(far-near) / mZLightFar
|
||||
mParamsZ[1] = Pw / (Pz * mZLightFar); // far / mZLightFar
|
||||
mParamsZ[2] = mLinearizer;
|
||||
mParamsZ[2] = mLinearizer[1];
|
||||
}
|
||||
uniformsNeedUpdating = true;
|
||||
}
|
||||
@@ -507,7 +526,7 @@ size_t Froxelizer::findSliceZ(float const z) const noexcept {
|
||||
|
||||
// This whole function is now branch-less.
|
||||
|
||||
int s = int( fast::log2(-z / mZLightFar) * mLinearizer + float(mFroxelCountZ) );
|
||||
int s = int( fast::log2(-z / mZLightFar) * mLinearizer[1] + float(mFroxelCountZ) );
|
||||
|
||||
// there are cases where z can be negative here, e.g.:
|
||||
// - the light is visible, but its center is behind the camera
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "details/Scene.h"
|
||||
#include "details/Engine.h"
|
||||
|
||||
#include <filament/View.h>
|
||||
#include <filament/Viewport.h>
|
||||
|
||||
#include <backend/Handle.h>
|
||||
@@ -31,6 +32,7 @@
|
||||
#include <utils/Slice.h>
|
||||
|
||||
#include <math/mat4.h>
|
||||
#include <math/vec2.h>
|
||||
#include <math/vec4.h>
|
||||
|
||||
namespace filament {
|
||||
@@ -112,7 +114,8 @@ public:
|
||||
* return true if updateUniforms() needs to be called
|
||||
*/
|
||||
bool prepare(backend::DriverApi& driverApi, RootArenaScope& rootArenaScope, Viewport const& viewport,
|
||||
const math::mat4f& projection, float projectionNear, float projectionFar) noexcept;
|
||||
const math::mat4f& projection, float projectionNear, float projectionFar,
|
||||
math::float4 const& clipTransform) noexcept;
|
||||
|
||||
Froxel getFroxelAt(size_t x, size_t y, size_t z) const noexcept;
|
||||
size_t getFroxelCountX() const noexcept { return mFroxelCountX; }
|
||||
@@ -161,6 +164,8 @@ public:
|
||||
|
||||
static size_t getFroxelBufferByteCount(FEngine::DriverApi& driverApi) noexcept;
|
||||
|
||||
View::FroxelConfigurationInfo getFroxelConfigurationInfo() const noexcept;
|
||||
|
||||
private:
|
||||
size_t getFroxelBufferEntryCount() const noexcept {
|
||||
return mFroxelBufferEntryCount;
|
||||
@@ -260,9 +265,10 @@ private:
|
||||
uint16_t mFroxelCountZ = 0;
|
||||
uint32_t mFroxelCount = 0;
|
||||
math::uint2 mFroxelDimension = {};
|
||||
math::float4 mClipTransform = { 1, 1, 0, 0 };
|
||||
|
||||
math::mat4f mProjection;
|
||||
float mLinearizer = 0.0f;
|
||||
math::float2 mLinearizer{};
|
||||
float mClipToFroxelX = 0.0f;
|
||||
float mClipToFroxelY = 0.0f;
|
||||
backend::BufferObjectHandle mRecordsBuffer;
|
||||
|
||||
@@ -233,6 +233,46 @@ template UTILS_PUBLIC mat3f MaterialInstance::getParameter<mat3f> (const ch
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
template<>
|
||||
inline void FMaterialInstance::setConstantImpl<bool>(std::string_view const name, bool const& value) {
|
||||
std::optional<uint32_t> id = mMaterial->getMutableConstantId(name);
|
||||
FILAMENT_CHECK_PRECONDITION(id.has_value()) << "No mutable constant with name " << name;
|
||||
mConstants.set(*id, value);
|
||||
}
|
||||
|
||||
template<typename T, typename>
|
||||
void MaterialInstance::setConstant(const char* name, size_t nameLength, T const& value) {
|
||||
downcast(this)->setConstantImpl({ name, nameLength }, value);
|
||||
}
|
||||
|
||||
// Explicit template instantiation of our supported types.
|
||||
//
|
||||
// Mutable spec constants will probably only ever allow bools, but it's nice to keep the API
|
||||
// forwards-compatible.
|
||||
template UTILS_PUBLIC void MaterialInstance::setConstant<bool>(const char* name, size_t nameLength, bool const& v);
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
template<>
|
||||
inline bool FMaterialInstance::getConstantImpl<bool>(std::string_view const name) const {
|
||||
std::optional<uint32_t> id = mMaterial->getMutableConstantId(name);
|
||||
FILAMENT_CHECK_PRECONDITION(id.has_value()) << "No mutable constant with name " << name;
|
||||
return mConstants[*id];
|
||||
}
|
||||
|
||||
template<typename T, typename>
|
||||
T MaterialInstance::getConstant(const char* name, size_t nameLength) const {
|
||||
return downcast(this)->getConstantImpl<T>({ name, nameLength });
|
||||
}
|
||||
|
||||
// Explicit template instantiation of our supported types.
|
||||
//
|
||||
// Mutable spec constants will probably only ever allow bools, but it's nice to keep the API
|
||||
// forwards-compatible.
|
||||
template UTILS_PUBLIC bool MaterialInstance::getConstant<bool>(const char* name, size_t nameLength) const;
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
Material const* MaterialInstance::getMaterial() const noexcept {
|
||||
return downcast(this)->getMaterial();
|
||||
}
|
||||
|
||||
@@ -229,6 +229,11 @@ bool MaterialParser::getConstants(FixedCapacityVector<MaterialConstant>* contain
|
||||
return get<ChunkMaterialConstants>(container);
|
||||
}
|
||||
|
||||
bool MaterialParser::getMutableConstants(
|
||||
FixedCapacityVector<MaterialMutableConstant>* container) const noexcept {
|
||||
return get<ChunkMaterialMutableConstants>(container);
|
||||
}
|
||||
|
||||
bool MaterialParser::getPushConstants(CString* structVarName,
|
||||
FixedCapacityVector<MaterialPushConstant>* value) const noexcept {
|
||||
auto [start, end] = mImpl.mChunkContainer.getChunkRange(MaterialPushConstants);
|
||||
@@ -761,6 +766,38 @@ bool ChunkMaterialConstants::unflatten(Unflattener& unflattener,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChunkMaterialMutableConstants::unflatten(Unflattener& unflattener,
|
||||
FixedCapacityVector<MaterialMutableConstant>* materialConstants) {
|
||||
assert_invariant(materialConstants);
|
||||
|
||||
// Read number of constants.
|
||||
uint64_t numConstants = 0;
|
||||
if (!unflattener.read(&numConstants)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
materialConstants->reserve(numConstants);
|
||||
materialConstants->resize(numConstants);
|
||||
|
||||
for (uint64_t i = 0; i < numConstants; i++) {
|
||||
CString constantName;
|
||||
bool defaultValue;
|
||||
|
||||
if (!unflattener.read(&constantName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!unflattener.read(&defaultValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
(*materialConstants)[i].name = std::move(constantName);
|
||||
(*materialConstants)[i].defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChunkMaterialPushConstants::unflatten(Unflattener& unflattener,
|
||||
CString* structVarName,
|
||||
FixedCapacityVector<MaterialPushConstant>* materialPushConstants) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user