Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a2b46e134 |
@@ -8,8 +8,8 @@ runs:
|
||||
uses: actions/cache@v3
|
||||
id: cache-vulkan-sdk
|
||||
with:
|
||||
path: ${{ runner.homedir }}/VulkanSDK
|
||||
key: vulkansdk-${{ env.GITHUB_VULKANSDK_VERSION }}-2-${{ runner.os }}
|
||||
path: vulkansdk
|
||||
key: vulkansdk-${{ env.GITHUB_VULKANSDK_VERSION }}-${{ runner.os }}
|
||||
- name: Download Vulkan SDK
|
||||
if: steps.cache-vulkan-sdk.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
@@ -21,8 +21,4 @@ runs:
|
||||
run: |
|
||||
source ${{ github.workspace }}/build/common/get-vulkan-sdk.sh
|
||||
unpack_vulkan_installer
|
||||
pushd .
|
||||
cd ~/VulkanSDK/${GITHUB_VULKANSDK_VERSION}
|
||||
sudo ./install_vulkan.py
|
||||
popd
|
||||
shell: bash
|
||||
+180
-171
@@ -9,36 +9,6 @@ on:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build-desktop-mac:
|
||||
name: build-mac
|
||||
runs-on: macos-14-xlarge
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/mac-prereq
|
||||
- name: Run build script
|
||||
run: |
|
||||
cd build/mac && printf "y" | ./build.sh presubmit
|
||||
- name: Test material parser
|
||||
run: |
|
||||
out/cmake-release/filament/test/test_material_parser
|
||||
|
||||
build-desktop-linux:
|
||||
name: build-linux
|
||||
runs-on: ubuntu-22.04-16core
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/linux-prereq
|
||||
- name: Run build script
|
||||
run: |
|
||||
cd build/linux && printf "y" | ./build.sh presubmit
|
||||
- name: Test material parser
|
||||
run: |
|
||||
out/cmake-release/filament/test/test_material_parser
|
||||
|
||||
build-windows:
|
||||
name: build-windows
|
||||
runs-on: windows-2022-32core
|
||||
@@ -48,155 +18,194 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- name: Run build script
|
||||
run: |
|
||||
build\windows\build-github.bat presubmit
|
||||
build\windows\build-github.bat continuous
|
||||
shell: cmd
|
||||
|
||||
build-android:
|
||||
name: build-android
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/linux-prereq
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
- name: Run build script
|
||||
# Only build 1 64 bit target during presubmit to cut down build times during presubmit
|
||||
# Continuous builds will build everything
|
||||
run: |
|
||||
cd build/android && printf "y" | ./build.sh presubmit arm64-v8a
|
||||
# build-desktop-mac:
|
||||
# name: build-mac
|
||||
# runs-on: macos-14-xlarge
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4.1.6
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
# - uses: ./.github/actions/mac-prereq
|
||||
# - name: Run build script
|
||||
# run: |
|
||||
# cd build/mac && printf "y" | ./build.sh presubmit
|
||||
# - name: Test material parser
|
||||
# run: |
|
||||
# out/cmake-release/filament/test/test_material_parser
|
||||
|
||||
build-ios:
|
||||
name: build-iOS
|
||||
runs-on: macos-14-xlarge
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/mac-prereq
|
||||
- name: Run build script
|
||||
run: |
|
||||
cd build/ios && printf "y" | ./build.sh presubmit
|
||||
- name: Build iOS samples
|
||||
run: |
|
||||
cd build/ios && ./build-samples.sh presubmit
|
||||
# build-desktop-linux:
|
||||
# name: build-linux
|
||||
# runs-on: ubuntu-22.04-16core
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4.1.6
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
# - uses: ./.github/actions/linux-prereq
|
||||
# - name: Run build script
|
||||
# run: |
|
||||
# cd build/linux && printf "y" | ./build.sh presubmit
|
||||
# - name: Test material parser
|
||||
# run: |
|
||||
# out/cmake-release/filament/test/test_material_parser
|
||||
|
||||
build-web:
|
||||
name: build-web
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/linux-prereq
|
||||
- uses: ./.github/actions/web-prereq
|
||||
- name: Run build script
|
||||
run: |
|
||||
cd build/web && printf "y" | ./build.sh presubmit
|
||||
# build-windows:
|
||||
# name: build-windows
|
||||
# runs-on: windows-2022-32core
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4.1.6
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
# - name: Run build script
|
||||
# run: |
|
||||
# build\windows\build-github.bat presubmit
|
||||
# shell: cmd
|
||||
|
||||
validate-docs:
|
||||
name: validate-docs
|
||||
runs-on: 'ubuntu-24.04-4core'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- id: get_commit_msg
|
||||
uses: ./.github/actions/get-commit-msg
|
||||
- name: Check for manual edits to /docs
|
||||
run: |
|
||||
COMMIT_ID=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | head -n 1 | sed "s/commit //g")
|
||||
bash docs_src/build/presubmit_check.sh ${COMMIT_ID}
|
||||
# build-android:
|
||||
# name: build-android
|
||||
# runs-on: 'ubuntu-24.04-16core'
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4.1.6
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
# - uses: ./.github/actions/linux-prereq
|
||||
# - uses: actions/setup-java@v3
|
||||
# with:
|
||||
# distribution: 'temurin'
|
||||
# java-version: '17'
|
||||
# - name: Run build script
|
||||
# # Only build 1 64 bit target during presubmit to cut down build times during presubmit
|
||||
# # Continuous builds will build everything
|
||||
# run: |
|
||||
# cd build/android && printf "y" | ./build.sh presubmit arm64-v8a
|
||||
|
||||
test-renderdiff:
|
||||
name: test-renderdiff
|
||||
runs-on: macos-14-xlarge
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- id: get_commit_msg
|
||||
uses: ./.github/actions/get-commit-msg
|
||||
- uses: ./.github/actions/mac-prereq
|
||||
- uses: ./.github/actions/get-mesa
|
||||
- uses: ./.github/actions/get-vulkan-sdk
|
||||
- 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: |
|
||||
TEST_DIR=test/renderdiff
|
||||
source ${TEST_DIR}/src/preamble.sh
|
||||
start_
|
||||
GOLDEN_BRANCH=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | python3 ${TEST_DIR}/src/commit_msg.py)
|
||||
bash ${TEST_DIR}/generate.sh && \
|
||||
python3 ${TEST_DIR}/src/golden_manager.py \
|
||||
--branch=${GOLDEN_BRANCH} \
|
||||
--output=${GOLDEN_OUTPUT_DIR}
|
||||
# build-ios:
|
||||
# name: build-iOS
|
||||
# runs-on: macos-14-xlarge
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4.1.6
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
# - uses: ./.github/actions/mac-prereq
|
||||
# - name: Run build script
|
||||
# run: |
|
||||
# cd build/ios && printf "y" | ./build.sh presubmit
|
||||
# - name: Build iOS samples
|
||||
# run: |
|
||||
# cd build/ios && ./build-samples.sh presubmit
|
||||
|
||||
# Note that we need to upload the output even if comparison fails, so we undo `set -ex`
|
||||
end_
|
||||
# build-web:
|
||||
# name: build-web
|
||||
# runs-on: 'ubuntu-24.04-16core'
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4.1.6
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
# - uses: ./.github/actions/linux-prereq
|
||||
# - uses: ./.github/actions/web-prereq
|
||||
# - name: Run build script
|
||||
# run: |
|
||||
# cd build/web && printf "y" | ./build.sh presubmit
|
||||
|
||||
python3 ${TEST_DIR}/src/compare.py \
|
||||
--src=${GOLDEN_OUTPUT_DIR} \
|
||||
--dest=${RENDER_OUTPUT_DIR} \
|
||||
--out=${DIFF_OUTPUT_DIR} 2>&1 | tee compare_output.txt
|
||||
# validate-docs:
|
||||
# name: validate-docs
|
||||
# runs-on: 'ubuntu-24.04-4core'
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4.1.6
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
# - id: get_commit_msg
|
||||
# uses: ./.github/actions/get-commit-msg
|
||||
# - name: Check for manual edits to /docs
|
||||
# run: |
|
||||
# COMMIT_ID=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | head -n 1 | sed "s/commit //g")
|
||||
# bash docs_src/build/presubmit_check.sh ${COMMIT_ID}
|
||||
|
||||
if grep "Failed" compare_output.txt > /dev/null; then
|
||||
DELIMITER="EOF_FILE_CONTENT_$(date +%s)" # Using timestamp to make it more unique
|
||||
echo "err<<$DELIMITER" >> "$GITHUB_OUTPUT"
|
||||
cat compare_output.txt >> "$GITHUB_OUTPUT"
|
||||
echo "$DELIMITER" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: presubmit-renderdiff-result
|
||||
path: ./out/renderdiff
|
||||
- name: Compare result
|
||||
run: |
|
||||
ERROR_STR="${{ steps.render_compare.outputs.err }}"
|
||||
if [ -n "${ERROR_STR}" ]; then
|
||||
echo "${ERROR_STR}"
|
||||
exit 1
|
||||
fi
|
||||
# test-renderdiff:
|
||||
# name: test-renderdiff
|
||||
# runs-on: macos-14-xlarge
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4.1.6
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
# - id: get_commit_msg
|
||||
# uses: ./.github/actions/get-commit-msg
|
||||
# - uses: ./.github/actions/mac-prereq
|
||||
# - uses: ./.github/actions/get-mesa
|
||||
# - name: Prerequisites
|
||||
# run: |
|
||||
# pip install tifffile numpy
|
||||
# shell: bash
|
||||
# - name: Render and compare
|
||||
# id: render_compare
|
||||
# run: |
|
||||
# TEST_DIR=test/renderdiff
|
||||
# source ${TEST_DIR}/src/preamble.sh
|
||||
# start_
|
||||
# GOLDEN_BRANCH=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | python3 ${TEST_DIR}/src/commit_msg.py)
|
||||
# bash ${TEST_DIR}/generate.sh && \
|
||||
# python3 ${TEST_DIR}/src/golden_manager.py \
|
||||
# --branch=${GOLDEN_BRANCH} \
|
||||
# --output=${GOLDEN_OUTPUT_DIR}
|
||||
|
||||
validate-wgsl-webgpu:
|
||||
name: validate-wgsl-webgpu
|
||||
runs-on: 'ubuntu-24.04-8core'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/linux-prereq
|
||||
- name: Run build script
|
||||
run: ./build.sh -W debug test_filamat filament
|
||||
- name: Run test
|
||||
run: ./out/cmake-debug/libs/filamat/test_filamat --gtest_filter=MaterialCompiler.Wgsl*
|
||||
# # Note that we need to upload the output even if comparison fails, so we undo `set -ex`
|
||||
# end_
|
||||
|
||||
test-code-correctness:
|
||||
name: test-code-correctness
|
||||
runs-on: 'macos-14-xlarge'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/mac-prereq
|
||||
- name: Install clang-tidy and deps
|
||||
run: |
|
||||
pip install pyyaml
|
||||
brew install llvm@${GITHUB_LLVM_VERSION}
|
||||
sudo ln -s "$(brew --prefix llvm)@${GITHUB_LLVM_VERSION}/bin/clang-tidy" "/usr/local/bin/clang-tidy"
|
||||
- name: Run build script
|
||||
# We need to build before clang-tidy can run analysis
|
||||
run: |
|
||||
# This will build for all three desktop backends on mac
|
||||
./build.sh -p desktop debug gltf_viewer
|
||||
- name: Run test
|
||||
run: bash test/code-correctness/test.sh
|
||||
# python3 ${TEST_DIR}/src/compare.py \
|
||||
# --src=${GOLDEN_OUTPUT_DIR} \
|
||||
# --dest=${RENDER_OUTPUT_DIR} \
|
||||
# --out=${DIFF_OUTPUT_DIR} 2>&1 | tee compare_output.txt
|
||||
|
||||
# if grep "Failed" compare_output.txt > /dev/null; then
|
||||
# DELIMITER="EOF_FILE_CONTENT_$(date +%s)" # Using timestamp to make it more unique
|
||||
# echo "err<<$DELIMITER" >> "$GITHUB_OUTPUT"
|
||||
# cat compare_output.txt >> "$GITHUB_OUTPUT"
|
||||
# echo "$DELIMITER" >> "$GITHUB_OUTPUT"
|
||||
# fi
|
||||
# - uses: actions/upload-artifact@v4
|
||||
# with:
|
||||
# name: presubmit-renderdiff-result
|
||||
# path: ./out/renderdiff
|
||||
# - name: Compare result
|
||||
# run: |
|
||||
# ERROR_STR="${{ steps.render_compare.outputs.err }}"
|
||||
# if [ -n "${ERROR_STR}" ]; then
|
||||
# echo "${ERROR_STR}"
|
||||
# exit 1
|
||||
# fi
|
||||
|
||||
# validate-wgsl-webgpu:
|
||||
# name: validate-wgsl-webgpu
|
||||
# runs-on: 'ubuntu-24.04-8core'
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4.1.6
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
# - uses: ./.github/actions/linux-prereq
|
||||
# - name: Run build script
|
||||
# run: ./build.sh -W debug test_filamat filament
|
||||
# - name: Run test
|
||||
# run: ./out/cmake-debug/libs/filamat/test_filamat --gtest_filter=MaterialCompiler.Wgsl*
|
||||
|
||||
# test-code-correctness:
|
||||
# name: test-code-correctness
|
||||
# runs-on: 'macos-14-xlarge'
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4.1.6
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
# - uses: ./.github/actions/mac-prereq
|
||||
# - name: Install clang-tidy and deps
|
||||
# run: |
|
||||
# pip install pyyaml
|
||||
# brew install llvm@${GITHUB_LLVM_VERSION}
|
||||
# sudo ln -s "$(brew --prefix llvm)@${GITHUB_LLVM_VERSION}/bin/clang-tidy" "/usr/local/bin/clang-tidy"
|
||||
# - name: Run build script
|
||||
# # We need to build before clang-tidy can run analysis
|
||||
# run: |
|
||||
# # This will build for all three desktop backends on mac
|
||||
# ./build.sh -p desktop debug gltf_viewer
|
||||
# - name: Run test
|
||||
# run: bash test/code-correctness/test.sh
|
||||
|
||||
@@ -544,6 +544,7 @@ if (FILAMENT_SUPPORTS_VULKAN)
|
||||
endif()
|
||||
|
||||
if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
message(AUTHOR_WARNING "WebGPU is in development stage and broken at this stage. Its support is very limited.")
|
||||
add_definitions(-DFILAMENT_SUPPORTS_WEBGPU)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -7,3 +7,6 @@ We are chaning the way Vulkan buffers are handled. We need to switch over to a m
|
||||
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)
|
||||
|
||||
@@ -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.64.1'
|
||||
implementation 'com.google.android.filament:filament-android:1.63.1'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
||||
iOS projects can use CocoaPods to install the latest release:
|
||||
|
||||
```shell
|
||||
pod 'Filament', '~> 1.64.1'
|
||||
pod 'Filament', '~> 1.63.1'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
@@ -222,7 +222,7 @@ MaterialInstance* materialInstance = material->createInstance();
|
||||
```
|
||||
|
||||
To learn more about materials and `matc`, please refer to the
|
||||
[materials documentation](https://google.github.io/filament/Materials.html).
|
||||
[materials documentation](./docs/Materials.md.html).
|
||||
|
||||
To render, simply pass the `View` to the `Renderer`:
|
||||
|
||||
@@ -240,7 +240,7 @@ in the `samples/` directory. These samples are all based on `libs/filamentapp/`
|
||||
code that creates a native window with SDL2 and initializes the Filament engine, renderer and views.
|
||||
|
||||
For more information on how to prepare environment maps for image-based lighting please refer to
|
||||
[BUILDING.md](/BUILDING.md#running-the-native-samples).
|
||||
[BUILDING.md](https://github.com/google/filament/blob/main/BUILDING.md#running-the-native-samples).
|
||||
|
||||
### Android
|
||||
|
||||
@@ -272,7 +272,7 @@ To get started you can use the textures and environment maps found respectively
|
||||
refer to their respective `URL.txt` files to know more about the original authors.
|
||||
|
||||
Environments must be pre-processed using
|
||||
[`cmgen`](/BUILDING.md#running-the-native-samples) or
|
||||
[`cmgen`](https://github.com/google/filament/blob/main/BUILDING.md#running-the-native-samples) or
|
||||
using the `libiblprefilter` library.
|
||||
|
||||
## How to make contributions
|
||||
|
||||
@@ -7,11 +7,6 @@ A new header is inserted each time a *tag* is created.
|
||||
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||
|
||||
## v1.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**]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.64.1
|
||||
VERSION_NAME=1.63.1
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -137,9 +137,6 @@ if [[ "$OS_NAME" == "Darwin" ]]; then
|
||||
|
||||
# This is necessary to be able to build vk (lavapipe) on macOS. Doesn't seem like a real dependency.
|
||||
sed -I '' "s/error('Vulkan drivers require dri3 for X11 support')//g" meson.build
|
||||
# This is to properly link lib-xcb-present on the mac build (though we won't be drawing to any
|
||||
# real hardware surface).
|
||||
sed -I '' "s/dep_xcb_present = null_dep/dep_xcb_present = dependency('xcb-present')/g" meson.build
|
||||
fi
|
||||
|
||||
# -Dosmesa=true => builds OSMesa, which is an offscreen GL context
|
||||
|
||||
@@ -109,8 +109,13 @@ function install_mac() {
|
||||
unzip -t vulkan_sdk.zip
|
||||
fi
|
||||
echo "recognized zip layout 'vulkan_sdk.zip' ${InstallVulkan}.app/Contents" >&2
|
||||
${InstallVulkan}.app/Contents/MacOS/${InstallVulkan} --accept-licenses --default-answer --confirm-command install
|
||||
local sdk_temp=${VULKAN_SDK_DIR}.tmp
|
||||
sudo ${InstallVulkan}.app/Contents/MacOS/${InstallVulkan} --root "$sdk_temp" --accept-licenses --default-answer --confirm-command install
|
||||
du -hs $sdk_temp
|
||||
test -d $sdk_temp/macOS || { echo "unrecognized dmg folder layout: $sdk_temp" ; ls -l $sdk_temp ; }
|
||||
cp -r $sdk_temp/macOS/* $VULKAN_SDK_DIR/
|
||||
if [[ -d ${InstallVulkan}.app/Contents ]] ; then
|
||||
sudo rm -rf "$sdk_temp"
|
||||
rm -rf ${InstallVulkan}.app
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -135,7 +135,9 @@ set build_flags=-j %NUMBER_OF_PROCESSORS%
|
||||
:: cmake --build . --target sample-resources --config %config% %build_flags% || exit /b
|
||||
:: cmake --build . --target suzanne-resources --config %config% %build_flags% || exit /b
|
||||
|
||||
cmake --build . %INSTALL% --config %config% %build_flags% -- /m || exit /b
|
||||
cmake --build . --target sample-resources --config %config% %build_flags% -- /m || exit /b
|
||||
|
||||
:: cmake --build . %INSTALL% --config %config% %build_flags% -- /m || exit /b
|
||||
@echo off
|
||||
|
||||
echo Disk info after building variant: %variant%
|
||||
|
||||
+2
-4
@@ -221,8 +221,6 @@ 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
|
||||
@@ -231,7 +229,7 @@ located in <code>tools</code>. Some of tools also have README.md as description.
|
||||
<p>The process for copying and processing these READMEs is outlined in <a href="#introductory-doc">Introductory docs</a>.</p>
|
||||
<h3 id="other-technical-notes"><a class="header" href="#other-technical-notes">Other technical notes</a></h3>
|
||||
<p>These are technical documents that do not fit into a library, tool, or directory of the
|
||||
Filament source tree. We collect them into the <code>docs_src/src_mdbook/src/notes</code> directory. No additional
|
||||
Filament source tree. We collect them into the <code>docs_src/src/notes</code> directory. No additional
|
||||
processing is needed for these documents.</p>
|
||||
<h3 id="raw-source-files"><a class="header" href="#raw-source-files">Raw source files</a></h3>
|
||||
<p>These are files that are not part of the <code>mdbook</code> generation, but should be included output in <code>/docs</code>
|
||||
@@ -244,7 +242,7 @@ add a link in <code>SUMMARY.md</code>, and perform the steps outlined in
|
||||
<a href="#how-to-create">how-to create section</a>.</p>
|
||||
<p>For example, if you are adding a general technical note, then you would</p>
|
||||
<ul>
|
||||
<li>Place the document (file with extension <code>.md</code>) in <code>docs_src/src_mdbook/src/notes</code></li>
|
||||
<li>Place the document (file with extension <code>.md</code>) in <code>docs_src/src/notes</code></li>
|
||||
<li>Add a link in <a href="https://github.com/google/filament/blob/main/docs_src/src_mdbook/src/SUMMARY.md"><code>docs_src/src_mdbook/src/SUMMARY.md</code></a></li>
|
||||
<li>Run the commands in the <a href="#how-to-generate">Generate</a> section</li>
|
||||
</ul>
|
||||
|
||||
+9
-9
@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.64.0'
|
||||
implementation 'com.google.android.filament:filament-android:1.63.1'
|
||||
}
|
||||
</code></pre>
|
||||
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
|
||||
@@ -196,18 +196,18 @@ dependencies {
|
||||
</div>
|
||||
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
|
||||
<p>iOS projects can use CocoaPods to install the latest release:</p>
|
||||
<pre><code class="language-shell">pod 'Filament', '~> 1.64.0'
|
||||
<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="../main/filament.html">Filament</a>, an in-depth explanation of
|
||||
<li><a href="https://google.github.io/filament/Filament.html">Filament</a>, an in-depth explanation of
|
||||
real-time physically based rendering, the graphics capabilities and implementation of Filament.
|
||||
This document explains the math and reasoning behind most of our decisions. This document is a
|
||||
good introduction to PBR for graphics programmers.</li>
|
||||
<li><a href="../main/materials.html">Materials</a>, the full reference
|
||||
<li><a href="https://google.github.io/filament/Materials.html">Materials</a>, the full reference
|
||||
documentation for our material system. This document explains our different material models, how
|
||||
to use the material compiler <code>matc</code> and how to write custom materials.</li>
|
||||
<li><a href="../notes/material_properties.html">Material Properties</a>, a reference
|
||||
<li><a href="https://google.github.io/filament/notes/material_properties.html">Material Properties</a>, a reference
|
||||
sheet for the standard material model.</li>
|
||||
</ul>
|
||||
<h2 id="examples"><a class="header" href="#examples">Examples</a></h2>
|
||||
@@ -395,7 +395,7 @@ by <code>matc</code>:</p>
|
||||
MaterialInstance* materialInstance = material->createInstance();
|
||||
</code></pre>
|
||||
<p>To learn more about materials and <code>matc</code>, please refer to the
|
||||
<a href="../main/materials.html">materials documentation</a>.</p>
|
||||
<a href="./docs/Materials.html">materials documentation</a>.</p>
|
||||
<p>To render, simply pass the <code>View</code> to the <code>Renderer</code>:</p>
|
||||
<pre><code class="language-c++">// beginFrame() returns false if we need to skip a frame
|
||||
if (renderer->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="building.html#running-the-native-samples">BUILDING.md</a>.</p>
|
||||
<a href="https://github.com/google/filament/blob/main/BUILDING.md#running-the-native-samples">BUILDING.md</a>.</p>
|
||||
<h3 id="android-1"><a class="header" href="#android-1">Android</a></h3>
|
||||
<p>See <code>android/samples</code> for examples of how to use Filament on Android.</p>
|
||||
<p>You must always first initialize Filament by calling <code>Filament.init()</code>.</p>
|
||||
@@ -430,7 +430,7 @@ OpenGL ES.</p>
|
||||
<code>third_party/textures</code> and <code>third_party/environments</code>. These assets are under CC0 license. Please
|
||||
refer to their respective <code>URL.txt</code> files to know more about the original authors.</p>
|
||||
<p>Environments must be pre-processed using
|
||||
<a href="building.html#running-the-native-samples"><code>cmgen</code></a> or
|
||||
<a href="https://github.com/google/filament/blob/main/BUILDING.md#running-the-native-samples"><code>cmgen</code></a> or
|
||||
using the <code>libiblprefilter</code> library.</p>
|
||||
<h2 id="how-to-make-contributions"><a class="header" href="#how-to-make-contributions">How to make contributions</a></h2>
|
||||
<p>Please read and follow the steps in <a href="contributing.html">CONTRIBUTING.md</a>. Make sure you are
|
||||
@@ -513,7 +513,7 @@ and tools.</p>
|
||||
<li><code>web</code>: JavaScript bindings, documentation, and samples</li>
|
||||
</ul>
|
||||
<h2 id="license"><a class="header" href="#license">License</a></h2>
|
||||
<p>Please see <a href="https://github.com/google/filament/blob/main/LICENSE">LICENSE</a>.</p>
|
||||
<p>Please see <a href="/LICENSE">LICENSE</a>.</p>
|
||||
<h2 id="disclaimer"><a class="header" href="#disclaimer">Disclaimer</a></h2>
|
||||
<p>This is not an officially supported Google product.</p>
|
||||
|
||||
|
||||
+563
-10
@@ -1,11 +1,564 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Redirecting...</title>
|
||||
<meta http-equiv="refresh" content="0; url=./dup/intro.html">
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="light sidebar-visible" dir="ltr">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>Introduction - Filament</title>
|
||||
|
||||
|
||||
<!-- Custom HTML head -->
|
||||
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
<link rel="shortcut icon" href="favicon.png">
|
||||
<link rel="stylesheet" href="css/variables.css">
|
||||
<link rel="stylesheet" href="css/general.css">
|
||||
<link rel="stylesheet" href="css/chrome.css">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
|
||||
<link rel="stylesheet" href="fonts/fonts.css">
|
||||
|
||||
<!-- Highlight.js Stylesheets -->
|
||||
<link rel="stylesheet" href="highlight.css">
|
||||
<link rel="stylesheet" href="tomorrow-night.css">
|
||||
<link rel="stylesheet" href="ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme stylesheets -->
|
||||
|
||||
<!-- MathJax -->
|
||||
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
||||
|
||||
<!-- Provide site root to javascript -->
|
||||
<script>
|
||||
var path_to_root = "";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
|
||||
</script>
|
||||
<!-- Start loading toc.js asap -->
|
||||
<script src="toc.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="body-container">
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script>
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
|
||||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||||
}
|
||||
|
||||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||||
}
|
||||
} catch (e) { }
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script>
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
const html = document.documentElement;
|
||||
html.classList.remove('light')
|
||||
html.classList.add(theme);
|
||||
html.classList.add("js");
|
||||
</script>
|
||||
|
||||
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script>
|
||||
var sidebar = null;
|
||||
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||||
sidebar = sidebar || 'visible';
|
||||
} else {
|
||||
sidebar = 'hidden';
|
||||
}
|
||||
sidebar_toggle.checked = sidebar === 'visible';
|
||||
html.classList.remove('sidebar-visible');
|
||||
html.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<div style="display:flex;align-items:center;justify-content:center">
|
||||
<img class="flogo" src="images/filament_logo_small.png"></img>
|
||||
</div>
|
||||
<!-- populated by js -->
|
||||
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
|
||||
<noscript>
|
||||
<iframe class="sidebar-iframe-outer" src="toc.html"></iframe>
|
||||
</noscript>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
|
||||
<div class="sidebar-resize-indicator"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
<div id="menu-bar-hover-placeholder"></div>
|
||||
<div id="menu-bar" class="menu-bar sticky">
|
||||
<div class="left-buttons">
|
||||
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</label>
|
||||
<!-- Filament: disable themes because the markdeep part does not look good for dark themes -->
|
||||
<!--
|
||||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
</ul>
|
||||
-->
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h1 class="menu-title">Filament</h1>
|
||||
|
||||
<div class="right-buttons">
|
||||
<a href="https://github.com/google/filament" title="Git repository" aria-label="Git repository">
|
||||
<i id="git-repository-button" class="fa fa-github"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||||
<div id="searchresults-header" class="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script>
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1 id="filament"><a class="header" href="#filament">Filament</a></h1>
|
||||
<p><a href="https://github.com/google/filament/actions?query=workflow%3AAndroid"><img src="https://github.com/google/filament/workflows/Android/badge.svg" alt="Android Build Status" /></a>
|
||||
<a href="https://github.com/google/filament/actions?query=workflow%3AiOS"><img src="https://github.com/google/filament/workflows/iOS/badge.svg" alt="iOS Build Status" /></a>
|
||||
<a href="https://github.com/google/filament/actions?query=workflow%3ALinux"><img src="https://github.com/google/filament/workflows/Linux/badge.svg" alt="Linux Build Status" /></a>
|
||||
<a href="https://github.com/google/filament/actions?query=workflow%3AmacOS"><img src="https://github.com/google/filament/workflows/macOS/badge.svg" alt="macOS Build Status" /></a>
|
||||
<a href="https://github.com/google/filament/actions?query=workflow%3AWindows"><img src="https://github.com/google/filament/workflows/Windows/badge.svg" alt="Windows Build Status" /></a>
|
||||
<a href="https://github.com/google/filament/actions?query=workflow%3AWeb"><img src="https://github.com/google/filament/workflows/Web/badge.svg" alt="Web Build Status" /></a></p>
|
||||
<p>Filament is a real-time physically based rendering engine for Android, iOS, Linux, macOS, Windows,
|
||||
and WebGL. It is designed to be as small as possible and as efficient as possible on Android.</p>
|
||||
<h2 id="download"><a class="header" href="#download">Download</a></h2>
|
||||
<p><a href="https://github.com/google/filament/releases">Download Filament releases</a> to access stable builds.
|
||||
Filament release archives contains host-side tools that are required to generate assets.</p>
|
||||
<p>Make sure you always use tools from the same release as the runtime library. This is particularly
|
||||
important for <code>matc</code> (material compiler).</p>
|
||||
<p>If you'd rather build Filament yourself, please refer to our <a href="building.html">build manual</a>.</p>
|
||||
<h3 id="android"><a class="header" href="#android">Android</a></h3>
|
||||
<p>Android projects can simply declare Filament libraries as Maven dependencies:</p>
|
||||
<pre><code class="language-gradle">repositories {
|
||||
// ...
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.63.1'
|
||||
}
|
||||
</code></pre>
|
||||
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
|
||||
<div class="table-wrapper"><table><thead><tr><th>Artifact</th><th>Description</th></tr></thead><tbody>
|
||||
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android/badge.svg?subject=filament-android" alt="filament-android" /></a></td><td>The Filament rendering engine itself.</td></tr>
|
||||
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android-debug"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android-debug/badge.svg?subject=filament-android-debug" alt="filament-android-debug" /></a></td><td>Debug version of <code>filament-android</code>.</td></tr>
|
||||
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/gltfio-android"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/gltfio-android/badge.svg?subject=gltfio-android" alt="gltfio-android" /></a></td><td>A glTF 2.0 loader for Filament, depends on <code>filament-android</code>.</td></tr>
|
||||
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-utils-android"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-utils-android/badge.svg?subject=filament-utils-android" alt="filament-utils-android" /></a></td><td>KTX loading, Kotlin math, and camera utilities, depends on <code>gltfio-android</code>.</td></tr>
|
||||
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android/badge.svg?subject=filamat-android" alt="filamat-android" /></a></td><td>A runtime material builder/compiler. This library is large but contains a full shader compiler/validator/optimizer and supports both OpenGL and Vulkan.</td></tr>
|
||||
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android-lite"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android-lite/badge.svg?subject=filamat-android-lite" alt="filamat-android-lite" /></a></td><td>A much smaller alternative to <code>filamat-android</code> that can only generate OpenGL shaders. It does not provide validation or optimizations.</td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
|
||||
<p>iOS projects can use CocoaPods to install the latest release:</p>
|
||||
<pre><code class="language-shell">pod 'Filament', '~> 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>
|
||||
</html>
|
||||
|
||||
@@ -160,8 +160,8 @@
|
||||
<main>
|
||||
<h1 id="core-concepts"><a class="header" href="#core-concepts">Core Concepts</a></h1>
|
||||
<ul>
|
||||
<li><a href="filament.html">Filament</a> - High-level designs; Filament's PBR/math assumptions; implementation details.</li>
|
||||
<li><a href="materials.html">Materials</a> - A guide to Filament's material definition.</li>
|
||||
<li><a href="main/filament.html">Filament</a> - High-level designs; Filament's PBR/math assumptions; implementation details.</li>
|
||||
<li><a href="main/materials.html">Materials</a> - A guide to Filament's material definition.</li>
|
||||
</ul>
|
||||
|
||||
</main>
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
-5
@@ -65,9 +65,6 @@ We describe step 1 in detail for the sake of record:
|
||||
- Replace css styling in the exported output as needed (so they don't interfere with the book's css.
|
||||
- Replace resource urls to refer to locations relative to the mdbook structure.
|
||||
|
||||
Any `markdeep` doc can be placed in `docs_src/src_markdeep/` and they will be parsed to html and included
|
||||
in the book as above.
|
||||
|
||||
### READMEs
|
||||
Filament depends on a number of libraries, which reside in the directory `libs`. These individual
|
||||
libaries often have README.md in their root to describe itself. We collect these descriptions into our
|
||||
@@ -78,7 +75,7 @@ The process for copying and processing these READMEs is outlined in [Introductor
|
||||
|
||||
### Other technical notes
|
||||
These are technical documents that do not fit into a library, tool, or directory of the
|
||||
Filament source tree. We collect them into the `docs_src/src_mdbook/src/notes` directory. No additional
|
||||
Filament source tree. We collect them into the `docs_src/src/notes` directory. No additional
|
||||
processing is needed for these documents.
|
||||
|
||||
### Raw source files
|
||||
@@ -93,7 +90,7 @@ add a link in `SUMMARY.md`, and perform the steps outlined in
|
||||
[how-to create section](#how-to-create).
|
||||
|
||||
For example, if you are adding a general technical note, then you would
|
||||
- Place the document (file with extension `.md`) in `docs_src/src_mdbook/src/notes`
|
||||
- Place the document (file with extension `.md`) in `docs_src/src/notes`
|
||||
- Add a link in [`docs_src/src_mdbook/src/SUMMARY.md`]
|
||||
- Run the commands in the [Generate](#how-to-generate) section
|
||||
|
||||
|
||||
@@ -2,14 +2,9 @@
|
||||
"README.md": {
|
||||
"dest": "dup/intro.md",
|
||||
"link_transforms": {
|
||||
"/BUILDING.md#running-the-native-samples": "building.md#running-the-native-samples",
|
||||
"/BUILDING.md": "building.md",
|
||||
"BUILDING.md": "building.md",
|
||||
"/CONTRIBUTING.md": "contributing.md",
|
||||
"/CODE_STYLE.md": "code_style.md",
|
||||
"/LICENSE": "https://github.com/google/filament/blob/main/LICENSE",
|
||||
"https://google.github.io/filament/Filament.html": "../main/filament.md",
|
||||
"https://google.github.io/filament/Materials.html": "../main/materials.md",
|
||||
"https://google.github.io/filament/notes/material_properties.html": "../notes/material_properties.md",
|
||||
"docs/images/samples": "../images/samples"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Core Concepts
|
||||
|
||||
|
||||
- [Filament](filament.md) - High-level designs; Filament's PBR/math assumptions; implementation details.
|
||||
- [Materials](materials.md) - A guide to Filament's material definition.
|
||||
- [Filament](main/filament.md) - High-level designs; Filament's PBR/math assumptions; implementation details.
|
||||
- [Materials](main/materials.md) - A guide to Filament's material definition.
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Redirecting...</title>
|
||||
<meta http-equiv="refresh" content="0; url=./dup/intro.html">
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -258,8 +258,6 @@ if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
include/backend/platforms/WebGPUPlatform.h
|
||||
src/webgpu/platform/WebGPUPlatform.cpp
|
||||
src/webgpu/SpdMipmapGenerator/SpdMipmapGenerator.cpp
|
||||
src/webgpu/utils/AsyncTaskCounter.cpp
|
||||
src/webgpu/utils/AsyncTaskCounter.h
|
||||
src/webgpu/utils/StringPlaceholderTemplateProcessor.cpp
|
||||
src/webgpu/utils/StringPlaceholderTemplateProcessor.h
|
||||
src/webgpu/WebGPUBlitter.cpp
|
||||
@@ -414,7 +412,6 @@ endif()
|
||||
|
||||
if (APPLE AND NOT IOS)
|
||||
target_link_libraries(${TARGET} PRIVATE "-framework Cocoa")
|
||||
target_link_libraries(${TARGET} PRIVATE "-framework QuartzCore")
|
||||
endif()
|
||||
|
||||
target_link_libraries(${TARGET} PUBLIC math)
|
||||
@@ -579,9 +576,6 @@ if (APPLE OR LINUX)
|
||||
test/test_CommandBufferQueue.cpp
|
||||
test/test_Template.cpp
|
||||
)
|
||||
if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
list(APPEND BACKEND_TEST_SRC test/test_WebGPUAsyncTaskCounter.cpp)
|
||||
endif()
|
||||
if (APPLE)
|
||||
# Metal-specific tests
|
||||
list(APPEND BACKEND_TEST_SRC
|
||||
@@ -599,7 +593,6 @@ if (APPLE OR LINUX)
|
||||
spirv-cross-glsl)
|
||||
# Create input/output directories for test result images.
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/actual_images)
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/diff_images)
|
||||
file(COPY test/expected_images DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/images)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ public:
|
||||
* Set to `true` to forcibly disable amortized shader compilation in the backend.
|
||||
* Currently only honored by the GL backend.
|
||||
*/
|
||||
bool disableAmortizedShaderCompile = true;
|
||||
bool disableAmortizedShaderCompile = false;
|
||||
|
||||
/**
|
||||
* Disable backend handles use-after-free checks.
|
||||
|
||||
@@ -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 : public Platform {
|
||||
class WebGPUPlatform final : public Platform {
|
||||
public:
|
||||
WebGPUPlatform();
|
||||
~WebGPUPlatform() override = default;
|
||||
@@ -52,14 +52,6 @@ public:
|
||||
// either returns a valid device or panics
|
||||
[[nodiscard]] wgpu::Device requestDevice(wgpu::Adapter const& adapter);
|
||||
|
||||
struct Configuration {
|
||||
wgpu::BackendType forceBackendType = wgpu::BackendType::Undefined;
|
||||
};
|
||||
|
||||
[[nodiscard]] virtual Configuration getConfiguration() const noexcept {
|
||||
return {};
|
||||
}
|
||||
|
||||
protected:
|
||||
[[nodiscard]] Driver* createDriver(void* sharedContext,
|
||||
const Platform::DriverConfig& driverConfig) noexcept override;
|
||||
|
||||
@@ -97,13 +97,11 @@ void CommandStream::execute(void* buffer) {
|
||||
}
|
||||
}
|
||||
|
||||
Driver& UTILS_RESTRICT driver = mDriver;
|
||||
CommandBase* UTILS_RESTRICT base = static_cast<CommandBase*>(buffer);
|
||||
mDriver.execute([&driver, base] {
|
||||
auto& d = driver;
|
||||
auto p = base;
|
||||
while (UTILS_LIKELY(p)) {
|
||||
p = p->execute(d);
|
||||
mDriver.execute([this, buffer]() {
|
||||
Driver& UTILS_RESTRICT driver = mDriver;
|
||||
CommandBase* UTILS_RESTRICT base = static_cast<CommandBase*>(buffer);
|
||||
while (UTILS_LIKELY(base)) {
|
||||
base = base->execute(driver);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -118,10 +118,6 @@ MetalSwapChain::MetalSwapChain(
|
||||
layerDrawableMutex(std::make_shared<std::mutex>()),
|
||||
type(SwapChainType::CAMETALLAYER) {
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION([nativeWindow isKindOfClass:[CAMetalLayer class]])
|
||||
<< "nativeWindow pointer of class "
|
||||
<< [NSStringFromClass([nativeWindow class]) UTF8String] << " is not a CAMetalLayer";
|
||||
|
||||
if (!(flags & SwapChain::CONFIG_TRANSPARENT) && !nativeWindow.opaque) {
|
||||
LOG(WARNING) << "Warning: Filament SwapChain has no CONFIG_TRANSPARENT flag, but the "
|
||||
"CAMetaLayer("
|
||||
|
||||
@@ -1948,7 +1948,7 @@ void OpenGLDriver::destroyTexture(Handle<HwTexture> th) {
|
||||
if (UTILS_UNLIKELY(t->hwStream)) {
|
||||
detachStream(t);
|
||||
}
|
||||
if (UTILS_UNLIKELY(t->externalTexture)) {
|
||||
if (UTILS_UNLIKELY(t->target == SamplerType::SAMPLER_EXTERNAL)) {
|
||||
mPlatform.destroyExternalImageTexture(t->externalTexture);
|
||||
} else {
|
||||
glDeleteTextures(1, &t->gl.id);
|
||||
|
||||
@@ -191,11 +191,9 @@ void VulkanExternalImageManager::updateSetAndLayout(
|
||||
VkDescriptorSet const srcSet = oldSet != VK_NULL_HANDLE ? oldSet : set->getVkSet();
|
||||
copySet(mPlatform->getDevice(), srcSet, newSet, copyBindings);
|
||||
|
||||
set->setExternalSamplerVkSet(newSet,
|
||||
[&descriptorSetCache = mDescriptorSetCache, layoutCount = layout->count, newLayout,
|
||||
newSet](VulkanDescriptorSet*) {
|
||||
descriptorSetCache->manualRecycle(layoutCount, newLayout, newSet);
|
||||
});
|
||||
set->setExternalSamplerVkSet(newSet, [&](VulkanDescriptorSet*) {
|
||||
mDescriptorSetCache->manualRecycle(layout->count, newLayout, newSet);
|
||||
});
|
||||
if (oldLayout != newLayout) {
|
||||
layout->setExternalSamplerVkLayout(newLayout);
|
||||
}
|
||||
|
||||
@@ -308,10 +308,7 @@ struct VulkanRenderTarget : private HwRenderTarget, fvkmemory::Resource {
|
||||
// Creates a special "default" render target (i.e. associated with the swap chain)
|
||||
explicit VulkanRenderTarget();
|
||||
|
||||
VulkanRenderTarget(VulkanRenderTarget&& target)
|
||||
: HwRenderTarget(0, 0),
|
||||
mOffscreen(false),
|
||||
mProtected(false) {
|
||||
VulkanRenderTarget(VulkanRenderTarget&& target) {
|
||||
swap(std::move(target));
|
||||
}
|
||||
|
||||
@@ -368,8 +365,6 @@ struct VulkanRenderTarget : private HwRenderTarget, fvkmemory::Resource {
|
||||
|
||||
private:
|
||||
void swap(VulkanRenderTarget&& target) {
|
||||
std::swap(width, target.width);
|
||||
std::swap(height, target.height);
|
||||
std::swap(mOffscreen, target.mOffscreen);
|
||||
std::swap(mProtected, target.mProtected);
|
||||
std::swap(mInfo, target.mInfo);
|
||||
|
||||
@@ -58,10 +58,10 @@ namespace {
|
||||
areCopyCompatible(sourceFormat, destinationFormat) &&
|
||||
(sourceExtent.width == destinationExtent.width) &&
|
||||
(sourceExtent.height == destinationExtent.height) &&
|
||||
((sourceOrigin.x % sourceBlockSize.width) == 0) &&
|
||||
((sourceOrigin.y % sourceBlockSize.height) == 0) &&
|
||||
((destinationOrigin.x % destinationBlockSize.width) == 0) &&
|
||||
((destinationOrigin.y % destinationBlockSize.height) == 0);
|
||||
((sourceOrigin.x & sourceBlockSize.width) == 0) &&
|
||||
((sourceOrigin.y & sourceBlockSize.height) == 0) &&
|
||||
((destinationOrigin.x & destinationBlockSize.width) == 0) &&
|
||||
((destinationOrigin.y & destinationBlockSize.height) == 0);
|
||||
}
|
||||
|
||||
struct BlitFragmentShaderArgs final {
|
||||
@@ -206,20 +206,9 @@ constexpr std::string_view FRAGMENT_SHADER_SNIPPET_2D_INPUT_TEMPLATE{ R"(
|
||||
|
||||
} // namespace
|
||||
|
||||
// It can perform a direct memory copy if the formats are compatible and no scaling or format
|
||||
// conversion is needed. Otherwise, it uses a render pass with a custom shader to perform
|
||||
// the blit. This allows for scaling, format conversion, and resolving multisampled textures.
|
||||
// The blitter caches pipelines, layouts, and shaders to avoid recompilation.
|
||||
WebGPUBlitter::WebGPUBlitter(wgpu::Device const& device)
|
||||
: mDevice{ device } {}
|
||||
|
||||
// Performs a blit operation from a source to a destination texture.
|
||||
// This function first checks if a direct texture-to-texture copy can be performed.
|
||||
// A direct copy is possible if the source and destination textures have the same sample count,
|
||||
// compatible formats, and the copy extents match. If a direct copy is not possible, this
|
||||
// function falls back to a render pass-based blit. The render pass uses a shader to read from
|
||||
// the source texture and write to the destination texture, allowing for format conversion,
|
||||
// scaling, and MSAA resolves.
|
||||
void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& commandEncoder,
|
||||
BlitArgs const& args) {
|
||||
// current assumptions/simplifications made in this implementation:
|
||||
@@ -278,20 +267,22 @@ void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& c
|
||||
args.destination.texture.GetUsage() & wgpu::TextureUsage::CopyDst)
|
||||
<< "destination texture usage doesn't have wgpu::TextureUsage::CopyDst";
|
||||
|
||||
copyByteByByte(commandEncoder, args);
|
||||
copy(commandEncoder, args);
|
||||
return;
|
||||
}
|
||||
// need to do a render pass blit...
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(args.destination.texture.GetSampleCount() == 1)
|
||||
<< "Blitting does not currently support writing to multisampled output textures.";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(args.source.texture.GetUsage() & wgpu::TextureUsage::TextureBinding)
|
||||
<< "Source texture usage doesn't have wgpu::TextureUsage::TextureBinding";
|
||||
<< "source texture usage doesn't have wgpu::TextureUsage::TextureBinding";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
args.destination.texture.GetUsage() & wgpu::TextureUsage::RenderAttachment)
|
||||
<< "Destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
|
||||
<< "destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
|
||||
|
||||
// create input/output texture views...
|
||||
const wgpu::TextureViewDimension sourceDimension{ args.source.texture.GetDimension() ==
|
||||
wgpu::TextureDimension::e3D
|
||||
? wgpu::TextureViewDimension::e3D
|
||||
@@ -336,6 +327,7 @@ void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& c
|
||||
<< destinationViewDescriptor.baseArrayLayer << " and mip level "
|
||||
<< destinationViewDescriptor.baseMipLevel << " for render pass blit?";
|
||||
|
||||
// create uniform buffer (for shader args)...
|
||||
const BlitFragmentShaderArgs blitFragmentShaderArgs{
|
||||
.depthPlane = args.source.layerOrDepth,
|
||||
.scale = { static_cast<float>(args.source.extent.width) / args.destination.extent.width,
|
||||
@@ -446,7 +438,7 @@ void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& c
|
||||
const wgpu::RenderPassEncoder renderPassEncoder{ commandEncoder.BeginRenderPass(
|
||||
&renderPassDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(renderPassEncoder)
|
||||
<< "Failed to create render pass encoder for blit.";
|
||||
<< "Failed to create render pass encoder for blit?";
|
||||
renderPassEncoder.SetPipeline(getOrCreateRenderPipeline(args.filter, sourceDimension,
|
||||
args.source.texture.GetSampleCount(), depthSource,
|
||||
args.destination.texture.GetFormat()));
|
||||
@@ -456,7 +448,7 @@ void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& c
|
||||
renderPassEncoder.End();
|
||||
}
|
||||
|
||||
void WebGPUBlitter::copyByteByByte(wgpu::CommandEncoder const& commandEncoder, BlitArgs const& args) {
|
||||
void WebGPUBlitter::copy(wgpu::CommandEncoder const& commandEncoder, BlitArgs const& args) {
|
||||
const wgpu::TexelCopyTextureInfo sourceCopyInfo{
|
||||
.texture = args.source.texture,
|
||||
.mipLevel = args.source.mipLevel,
|
||||
@@ -513,7 +505,7 @@ void WebGPUBlitter::createSampler(const SamplerMagFilter filter) {
|
||||
.maxAnisotropy = 1, // should not matter, just being consistently defined
|
||||
};
|
||||
const wgpu::Sampler sampler{ mDevice.CreateSampler(&descriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(sampler) << "Failed to create blit sampler.";
|
||||
FILAMENT_CHECK_POSTCONDITION(sampler) << "Failed to create blit sampler?";
|
||||
switch (filter) {
|
||||
case SamplerMagFilter::NEAREST:
|
||||
mNearestSampler = sampler;
|
||||
@@ -524,8 +516,6 @@ void WebGPUBlitter::createSampler(const SamplerMagFilter filter) {
|
||||
}
|
||||
}
|
||||
|
||||
// Caches and returns a render pipeline for a given blit configuration.
|
||||
// If a pipeline for the given configuration does not exist, it creates and caches one.
|
||||
wgpu::RenderPipeline const& WebGPUBlitter::getOrCreateRenderPipeline(
|
||||
const SamplerMagFilter filterType, const wgpu::TextureViewDimension sourceDimension,
|
||||
const uint32_t sourceSampleCount, const bool depthSource,
|
||||
@@ -539,9 +529,6 @@ wgpu::RenderPipeline const& WebGPUBlitter::getOrCreateRenderPipeline(
|
||||
return mRenderPipelines[key];
|
||||
}
|
||||
|
||||
// Creates a render pipeline for a blit operation.
|
||||
// The pipeline is configured with the appropriate vertex and fragment shaders,
|
||||
// pipeline layout, and render target state based on the blit parameters.
|
||||
wgpu::RenderPipeline WebGPUBlitter::createRenderPipeline(const SamplerMagFilter filterType,
|
||||
const wgpu::TextureViewDimension sourceDimension, const uint32_t sourceSampleCount,
|
||||
const bool depthSource, const wgpu::TextureFormat destinationTextureFormat) {
|
||||
@@ -593,7 +580,7 @@ wgpu::RenderPipeline WebGPUBlitter::createRenderPipeline(const SamplerMagFilter
|
||||
.fragment = &fragmentState,
|
||||
};
|
||||
const wgpu::RenderPipeline pipeline{ mDevice.CreateRenderPipeline(&pipelineDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline) << "Failed to create pipeline for render pass blit.";
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline) << "Failed to create pipeline for render pass blit?";
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
@@ -608,8 +595,6 @@ size_t WebGPUBlitter::hashRenderPipelineKey(const SamplerMagFilter filterType,
|
||||
return seed;
|
||||
}
|
||||
|
||||
// Caches and returns a pipeline layout for a given blit configuration.
|
||||
// If a layout for the given configuration does not exist, it creates and caches one.
|
||||
wgpu::PipelineLayout const& WebGPUBlitter::getOrCreatePipelineLayout(
|
||||
const SamplerMagFilter filterType, const wgpu::TextureViewDimension sourceDimension,
|
||||
const bool multisampledSource, const bool depthSource) {
|
||||
@@ -622,8 +607,6 @@ wgpu::PipelineLayout const& WebGPUBlitter::getOrCreatePipelineLayout(
|
||||
return mPipelineLayouts[key];
|
||||
}
|
||||
|
||||
// Creates a pipeline layout for a blit operation.
|
||||
// The layout defines the bind group layouts used by the blit pipeline.
|
||||
wgpu::PipelineLayout WebGPUBlitter::createPipelineLayout(const SamplerMagFilter filterType,
|
||||
const wgpu::TextureViewDimension sourceDimension, const bool multisampledSource,
|
||||
const bool depthSource) {
|
||||
@@ -636,7 +619,7 @@ wgpu::PipelineLayout WebGPUBlitter::createPipelineLayout(const SamplerMagFilter
|
||||
const wgpu::PipelineLayout pipelineLayout{ mDevice.CreatePipelineLayout(
|
||||
&pipelineLayoutDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipelineLayout)
|
||||
<< "Failed to create pipeline layout for render pass blit.";
|
||||
<< "Failed to create pipeline layout for render pass blit?";
|
||||
return pipelineLayout;
|
||||
}
|
||||
|
||||
@@ -650,8 +633,6 @@ size_t WebGPUBlitter::hashPipelineLayoutKey(const SamplerMagFilter filterType,
|
||||
return seed;
|
||||
}
|
||||
|
||||
// Caches and returns a bind group layout for a given blit configuration.
|
||||
// If a layout for the given configuration does not exist, it creates and caches one.
|
||||
wgpu::BindGroupLayout const& WebGPUBlitter::getOrCreateTextureBindGroupLayout(
|
||||
const SamplerMagFilter filterType, const wgpu::TextureViewDimension sourceDimension,
|
||||
const bool multisampledSource, const bool depthSource) {
|
||||
@@ -664,9 +645,6 @@ wgpu::BindGroupLayout const& WebGPUBlitter::getOrCreateTextureBindGroupLayout(
|
||||
return mTextureBindGroupLayouts[key];
|
||||
}
|
||||
|
||||
// Creates a bind group layout for the blit operation's texture resources.
|
||||
// This layout specifies the bindings for the source texture, a uniform buffer with blit parameters,
|
||||
// and optionally a sampler.
|
||||
wgpu::BindGroupLayout WebGPUBlitter::createTextureBindGroupLayout(const SamplerMagFilter filterType,
|
||||
const wgpu::TextureViewDimension sourceDimension, const bool multisampledSource,
|
||||
const bool depthSource) {
|
||||
@@ -677,11 +655,11 @@ wgpu::BindGroupLayout WebGPUBlitter::createTextureBindGroupLayout(const SamplerM
|
||||
.texture = {
|
||||
.sampleType = depthSource
|
||||
? wgpu::TextureSampleType::Depth
|
||||
: (multisampledSource
|
||||
? wgpu::TextureSampleType::UnfilterableFloat
|
||||
: wgpu::TextureSampleType::Float), // only F32 scalar sample
|
||||
// type supported for now
|
||||
// (aside from depth)
|
||||
: wgpu::TextureSampleType::Float, // only F32 scalar sample
|
||||
// type supported for now
|
||||
// (aside from depth)
|
||||
// see assumptions listed
|
||||
// in blit function
|
||||
.viewDimension = sourceDimension,
|
||||
.multisampled = multisampledSource,
|
||||
},
|
||||
@@ -715,7 +693,7 @@ wgpu::BindGroupLayout WebGPUBlitter::createTextureBindGroupLayout(const SamplerM
|
||||
const wgpu::BindGroupLayout textureBindGroupLayout{ mDevice.CreateBindGroupLayout(
|
||||
&textureBindGroupLayoutDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(textureBindGroupLayout)
|
||||
<< "Failed to create texture bind group layout for render pass blit.";
|
||||
<< "Failed to create texture bind group layout for render pass blit?";
|
||||
return textureBindGroupLayout;
|
||||
}
|
||||
|
||||
@@ -729,8 +707,6 @@ size_t WebGPUBlitter::hashTextureBindGroupLayoutKey(const SamplerMagFilter filte
|
||||
return seed;
|
||||
}
|
||||
|
||||
// Caches and returns a shader module for a given blit configuration.
|
||||
// If a shader module for the given configuration does not exist, it creates and caches one.
|
||||
wgpu::ShaderModule const& WebGPUBlitter::getOrCreateShaderModule(
|
||||
const wgpu::TextureViewDimension sourceDimension, const bool multisampledSource,
|
||||
const bool depthSource, const bool depthDestination) {
|
||||
@@ -743,9 +719,6 @@ wgpu::ShaderModule const& WebGPUBlitter::getOrCreateShaderModule(
|
||||
return mShaderModules[key];
|
||||
}
|
||||
|
||||
// Creates a shader module containing the vertex and fragment shaders for the blit operation.
|
||||
// The shader source is generated from a template, with placeholders filled in based on the
|
||||
// blit configuration (e.g., texture type, sample count).
|
||||
wgpu::ShaderModule WebGPUBlitter::createShaderModule(
|
||||
const wgpu::TextureViewDimension sourceDimension, const bool multisampledSource,
|
||||
const bool depthSource, const bool depthDestination) {
|
||||
@@ -757,8 +730,9 @@ wgpu::ShaderModule WebGPUBlitter::createShaderModule(
|
||||
textureType = "texture_depth_2d";
|
||||
}
|
||||
} else {
|
||||
// non-depth
|
||||
if (multisampledSource) {
|
||||
textureType = "texture_multisampled_2d<f32>";
|
||||
textureType = "texture_multisampled_2d";
|
||||
} else {
|
||||
if (sourceDimension == wgpu::TextureViewDimension::e3D) {
|
||||
textureType = "texture_3d<f32>";
|
||||
@@ -811,7 +785,7 @@ wgpu::ShaderModule WebGPUBlitter::createShaderModule(
|
||||
};
|
||||
const wgpu::ShaderModule shaderModule{ mDevice.CreateShaderModule(&shaderModuleDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(shaderModule)
|
||||
<< "Failed to create shader module for render pass blit.";
|
||||
<< "Failed to create shader module for render pass blit?";
|
||||
return shaderModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,17 +26,6 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A blit essentially writes pixels to a region of one texture given a region of another texture.
|
||||
* Such a process can be a byte-by-byte copy when possible, while other scenarios involve various
|
||||
* transformations, such as pixel format conversion, image scaling, filtering/sampling
|
||||
* (such as down sampling), or even multi-sample resolves. Some blits can be faster than others,
|
||||
* depending on what is needed; for example, a byte-by-byte copy is generally faster than other
|
||||
* operations.
|
||||
*
|
||||
* This class is responsible for performing blits throughout the lifecycle of its owner/caller,
|
||||
* e.g. the WebGPU backend/driver, where it can leverage state to optimize such blit calls over time.
|
||||
*/
|
||||
class WebGPUBlitter final {
|
||||
public:
|
||||
struct BlitArgs final {
|
||||
@@ -69,15 +58,10 @@ private:
|
||||
/**
|
||||
* ONLY should be called if canDoDirectCopy(...) is true (see it in WebGPUBlitter.cpp)
|
||||
*/
|
||||
void copyByteByByte(wgpu::CommandEncoder const&, BlitArgs const&);
|
||||
void copy(wgpu::CommandEncoder const&, BlitArgs const&);
|
||||
|
||||
void createSampler(SamplerMagFilter);
|
||||
|
||||
// The following methods are used to create and cache WebGPU objects.
|
||||
// The pattern is to have a `getOrCreate...` method that looks up the object in a cache,
|
||||
// and if it's not found, it calls a `create...` method to create it and then stores it in the
|
||||
// cache. A `hash...` method is used to generate a key for the cache.
|
||||
|
||||
[[nodiscard]] wgpu::RenderPipeline const& getOrCreateRenderPipeline(SamplerMagFilter,
|
||||
wgpu::TextureViewDimension sourceDimension, uint32_t sourceSampleCount,
|
||||
bool depthSource, wgpu::TextureFormat destinationTextureFormat);
|
||||
|
||||
@@ -33,9 +33,6 @@ namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
// Creates a wgpu::Buffer, ensuring its size is a multiple of FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS.
|
||||
// WebGPU's WriteBuffer requires the write size to be a multiple of 4. By ensuring the buffer
|
||||
// size is also a multiple of 4, we simplify the update logic.
|
||||
[[nodiscard]] wgpu::Buffer createBuffer(wgpu::Device const& device, const wgpu::BufferUsage usage,
|
||||
uint32_t size, const char* const label) {
|
||||
// Write size must be divisible by WEBGPU_BUFFER_SIZE_MODULUS (e.g. 4).
|
||||
@@ -58,11 +55,6 @@ WebGPUBufferBase::WebGPUBufferBase(wgpu::Device const& device, const wgpu::Buffe
|
||||
const uint32_t size, char const* const label)
|
||||
: mBuffer{ createBuffer(device, usage, size, label) } {}
|
||||
|
||||
// Updates the GPU buffer with data from a BufferDescriptor.
|
||||
// WebGPU requires that the size of the data written to a buffer is a multiple of 4.
|
||||
// This function handles cases where the buffer descriptor's size is not a multiple of 4
|
||||
// by writing the bulk of the data first, and then copying the remaining bytes into a
|
||||
// padded temporary chunk which is then written to the buffer.
|
||||
void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
|
||||
const uint32_t byteOffset, wgpu::Queue const& queue) {
|
||||
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.buffer)
|
||||
|
||||
@@ -28,10 +28,6 @@ namespace filament::backend {
|
||||
|
||||
class BufferDescriptor;
|
||||
|
||||
/**
|
||||
* A base class for WebGPU buffer objects, providing common functionality for creating and
|
||||
* updating GPU buffers.
|
||||
*/
|
||||
class WebGPUBufferBase /* intended to be extended */ {
|
||||
public:
|
||||
/**
|
||||
@@ -50,8 +46,7 @@ protected:
|
||||
|
||||
private:
|
||||
const wgpu::Buffer mBuffer;
|
||||
// WebGPU requires that the source buffer of a writeBuffer call has a size that is a multiple
|
||||
// of 4. This member is used to pad the data if the source size is not a multiple of 4.
|
||||
// FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS (e.g. 4) bytes to hold any extra chunk we need.
|
||||
std::array<uint8_t, FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS> mRemainderChunk{};
|
||||
};
|
||||
|
||||
|
||||
@@ -40,8 +40,6 @@ namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
// The usage flags are determined by the binding type, and always include CopyDst to allow for
|
||||
// updating the buffer.
|
||||
WebGPUBufferObject::WebGPUBufferObject(wgpu::Device const& device,
|
||||
const BufferObjectBinding bindingType, const uint32_t byteCount)
|
||||
: HwBufferObject{ byteCount },
|
||||
|
||||
@@ -31,12 +31,6 @@ namespace filament::backend {
|
||||
|
||||
enum class BufferObjectBinding : uint8_t;
|
||||
|
||||
/**
|
||||
* A WebGPU-specific implementation of BufferObject.
|
||||
* A BufferObject is a GPU buffer that can be used for a variety of purposes, such as storing
|
||||
* uniform data, vertex attributes, or pixel data. The specific usage is determined by the
|
||||
* BufferObjectBinding.
|
||||
*/
|
||||
class WebGPUBufferObject final : public HwBufferObject, public WebGPUBufferBase {
|
||||
public:
|
||||
WebGPUBufferObject(wgpu::Device const&, BufferObjectBinding, uint32_t byteCount);
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// WebGPU requires that the source buffer of a writeBuffer call has a size that is a multiple of 4.
|
||||
constexpr size_t FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS = 4;
|
||||
|
||||
// FWGPU is short for Filament WebGPU
|
||||
|
||||
@@ -42,9 +42,6 @@ constexpr uint8_t INVALID_INDEX = MAX_DESCRIPTOR_COUNT + 1;
|
||||
|
||||
} // namespace
|
||||
|
||||
// The constructor initializes the descriptor set based on a given layout.
|
||||
// It pre-allocates space for the bind group entries and sets up a mapping from binding index
|
||||
// to the internal entry array index. This prepares the object to be populated with resources.
|
||||
WebGPUDescriptorSet::WebGPUDescriptorSet(wgpu::BindGroupLayout const& layout,
|
||||
std::vector<WebGPUDescriptorSetLayout::BindGroupEntryInfo> const& bindGroupEntries)
|
||||
: mLayout{ layout },
|
||||
@@ -55,10 +52,11 @@ WebGPUDescriptorSet::WebGPUDescriptorSet(wgpu::BindGroupLayout const& layout,
|
||||
for (size_t i = 0; i < bindGroupEntries.size(); ++i) {
|
||||
mEntries[i].binding = bindGroupEntries[i].binding;
|
||||
}
|
||||
|
||||
// Create a mapping from binding index to entry index for efficient lookup.
|
||||
// Establish the size of entries based on the layout. This should be reliable and efficient.
|
||||
assert_invariant(INVALID_INDEX > mEntryIndexByBinding.size());
|
||||
std::fill(mEntryIndexByBinding.begin(), mEntryIndexByBinding.end(), INVALID_INDEX);
|
||||
for (size_t i = 0; i < mEntryIndexByBinding.size(); i++) {
|
||||
mEntryIndexByBinding[i] = INVALID_INDEX;
|
||||
}
|
||||
for (size_t index = 0; index < mEntries.size(); index++) {
|
||||
wgpu::BindGroupEntry const& entry = mEntries[index];
|
||||
assert_invariant(entry.binding < mEntryIndexByBinding.size());
|
||||
@@ -68,20 +66,19 @@ WebGPUDescriptorSet::WebGPUDescriptorSet(wgpu::BindGroupLayout const& layout,
|
||||
|
||||
void WebGPUDescriptorSet::addEntry(const unsigned int index, wgpu::BindGroupEntry&& entry) {
|
||||
if (mBindGroup) {
|
||||
// The bind group has already been created, so we can't add new entries.
|
||||
// Filament guarantees that the descriptor set will not be modified after it has been locked.
|
||||
// We will keep getting hits from future updates, but shouldn't do anything
|
||||
// Filament guarantees this won't change after things have locked.
|
||||
return;
|
||||
}
|
||||
// TODO: Putting some level of trust that Filament is not going to reuse indexes or go past the
|
||||
// layout index for efficiency. Add guards if wrong.
|
||||
FILAMENT_CHECK_POSTCONDITION(index < mEntryIndexByBinding.size())
|
||||
<< "impossible/invalid index for a descriptor/binding (out of range or >= "
|
||||
<< "impossible/invalid index for a descriptor/binding (our of range or >= "
|
||||
"MAX_DESCRIPTOR_COUNT) "
|
||||
<< index;
|
||||
uint8_t entryIndex = mEntryIndexByBinding[index];
|
||||
FILAMENT_CHECK_POSTCONDITION(entryIndex != INVALID_INDEX && entryIndex < mEntries.size())
|
||||
<< "Invalid binding index: " << index;
|
||||
|
||||
<< "Invalid binding " << index;
|
||||
entry.binding = index;
|
||||
mEntries[entryIndex] = std::move(entry);
|
||||
}
|
||||
@@ -97,9 +94,8 @@ wgpu::BindGroup WebGPUDescriptorSet::lockAndReturn(wgpu::Device const& device) {
|
||||
.entries = mEntries.data()
|
||||
};
|
||||
mBindGroup = device.CreateBindGroup(&descriptor);
|
||||
FILAMENT_CHECK_POSTCONDITION(mBindGroup) << "Failed to create bind group.";
|
||||
|
||||
// Once the bind group is created, we can release the resources used to create it.
|
||||
FILAMENT_CHECK_POSTCONDITION(mBindGroup) << "Failed to create bind group?";
|
||||
// once we have created the bind group itself we should no longer need any other state
|
||||
#if FWGPU_ENABLED(FWGPU_DEBUG_BIND_GROUPS)
|
||||
FWGPU_LOGD << "WebGPUDescriptorSet (lockAndReturn):";
|
||||
FWGPU_LOGD << " wgpu::BindGroupLayout handle: " << mLayout.Get();
|
||||
|
||||
@@ -32,46 +32,23 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A WebGPU-specific implementation of DescriptorSet.
|
||||
* This class manages a collection of resource bindings (e.g., textures, buffers) that are used by
|
||||
* a shader. It corresponds to a `wgpu::BindGroup` in WebGPU. The actual `wgpu::BindGroup` is
|
||||
* created lazily when `lockAndReturn` is called, at which point the descriptor set becomes
|
||||
* immutable.
|
||||
*/
|
||||
class WebGPUDescriptorSet final : public HwDescriptorSet {
|
||||
public:
|
||||
WebGPUDescriptorSet(wgpu::BindGroupLayout const&,
|
||||
std::vector<WebGPUDescriptorSetLayout::BindGroupEntryInfo> const&);
|
||||
~WebGPUDescriptorSet();
|
||||
|
||||
/**
|
||||
* Adds or updates a resource binding in the descriptor set.
|
||||
* This method is called to populate the descriptor set with buffers, textures, and samplers.
|
||||
* It is only valid to call this before the descriptor set is locked (i.e., before
|
||||
* `lockAndReturn` is called).
|
||||
*/
|
||||
void addEntry(unsigned int index, wgpu::BindGroupEntry&& entry);
|
||||
|
||||
/**
|
||||
* Finalizes the descriptor set by creating the wgpu::BindGroup. After this call, the
|
||||
* descriptor set is considered "locked" and cannot be modified further.
|
||||
*/
|
||||
[[nodiscard]] wgpu::BindGroup lockAndReturn(wgpu::Device const&);
|
||||
|
||||
/**
|
||||
* @return true if the descriptor set has been locked (i.e., the wgpu::BindGroup has been
|
||||
* created).
|
||||
*/
|
||||
[[nodiscard]] bool getIsLocked() const { return mBindGroup != nullptr; }
|
||||
|
||||
[[nodiscard]] size_t getEntitiesWithDynamicOffsetsCount() const {
|
||||
return mEntriesWithDynamicOffsetsCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* May be nullptr. Use lockAndReturn to create the bind group when appropriate
|
||||
*/
|
||||
// May be nullptr. Use lockAndReturn to create the bind group when appropriate
|
||||
[[nodiscard]] wgpu::BindGroup const& getBindGroup() const { return mBindGroup; }
|
||||
|
||||
#if FWGPU_ENABLED(FWGPU_DEBUG_BIND_GROUPS)
|
||||
@@ -83,7 +60,6 @@ private:
|
||||
std::array<uint8_t, MAX_DESCRIPTOR_COUNT> mEntryIndexByBinding{};
|
||||
std::vector<wgpu::BindGroupEntry> mEntries;
|
||||
const size_t mEntriesWithDynamicOffsetsCount;
|
||||
// This is created lazily when lockAndReturn is called.
|
||||
wgpu::BindGroup mBindGroup = nullptr;
|
||||
};
|
||||
|
||||
|
||||
@@ -40,9 +40,7 @@ namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Converts Filament shader stage flags to the corresponding WebGPU shader stage bitmask.
|
||||
*/
|
||||
// Convert Filament Shader Stage Flags bitmask to webgpu equivalent
|
||||
[[nodiscard]] wgpu::ShaderStage filamentStageToWGPUStage(const ShaderStageFlags fFlags) {
|
||||
wgpu::ShaderStage retStages = wgpu::ShaderStage::None;
|
||||
if (any(ShaderStageFlags::VERTEX & fFlags)) {
|
||||
@@ -72,6 +70,10 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
|
||||
baseLabel = temp->c_str();
|
||||
}
|
||||
|
||||
// TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for
|
||||
// debugging. For now, hack an incrementing value.
|
||||
static int layoutNum = 0;
|
||||
|
||||
const unsigned int samplerCount =
|
||||
std::count_if(layout.bindings.begin(), layout.bindings.end(), [](auto& fEntry) {
|
||||
return DescriptorSetLayoutBinding::isSampler(fEntry.type);
|
||||
@@ -85,11 +87,6 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
|
||||
auto& wEntry = wEntries.emplace_back();
|
||||
auto& entryInfo = mBindGroupEntries.emplace_back();
|
||||
wEntry.visibility = filamentStageToWGPUStage(fEntry.stageFlags);
|
||||
|
||||
// In WebGPU, textures and samplers are separate bindings.
|
||||
// We map the Filament binding index to two WebGPU binding indices:
|
||||
// - texture: binding * 2
|
||||
// - sampler: binding * 2 + 1
|
||||
wEntry.binding = fEntry.binding * 2;
|
||||
entryInfo.binding = wEntry.binding;
|
||||
|
||||
@@ -181,13 +178,11 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
|
||||
} else if (isCubeArrayTypeDescriptor(fEntry.type)) {
|
||||
wEntry.texture.viewDimension = wgpu::TextureViewDimension::CubeArray;
|
||||
}
|
||||
// fEntry.count is unused currently
|
||||
}
|
||||
// TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for
|
||||
// debugging. For now, hack an incrementing value.
|
||||
static int layoutNum = 0;
|
||||
std::string label = "layout_" + baseLabel + "_" + std::to_string(++layoutNum) ;
|
||||
std::string label = "layout_" + baseLabel + std::to_string(++layoutNum) ;
|
||||
const wgpu::BindGroupLayoutDescriptor layoutDescriptor{
|
||||
.label{label.c_str()},
|
||||
.label{label.c_str()}, // Use .c_str() if label needs to be const char*
|
||||
.entryCount = wEntries.size(),
|
||||
.entries = wEntries.data()
|
||||
};
|
||||
|
||||
@@ -27,11 +27,6 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A WebGPU implementation of the HwDescriptorSetLayout.
|
||||
* This class defines the layout of a descriptor set, which specifies the types and arrangement
|
||||
* of resources that can be bound to a pipeline.
|
||||
*/
|
||||
class WebGPUDescriptorSetLayout final : public HwDescriptorSetLayout {
|
||||
public:
|
||||
struct BindGroupEntryInfo final {
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
#include "WebGPUTextureHelpers.h"
|
||||
#include "WebGPUVertexBuffer.h"
|
||||
#include "WebGPUVertexBufferInfo.h"
|
||||
#include "webgpu/utils/AsyncTaskCounter.h"
|
||||
#include <backend/platforms/WebGPUPlatform.h>
|
||||
|
||||
#include "CommandStreamDispatcher.h"
|
||||
@@ -72,7 +71,7 @@ void setDefaultTargetFlags(WebGPURenderTarget& defaultRenderTarget,
|
||||
const wgpu::TextureFormat depthFormat{ swapChain.getDepthFormat() };
|
||||
TargetBufferFlags newTargetFlags{ filament::backend::TargetBufferFlags::NONE };
|
||||
|
||||
// Color is always assumed to be present in the default render target.
|
||||
// Assuming Color always present in default render target.
|
||||
newTargetFlags |= filament::backend::TargetBufferFlags::COLOR;
|
||||
if (depthFormat != wgpu::TextureFormat::Undefined) {
|
||||
if (hasDepth(depthFormat)) {
|
||||
@@ -87,12 +86,6 @@ void setDefaultTargetFlags(WebGPURenderTarget& defaultRenderTarget,
|
||||
|
||||
} // namespace
|
||||
|
||||
// The WebGPUDriver is the main entry point for the WebGPU backend. It is responsible for
|
||||
// creating and managing all WebGPU resources, and for submitting commands to the GPU.
|
||||
// It implements the Driver interface, which is the abstraction layer used by Filament's
|
||||
// renderer. The driver uses a handle-based system to manage resources, and it operates
|
||||
// asynchronously, with `create...S` methods for synchronous handle allocation and
|
||||
// `create...R` methods for resource creation on the backend thread.
|
||||
Driver* WebGPUDriver::create(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept {
|
||||
constexpr size_t defaultSize = FILAMENT_WEBGPU_HANDLE_ARENA_SIZE_IN_MB * 1024U * 1024U;
|
||||
Platform::DriverConfig validConfig {driverConfig};
|
||||
@@ -167,7 +160,6 @@ void WebGPUDriver::endFrame(const uint32_t /* frameId */) {
|
||||
mPipelineLayoutCache.onFrameEnd();
|
||||
mPipelineCache.onFrameEnd();
|
||||
|
||||
// Clear the currently bound descriptor sets at the end of the frame.
|
||||
for (size_t i = 0; i < MAX_DESCRIPTOR_SET_COUNT; i++) {
|
||||
mCurrentDescriptorSets[i] = {};
|
||||
}
|
||||
@@ -180,7 +172,7 @@ void WebGPUDriver::flush(int /* dummy */) {
|
||||
if (mCommandEncoder == nullptr) {
|
||||
return;
|
||||
}
|
||||
// Submit the commands recorded so far.
|
||||
// submit the command buffer thus far...
|
||||
assert_invariant(mRenderPassEncoder == nullptr);
|
||||
wgpu::CommandBufferDescriptor commandBufferDescriptor{
|
||||
.label = "frame_command_after_flush",
|
||||
@@ -197,13 +189,12 @@ void WebGPUDriver::flush(int /* dummy */) {
|
||||
assert_invariant(mCommandEncoder);
|
||||
}
|
||||
|
||||
// Submits the currently recorded commands and waits for them to complete on the GPU.
|
||||
// This is a synchronous operation and should be used sparingly.
|
||||
void WebGPUDriver::finish(int /* dummy */) {
|
||||
mDevice.Tick();
|
||||
mAdapter.GetInstance().ProcessEvents();
|
||||
if (mCommandEncoder == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
flush();
|
||||
// Wait for all previously submitted work to finish.
|
||||
std::mutex syncPoint;
|
||||
std::condition_variable syncCondition;
|
||||
bool done = false;
|
||||
@@ -220,7 +211,6 @@ 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) {
|
||||
@@ -274,8 +264,10 @@ void WebGPUDriver::destroyRenderTarget(Handle<HwRenderTarget> renderTargetHandle
|
||||
if (renderTarget == mCurrentRenderTarget) {
|
||||
mCurrentRenderTarget = nullptr;
|
||||
}
|
||||
// The HwTexture handles stored within WGPURenderTarget are not owned by it,
|
||||
// so they are not destroyed here. They are destroyed via WebGPUDriver::destroyTexture.
|
||||
// WGPURenderTarget destructor is trivial.
|
||||
// The HwTexture handles stored within WGPURenderTarget (via MRT, TargetBufferInfo)
|
||||
// are not owned by WGPURenderTarget, so they are not destroyed here.
|
||||
// They are destroyed via WebGPUDriver::destroyTexture.
|
||||
destructHandle<WebGPURenderTarget>(renderTargetHandle);
|
||||
}
|
||||
}
|
||||
@@ -320,14 +312,6 @@ void WebGPUDriver::destroyDescriptorSet(Handle<HwDescriptorSet> descriptorSetHan
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Synchronous handle creation methods (S-methods)
|
||||
//
|
||||
// These methods are called from the frontend thread to allocate a handle for a backend resource.
|
||||
// They are lightweight and do not perform any GPU operations. The actual resource creation
|
||||
// happens in the corresponding R-method on the backend thread.
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
Handle<HwSwapChain> WebGPUDriver::createSwapChainS() noexcept {
|
||||
return allocHandle<WebGPUSwapChain>();
|
||||
}
|
||||
@@ -412,15 +396,9 @@ Handle<HwTexture> WebGPUDriver::createTextureExternalImagePlaneS() noexcept {
|
||||
return allocHandle<WebGPUTexture>();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Asynchronous resource creation methods (R-methods)
|
||||
//
|
||||
// These methods are called on the backend thread to create the actual GPU resources
|
||||
// associated with a handle that was previously allocated by an S-method.
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
|
||||
mNativeWindow = nativeWindow;
|
||||
assert_invariant(!mSwapChain);
|
||||
wgpu::Surface surface = mPlatform.createSurface(nativeWindow, flags);
|
||||
|
||||
wgpu::Extent2D extent = mPlatform.getSurfaceExtent(mNativeWindow);
|
||||
@@ -566,7 +544,7 @@ void WebGPUDriver::createRenderTargetR(Handle<HwRenderTarget> renderTargetHandle
|
||||
}
|
||||
|
||||
void WebGPUDriver::createFenceR(Handle<HwFence> fenceHandle, const int /* dummy */) {
|
||||
// The handle is constructed synchronously in createFenceS.
|
||||
// the handle was already constructed in createFenceS
|
||||
const auto fence = handleCast<WebGPUFence>(fenceHandle);
|
||||
assert_invariant(mQueue);
|
||||
fence->addMarkerToQueueState(mQueue);
|
||||
@@ -636,6 +614,8 @@ 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;
|
||||
}
|
||||
@@ -816,10 +796,6 @@ void WebGPUDriver::setVertexBufferObject(Handle<HwVertexBuffer> vertexBufferHand
|
||||
vertexBuffer->getBuffers()[index] = bufferObject->getBuffer();
|
||||
}
|
||||
|
||||
// Updates a 3D texture region with pixel data from a buffer.
|
||||
// This function handles various pixel formats and performs a blit operation if the source
|
||||
// format needs to be converted to the destination format. For compressed textures, it ensures
|
||||
// that the offsets are aligned to the block dimensions.
|
||||
void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t level,
|
||||
const uint32_t xoffset, const uint32_t yoffset, const uint32_t zoffset,
|
||||
const uint32_t width, const uint32_t height, const uint32_t depth,
|
||||
@@ -836,15 +812,15 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
|
||||
const auto texture{ handleCast<WebGPUTexture>(textureHandle) };
|
||||
FILAMENT_CHECK_PRECONDITION((texture->getTexture().GetWidth() + xoffset) >= width)
|
||||
<< "Blitting requires the destination region to have enough width ("
|
||||
<< texture->getTexture().GetWidth() << ") to accommodate the requested " << width
|
||||
<< texture->getTexture().GetWidth() << " to accommodate the requested " << width
|
||||
<< " width to write/blit (accounting for xoffset, which is " << xoffset << ").";
|
||||
FILAMENT_CHECK_PRECONDITION((texture->getTexture().GetHeight() + yoffset) >= height)
|
||||
<< "Blitting requires the destination region to have enough height ("
|
||||
<< texture->getTexture().GetHeight() << ") to accommodate the requested " << height
|
||||
<< texture->getTexture().GetHeight() << " to accommodate the requested " << height
|
||||
<< " height to write/blit (accounting for yoffset, which is " << yoffset << ").";
|
||||
FILAMENT_CHECK_PRECONDITION((texture->getTexture().GetDepthOrArrayLayers() + zoffset) >= depth)
|
||||
<< "Blitting requires the destination region to have enough depth/arrayLayers ("
|
||||
<< texture->getTexture().GetDepthOrArrayLayers() << ") to accommodate the requested "
|
||||
<< texture->getTexture().GetDepthOrArrayLayers() << " to accommodate the requested "
|
||||
<< depth << " depth to write/blit (accounting for zoffset, which is " << zoffset << ").";
|
||||
|
||||
// TODO: Writing to a depth texture is illegal and errors. I'm not sure why Filament is trying
|
||||
@@ -857,8 +833,10 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
|
||||
inputData->type) };
|
||||
const wgpu::TextureFormat outputLinearFormat{ toLinearFormat(
|
||||
texture->getTexture().GetFormat()) };
|
||||
|
||||
const bool doBlit{ conversionNecessary(inputPixelFormat, outputLinearFormat, inputData->type) };
|
||||
const bool conversionNecessary{
|
||||
inputPixelFormat != outputLinearFormat && inputData->type != PixelDataType::COMPRESSED
|
||||
}; // compressed formats should never need conversion
|
||||
const bool doBlit{ conversionNecessary };
|
||||
#if FWGPU_ENABLED(FWGPU_DEBUG_UPDATE_IMAGE)
|
||||
if (texture->width > 1000 && texture->height > 500) {
|
||||
FWGPU_LOGD << "Update3DImage(..., level=" << level << ", xoffset=" << xoffset
|
||||
@@ -980,7 +958,7 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
|
||||
}
|
||||
stagingTexture.Destroy();
|
||||
} else {
|
||||
// Direct copy without a blit.
|
||||
// not doing blit (copy byte-by-byte)...
|
||||
const auto copyInfo { wgpu::TexelCopyTextureInfo{
|
||||
.texture = texture->getTexture(),
|
||||
.mipLevel = level,
|
||||
@@ -1092,14 +1070,10 @@ void WebGPUDriver::compilePrograms(CompilerPriorityQueue priority,
|
||||
}
|
||||
}
|
||||
|
||||
// Begins a render pass. This sets up the render pass descriptor with the appropriate
|
||||
// color and depth/stencil attachments, based on whether the target is the default
|
||||
// swap chain render target or a custom one. It also handles MSAA by setting up
|
||||
// resolve targets if necessary.
|
||||
void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
|
||||
RenderPassParams const& params) {
|
||||
if (!mCommandEncoder) {
|
||||
wgpu::CommandEncoderDescriptor commandEncoderDescriptor{ .label = "frame_command_encoder" };
|
||||
wgpu::CommandEncoderDescriptor commandEncoderDescriptor = { .label = "frame_command_encoder" };
|
||||
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
|
||||
}
|
||||
assert_invariant(mCommandEncoder);
|
||||
@@ -1347,24 +1321,19 @@ void WebGPUDriver::stopCapture(int) {
|
||||
//todo
|
||||
}
|
||||
|
||||
// Reads a block of pixels from a render target into a buffer.
|
||||
// This function is asynchronous. It copies the pixel data to a staging buffer on the GPU,
|
||||
// and then maps the buffer for reading on the CPU. The provided callback is invoked when the
|
||||
// data is ready. This function also handles the 256-byte row alignment requirement for
|
||||
// buffer-to-texture copies in WebGPU.
|
||||
void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, const uint32_t x,
|
||||
const uint32_t y, const uint32_t width, const uint32_t height,
|
||||
PixelBufferDescriptor&& pixelBufferDescriptor) {
|
||||
const auto srcTarget{ handleCast<WebGPURenderTarget>(sourceRenderTargetHandle) };
|
||||
auto srcTarget = handleCast<WebGPURenderTarget>(sourceRenderTargetHandle);
|
||||
assert_invariant(srcTarget);
|
||||
|
||||
wgpu::Texture srcTexture{ nullptr };
|
||||
wgpu::Texture srcTexture = nullptr;
|
||||
if (srcTarget->isDefaultRenderTarget()) {
|
||||
assert_invariant(mSwapChain);
|
||||
srcTexture = mSwapChain->getCurrentTexture();
|
||||
} else {
|
||||
// Handle custom render targets. Read from the first color attachment.
|
||||
const auto& colorAttachmentInfos{ srcTarget->getColorAttachmentInfos() };
|
||||
const auto& colorAttachmentInfos = srcTarget->getColorAttachmentInfos();
|
||||
// TODO we are currently assuming the first attachment is the desired texture.
|
||||
if (colorAttachmentInfos[0].handle) {
|
||||
auto texture = handleCast<WebGPUTexture>(colorAttachmentInfos[0].handle);
|
||||
@@ -1379,172 +1348,116 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, c
|
||||
scheduleDestroy(std::move(pixelBufferDescriptor));
|
||||
return;
|
||||
}
|
||||
const uint32_t srcWidth {srcTexture.GetWidth()};
|
||||
const uint32_t srcHeight{srcTexture.GetHeight()};
|
||||
const uint32_t srcWidth = srcTexture.GetWidth();
|
||||
const uint32_t srcHeight = srcTexture.GetHeight();
|
||||
|
||||
// Clamp read region to texture bounds
|
||||
if (UTILS_UNLIKELY(x >= srcWidth || y >= srcHeight)) {
|
||||
if (x >= srcWidth || y >= srcHeight) {
|
||||
scheduleDestroy(std::move(pixelBufferDescriptor));
|
||||
return;
|
||||
}
|
||||
auto actualWidth{ std::min(width, srcWidth - x) };
|
||||
auto actualHeight{ std::min(height, srcHeight - y)};
|
||||
auto actualWidth = std::min(width, srcWidth - x);
|
||||
auto actualHeight = std::min(height, srcHeight - y);
|
||||
if (UTILS_UNLIKELY(actualWidth == 0 || actualHeight == 0)) {
|
||||
scheduleDestroy(std::move(pixelBufferDescriptor));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mCommandEncoder) {
|
||||
const wgpu::CommandEncoderDescriptor commandEncoderDescriptor{
|
||||
.label = "read_pixels_command",
|
||||
};
|
||||
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
|
||||
FILAMENT_CHECK_POSTCONDITION(mCommandEncoder)
|
||||
<< "Failed to create command encoder for readPixels?";
|
||||
}
|
||||
// Once we're ready to read the pixels, we need to flush all the previous work of this frame.
|
||||
// This ensures that the readPixels will be ordered after all the draws.
|
||||
flush();
|
||||
|
||||
const wgpu::TextureFormat srcFormat{srcTexture.GetFormat()};
|
||||
const wgpu::TextureFormat dstFormat{toWebGPUFormat(pixelBufferDescriptor.format,
|
||||
pixelBufferDescriptor.type)};
|
||||
wgpu::Texture textureToReadFrom{srcTexture};
|
||||
wgpu::Texture stagingTexture{ nullptr };
|
||||
uint32_t readX{x};
|
||||
uint32_t readY{y};
|
||||
const size_t dstBytesPerPixel = PixelBufferDescriptor::computePixelSize(
|
||||
pixelBufferDescriptor.format, pixelBufferDescriptor.type);
|
||||
const size_t srcBytesPerPixel = getWGPUTextureFormatPixelSize(srcTexture.GetFormat());
|
||||
|
||||
// If the source format is different from the destination (e.g. BGRA vs RGBA),
|
||||
// we need to perform a conversion using an intermediate blit.
|
||||
if (conversionNecessary(srcFormat, dstFormat, pixelBufferDescriptor.type)) {
|
||||
const wgpu::TextureDescriptor stagingDescriptor{
|
||||
.label = "readpixels_staging_texture",
|
||||
.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment,
|
||||
.dimension = wgpu::TextureDimension::e2D, // are we sure we can assume this? thus far we have
|
||||
.size = {
|
||||
.width = actualWidth,
|
||||
.height = actualHeight,
|
||||
.depthOrArrayLayers = 1,
|
||||
},
|
||||
.format = dstFormat,
|
||||
.mipLevelCount = 1,
|
||||
.sampleCount = srcTexture.GetSampleCount(),
|
||||
};
|
||||
stagingTexture = mDevice.CreateTexture(&stagingDescriptor);
|
||||
assert_invariant(stagingTexture);
|
||||
textureToReadFrom = stagingTexture;
|
||||
const WebGPUBlitter::BlitArgs blitArgs{
|
||||
.source = {
|
||||
.texture = srcTexture,
|
||||
.origin = {.x = x, .y = y,},
|
||||
.extent = {
|
||||
.width = actualWidth,
|
||||
.height = actualHeight,
|
||||
},
|
||||
},
|
||||
.destination = {
|
||||
.texture = stagingTexture,
|
||||
.origin = {.x = 0, .y = 0},
|
||||
.extent = {
|
||||
.width = actualWidth,
|
||||
.height = actualHeight,
|
||||
},
|
||||
},
|
||||
.filter = SamplerMagFilter::NEAREST, // Use NEAREST for a 1:1 copy
|
||||
};
|
||||
mBlitter.blit(mQueue, mCommandEncoder, blitArgs);
|
||||
FILAMENT_CHECK_PRECONDITION(dstBytesPerPixel == srcBytesPerPixel && dstBytesPerPixel > 0)
|
||||
<< "Source texture pixel size (" << srcBytesPerPixel
|
||||
<< ") does not match destination pixel buffer pixel size (" << dstBytesPerPixel
|
||||
<< "), or the format is not supported for readPixels.";
|
||||
|
||||
// The subsequent read will be from the top-left of the new intermediate texture.
|
||||
readX = 0;
|
||||
readY = 0;
|
||||
}
|
||||
// Create a staging buffer to copy the texture to. WebGPU requires 256 byte alignment
|
||||
const size_t bytesPerPixel = dstBytesPerPixel;
|
||||
const size_t unpaddedBytesPerRow = actualWidth * bytesPerPixel;
|
||||
const size_t alignment = 256;
|
||||
const size_t paddedBytesPerRow = (unpaddedBytesPerRow + alignment - 1) & ~(alignment - 1);
|
||||
|
||||
// Create a staging buffer to copy the texture to. WebGPU requires 256 byte alignment for buffer-to-texture copies.
|
||||
const size_t bytesPerPixel{ PixelBufferDescriptor::computePixelSize(
|
||||
pixelBufferDescriptor.format, pixelBufferDescriptor.type) };
|
||||
const size_t unpaddedBytesPerRow{ actualWidth * bytesPerPixel };
|
||||
const size_t alignment{ 256 };
|
||||
const size_t paddedBytesPerRow = { (unpaddedBytesPerRow + alignment - 1) & ~(alignment - 1) };
|
||||
|
||||
const size_t bufferSize{ paddedBytesPerRow * actualHeight };
|
||||
const wgpu::BufferDescriptor bufferDesc{
|
||||
.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead,
|
||||
.size = bufferSize,
|
||||
};
|
||||
|
||||
wgpu::Buffer stagingBuffer{ mDevice.CreateBuffer(&bufferDesc) };
|
||||
size_t bufferSize = paddedBytesPerRow * height;
|
||||
wgpu::BufferDescriptor bufferDesc;
|
||||
bufferDesc.size = bufferSize;
|
||||
bufferDesc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead;
|
||||
wgpu::Buffer stagingBuffer = mDevice.CreateBuffer(&bufferDesc);
|
||||
assert_invariant(stagingBuffer);
|
||||
|
||||
// WebGPU's texture coordinates for copies are top-left, but Filament's y-coordinate is
|
||||
// bottom-left. We must flip the y-coordinate relative to the texture we are reading from.
|
||||
const uint32_t textureHeight{ textureToReadFrom.GetHeight() };
|
||||
const uint32_t flippedY{ textureHeight - readY - actualHeight };
|
||||
wgpu::CommandEncoder encoder = mDevice.CreateCommandEncoder();
|
||||
assert_invariant(encoder);
|
||||
|
||||
const wgpu::TexelCopyTextureInfo source{
|
||||
.texture = textureToReadFrom, // Read from the original or intermediate texture
|
||||
.mipLevel = 0,
|
||||
.origin = {.x = readX, .y = flippedY, .z = 0,},
|
||||
// WebGPU flips the y axis like Metal does
|
||||
const uint32_t flippedY = srcHeight - y - height;
|
||||
wgpu::TexelCopyTextureInfo source{
|
||||
.texture = srcTexture,
|
||||
.mipLevel = 0,
|
||||
.origin = { .x = x, .y = flippedY, .z = 0 },
|
||||
};
|
||||
const wgpu::TexelCopyBufferInfo destination{
|
||||
.layout = {
|
||||
.offset = 0,
|
||||
.bytesPerRow = static_cast<uint32_t>(paddedBytesPerRow),
|
||||
.rowsPerImage = actualHeight,
|
||||
},
|
||||
.buffer = stagingBuffer
|
||||
wgpu::TexelCopyBufferInfo destination {
|
||||
.layout = {
|
||||
.offset = 0,
|
||||
.bytesPerRow = static_cast<uint32_t>(paddedBytesPerRow),
|
||||
.rowsPerImage = actualHeight,
|
||||
},
|
||||
.buffer = stagingBuffer
|
||||
};
|
||||
const wgpu::Extent3D copySize{
|
||||
.width = actualWidth,
|
||||
wgpu::Extent3D copySize{ .width = actualWidth,
|
||||
.height = actualHeight,
|
||||
.depthOrArrayLayers = 1,
|
||||
};
|
||||
mCommandEncoder.CopyTextureToBuffer(&source, &destination, ©Size);
|
||||
mCommandBuffer = mCommandEncoder.Finish();
|
||||
assert_invariant(mCommandBuffer);
|
||||
mCommandEncoder = nullptr;
|
||||
mQueue.Submit(1, &mCommandBuffer);
|
||||
mCommandBuffer = nullptr;
|
||||
.depthOrArrayLayers = 1 };
|
||||
// TODO consider removing encoder finish + submit once we have test coverage and can
|
||||
// safely verify doing so will not result in strange errors. We don't think this
|
||||
// submit is necessary, as the encoder should get finished and the command
|
||||
// submitted in commit(), finish(), and/or flush() in this frame
|
||||
// or the next anyway. And, the read/copy bits will be encoded before other things.
|
||||
// Thus, not submitting here _seems_ ok. Nonetheless, doing the submit here likely
|
||||
// represents unnecessary inefficiency and higher bandwidth
|
||||
// between the CPU and GPU, but we are worried about functional correctness at this
|
||||
// stage.
|
||||
encoder.CopyTextureToBuffer(&source, &destination, ©Size);
|
||||
wgpu::CommandBuffer commandBuffer = encoder.Finish();
|
||||
mQueue.Submit(1, &commandBuffer);
|
||||
|
||||
// Map the buffer to read the data
|
||||
struct UserData final {
|
||||
struct UserData {
|
||||
PixelBufferDescriptor pixelBufferDescriptor;
|
||||
wgpu::Buffer buffer;
|
||||
size_t unpaddedBytesPerRow;
|
||||
size_t paddedBytesPerRow;
|
||||
uint32_t height;
|
||||
WebGPUDriver *driver;
|
||||
WebGPUDriver* driver;
|
||||
};
|
||||
auto userData = std::make_unique<UserData>(UserData{
|
||||
.pixelBufferDescriptor = std::move(pixelBufferDescriptor),
|
||||
.buffer = std::move(stagingBuffer),
|
||||
.unpaddedBytesPerRow = unpaddedBytesPerRow,
|
||||
.paddedBytesPerRow = paddedBytesPerRow,
|
||||
.height = actualHeight,
|
||||
.driver = this,
|
||||
.pixelBufferDescriptor = std::move(pixelBufferDescriptor),
|
||||
.buffer = std::move(stagingBuffer),
|
||||
.unpaddedBytesPerRow = unpaddedBytesPerRow,
|
||||
.paddedBytesPerRow = paddedBytesPerRow,
|
||||
.height = height,
|
||||
.driver = this,
|
||||
});
|
||||
|
||||
mReadPixelMapsCounter.startTask();
|
||||
userData->buffer.MapAsync(
|
||||
wgpu::MapMode::Read, 0, bufferSize, wgpu::CallbackMode::AllowSpontaneous,
|
||||
[](wgpu::MapAsyncStatus status, const char* message, UserData* userdata) {
|
||||
std::unique_ptr<UserData> data(static_cast<UserData*>(userdata));
|
||||
if (UTILS_LIKELY(status == wgpu::MapAsyncStatus::Success)) {
|
||||
const char *src {static_cast<const char *>(
|
||||
data->buffer.GetConstMappedRange(0, data->buffer.GetSize()))};
|
||||
char* dst{ static_cast<char*>(data->pixelBufferDescriptor.buffer) };
|
||||
|
||||
// If padding was added for alignment, we need to copy row by row.
|
||||
if (data->paddedBytesPerRow == data->unpaddedBytesPerRow) {
|
||||
memcpy(dst, src, data->unpaddedBytesPerRow * data->height);
|
||||
} else {
|
||||
for (uint32_t i{ 0 }; i < data->height; ++i) {
|
||||
memcpy(dst + i * data->unpaddedBytesPerRow,
|
||||
src + i * data->paddedBytesPerRow, data->unpaddedBytesPerRow);
|
||||
}
|
||||
const char* src = static_cast<const char*>(
|
||||
data->buffer.GetConstMappedRange(0, data->buffer.GetSize()));
|
||||
char* dst = static_cast<char*>(data->pixelBufferDescriptor.buffer);
|
||||
for (uint32_t i = 0; i < data->height; ++i) {
|
||||
memcpy(dst + i * data->unpaddedBytesPerRow,
|
||||
src + i * data->paddedBytesPerRow, data->unpaddedBytesPerRow);
|
||||
}
|
||||
data->buffer.Unmap();
|
||||
} else {
|
||||
FWGPU_LOGE << "Failed to map staging buffer for readPixels: " << message;
|
||||
}
|
||||
data->driver->scheduleDestroy(std::move(data->pixelBufferDescriptor));
|
||||
data->driver->mReadPixelMapsCounter.finishTask();
|
||||
},
|
||||
userData.release());
|
||||
}
|
||||
@@ -1559,98 +1472,14 @@ void WebGPUDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
Handle<HwRenderTarget> destinationRenderTargetHandle, const Viewport destinationViewport,
|
||||
Handle<HwRenderTarget> sourceRenderTargetHandle, const Viewport sourceViewport,
|
||||
const SamplerMagFilter filter) {
|
||||
|
||||
auto const sourceTarget{ handleCast<WebGPURenderTarget>(sourceRenderTargetHandle) };
|
||||
auto const destinationTarget{ handleCast<WebGPURenderTarget>(destinationRenderTargetHandle) };
|
||||
assert_invariant(sourceTarget && destinationTarget);
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(buffers == TargetBufferFlags::COLOR0)
|
||||
<< "blitDEPRECATED only supports COLOR0";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(sourceViewport.left >= 0 && sourceViewport.bottom >= 0 &&
|
||||
destinationViewport.left >= 0 && destinationViewport.bottom >= 0)
|
||||
<< "Source and destination viewports must be positive.";
|
||||
|
||||
// We always blit from/to the COLOR0 attachment.
|
||||
auto sourceAttachment {sourceTarget->getColorAttachmentInfos()[0]};
|
||||
Handle<HwTexture> sourceTextureHandle{ sourceAttachment.handle };
|
||||
auto destinationAttachment {destinationTarget->getColorAttachmentInfos()[0]};
|
||||
Handle<HwTexture> destinationTextureHandle{
|
||||
destinationAttachment.handle
|
||||
};
|
||||
|
||||
if (UTILS_UNLIKELY(!sourceTextureHandle || !destinationTextureHandle)) {
|
||||
FWGPU_LOGE << "blitDEPRECATED could not find a valid color attachment to blit.";
|
||||
return;
|
||||
}
|
||||
|
||||
// WebGPU's texture coordinates are top-left, while Filament's are bottom-left.
|
||||
// We need to flip the y-coordinate for both source and destination.
|
||||
auto const sourceTexture{ handleCast<WebGPUTexture>(sourceTextureHandle) };
|
||||
auto const destinationTexture{ handleCast<WebGPUTexture>(destinationTextureHandle) };
|
||||
|
||||
const uint32_t sourceTextureHeight{ sourceTexture->height };
|
||||
const uint32_t flippedSourceY{ sourceTextureHeight - sourceViewport.bottom -
|
||||
sourceViewport.height };
|
||||
|
||||
const uint32_t destinationTextureHeight{ destinationTexture->height };
|
||||
const uint32_t flippedDestinationY{ destinationTextureHeight - destinationViewport.bottom -
|
||||
destinationViewport.height };
|
||||
|
||||
const wgpu::Origin2D sourceOrigin{ static_cast<uint32_t>(sourceViewport.left), flippedSourceY };
|
||||
const wgpu::Origin2D destinationOrigin{ static_cast<uint32_t>(destinationViewport.left),
|
||||
flippedDestinationY };
|
||||
|
||||
const wgpu::Extent2D sourceSize{ static_cast<uint32_t>(sourceViewport.width),
|
||||
static_cast<uint32_t>(sourceViewport.height) };
|
||||
const wgpu::Extent2D destinationSize{ static_cast<uint32_t>(destinationViewport.width),
|
||||
static_cast<uint32_t>(destinationViewport.height) };
|
||||
|
||||
bool reusedCommandEncoder{ true };
|
||||
if (mCommandEncoder) {
|
||||
flush();
|
||||
} else {
|
||||
reusedCommandEncoder = false;
|
||||
const wgpu::CommandEncoderDescriptor desc{ .label = "blit_deprecated_command" };
|
||||
mCommandEncoder = mDevice.CreateCommandEncoder(&desc);
|
||||
}
|
||||
|
||||
const WebGPUBlitter::BlitArgs blitArgs{
|
||||
.source = { .texture = sourceTexture->getTexture(),
|
||||
.aspect = sourceTexture->getAspect(),
|
||||
.origin = sourceOrigin,
|
||||
.extent = sourceSize,
|
||||
.mipLevel = sourceAttachment.level,
|
||||
.layerOrDepth = sourceAttachment.layer,
|
||||
},
|
||||
.destination = { .texture = destinationTexture->getTexture(),
|
||||
.aspect = destinationTexture->getAspect(),
|
||||
.origin = destinationOrigin,
|
||||
.extent = destinationSize,
|
||||
.mipLevel = destinationAttachment.level,
|
||||
.layerOrDepth = destinationAttachment.layer,
|
||||
},
|
||||
.filter = filter,
|
||||
};
|
||||
mBlitter.blit(mQueue, mCommandEncoder, blitArgs);
|
||||
|
||||
if (!reusedCommandEncoder) {
|
||||
const wgpu::CommandBufferDescriptor desc{ .label = "blit_deprecated_command_buffer" };
|
||||
const wgpu::CommandBuffer blitCommand{ mCommandEncoder.Finish(&desc) };
|
||||
mQueue.Submit(1, &blitCommand);
|
||||
mCommandEncoder = nullptr;
|
||||
}
|
||||
PANIC_PRECONDITION("WebGPUDriver::blitDEPRECATED not supported");
|
||||
}
|
||||
|
||||
void WebGPUDriver::resolve(Handle<HwTexture> destinationTextureHandle, const uint8_t sourceLevel,
|
||||
const uint8_t sourceLayer, Handle<HwTexture> sourceTextureHandle,
|
||||
const uint8_t destinationLevel, const uint8_t destinationLayer) {
|
||||
if (!mCommandEncoder) {
|
||||
const wgpu::CommandEncoderDescriptor encoderDescriptor{
|
||||
.label = "command_created_with_resolve",
|
||||
};
|
||||
mCommandEncoder = mDevice.CreateCommandEncoder(&encoderDescriptor);
|
||||
}
|
||||
FILAMENT_CHECK_PRECONDITION(mCommandEncoder)
|
||||
<< "Resolve assumes there is a valid command encoder to piggyback on.";
|
||||
FILAMENT_CHECK_PRECONDITION(mRenderPassEncoder == nullptr)
|
||||
<< "Resolve cannot be called during an existing render pass";
|
||||
const auto sourceTexture{ handleCast<WebGPUTexture>(sourceTextureHandle) };
|
||||
@@ -1776,10 +1605,13 @@ void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
};
|
||||
wgpu::TextureFormat depthStencilFormat{ wgpu::TextureFormat::Undefined };
|
||||
if (renderTarget->isDefaultRenderTarget()) {
|
||||
// default render target color(s) (one)...
|
||||
colorFormatCount = 1;
|
||||
colorFormats[0] = mSwapChain->getColorFormat();
|
||||
// default render target depth/stencil...
|
||||
depthStencilFormat = mSwapChain->getDepthFormat();
|
||||
} else {
|
||||
// custom render target color(s)...
|
||||
MRT const& mrtColorAttachments{ mCurrentRenderTarget->getColorAttachmentInfos() };
|
||||
for (size_t i{ 0 }; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; ++i) {
|
||||
if (mrtColorAttachments[i].handle) {
|
||||
@@ -1845,7 +1677,7 @@ void WebGPUDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> renderPrimitive
|
||||
|
||||
void WebGPUDriver::draw2(const uint32_t indexOffset, const uint32_t indexCount,
|
||||
const uint32_t instanceCount) {
|
||||
// Bind groups are deferred until the actual draw call.
|
||||
// We defer actually binding until we actually draw
|
||||
for (size_t i = 0; i < MAX_DESCRIPTOR_SET_COUNT; i++) {
|
||||
auto& binding = mCurrentDescriptorSets[i];
|
||||
if (binding.bindGroup) {
|
||||
@@ -1915,8 +1747,10 @@ void WebGPUDriver::updateDescriptorSetTexture(Handle<HwDescriptorSet> descriptor
|
||||
auto texture = handleCast<WebGPUTexture>(textureHandle);
|
||||
|
||||
if (!bindGroup->getIsLocked()) {
|
||||
// Dawn will cache duplicate samplers, so we don't strictly need to maintain a cache here.
|
||||
// Dawn will cache duplicate samplers, so we don't strictly need to maintain a cache.
|
||||
// Making a cache might save us minor perf by reducing param translation
|
||||
const auto sampler = makeSampler(params);
|
||||
// TODO making assumptions that size and offset mean the same thing here.
|
||||
wgpu::BindGroupEntry tEntry{
|
||||
.binding = static_cast<uint32_t>(binding * 2),
|
||||
.textureView = texture->getDefaultTextureView() };
|
||||
@@ -1950,7 +1784,7 @@ void WebGPUDriver::bindDescriptorSet(Handle<HwDescriptorSet> descriptorSetHandle
|
||||
const backend::descriptor_set_t setIndex, backend::DescriptorSetOffsetArray&& offsets) {
|
||||
assert_invariant(setIndex < MAX_DESCRIPTOR_SET_COUNT);
|
||||
|
||||
// An empty handle unbinds the descriptor set.
|
||||
// An empty handle signifies we need to release this bind point
|
||||
if (!descriptorSetHandle) {
|
||||
mCurrentDescriptorSets[setIndex] = {};
|
||||
return;
|
||||
@@ -2080,4 +1914,4 @@ wgpu::AddressMode WebGPUDriver::fWrapModeToWAddressMode(const SamplerWrapMode& f
|
||||
return wgpu::AddressMode::Undefined;
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
} // namespace filament
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "webgpu/WebGPUPipelineCache.h"
|
||||
#include "webgpu/WebGPUPipelineLayoutCache.h"
|
||||
#include "webgpu/WebGPURenderPassMipmapGenerator.h"
|
||||
#include "webgpu/utils/AsyncTaskCounter.h"
|
||||
#include <backend/platforms/WebGPUPlatform.h>
|
||||
|
||||
#include "DriverBase.h"
|
||||
@@ -48,12 +47,8 @@
|
||||
namespace filament::backend {
|
||||
|
||||
class WebGPUSwapChain;
|
||||
|
||||
/**
|
||||
* Implements the private backend driver API for WebGPU specifically (that API is essentially
|
||||
* expressed in private/backend/DriverAPI.inc)
|
||||
*
|
||||
* It manages all the WebGPU resources necessary to accomplish this.
|
||||
* WebGPU backend (driver) implementation
|
||||
*/
|
||||
class WebGPUDriver final : public DriverBase {
|
||||
public:
|
||||
@@ -69,8 +64,8 @@ private:
|
||||
[[nodiscard]] wgpu::Sampler makeSampler(SamplerParams const& params);
|
||||
[[nodiscard]] static wgpu::AddressMode fWrapModeToWAddressMode(const filament::backend::SamplerWrapMode& fUsage);
|
||||
|
||||
// The platform (e.g. OS) specific aspects of the WebGPU backend are strictly only
|
||||
// handled in the WebGPUPlatform.
|
||||
// the platform (e.g. OS) specific aspects of the WebGPU backend are strictly only
|
||||
// handled in the WebGPUPlatform
|
||||
WebGPUPlatform& mPlatform;
|
||||
wgpu::Adapter mAdapter = nullptr;
|
||||
wgpu::Device mDevice = nullptr;
|
||||
@@ -91,7 +86,6 @@ private:
|
||||
spd::MipmapGenerator mSpdComputePassMipmapGenerator;
|
||||
WebGPUMsaaTextureResolver mMsaaTextureResolver{};
|
||||
WebGPUBlitter mBlitter;
|
||||
webgpuutils::AsyncTaskCounter mReadPixelMapsCounter{};
|
||||
|
||||
struct DescriptorSetBindingInfo{
|
||||
wgpu::BindGroup bindGroup;
|
||||
|
||||
@@ -28,11 +28,12 @@ namespace filament::backend {
|
||||
FenceStatus WebGPUFence::getStatus() { return mStatus.load(); }
|
||||
|
||||
void WebGPUFence::addMarkerToQueueState(wgpu::Queue const& queue) {
|
||||
// The lambda function is called when the work is done. It updates the fence status based on the
|
||||
// result of the work.
|
||||
queue.OnSubmittedWorkDone(
|
||||
wgpu::CallbackMode::AllowSpontaneous,
|
||||
[this](const wgpu::QueueWorkDoneStatus status, wgpu::StringView message) {
|
||||
// Note: The 'message' parameter is required by the function signature
|
||||
// but can be ignored if not needed.
|
||||
|
||||
switch (status) {
|
||||
case wgpu::QueueWorkDoneStatus::Success:
|
||||
mStatus.store(FenceStatus::CONDITION_SATISFIED);
|
||||
|
||||
@@ -28,11 +28,6 @@ class Queue;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A WebGPU-specific implementation of the HwFence.
|
||||
* This class is used to synchronize the CPU and GPU. It allows the CPU to know when the GPU has
|
||||
* finished a set of commands.
|
||||
*/
|
||||
class WebGPUFence final : public HwFence {
|
||||
public:
|
||||
[[nodiscard]] FenceStatus getStatus();
|
||||
@@ -40,9 +35,6 @@ public:
|
||||
void addMarkerToQueueState(wgpu::Queue const&);
|
||||
|
||||
private:
|
||||
// The current status of the fence.
|
||||
// This is atomic because it can be updated by a WebGPU callback thread and read from the main
|
||||
// thread.
|
||||
std::atomic<FenceStatus> mStatus{ FenceStatus::TIMEOUT_EXPIRED };
|
||||
};
|
||||
|
||||
|
||||
@@ -30,10 +30,6 @@ enum class IndexFormat : uint32_t;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A WebGPU implementation of the HwIndexBuffer.
|
||||
* This class represents a GPU buffer that stores indices for indexed drawing.
|
||||
*/
|
||||
class WebGPUIndexBuffer final : public HwIndexBuffer, public WebGPUBufferBase {
|
||||
public:
|
||||
WebGPUIndexBuffer(wgpu::Device const&, uint8_t elementSize, uint32_t indexCount);
|
||||
|
||||
@@ -48,8 +48,8 @@ void resolveColorTextures(wgpu::CommandEncoder const& commandEncoder,
|
||||
const wgpu::RenderPassEncoder renderPassEncoder{ commandEncoder.BeginRenderPass(
|
||||
&renderPassDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(renderPassEncoder)
|
||||
<< "Failed to create wgpu::RenderPassEncoder for MSAA resolve.";
|
||||
renderPassEncoder.End(); // The resolve is implicit in the pass.
|
||||
<< "Failed to create wgpu::RenderPassEncoder for WebGPUDriver::resolve";
|
||||
renderPassEncoder.End(); // only the implicit resolve is happening in the pass
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -59,29 +59,30 @@ void WebGPUMsaaTextureResolver::resolve(ResolveRequest const& request) {
|
||||
ResolveRequest::TextureInfo const& destination{ request.destination };
|
||||
FILAMENT_CHECK_PRECONDITION(destination.texture.GetWidth() == source.texture.GetWidth() &&
|
||||
destination.texture.GetHeight() == source.texture.GetHeight())
|
||||
<< "Invalid resolve: source and destination sizes don't match.";
|
||||
<< "invalid resolve: source and destination sizes don't match";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
source.texture.GetSampleCount() > 1 && destination.texture.GetSampleCount() == 1)
|
||||
<< "Invalid resolve: source.samples=" << source.texture.GetSampleCount()
|
||||
<< "invalid resolve: source.samples=" << source.texture.GetSampleCount()
|
||||
<< ", destination.samples=" << destination.texture.GetSampleCount();
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(source.texture.GetFormat() == destination.texture.GetFormat())
|
||||
<< "Source and destination texture format don't match.";
|
||||
<< "source and destination texture format don't match";
|
||||
const wgpu::TextureFormat format{ source.texture.GetFormat() };
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(!hasDepth(format)) << "Can't resolve depth formats.";
|
||||
FILAMENT_CHECK_PRECONDITION(!hasStencil(format)) << "Can't resolve stencil formats.";
|
||||
FILAMENT_CHECK_PRECONDITION(!hasDepth(format)) << "can't resolve depth formats";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(!hasStencil(format)) << "can't resolve stencil formats";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(source.texture.GetUsage() & wgpu::TextureUsage::RenderAttachment)
|
||||
<< "Source texture usage doesn't have wgpu::TextureUsage::RenderAttachment.";
|
||||
<< "source texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
destination.texture.GetUsage() & wgpu::TextureUsage::RenderAttachment)
|
||||
<< "Destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment.";
|
||||
<< "destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(destination.texture.GetUsage() & wgpu::TextureUsage::TextureBinding)
|
||||
<< "Destination texture usage doesn't have wgpu::TextureUsage::TextureBinding.";
|
||||
<< "destination texture usage doesn't have wgpu::TextureUsage::TextureBinding";
|
||||
|
||||
const wgpu::TextureViewDescriptor sourceTextureViewDescriptor{
|
||||
.label = "resolve_source_texture_view",
|
||||
@@ -115,7 +116,7 @@ void WebGPUMsaaTextureResolver::resolve(ResolveRequest const& request) {
|
||||
<< "Failed to create wgpu::TextureView destinationTextureView.";
|
||||
|
||||
if (hasDepth(format)) {
|
||||
PANIC_PRECONDITION("Depth resolve not yet implemented.");
|
||||
PANIC_PRECONDITION("DEPTH RESOLVE NOT IMPLEMENTED YET");
|
||||
} else {
|
||||
resolveColorTextures(request.commandEncoder, sourceTextureView, destinationTextureView);
|
||||
}
|
||||
|
||||
@@ -31,10 +31,6 @@ namespace filament::backend {
|
||||
|
||||
class WebGPUTexture;
|
||||
|
||||
/**
|
||||
* A utility class for resolving multisampled textures in WebGPU.
|
||||
* This is necessary because WebGPU does not have an implicit resolve step like other APIs.
|
||||
*/
|
||||
class WebGPUMsaaTextureResolver final {
|
||||
public:
|
||||
struct ResolveRequest final {
|
||||
@@ -51,9 +47,6 @@ public:
|
||||
TextureInfo destination;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolves a multisampled texture into a single-sampled texture.
|
||||
*/
|
||||
void resolve(ResolveRequest const&);
|
||||
};
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace {
|
||||
case CullingMode::FRONT: return wgpu::CullMode::Front;
|
||||
case CullingMode::BACK: return wgpu::CullMode::Back;
|
||||
case CullingMode::FRONT_AND_BACK:
|
||||
// WebGPU does not support culling both front and back faces simultaneously.
|
||||
// no WegGPU equivalent of front and back
|
||||
FILAMENT_CHECK_POSTCONDITION(false)
|
||||
<< "WebGPU does not support CullingMode::FRONT_AND_BACK";
|
||||
return wgpu::CullMode::Undefined;
|
||||
@@ -220,7 +220,7 @@ wgpu::RenderPipeline WebGPUPipelineCache::createRenderPipeline(
|
||||
const bool requestedDepth{ any(request.targetRenderFlags & TargetBufferFlags::DEPTH) };
|
||||
const bool requestedStencil{ any(request.targetRenderFlags & TargetBufferFlags::STENCIL) };
|
||||
const bool depthOrStencilRequested{ requestedDepth || requestedStencil };
|
||||
|
||||
// depth/stencil...
|
||||
if (depthOrStencilRequested) {
|
||||
FILAMENT_CHECK_PRECONDITION(request.depthStencilFormat != wgpu::TextureFormat::Undefined)
|
||||
<< "Depth or Stencil requested for pipeline, but depthStencilFormat is "
|
||||
@@ -312,13 +312,6 @@ 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 = {
|
||||
|
||||
@@ -34,11 +34,6 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A cache for WebGPU render pipelines.
|
||||
* This class is responsible for creating and caching wgpu::RenderPipeline objects to avoid
|
||||
* expensive pipeline creation at runtime.
|
||||
*/
|
||||
class WebGPUPipelineCache final {
|
||||
public:
|
||||
struct RenderPipelineRequest final {
|
||||
@@ -68,9 +63,6 @@ public:
|
||||
[[nodiscard]] wgpu::RenderPipeline const& getOrCreateRenderPipeline(
|
||||
RenderPipelineRequest const&);
|
||||
|
||||
/**
|
||||
* Should be called at the end of each frame to perform cache maintenance.
|
||||
*/
|
||||
void onFrameEnd();
|
||||
|
||||
private:
|
||||
|
||||
@@ -77,13 +77,12 @@ 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));
|
||||
}
|
||||
@@ -94,9 +93,10 @@ void WebGPUPipelineLayoutCache::removeExpiredPipelineLayouts() {
|
||||
PipelineLayoutCacheEntry const& entry{ iterator.value() };
|
||||
if (mFrameCount > (entry.lastUsedFrameCount +
|
||||
FILAMENT_WEBGPU_PIPELINE_LAYOUT_EXPIRATION_IN_FRAME_COUNT)) {
|
||||
// The pipeline layout has not been used recently, so we can remove it from the cache.
|
||||
// pipeline layout expired...
|
||||
iterator = mPipelineLayouts.erase(iterator);
|
||||
} else {
|
||||
// pipeline layout not yet expired...
|
||||
++iterator;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,11 +31,6 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A cache for WebGPU pipeline layouts.
|
||||
* This class is responsible for creating and caching wgpu::PipelineLayout objects to avoid
|
||||
* expensive pipeline layout creation at runtime.
|
||||
*/
|
||||
class WebGPUPipelineLayoutCache final {
|
||||
public:
|
||||
struct PipelineLayoutRequest final {
|
||||
@@ -50,16 +45,9 @@ public:
|
||||
WebGPUPipelineLayoutCache& operator=(WebGPUPipelineLayoutCache const&) = delete;
|
||||
WebGPUPipelineLayoutCache& operator=(WebGPUPipelineLayoutCache const&&) = delete;
|
||||
|
||||
/**
|
||||
* Retrieves a pipeline layout from the cache or creates a new one if it doesn't exist.
|
||||
* @return A constant reference to the cached or newly created pipeline layout.
|
||||
*/
|
||||
[[nodiscard]] wgpu::PipelineLayout const& getOrCreatePipelineLayout(
|
||||
PipelineLayoutRequest const&);
|
||||
|
||||
/**
|
||||
* Should be called at the end of each frame to perform cache maintenance.
|
||||
*/
|
||||
void onFrameEnd();
|
||||
|
||||
private:
|
||||
@@ -71,10 +59,10 @@ private:
|
||||
* instances, single bytes for booleans etc.), trivial copying and comparison (byte by byte),
|
||||
* and a word-aligned structure with a size in bytes as a multiple of 4 (for murmer hash).
|
||||
*/
|
||||
struct PipelineLayoutKey final {
|
||||
WGPUBindGroupLayout bindGroupLayoutHandles[MAX_DESCRIPTOR_SET_COUNT]{ nullptr }; // 32 :0
|
||||
uint8_t bindGroupLayoutCount{ 0 }; // 1 :32
|
||||
uint8_t padding[7]{ 0 }; // 7 :33
|
||||
struct PipelineLayoutKey final { // size : offset (need multiples of 4 bytes for hashing)
|
||||
WGPUBindGroupLayout bindGroupLayoutHandles[MAX_DESCRIPTOR_SET_COUNT]{ nullptr }; // 32 : 0
|
||||
uint8_t bindGroupLayoutCount{ 0 }; // 1 : 32
|
||||
uint8_t padding[7]{ 0 }; // 7 : 33
|
||||
};
|
||||
static_assert(sizeof(PipelineLayoutKey) == 40,
|
||||
"PipelineLayoutKey must not have implicit padding.");
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace {
|
||||
std::string_view(sourceData + posAfterId, posEndOfStatement - posAfterId);
|
||||
size_t posOfEqual = statementSegment.find('=');
|
||||
if (posOfEqual == std::string::npos) {
|
||||
// Not an assignment, so we don't need to replace it.
|
||||
// not an assignment statement, so stream to the end of the statement and continue...
|
||||
processedShaderSource << std::string_view(sourceData + pos,
|
||||
posEndOfStatement + 1 - pos);
|
||||
pos = posEndOfStatement + 1;
|
||||
@@ -122,7 +122,10 @@ namespace {
|
||||
}
|
||||
const auto newValueItr = specConstants.find(static_cast<uint32_t>(constantId));
|
||||
if (newValueItr == specConstants.end()) {
|
||||
// The constant is not being overridden, so keep the default value.
|
||||
// not going to override the constant,
|
||||
// as the specConstants parameter doesn't specify it. So, we will keep the default
|
||||
// already in the source text
|
||||
// (stream to the end of the statement)...
|
||||
processedShaderSource << std::string_view(sourceData + pos,
|
||||
posEndOfStatement + 1 - pos);
|
||||
pos = posEndOfStatement + 1;
|
||||
@@ -171,7 +174,7 @@ namespace {
|
||||
utils::FixedCapacityVector<uint8_t> const& sourceBytes =
|
||||
shaderSource[static_cast<size_t>(stage)];
|
||||
if (sourceBytes.empty()) {
|
||||
return nullptr; // No shader source to compile.
|
||||
return nullptr;// nothing to compile/create, the shader was not provided
|
||||
}
|
||||
std::stringstream labelStream;
|
||||
labelStream << programName << " " << filamentShaderStageToString(stage) << " shader";
|
||||
@@ -189,8 +192,7 @@ namespace {
|
||||
};
|
||||
const wgpu::ShaderModule shaderModule = device.CreateShaderModule(&descriptor);
|
||||
const wgpu::Instance instance = device.GetAdapter().GetInstance();
|
||||
|
||||
// Synchronously compile the shader module.
|
||||
// synchronously creates the shader module...
|
||||
const wgpu::WaitStatus waitResult = instance.WaitAny(
|
||||
shaderModule.GetCompilationInfo(wgpu::CallbackMode::WaitAnyOnly,
|
||||
[&descriptor](auto const& status,
|
||||
@@ -280,7 +282,7 @@ WebGPUProgram::WebGPUProgram(wgpu::Device const& device, Program const& program)
|
||||
: HwProgram{ program.getName() } {
|
||||
std::unordered_map<uint32_t, std::variant<int32_t, float, bool>> specConstants;
|
||||
toMap(program.getSpecializationConstants(), specConstants);
|
||||
// TODO: Consider creating/compiling these shaders in parallel.
|
||||
// TODO consider creating/compiling these shaders in parallel
|
||||
vertexShaderModule = createShaderModule(device, program, ShaderStage::VERTEX, specConstants);
|
||||
fragmentShaderModule =
|
||||
createShaderModule(device, program, ShaderStage::FRAGMENT, specConstants);
|
||||
|
||||
@@ -25,10 +25,6 @@ namespace filament::backend {
|
||||
|
||||
class Program;
|
||||
|
||||
/**
|
||||
* A WebGPU implementation of the HwProgram.
|
||||
* This class holds the compiled shader modules for a given program.
|
||||
*/
|
||||
class WebGPUProgram final : public HwProgram {
|
||||
public:
|
||||
WebGPUProgram(wgpu::Device const&, Program const&);
|
||||
|
||||
@@ -27,8 +27,9 @@ namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
// The shaders expect a single-sampled 2D texture with a format compatible with a scalar sample format of f32.
|
||||
// This is checked with the getCompatibilityFor function.
|
||||
// The shaders expect a single-sampled 2D texture
|
||||
// with a format compatible with scalar sample format f32
|
||||
// (this is checked with the getCompatibilityFor function)
|
||||
constexpr uint32_t TEXTURE_BIND_GROUP_INDEX{ 0 };
|
||||
constexpr size_t TEXTURE_BIND_GROUP_ENTRY_SIZE{ 2 }; // sampler and texture
|
||||
constexpr uint32_t SAMPLER_BINDING_INDEX{ 0 };
|
||||
@@ -84,7 +85,7 @@ constexpr std::string_view SHADER_SOURCE{ R"(
|
||||
.maxAnisotropy = 1, // should not matter, just being consistently defined
|
||||
};
|
||||
const wgpu::Sampler sampler{ device.CreateSampler(&descriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(sampler) << "Failed to create previous mip level sampler.";
|
||||
FILAMENT_CHECK_POSTCONDITION(sampler) << "Failed to create previous mip level sampler?";
|
||||
return sampler;
|
||||
}
|
||||
|
||||
@@ -97,7 +98,7 @@ constexpr std::string_view SHADER_SOURCE{ R"(
|
||||
};
|
||||
const wgpu::ShaderModule shaderModule{ device.CreateShaderModule(&shaderModuleDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(shaderModule)
|
||||
<< "Failed to create shader module for render pass mipmap generation.";
|
||||
<< "Failed to create shader module for render pass mipmap generation?";
|
||||
return shaderModule;
|
||||
}
|
||||
|
||||
@@ -130,7 +131,7 @@ constexpr std::string_view SHADER_SOURCE{ R"(
|
||||
const wgpu::BindGroupLayout textureBindGroupLayout{ device.CreateBindGroupLayout(
|
||||
&textureBindGroupLayoutDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(textureBindGroupLayout)
|
||||
<< "Failed to create texture bind group layout for render pass mipmap generation.";
|
||||
<< "Failed to create texture bind group layout for render pass mipmap generation?";
|
||||
return textureBindGroupLayout;
|
||||
}
|
||||
|
||||
@@ -144,7 +145,7 @@ constexpr std::string_view SHADER_SOURCE{ R"(
|
||||
const wgpu::PipelineLayout pipelineLayout{ device.CreatePipelineLayout(
|
||||
&pipelineLayoutDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipelineLayout)
|
||||
<< "Failed to create pipeline layout for render pass mipmap generation.";
|
||||
<< "Failed to create pipeline layout for render pass mipmap generation?";
|
||||
return pipelineLayout;
|
||||
}
|
||||
|
||||
@@ -193,7 +194,7 @@ constexpr std::string_view SHADER_SOURCE{ R"(
|
||||
};
|
||||
const wgpu::RenderPipeline pipeline{ device.CreateRenderPipeline(&pipelineDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline)
|
||||
<< "Failed to create pipeline for render pass mipmap generation.";
|
||||
<< "Failed to create pipeline for render pass mipmap generation?";
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
@@ -209,7 +210,8 @@ WebGPURenderPassMipmapGenerator::WebGPURenderPassMipmapGenerator(wgpu::Device co
|
||||
WebGPURenderPassMipmapGenerator::FormatCompatibility
|
||||
WebGPURenderPassMipmapGenerator::getCompatibilityFor(const wgpu::TextureFormat format,
|
||||
const wgpu::TextureDimension dimension, const uint32_t sampleCount) {
|
||||
// Check that the format is compatible (currently expects that the scalar sample type is f32).
|
||||
// check that the format is compatible (currently expects that the scalar sample type is
|
||||
// f32)...
|
||||
switch (format) {
|
||||
case wgpu::TextureFormat::Depth16Unorm:
|
||||
case wgpu::TextureFormat::Depth24Plus:
|
||||
@@ -218,16 +220,20 @@ WebGPURenderPassMipmapGenerator::getCompatibilityFor(const wgpu::TextureFormat f
|
||||
case wgpu::TextureFormat::Depth32FloatStencil8:
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "A depth texture format requires special sampler treatment and does not "
|
||||
"generally support linear filtering needed for render pass based mipmap "
|
||||
"generation.",
|
||||
.reason = "A depth texture format requires special sampler treatment and does "
|
||||
"not "
|
||||
"generally support linear filtering needed for render pass based "
|
||||
"mipmap "
|
||||
"generation, thus this texture is not supported, as it is a kind of "
|
||||
"depth texture.",
|
||||
};
|
||||
case wgpu::TextureFormat::Undefined:
|
||||
case wgpu::TextureFormat::External:
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "Undefined or External textures are not supported for render pass based "
|
||||
"mipmap generation.",
|
||||
.reason = "Undefined or External textures are not supported for render pass "
|
||||
"based "
|
||||
"mipmap generation",
|
||||
};
|
||||
default:
|
||||
const ScalarSampleType scalarSampleType{ getScalarSampleTypeFrom(format) };
|
||||
@@ -236,19 +242,21 @@ WebGPURenderPassMipmapGenerator::getCompatibilityFor(const wgpu::TextureFormat f
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "The provided texture format requires an unsigned integer "
|
||||
"sampler, which does not natively support linear filtering.",
|
||||
"sampler, which does not natively support linear filtering, "
|
||||
"which is needed for render pass based mipmap generation.",
|
||||
};
|
||||
case ScalarSampleType::I32:
|
||||
return {
|
||||
.compatible = false,
|
||||
.reason = "The provided texture format requires a signed integer "
|
||||
"sampler, which does not natively support linear filtering.",
|
||||
"sampler, which does not natively support linear filtering, "
|
||||
"which is needed for render pass based mipmap generation.",
|
||||
};
|
||||
case ScalarSampleType::F32:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Check that the dimensionality is compatible (currently expects 2D textures).
|
||||
// check that the dimensionality is compatible (currently expects 2D textures)...
|
||||
switch (dimension) {
|
||||
case wgpu::TextureDimension::Undefined:
|
||||
return {
|
||||
@@ -274,7 +282,7 @@ WebGPURenderPassMipmapGenerator::getCompatibilityFor(const wgpu::TextureFormat f
|
||||
case wgpu::TextureDimension::e2D:
|
||||
break;
|
||||
}
|
||||
// Check that the texture is single-sampled.
|
||||
// check that the texture is single-sampled (for now)...
|
||||
if (sampleCount > 1) {
|
||||
return {
|
||||
.compatible = false,
|
||||
@@ -419,7 +427,7 @@ void WebGPURenderPassMipmapGenerator::generateMipmaps(wgpu::Queue const& queue,
|
||||
wgpu::Texture const& texture) {
|
||||
const uint32_t mipLevelCount{ texture.GetMipLevelCount() };
|
||||
if (mipLevelCount < 2) {
|
||||
return; // Nothing to do.
|
||||
return; // nothing to do
|
||||
}
|
||||
wgpu::RenderPipeline const& pipeline{ getOrCreatePipelineFor(texture.GetFormat()) };
|
||||
const wgpu::CommandEncoderDescriptor commandEncoderDescriptor{
|
||||
@@ -428,19 +436,20 @@ void WebGPURenderPassMipmapGenerator::generateMipmaps(wgpu::Queue const& queue,
|
||||
const wgpu::CommandEncoder commandEncoder{ mDevice.CreateCommandEncoder(
|
||||
&commandEncoderDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(commandEncoder)
|
||||
<< "Failed to create command encoder for render pass mipmap generation.";
|
||||
<< "Failed to create command encoder for layer for render pass mipmap generation?";
|
||||
const uint32_t layerCount{ texture.GetDepthOrArrayLayers() };
|
||||
for (uint32_t layer = 0; layer < layerCount; layer++) {
|
||||
for (uint32_t mipLevel = 1; mipLevel < mipLevelCount; mipLevel++) {
|
||||
generateMipmap(commandEncoder, texture, pipeline, layer, mipLevel);
|
||||
}
|
||||
}
|
||||
// submit the command buffer...
|
||||
const wgpu::CommandBufferDescriptor commandBufferDescriptor{
|
||||
.label = "mipmap_generation_render_pass_cmd_buffer",
|
||||
};
|
||||
const wgpu::CommandBuffer commandBuffer{ commandEncoder.Finish(&commandBufferDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(commandBuffer)
|
||||
<< "Failed to create command buffer for render pass mipmap generation.";
|
||||
<< "Failed to create command buffer for layer for render pass mipmap generation?";
|
||||
queue.Submit(1, &commandBuffer);
|
||||
}
|
||||
|
||||
@@ -463,7 +472,7 @@ void WebGPURenderPassMipmapGenerator::generateMipmap(wgpu::CommandEncoder const&
|
||||
.usage = wgpu::TextureUsage::TextureBinding,
|
||||
};
|
||||
const wgpu::TextureView sourceView{ texture.CreateView(&sourceViewDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(sourceView)
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline)
|
||||
<< "Failed to create source texture view for layer " << layer << " and mip level "
|
||||
<< mipLevel << " for render pass mipmap generation?";
|
||||
const wgpu::TextureViewDescriptor destinationViewDescriptor{
|
||||
@@ -478,7 +487,7 @@ void WebGPURenderPassMipmapGenerator::generateMipmap(wgpu::CommandEncoder const&
|
||||
.usage = wgpu::TextureUsage::RenderAttachment,
|
||||
};
|
||||
const wgpu::TextureView destinationView{ texture.CreateView(&destinationViewDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(destinationView)
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline)
|
||||
<< "Failed to create destination texture view for layer " << layer << " and mip level "
|
||||
<< mipLevel << " for render pass mipmap generation?";
|
||||
// create the render pass...
|
||||
@@ -499,7 +508,7 @@ void WebGPURenderPassMipmapGenerator::generateMipmap(wgpu::CommandEncoder const&
|
||||
.entries = textureBindGroupEntries,
|
||||
};
|
||||
const wgpu::BindGroup textureBindGroup{ mDevice.CreateBindGroup(&textureBindGroupDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(textureBindGroup)
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline)
|
||||
<< "Failed to create texture bind group for layer " << layer << " and mip level "
|
||||
<< mipLevel << " for render pass mipmap generation?";
|
||||
const wgpu::RenderPassColorAttachment colorAttachment{
|
||||
@@ -525,7 +534,8 @@ void WebGPURenderPassMipmapGenerator::generateMipmap(wgpu::CommandEncoder const&
|
||||
<< mipLevel << " for render pass mipmap generation?";
|
||||
renderPassEncoder.SetPipeline(pipeline);
|
||||
renderPassEncoder.SetBindGroup(TEXTURE_BIND_GROUP_INDEX, textureBindGroup);
|
||||
renderPassEncoder.Draw(3); // Draw a full-screen triangle.
|
||||
renderPassEncoder.Draw(3); // draw the full-screen triangle
|
||||
// with hard-coded vertices in the shader
|
||||
renderPassEncoder.End();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,9 +27,6 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A utility class for generating mipmaps for a texture using a series of render passes.
|
||||
*/
|
||||
class WebGPURenderPassMipmapGenerator final {
|
||||
public:
|
||||
struct FormatCompatibility final {
|
||||
@@ -37,11 +34,6 @@ public:
|
||||
std::string_view reason;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a given texture format is compatible with render pass-based mipmap generation.
|
||||
* @return A FormatCompatibility struct indicating whether the format is compatible
|
||||
* and a reason if not.
|
||||
*/
|
||||
[[nodiscard]] static FormatCompatibility getCompatibilityFor(wgpu::TextureFormat,
|
||||
wgpu::TextureDimension, uint32_t sampleCount);
|
||||
|
||||
|
||||
@@ -24,10 +24,6 @@ namespace filament::backend {
|
||||
class WebGPUVertexBuffer;
|
||||
class WebGPUIndexBuffer;
|
||||
|
||||
/**
|
||||
* A WebGPU implementation of the HwRenderPrimitive.
|
||||
* This struct holds the vertex and index buffers that define a renderable primitive.
|
||||
*/
|
||||
struct WebGPURenderPrimitive : public HwRenderPrimitive {
|
||||
WebGPUVertexBuffer* vertexBuffer = nullptr;
|
||||
WebGPUIndexBuffer* indexBuffer = nullptr;
|
||||
|
||||
@@ -41,9 +41,9 @@ namespace filament::backend {
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Creates MSAA sidecar textures for attachments if necessary.
|
||||
* This function also verifies that all attachments have the same sample count.
|
||||
* @return The number of samples per attachment.
|
||||
* Panics if the number of samples in the original attachment textures do not match.
|
||||
* @return The number of samples in the original attachment textures (the number/count in each one).
|
||||
* Returns 0 if there are no attachments.
|
||||
*/
|
||||
[[nodiscard]] uint8_t createMsaaSidecarTextures(const uint8_t renderTargetSampleCount,
|
||||
const TargetBufferFlags targetFlags, MRT const& colorAttachments,
|
||||
@@ -100,6 +100,9 @@ namespace {
|
||||
sampleCountPerAttachment = texture->samples;
|
||||
firstAttachment = false;
|
||||
}
|
||||
FILAMENT_CHECK_PRECONDITION(texture->samples == sampleCountPerAttachment)
|
||||
<< target.name << " attachment texture has " << +texture->samples
|
||||
<< " but the other attachment(s) have " << +sampleCountPerAttachment;
|
||||
if (renderTargetSampleCount > 1 && sampleCountPerAttachment == 1) {
|
||||
texture->createMsaaSidecarTextureIfNotAlreadyCreated(renderTargetSampleCount,
|
||||
device);
|
||||
@@ -133,7 +136,7 @@ WebGPURenderTarget::WebGPURenderTarget(const uint32_t width, const uint32_t heig
|
||||
mColorAttachmentDesc.reserve(MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
|
||||
}
|
||||
|
||||
// Constructor for the default render target
|
||||
// Default constructor for the default render target
|
||||
WebGPURenderTarget::WebGPURenderTarget()
|
||||
: HwRenderTarget{ 0, 0 },
|
||||
mDefaultRenderTarget{ true },
|
||||
@@ -179,19 +182,19 @@ void WebGPURenderTarget::setUpRenderPassAttachments(wgpu::RenderPassDescriptor&
|
||||
FILAMENT_CHECK_PRECONDITION(std::all_of(customColorMsaaSidecarTextureViews,
|
||||
customColorMsaaSidecarTextureViews + customColorTextureViewCount,
|
||||
[](wgpu::TextureView const& msaaView) { return msaaView != nullptr; }))
|
||||
<< "A color or depth/stencil attachment texture has an MSAA sidecar but at least "
|
||||
<< "A color or depth/stencil attachment texture has a MSAA sidecar but at least "
|
||||
"one other color attachment texture does not.";
|
||||
FILAMENT_CHECK_PRECONDITION(customDepthStencilMsaaSidecarTextureView != nullptr)
|
||||
<< "The color attachment texture(s) have MSAA sidecars but the depth/stencil "
|
||||
<< "The color attachment texture(s) have MSAA sidecar(s) but the depth/stencil "
|
||||
"texture does not.";
|
||||
} else {
|
||||
FILAMENT_CHECK_PRECONDITION(std::all_of(customColorMsaaSidecarTextureViews,
|
||||
customColorMsaaSidecarTextureViews + customColorTextureViewCount,
|
||||
[](wgpu::TextureView const& msaaView) { return msaaView == nullptr; }))
|
||||
<< "A color or depth/stencil attachment texture does not have an MSAA sidecar but "
|
||||
<< "A color or depth/stencil attachment texture does not have a MSAA sidecar but "
|
||||
"at least one color attachment texture does.";
|
||||
FILAMENT_CHECK_PRECONDITION(customDepthStencilMsaaSidecarTextureView == nullptr)
|
||||
<< "Custom color textures for the render target do not have MSAA sidecars but "
|
||||
<< "Custom color textures for the render target do not have MSAA sidecar(s) but "
|
||||
"the depth/stencil texture does.";
|
||||
}
|
||||
|
||||
|
||||
@@ -31,10 +31,6 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
* A WebGPU implementation of the HwRenderTarget.
|
||||
* This class represents a collection of attachments (textures) that can be rendered to.
|
||||
*/
|
||||
class WebGPURenderTarget : public HwRenderTarget {
|
||||
public:
|
||||
using Attachment = TargetBufferInfo; // Using TargetBufferInfo directly for attachments
|
||||
@@ -42,8 +38,7 @@ public:
|
||||
WebGPURenderTarget(uint32_t width, uint32_t height, uint8_t samples, uint8_t layerCount,
|
||||
MRT const& colorAttachments, Attachment const& depthAttachment,
|
||||
Attachment const& stencilAttachment, TargetBufferFlags const& targetFlags,
|
||||
std::function<WebGPUTexture*(const Handle<HwTexture>)> const&,
|
||||
wgpu::Device const&);
|
||||
std::function<WebGPUTexture*(const Handle<HwTexture>)> const&, wgpu::Device const&);
|
||||
|
||||
// Default constructor for the default render target
|
||||
WebGPURenderTarget();
|
||||
@@ -85,7 +80,7 @@ private:
|
||||
|
||||
MRT mColorAttachments{};
|
||||
// TODO WebGPU only supports a DepthStencil attachment, should this be just
|
||||
// mDepthStencilAttachment?
|
||||
// mDepthStencilAttachment?
|
||||
Attachment mDepthAttachment{};
|
||||
Attachment mStencilAttachment{};
|
||||
|
||||
|
||||
@@ -187,8 +187,7 @@ void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device const& device,
|
||||
wgpu::SurfaceCapabilities const& capabilities, wgpu::Extent2D const& extent,
|
||||
bool useSRGBColorSpace) {
|
||||
config.device = device;
|
||||
config.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc |
|
||||
wgpu::TextureUsage::TextureBinding;
|
||||
config.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
|
||||
config.width = extent.width;
|
||||
config.height = extent.height;
|
||||
config.format = selectColorFormat(capabilities.formatCount, capabilities.formats, useSRGBColorSpace);
|
||||
@@ -270,6 +269,8 @@ WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const&
|
||||
const bool useSRGBColorSpace = (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0;
|
||||
|
||||
initConfig(mConfig, device, capabilities, extent, useSRGBColorSpace);
|
||||
mDepthFormat = selectDepthFormat(device.HasFeature(wgpu::FeatureName::Depth32FloatStencil8),
|
||||
mNeedStencil);
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
printSurfaceConfiguration(mConfig, mDepthFormat);
|
||||
#endif
|
||||
@@ -296,6 +297,8 @@ WebGPUSwapChain::WebGPUSwapChain(wgpu::Extent2D const& extent,
|
||||
mConfig.presentMode = wgpu::PresentMode::Fifo;
|
||||
mConfig.alphaMode = wgpu::CompositeAlphaMode::Auto;
|
||||
|
||||
mDepthFormat = selectDepthFormat(device.HasFeature(wgpu::FeatureName::Depth32FloatStencil8),
|
||||
mNeedStencil);
|
||||
const wgpu::TextureDescriptor textureDescriptor = {
|
||||
.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc,
|
||||
.dimension = wgpu::TextureDimension::e2D,
|
||||
|
||||
@@ -107,6 +107,84 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fUsage Filament's requested texture usage
|
||||
* @param samples How many samples to use for MSAA
|
||||
* @param needsComputeStorageSupport if we need to use this texture as storage binding in something
|
||||
* like a compute shader
|
||||
* @param needsRenderAttachmentSupport if we need to use this texture as a render pass attachment
|
||||
* in something like a render pass blit (e.g. mipmap generation)
|
||||
* @return The appropriate texture usage flags for the underlying texture
|
||||
*/
|
||||
[[nodiscard]] wgpu::TextureUsage fToWGPUTextureUsage(TextureUsage const& fUsage,
|
||||
const uint8_t samples, const bool needsComputeStorageSupport,
|
||||
const bool needsRenderAttachmentSupport) {
|
||||
wgpu::TextureUsage retUsage = wgpu::TextureUsage::None;
|
||||
|
||||
// if needsComputeStorageSupport we need to read and write to the texture in a shader and thus
|
||||
// require CopySrc, CopyDst, TextureBinding, & StorageBinding
|
||||
|
||||
// Basing this mapping off of VulkanTexture.cpp's getUsage func and suggestions from Gemini
|
||||
// TODO Validate assumptions, revisit if issues.
|
||||
if (needsComputeStorageSupport || any(TextureUsage::BLIT_SRC & fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::CopySrc;
|
||||
}
|
||||
if (needsComputeStorageSupport ||
|
||||
any((TextureUsage::BLIT_DST | TextureUsage::UPLOADABLE) & fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::CopyDst;
|
||||
}
|
||||
if (needsComputeStorageSupport || any(TextureUsage::SAMPLEABLE & fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::TextureBinding;
|
||||
}
|
||||
if (needsComputeStorageSupport) {
|
||||
retUsage |= wgpu::TextureUsage::StorageBinding;
|
||||
}
|
||||
if (needsRenderAttachmentSupport) {
|
||||
retUsage |= wgpu::TextureUsage::RenderAttachment;
|
||||
}
|
||||
if (any(TextureUsage::BLIT_SRC & fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::TextureBinding;
|
||||
}
|
||||
if (any(TextureUsage::BLIT_DST & fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::RenderAttachment;
|
||||
}
|
||||
if (any(TextureUsage::GEN_MIPMAPPABLE & fUsage)) {
|
||||
retUsage |= (wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding);
|
||||
}
|
||||
// WGPU Render attachment covers either color or stencil situation dependant
|
||||
// NOTE: Depth attachment isn't used this way in Vulkan but logically maps to WGPU docs. If
|
||||
// issues, investigate here.
|
||||
if (any((TextureUsage::COLOR_ATTACHMENT | TextureUsage::STENCIL_ATTACHMENT |
|
||||
TextureUsage::DEPTH_ATTACHMENT) &
|
||||
fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::RenderAttachment;
|
||||
}
|
||||
|
||||
// This is from Vulkan logic- if there are any issues try disabling this first, allows perf
|
||||
// benefit though
|
||||
const bool useTransientAttachment =
|
||||
// Usage consists of attachment flags only.
|
||||
none(fUsage & ~TextureUsage::ALL_ATTACHMENTS) &&
|
||||
// Usage contains at least one attachment flag.
|
||||
any(fUsage & TextureUsage::ALL_ATTACHMENTS) &&
|
||||
// Depth resolve cannot use transient attachment because it uses a custom shader.
|
||||
// TODO: see VulkanDriver::isDepthStencilResolveSupported() to know when to remove this
|
||||
// restriction.
|
||||
// Note that the custom shader does not resolve stencil. We do need to move to vk 1.2
|
||||
// and above to be able to support stencil resolve (along with depth).
|
||||
!(any(fUsage & TextureUsage::DEPTH_ATTACHMENT) && samples > 1);
|
||||
if (useTransientAttachment) {
|
||||
retUsage |= wgpu::TextureUsage::TransientAttachment;
|
||||
}
|
||||
// NOTE: Unused wgpu flags:
|
||||
// StorageAttachment
|
||||
|
||||
// NOTE: Unused Filament flags:
|
||||
// SUBPASS_INPUT VK goes to input attachment which we don't support right now
|
||||
// PROTECTED
|
||||
return retUsage;
|
||||
}
|
||||
|
||||
[[nodiscard]] wgpu::TextureAspect fToWGPUTextureViewAspect(TextureUsage const& fUsage,
|
||||
TextureFormat const& fFormat) {
|
||||
|
||||
@@ -208,11 +286,9 @@ WebGPUTexture::WebGPUTexture(const SamplerType samplerType, const uint8_t levels
|
||||
mAspect{ fToWGPUTextureViewAspect(usage, format) },
|
||||
mWebGPUUsage{ fToWGPUTextureUsage(usage, samples,
|
||||
mMipmapGenerationStrategy == MipmapGenerationStrategy::SPD_COMPUTE_PASS,
|
||||
mMipmapGenerationStrategy == MipmapGenerationStrategy::RENDER_PASS,
|
||||
device.HasFeature(wgpu::FeatureName::TransientAttachments)) },
|
||||
mViewUsage{ fToWGPUTextureUsage(usage, samples, false, false,
|
||||
device.HasFeature(wgpu::FeatureName::TransientAttachments)) },
|
||||
mDimension{ toWebGPUTextureViewDimension(samplerType) },
|
||||
mMipmapGenerationStrategy == MipmapGenerationStrategy::RENDER_PASS) },
|
||||
mViewUsage{ fToWGPUTextureUsage(usage, samples, false, false) },
|
||||
mDimension{toWebGPUTextureViewDimension(samplerType)},
|
||||
mBlockWidth{ filament::backend::getBlockWidth(format) },
|
||||
mBlockHeight{ filament::backend::getBlockHeight(format) },
|
||||
mDefaultMipLevel{ 0 },
|
||||
|
||||
@@ -78,9 +78,9 @@ public:
|
||||
|
||||
/**
|
||||
* @param samples The number of samples the underlying texture supports
|
||||
* @param mipLevel The mip level into the underlying texture for which this view will reference
|
||||
* @param mipLevel The mip level into the underyling texture for which this view will reference
|
||||
* (this view will only have one mip level)
|
||||
* @param arrayLayer The layer into the underlying texture for which this view will reference
|
||||
* @param arrayLayer The layer into the underyling texture for which this view will reference
|
||||
* (this view will only have one layer)
|
||||
* @return A texture view for the MSAA sidecar texture
|
||||
*/
|
||||
|
||||
@@ -198,21 +198,81 @@ namespace filament::backend {
|
||||
case TextureFormat::RGB_BPTC_SIGNED_FLOAT: return wgpu::TextureFormat::BC6HRGBFloat;
|
||||
case TextureFormat::RGBA_BPTC_UNORM: return wgpu::TextureFormat::BC7RGBAUnorm;
|
||||
case TextureFormat::SRGB_ALPHA_BPTC_UNORM: return wgpu::TextureFormat::BC7RGBAUnormSrgb;
|
||||
case TextureFormat::RGB9_E5: return wgpu::TextureFormat::RGB9E5Ufloat;
|
||||
case TextureFormat::RGB8: return wgpu::TextureFormat::RGBA8Unorm;
|
||||
case TextureFormat::SRGB8: return wgpu::TextureFormat::RGBA8UnormSrgb;
|
||||
case TextureFormat::RGB8_SNORM: return wgpu::TextureFormat::RGBA8Snorm;
|
||||
case TextureFormat::RGB8UI: return wgpu::TextureFormat::RGBA8Uint;
|
||||
case TextureFormat::RGB8I: return wgpu::TextureFormat::RGBA8Sint;
|
||||
case TextureFormat::RGB565:
|
||||
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm
|
||||
// and discard the alpha and lower precision.
|
||||
FWGPU_LOGW << "Requested Filament texture format RGB565 but getting "
|
||||
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
|
||||
return wgpu::TextureFormat::Undefined;
|
||||
case TextureFormat::RGB9_E5: return wgpu::TextureFormat::RGB9E5Ufloat;
|
||||
case TextureFormat::RGB5_A1:
|
||||
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm
|
||||
// and handle the packing/unpacking in shaders.
|
||||
FWGPU_LOGW << "Requested Filament texture format RGB5_A1 but getting "
|
||||
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
|
||||
return wgpu::TextureFormat::Undefined;
|
||||
case TextureFormat::RGBA4:
|
||||
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm
|
||||
// and handle the packing/unpacking in shaders.
|
||||
FWGPU_LOGW << "Requested Filament texture format RGBA4 but getting "
|
||||
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
|
||||
return wgpu::TextureFormat::Undefined;
|
||||
case TextureFormat::RGB8:
|
||||
FWGPU_LOGW << "Requested Filament texture format RGB8 but getting "
|
||||
"wgpu::TextureFormat::RGBA8Unorm (no direct sRGB equivalent in wgpu "
|
||||
"without alpha)";
|
||||
return wgpu::TextureFormat::RGBA8Unorm;
|
||||
case TextureFormat::SRGB8:
|
||||
FWGPU_LOGW << "Requested Filament texture format SRGB8 but getting "
|
||||
"wgpu::TextureFormat::RGBA8UnormSrgb (no direct sRGB equivalent in wgpu "
|
||||
"without alpha)";
|
||||
return wgpu::TextureFormat::RGBA8UnormSrgb;
|
||||
case TextureFormat::RGB8_SNORM:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB8_SNORM but getting "
|
||||
"wgpu::TextureFormat::RGBA8Snorm (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA8Snorm;
|
||||
case TextureFormat::RGB8UI:
|
||||
FWGPU_LOGW << "Requested Filament texture format RGB8UI but getting "
|
||||
"wgpu::TextureFormat::RGBA8Uint (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA8Uint;
|
||||
case TextureFormat::RGB8I:
|
||||
FWGPU_LOGW << "Requested Filament texture format RGB8I but getting "
|
||||
"wgpu::TextureFormat::RGBA8Sint (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA8Sint;
|
||||
case TextureFormat::R11F_G11F_B10F: return wgpu::TextureFormat::RG11B10Ufloat;
|
||||
case TextureFormat::UNUSED: return wgpu::TextureFormat::Undefined;
|
||||
case TextureFormat::RGB10_A2: return wgpu::TextureFormat::RGB10A2Unorm;
|
||||
case TextureFormat::RGB16F: return wgpu::TextureFormat::RGBA16Float;
|
||||
case TextureFormat::RGB16UI: return wgpu::TextureFormat::RGBA16Uint;
|
||||
case TextureFormat::RGB16I: return wgpu::TextureFormat::RGBA16Sint;
|
||||
case TextureFormat::RGB32F: return wgpu::TextureFormat::RGBA32Float;
|
||||
case TextureFormat::RGB32UI: return wgpu::TextureFormat::RGBA32Uint;
|
||||
case TextureFormat::RGB32I: return wgpu::TextureFormat::RGBA32Sint;
|
||||
case TextureFormat::RGB16F:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB16F but getting "
|
||||
"wgpu::TextureFormat::RGBA16Float (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA16Float;
|
||||
case TextureFormat::RGB16UI:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB16UI but getting "
|
||||
"wgpu::TextureFormat::RGBA16Uint (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA16Uint;
|
||||
case TextureFormat::RGB16I:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB16I but getting "
|
||||
"wgpu::TextureFormat::RGBA16Sint (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA16Sint;
|
||||
case TextureFormat::RGB32F:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB32F but getting "
|
||||
"wgpu::TextureFormat::RGBA32Float (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA32Float;
|
||||
case TextureFormat::RGB32UI:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB32UI but getting "
|
||||
"wgpu::TextureFormat::RGBA32Uint (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA32Uint;
|
||||
case TextureFormat::RGB32I:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB32I but getting "
|
||||
"wgpu::TextureFormat::RGBA32Sint (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA32Sint;
|
||||
case TextureFormat::DXT1_RGB: return wgpu::TextureFormat::BC1RGBAUnorm;
|
||||
case TextureFormat::DXT1_RGBA: return wgpu::TextureFormat::BC1RGBAUnorm;
|
||||
case TextureFormat::DXT3_RGBA: return wgpu::TextureFormat::BC2RGBAUnorm;
|
||||
@@ -221,20 +281,6 @@ namespace filament::backend {
|
||||
case TextureFormat::DXT1_SRGBA: return wgpu::TextureFormat::BC1RGBAUnormSrgb;
|
||||
case TextureFormat::DXT3_SRGBA: return wgpu::TextureFormat::BC2RGBAUnormSrgb;
|
||||
case TextureFormat::DXT5_SRGBA: return wgpu::TextureFormat::BC3RGBAUnormSrgb;
|
||||
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm and handle the
|
||||
// packing/unpacking in shaders.
|
||||
case TextureFormat::RGB565:
|
||||
FWGPU_LOGW << "Requested Filament texture format RGB565 but getting "
|
||||
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
|
||||
return wgpu::TextureFormat::Undefined;
|
||||
case TextureFormat::RGB5_A1:
|
||||
FWGPU_LOGW << "Requested Filament texture format RGB5_A1 but getting "
|
||||
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
|
||||
return wgpu::TextureFormat::Undefined;
|
||||
case TextureFormat::RGBA4:
|
||||
FWGPU_LOGW << "Requested Filament texture format RGBA4 but getting "
|
||||
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
|
||||
return wgpu::TextureFormat::Undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,109 +534,6 @@ namespace filament::backend {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Todo: should this take into account sRGB/linear when determining if conversion is necessary?
|
||||
* For instance, if the output format is the same as the input, except the output is sRGB
|
||||
* do we really need to do a conversion? If the answer is no, we should do that check here
|
||||
* and return conversionNecessary false in that case.
|
||||
* However, doing a straight-forward comparison is the safest most conservative thing to do
|
||||
* for functional correctness and NOT doing a conversion in such cases could be considered
|
||||
* an optimization. Thus, consider the optimization when we have better test coverage to
|
||||
* experiment with such a refactor.
|
||||
* @return True if theres a format mismatch
|
||||
*/
|
||||
[[nodiscard]] constexpr bool conversionNecessary(const wgpu::TextureFormat source,
|
||||
const wgpu::TextureFormat destination, const PixelDataType pixelDataType) {
|
||||
return source != destination && pixelDataType != PixelDataType::COMPRESSED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fUsage Filament's requested texture usage
|
||||
* @param samples How many samples to use for MSAA
|
||||
* @param needsComputeStorageSupport if we need to use this texture as storage binding in something
|
||||
* like a compute shader
|
||||
* @param needsRenderAttachmentSupport if we need to use this texture as a render pass attachment
|
||||
* in something like a render pass blit (e.g. mipmap generation)
|
||||
* @param deviceSupportsTransientAttachments if the device itself supports Render Attachments
|
||||
* @return The appropriate texture usage flags for the underlying texture
|
||||
*/
|
||||
[[nodiscard]] constexpr wgpu::TextureUsage fToWGPUTextureUsage(TextureUsage const& fUsage,
|
||||
const uint8_t samples, const bool needsComputeStorageSupport,
|
||||
const bool needsRenderAttachmentSupport, const bool deviceSupportsTransientAttachments) {
|
||||
wgpu::TextureUsage retUsage{ wgpu::TextureUsage::None };
|
||||
|
||||
if (any(TextureUsage::BLIT_SRC & fUsage)) {
|
||||
retUsage |= (wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding);
|
||||
}
|
||||
if (any(TextureUsage::BLIT_DST & fUsage)) {
|
||||
retUsage |= (wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment);
|
||||
}
|
||||
if (any(TextureUsage::UPLOADABLE & fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::CopyDst;
|
||||
}
|
||||
if (any(TextureUsage::GEN_MIPMAPPABLE & fUsage)) {
|
||||
retUsage |= (wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding);
|
||||
}
|
||||
if (any(TextureUsage::SAMPLEABLE & fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::TextureBinding;
|
||||
}
|
||||
// if needsComputeStorageSupport we need to read and write to the texture in a shader and thus
|
||||
// require CopySrc, CopyDst, TextureBinding, & StorageBinding
|
||||
if (needsComputeStorageSupport) {
|
||||
retUsage |= (wgpu::TextureUsage::StorageBinding |
|
||||
wgpu::TextureUsage::CopySrc |
|
||||
wgpu::TextureUsage::CopyDst |
|
||||
wgpu::TextureUsage::TextureBinding);
|
||||
}
|
||||
|
||||
wgpu::TextureUsage transientAttachmentNeeded{ wgpu::TextureUsage::None };
|
||||
const bool useTransientAttachment {
|
||||
deviceSupportsTransientAttachments &&
|
||||
// Usage consists of attachment flags only.
|
||||
none(fUsage & ~TextureUsage::ALL_ATTACHMENTS) &&
|
||||
// Usage contains at least one attachment flag.
|
||||
any(fUsage & TextureUsage::ALL_ATTACHMENTS) &&
|
||||
// Depth resolve cannot use transient attachment because it uses a custom shader.
|
||||
// TODO: see VulkanDriver::isDepthStencilResolveSupported() to know when to remove this
|
||||
// restriction.
|
||||
// Note that the custom shader does not resolve stencil. We do need to move to vk 1.2
|
||||
// and above to be able to support stencil resolve (along with depth).
|
||||
!(any(fUsage & TextureUsage::DEPTH_ATTACHMENT) && samples > 1)};
|
||||
if (useTransientAttachment) {
|
||||
transientAttachmentNeeded |= wgpu::TextureUsage::TransientAttachment;
|
||||
}
|
||||
|
||||
// A texture that is a blit destination or render attachment will often need to be
|
||||
// a copy source for subsequent operations (e.g., mipmap generation, readbacks).
|
||||
// However, we dont need to add the CopySrc IF its a transientAttachment
|
||||
if (any((TextureUsage::BLIT_DST | TextureUsage::COLOR_ATTACHMENT |
|
||||
TextureUsage::DEPTH_ATTACHMENT) &
|
||||
fUsage)) {
|
||||
if (!useTransientAttachment) {
|
||||
retUsage |= wgpu::TextureUsage::CopySrc;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsRenderAttachmentSupport) {
|
||||
retUsage |= wgpu::TextureUsage::RenderAttachment;
|
||||
}
|
||||
// WGPU Render attachment covers either color or stencil situation dependant
|
||||
if (any((TextureUsage::COLOR_ATTACHMENT | TextureUsage::STENCIL_ATTACHMENT |
|
||||
TextureUsage::DEPTH_ATTACHMENT) &
|
||||
fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::RenderAttachment;
|
||||
retUsage |= transientAttachmentNeeded;
|
||||
}
|
||||
|
||||
// NOTE: Unused wgpu flags:
|
||||
// StorageAttachment
|
||||
|
||||
// NOTE: Unused Filament flags:
|
||||
// SUBPASS_INPUT: VK goes to input attachment which we don't support right now
|
||||
// PROTECTED
|
||||
return retUsage;
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_WEBGPUTEXTUREHELPERS_H
|
||||
|
||||
@@ -505,7 +505,6 @@ struct AdapterDetailsHash final {
|
||||
|
||||
// selects one preferred adapter or panics if none can be found
|
||||
wgpu::Adapter selectPreferredAdapter(
|
||||
WebGPUPlatform::Configuration const& configuration,
|
||||
std::unordered_set<AdapterDetails, AdapterDetailsHash> const& compatibleAdapters) {
|
||||
// for each unique adapter...
|
||||
AdapterDetails const* selectedAdapter = nullptr;
|
||||
@@ -513,10 +512,6 @@ wgpu::Adapter selectPreferredAdapter(
|
||||
|
||||
// choose the most desirable adapter that meets the minimum requirements...
|
||||
for (AdapterDetails const& details: compatibleAdapters) {
|
||||
if (configuration.forceBackendType != wgpu::BackendType::Undefined &&
|
||||
configuration.forceBackendType != details.info.backendType) {
|
||||
continue;
|
||||
}
|
||||
if (!adapterMeetsMinimumRequirements(details)) {
|
||||
continue;
|
||||
}
|
||||
@@ -592,7 +587,7 @@ wgpu::Adapter WebGPUPlatform::requestAdapter(wgpu::Surface const& surface) {
|
||||
}
|
||||
const std::unordered_set<AdapterDetails, AdapterDetailsHash> compatibleAdapters =
|
||||
requestCompatibleAdapters(mInstance, requests);
|
||||
return selectPreferredAdapter(getConfiguration(), compatibleAdapters);
|
||||
return selectPreferredAdapter(compatibleAdapters);
|
||||
}
|
||||
|
||||
wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) {
|
||||
|
||||
@@ -38,12 +38,7 @@ std::vector<wgpu::RequestAdapterOptions> WebGPUPlatform::getAdapterOptions() {
|
||||
constexpr std::array powerPreferences = {
|
||||
wgpu::PowerPreference::HighPerformance,
|
||||
wgpu::PowerPreference::LowPower };
|
||||
constexpr std::array backendTypes = {
|
||||
wgpu::BackendType::Metal,
|
||||
// To enable software rasterization on MacOS, we need to ensure Vulkan adapters are
|
||||
// available.
|
||||
wgpu::BackendType::Vulkan,
|
||||
};
|
||||
constexpr std::array backendTypes = { wgpu::BackendType::Metal };
|
||||
constexpr std::array forceFallbackAdapters = { false, true };
|
||||
constexpr size_t totalCombinations =
|
||||
powerPreferences.size() * backendTypes.size() * forceFallbackAdapters.size();
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "AsyncTaskCounter.h"
|
||||
|
||||
#include "utils/debug.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace filament::backend::webgpuutils {
|
||||
|
||||
void AsyncTaskCounter::startTask() {
|
||||
std::lock_guard<std::mutex> lock{ mMutex };
|
||||
++mTasksInProgress;
|
||||
}
|
||||
|
||||
void AsyncTaskCounter::finishTask() {
|
||||
std::lock_guard<std::mutex> lock{ mMutex };
|
||||
--mTasksInProgress;
|
||||
assert_invariant(mTasksInProgress >= 0);
|
||||
if (mTasksInProgress == 0) {
|
||||
mFinishedCondition.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncTaskCounter::waitForAllToFinish() {
|
||||
std::unique_lock<std::mutex> lock{ mMutex };
|
||||
mFinishedCondition.wait(lock, [this] { return mTasksInProgress == 0; });
|
||||
}
|
||||
|
||||
} // namespace filament::backend::webgpuutils
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_UTIL_ASYNCWORKCOUNTER_H
|
||||
#define TNT_FILAMENT_BACKEND_UTIL_ASYNCWORKCOUNTER_H
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
namespace filament::backend::webgpuutils {
|
||||
|
||||
/**
|
||||
* Counts the number of asynchronous tasks of a given type.
|
||||
* This can be used to wait for such a count to decrement to 0 (wait for work to complete) in a
|
||||
* thead safe manner.
|
||||
*/
|
||||
class AsyncTaskCounter final {
|
||||
public:
|
||||
AsyncTaskCounter() = default;
|
||||
AsyncTaskCounter(AsyncTaskCounter const&) = delete;
|
||||
AsyncTaskCounter(AsyncTaskCounter const&&) = delete;
|
||||
AsyncTaskCounter& operator=(AsyncTaskCounter const&) = delete;
|
||||
AsyncTaskCounter& operator=(AsyncTaskCounter const&&) = delete;
|
||||
~AsyncTaskCounter() = default;
|
||||
|
||||
/**
|
||||
* Asynchronously (thread-safe) increase the work counter by one
|
||||
*/
|
||||
void startTask();
|
||||
|
||||
/**
|
||||
* Asynchronously (thread-safe) decrease the work counter by one
|
||||
*/
|
||||
void finishTask();
|
||||
|
||||
/**
|
||||
* Wait for all tasks to finish (for the work counter to reach 0) (thread-safe)
|
||||
*/
|
||||
void waitForAllToFinish();
|
||||
|
||||
private:
|
||||
std::mutex mMutex;
|
||||
std::condition_variable mFinishedCondition;
|
||||
int mTasksInProgress{ 0 };
|
||||
};
|
||||
|
||||
} // namespace filament::backend::webgpuutils
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_UTIL_ASYNCWORKCOUNTER_H
|
||||
@@ -49,7 +49,7 @@ Backend parseArgumentsForBackend(int argc, char* argv[]) {
|
||||
} else if (arg == "webgpu") {
|
||||
backend = Backend::WEBGPU;
|
||||
} else {
|
||||
std::cerr << "Unrecognized target API. Must be 'opengl'|'vulkan'|'metal'|'webgpu'."
|
||||
std::cerr << "Unrecognized target API. Must be 'opengl'|'vulkan'|'metal'."
|
||||
<< std::endl
|
||||
<< "Defaulting to OpenGL."
|
||||
<< std::endl;
|
||||
|
||||
@@ -85,18 +85,6 @@ std::filesystem::path ScreenshotParams::expectedFilePath() const {
|
||||
return expectedDirectoryPath().append(expectedFileName());
|
||||
}
|
||||
|
||||
std::filesystem::path ScreenshotParams::diffFilePath() const {
|
||||
return diffDirectoryPath().append(diffFileName());
|
||||
}
|
||||
|
||||
std::filesystem::path ScreenshotParams::diffDirectoryPath() {
|
||||
return BackendTest::binaryDirectory().append("images/diff_images");
|
||||
}
|
||||
|
||||
std::string ScreenshotParams::diffFileName() const {
|
||||
return absl::StrFormat("%s_diff.png", mFileName);
|
||||
}
|
||||
|
||||
const std::string ScreenshotParams::filePrefix() const {
|
||||
// TODO(b/422804941): If there are platform specific goldens, when on those platforms append a
|
||||
// unique platform identifying string to this.
|
||||
@@ -140,6 +128,8 @@ void ImageExpectation::compareImage() const {
|
||||
if (bytesFilled) {
|
||||
#ifndef FILAMENT_IOS
|
||||
LoadedPng loadedImage(mParams.expectedFilePath());
|
||||
// Bytewise compare.
|
||||
EXPECT_EQ(loadedImage.bytes().size(), mResult.bytes().size());
|
||||
if (loadedImage.bytes().size() != mResult.bytes().size()) {
|
||||
// Something is wrong with the size of the expected result, which usually means the file
|
||||
// is missing. Fail the test and early return so later steps can assume the image is
|
||||
@@ -147,41 +137,18 @@ void ImageExpectation::compareImage() const {
|
||||
BackendTest::markImageAsFailure(mParams.filePrefix());
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize the vector to full saturation on all channels, bad pixels will have
|
||||
// color channels but not alpha set to 0 to produce highly contrasting black pixels.
|
||||
std::vector<unsigned char> imageDiff( loadedImage.bytes().size(), 255 );
|
||||
int pixelDeviations = 0;
|
||||
for (int i = 0; i < mResult.bytes().size(); i += 4) {
|
||||
// In order to handle color channels propoerly for the output, stride 4 bytes at a time.
|
||||
// A failure of any byte in a pixel counts as a failure of the whole pixel.
|
||||
for (int j = 0; j < 4; ++j) {
|
||||
if( std::abs( mResult.bytes()[i+j] - loadedImage.bytes()[i+j] ) >
|
||||
mParams.pixelMatchThreshold() ) {
|
||||
pixelDeviations++;
|
||||
imageDiff[i] = 0;
|
||||
imageDiff[i+1] = 0;
|
||||
imageDiff[i+2] = 0;
|
||||
break;
|
||||
}
|
||||
for (int i = 0; i < mResult.bytes().size(); ++i) {
|
||||
if (std::abs( mResult.bytes()[i] - loadedImage.bytes()[i] ) >
|
||||
mParams.pixelMatchThreshold()) {
|
||||
pixelDeviations++;
|
||||
}
|
||||
}
|
||||
EXPECT_LE(pixelDeviations, mParams.allowedPixelDeviations());
|
||||
if (pixelDeviations > mParams.allowedPixelDeviations()) {
|
||||
BackendTest::markImageAsFailure(mParams.filePrefix());
|
||||
image::LinearImage image;
|
||||
image = image::toLinearWithAlpha<uint8_t>(
|
||||
mParams.width(), mParams.height(),
|
||||
mParams.width() * 4, (uint8_t *)imageDiff.data(),
|
||||
[](uint8_t value) -> float { return value; },
|
||||
[](filament::math::float4 rgba) -> filament::math::float4 { return rgba; });
|
||||
std::string filePath = mParams.diffFilePath();
|
||||
std::ofstream pngStream(filePath, std::ios::binary | std::ios::trunc);
|
||||
// To avoid going from linear -> sRGB -> linear save the PNG as linear.
|
||||
image::ImageEncoder::encode(pngStream, image::ImageEncoder::Format::PNG_LINEAR, image, "",
|
||||
filePath);
|
||||
}
|
||||
EXPECT_LE(pixelDeviations, mParams.allowedPixelDeviations());
|
||||
// TODO: Add better debug output, such as generating a diff image.
|
||||
#else
|
||||
// For builds that can't load PNGs (currently iOS only) use the expected hash.
|
||||
uint32_t actualHash = mResult.hash();
|
||||
|
||||
@@ -58,9 +58,6 @@ public:
|
||||
static std::filesystem::path expectedDirectoryPath();
|
||||
std::string expectedFileName() const;
|
||||
std::filesystem::path expectedFilePath() const;
|
||||
static std::filesystem::path diffDirectoryPath();
|
||||
std::string diffFileName() const;
|
||||
std::filesystem::path diffFilePath() const;
|
||||
const std::string filePrefix() const;
|
||||
int allowedPixelDeviations() const;
|
||||
int pixelMatchThreshold() const;
|
||||
|
||||
@@ -19,8 +19,6 @@ code. When cmake is run it will copy this directory to the build output creating
|
||||
The unit tests will then write their resulting images into `images/actual_images` in order to make
|
||||
the tests easier to debug.
|
||||
|
||||
If an image comparison test fails, it writes a diff image to `images/diff_images` where each pixel of the diff is white (255,255,255) if the comparison for the corresponding pixel succeeds and black (0,0,0) of the comparison for the corresponding pixel fails.
|
||||
|
||||
### Python utility for updating golden images and comparing results
|
||||
|
||||
Inside the unit test source code directory there is a python script called
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_BACKEND_TEST_WORKAROUNDS_H
|
||||
#define TNT_BACKEND_TEST_WORKAROUNDS_H
|
||||
|
||||
#if defined(FILAMENT_SUPPORTS_WEBGPU)
|
||||
// consider, if needed, adding (as Vulkan also has transient attachments): || defined(FILAMENT_SUPPORTS_VULKAN)
|
||||
// TODO: support TextureUsage::READ_PIXELS (or something like that)
|
||||
// and search-and-replace TEXTURE_USAGE_READ_PIXELS with
|
||||
// "| TextureUsage::READ_PIXELS" when it is available.
|
||||
// Until then, this serves as a workaround for WebGPU testing, such that
|
||||
// it can easily be swapped out for the "right" thing when that is available.
|
||||
// The problem this is trying to address is that the WebGPU backend will
|
||||
// assign a texture with wgpu::TextureUsage::TransientAttachment if the Filament
|
||||
// usage flags don't indicate the pixels are needed CPU-side. If transient attachment is
|
||||
// assigned to the texture, the texture will not be available later for readPixels, which
|
||||
// is often needed for the unit tests. If we suggest that
|
||||
// the texture needs to support being a blit source, then TransientAttachment will
|
||||
// not get assigned. However, this is not quite right in the test, because a test
|
||||
// may not really want to do a blit with the texture, just read the pixels from it later for
|
||||
// image comparison (renderdiff).
|
||||
// Ideally, the test could express this with a TextureUsage::READ_PIXELS flag, but that
|
||||
// does not yet exist. So, a hack would be to explicitly set the BLIT_SRC flag instead in
|
||||
// its place.
|
||||
#define TEXTURE_USAGE_READ_PIXELS | TextureUsage::BLIT_SRC
|
||||
#else
|
||||
#define TEXTURE_USAGE_READ_PIXELS
|
||||
#endif
|
||||
|
||||
#endif // TNT_BACKEND_TEST_WORKAROUNDS_H
|
||||
@@ -160,18 +160,17 @@ TEST_F(BlitTest, ColorMagnify) {
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a source texture.
|
||||
Handle<HwTexture> const srcTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
|
||||
kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT |
|
||||
TextureUsage::BLIT_SRC));
|
||||
Handle<HwTexture> const srcTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
const bool flipY = sBackend == Backend::OPENGL;
|
||||
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, float3(0.5, 0, 0), flipY);
|
||||
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 1, float3(0, 0, 0.5), flipY);
|
||||
|
||||
// Create a destination texture.
|
||||
Handle<HwTexture> const dstTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
|
||||
kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT | TextureUsage::BLIT_DST));
|
||||
Handle<HwTexture> const dstTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
|
||||
// Create a RenderTarget for each texture's miplevel.
|
||||
Handle<HwRenderTarget> srcRenderTargets[kNumLevels];
|
||||
@@ -223,18 +222,17 @@ TEST_F(BlitTest, ColorMinify) {
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a source texture.
|
||||
Handle<HwTexture> const srcTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
|
||||
kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT |
|
||||
TextureUsage::BLIT_SRC));
|
||||
Handle<HwTexture> const srcTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
const bool flipY = sBackend == Backend::OPENGL;
|
||||
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, float3(0.5, 0, 0), flipY);
|
||||
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 1, float3(0, 0, 0.5), flipY);
|
||||
|
||||
// Create a destination texture.
|
||||
Handle<HwTexture> const dstTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
|
||||
kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT | TextureUsage::BLIT_DST));
|
||||
Handle<HwTexture> const dstTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
|
||||
// Create a RenderTarget for each texture's miplevel.
|
||||
Handle<HwRenderTarget> srcRenderTargets[kNumLevels];
|
||||
@@ -285,12 +283,13 @@ TEST_F(BlitTest, ColorResolve) {
|
||||
// Create 4-sample texture.
|
||||
Handle<HwTexture> const srcColorTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, 1, kColorTexFormat, kSampleCount, kSrcTexWidth, kSrcTexHeight,
|
||||
1, TextureUsage::COLOR_ATTACHMENT | TextureUsage::BLIT_SRC | TextureUsage::UPLOADABLE));
|
||||
1,
|
||||
TextureUsage::COLOR_ATTACHMENT));
|
||||
|
||||
// Create 1-sample texture.
|
||||
Handle<HwTexture> const dstColorTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, 1, kColorTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT | TextureUsage::BLIT_DST));
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
|
||||
// Create a 4-sample render target with the 4-sample texture.
|
||||
Handle<HwRenderTarget> const srcRenderTarget = mCleanup.add(api.createRenderTarget(
|
||||
@@ -365,18 +364,18 @@ TEST_F(BlitTest, Blit2DTextureArray) {
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a source texture.
|
||||
Handle<HwTexture> srcTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D_ARRAY,
|
||||
kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, kSrcTexDepth,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT |
|
||||
TextureUsage::BLIT_SRC));
|
||||
Handle<HwTexture> srcTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D_ARRAY, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth,
|
||||
kSrcTexHeight, kSrcTexDepth,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
const bool flipY = sBackend == Backend::OPENGL;
|
||||
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, kSrcTexLayer, float3(0.5, 0, 0),
|
||||
flipY);
|
||||
|
||||
// Create a destination texture.
|
||||
Handle<HwTexture> dstTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
|
||||
kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT | TextureUsage::BLIT_DST));
|
||||
Handle<HwTexture> dstTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
|
||||
// Create two RenderTargets.
|
||||
const int level = 0;
|
||||
@@ -430,18 +429,17 @@ TEST_F(BlitTest, BlitRegion) {
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
// Create a source texture.
|
||||
Handle<HwTexture> srcTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
|
||||
kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT |
|
||||
TextureUsage::BLIT_SRC));
|
||||
Handle<HwTexture> srcTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
const bool flipY = sBackend == Backend::OPENGL;
|
||||
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, float3(0.5, 0, 0), flipY);
|
||||
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 1, float3(0, 0, 0.5), flipY);
|
||||
|
||||
// Create a destination texture.
|
||||
Handle<HwTexture> dstTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
|
||||
kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT | TextureUsage::BLIT_DST));
|
||||
Handle<HwTexture> dstTexture = mCleanup.add(api.createTexture(
|
||||
SamplerType::SAMPLER_2D, kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
|
||||
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
|
||||
|
||||
// Blit one-quarter of src level 1 to dst level 0.
|
||||
Viewport srcRect = {
|
||||
@@ -489,7 +487,6 @@ TEST_F(BlitTest, BlitRegion) {
|
||||
|
||||
TEST_F(BlitTest, BlitRegionToSwapChain) {
|
||||
FAIL_IF(Backend::VULKAN, "Crashes due to not finding color attachment, see b/417481493");
|
||||
SKIP_IF(Backend::WEBGPU, "WebGPU Crashes due to not finding color attachment");
|
||||
auto& api = getDriverApi();
|
||||
mCleanup.addPostCall([&]() { executeCommands(); });
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#include "SharedShaders.h"
|
||||
#include "Skip.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
#include "Workarounds.h"
|
||||
|
||||
namespace test {
|
||||
|
||||
@@ -156,6 +155,8 @@ TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
|
||||
// This test renders two triangles in two separate draw calls. Between the draw calls, a uniform
|
||||
// buffer object is partially updated.
|
||||
TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
|
||||
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
|
||||
"All values including alpha are written as 0, see b/417254943");
|
||||
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
@@ -178,9 +179,9 @@ TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
|
||||
shader.bindUniform<SimpleMaterialParams>(api, ubuffer, kBindingConfig);
|
||||
|
||||
// Create a render target.
|
||||
auto colorTexture = cleanup.add(
|
||||
api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, screenWidth(),
|
||||
screenHeight(), 1, TextureUsage::COLOR_ATTACHMENT TEXTURE_USAGE_READ_PIXELS));
|
||||
auto colorTexture =
|
||||
cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1,
|
||||
screenWidth(), screenHeight(), 1, TextureUsage::COLOR_ATTACHMENT));
|
||||
auto renderTarget = cleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR0, screenWidth(),
|
||||
screenHeight(), 1, 0, { { colorTexture } }, {}, {}));
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ layout(location = 0) out vec4 fragColor;
|
||||
|
||||
// Filament's Vulkan backend requires a descriptor set index of 1 for all samplers.
|
||||
// This parameter is ignored for other backends.
|
||||
layout(binding = 0, set = 0) uniform {samplerType} test_tex;
|
||||
layout(location = 0, set = 0) uniform {samplerType} test_tex;
|
||||
|
||||
void main() {
|
||||
vec2 fbsize = vec2({texSize});
|
||||
@@ -66,7 +66,7 @@ std::string fragmentUpdateImage3DTemplate (R"(#version 450 core
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
// Filament's Vulkan backend requires a descriptor set index of 1 for all samplers.
|
||||
layout(binding = 0, set = 0) uniform {samplerType} test_tex;
|
||||
layout(location = 0, set = 0) uniform {samplerType} test_tex;
|
||||
|
||||
float getLayer(in sampler3D s) { return 2.5f / 4.0f; }
|
||||
float getLayer(in sampler2DArray s) { return 2.0f; }
|
||||
@@ -87,7 +87,7 @@ std::string fragmentUpdateImageMip (R"(#version 450 core
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
// Filament's Vulkan backend requires a descriptor set index of 1 for all samplers.
|
||||
layout(binding = 0, set = 0) uniform sampler2D test_tex;
|
||||
layout(location = 0, set = 0) uniform sampler2D test_tex;
|
||||
|
||||
void main() {
|
||||
vec2 fbsize = vec2({texSize});
|
||||
@@ -271,18 +271,15 @@ TEST_F(LoadImageTest, UpdateImage2D) {
|
||||
|
||||
// Test format conversion.
|
||||
// TODO: Vulkan crashes with `Texture at colorAttachment[0] has usage (0x01) which doesn't specify MTLTextureUsageRenderTarget (0x04)'
|
||||
// TODO: WebGPU Crashes with "destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment"
|
||||
testCases.emplace_back("RGBA FLOAT to RGBA16F", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F);
|
||||
|
||||
// Test texture formats not all backends support natively.
|
||||
// TODO: Vulkan crashes with "VK_FORMAT_R32G32B32_SFLOAT is not supported"
|
||||
// TODO: WebGPU Crashes with "destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment"
|
||||
testCases.emplace_back("RGB FLOAT to RGB32F", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F);
|
||||
testCases.emplace_back("RGB FLOAT to RGB16F", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB16F);
|
||||
|
||||
// Test packed format uploads.
|
||||
// TODO: Vulkan crashes with "Texture at colorAttachment[0] has usage (0x01) which doesn't specify MTLTextureUsageRenderTarget (0x04)"
|
||||
// TODO: WebGPU Crashes with "destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment"
|
||||
testCases.emplace_back("RGBA UINT_2_10_10_10_REV to RGB10_A2", PixelDataFormat::RGBA, PixelDataType::UINT_2_10_10_10_REV, TextureFormat::RGB10_A2);
|
||||
testCases.emplace_back("RGB UINT_10F_11F_11F_REV to R11F_G11F_B10F", PixelDataFormat::RGB, PixelDataType::UINT_10F_11F_11F_REV, TextureFormat::R11F_G11F_B10F);
|
||||
testCases.emplace_back("RGB HALF to R11F_G11F_B10F", PixelDataFormat::RGB, PixelDataType::HALF, TextureFormat::R11F_G11F_B10F);
|
||||
@@ -290,12 +287,9 @@ TEST_F(LoadImageTest, UpdateImage2D) {
|
||||
// Test integer format uploads.
|
||||
// TODO: These cases fail on OpenGL and Vulkan.
|
||||
// TODO: These cases now also fail on Metal, but at some point previously worked.
|
||||
// TODO: These cases fail for WebGPU. However, leaving them causes
|
||||
// Tint Reader Error: error: sampled image must have float component type
|
||||
// Beginning SpirV-output dump with ret 0
|
||||
// testCases.emplace_back("RGB_INTEGER UBYTE to RGB8UI", PixelDataFormat::RGB_INTEGER, PixelDataType::UBYTE, TextureFormat::RGB8UI);
|
||||
// testCases.emplace_back("RGB_INTEGER USHORT to RGB16UI", PixelDataFormat::RGB_INTEGER, PixelDataType::USHORT, TextureFormat::RGB16UI);
|
||||
// testCases.emplace_back("RGB_INTEGER INT to RGB32I", PixelDataFormat::RGB_INTEGER, PixelDataType::INT, TextureFormat::RGB32I);
|
||||
testCases.emplace_back("RGB_INTEGER UBYTE to RGB8UI", PixelDataFormat::RGB_INTEGER, PixelDataType::UBYTE, TextureFormat::RGB8UI);
|
||||
testCases.emplace_back("RGB_INTEGER USHORT to RGB16UI", PixelDataFormat::RGB_INTEGER, PixelDataType::USHORT, TextureFormat::RGB16UI);
|
||||
testCases.emplace_back("RGB_INTEGER INT to RGB32I", PixelDataFormat::RGB_INTEGER, PixelDataType::INT, TextureFormat::RGB32I);
|
||||
|
||||
// Test uploads with buffer padding.
|
||||
// TODO: Vulkan crashes with "Assertion failed: (offset + size <= allocationSize)"
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <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
|
||||
@@ -27,19 +27,7 @@ bool ZstdHelper::isCompressed(const void* src, size_t src_size) noexcept {
|
||||
if (src_size < 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// `src` may not be aligned to 4 bytes, which is violating the alignment requirement of
|
||||
// `UndefinedBehaviorSanitizer: misaligned-pointer-use`. So reconstruct the 32-bit integer from
|
||||
// bytes in little-endian order as the expected byte sequence is 28 B5 2F FD and
|
||||
// ZSTD_MAGICNUMBER refers to 0xFD2FB528. This should work correctly on both little-endian and
|
||||
// big-endian systems.
|
||||
const auto* p = static_cast<const uint8_t*>(src);
|
||||
const uint32_t magic =
|
||||
(uint32_t)p[0] |
|
||||
(uint32_t)(p[1] << 8) |
|
||||
(uint32_t)(p[2] << 16) |
|
||||
(uint32_t)(p[3] << 24);
|
||||
|
||||
const uint32_t magic = *static_cast<const uint32_t*>(src);
|
||||
return magic == ZSTD_MAGICNUMBER;
|
||||
}
|
||||
|
||||
|
||||
@@ -748,7 +748,7 @@ public:
|
||||
bool enable_staging_buffer_bypass = false;
|
||||
} vulkan;
|
||||
bool disable_parallel_shader_compile = false;
|
||||
bool disable_amortized_shader_compile = true;
|
||||
bool disable_amortized_shader_compile = false;
|
||||
bool disable_handle_use_after_free_check = false;
|
||||
bool disable_heap_handle_tags = true; // FIXME: this should be false
|
||||
} backend;
|
||||
|
||||
@@ -653,9 +653,7 @@ void FTexture::generateMipmaps(FEngine& engine) const noexcept {
|
||||
FILAMENT_CHECK_PRECONDITION(formatMipmappable)
|
||||
<< "Texture format " << (unsigned)mFormat << " is not mipmappable.";
|
||||
|
||||
auto const& featureFlags = downcast(engine).features.engine.debug;
|
||||
FILAMENT_FLAG_GUARDED_CHECK_PRECONDITION(any(mUsage & TextureUsage::GEN_MIPMAPPABLE),
|
||||
featureFlags.assert_texture_can_generate_mipmap)
|
||||
FILAMENT_CHECK_PRECONDITION(any(mUsage & TextureUsage::GEN_MIPMAPPABLE))
|
||||
<< "Texture usage does not have GEN_MIPMAPPABLE set";
|
||||
|
||||
if (mLevelCount < 2 || (mWidth == 1 && mHeight == 1)) {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Filament"
|
||||
spec.version = "1.64.1"
|
||||
spec.version = "1.63.1"
|
||||
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
|
||||
spec.homepage = "https://google.github.io/filament"
|
||||
spec.authors = "Google LLC."
|
||||
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
|
||||
spec.platform = :ios, "11.0"
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.64.1/filament-v1.64.1-ios.tgz" }
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.63.1/filament-v1.63.1-ios.tgz" }
|
||||
|
||||
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
|
||||
spec.pod_target_xcconfig = {
|
||||
|
||||
@@ -58,7 +58,6 @@ enum UTILS_PUBLIC ChunkType : uint64_t {
|
||||
|
||||
MaterialName = charTo64bitNum("MAT_NAME"),
|
||||
MaterialVersion = charTo64bitNum("MAT_VERS"),
|
||||
MaterialCompilationParameters = charTo64bitNum("MAT_CPRM"),
|
||||
MaterialCacheId = charTo64bitNum("MAT_UUID"),
|
||||
MaterialFeatureLevel = charTo64bitNum("MAT_FEAT"),
|
||||
MaterialShading = charTo64bitNum("MAT_SHAD"),
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
namespace filament {
|
||||
|
||||
// update this when a new version of filament wouldn't work with older materials
|
||||
static constexpr size_t MATERIAL_VERSION = 64;
|
||||
static constexpr size_t MATERIAL_VERSION = 63;
|
||||
|
||||
/**
|
||||
* Supported shading models
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <utils/BitmaskEnum.h>
|
||||
#include <utils/bitset.h>
|
||||
#include <utils/compiler.h>
|
||||
@@ -35,8 +36,6 @@
|
||||
#include <math/vec3.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -118,11 +117,6 @@ public:
|
||||
PERFORMANCE
|
||||
};
|
||||
|
||||
enum class Workarounds : uint64_t {
|
||||
NONE = 0,
|
||||
ALL = 0xFFFFFFFFFFFFFFFF
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize MaterialBuilder.
|
||||
*
|
||||
@@ -146,7 +140,6 @@ protected:
|
||||
Platform mPlatform = Platform::DESKTOP;
|
||||
TargetApi mTargetApi = (TargetApi) 0;
|
||||
Optimization mOptimization = Optimization::PERFORMANCE;
|
||||
Workarounds mWorkarounds = Workarounds::ALL;
|
||||
bool mPrintShaders = false;
|
||||
bool mSaveRawVariants = false;
|
||||
bool mGenerateDebugInfo = false;
|
||||
@@ -302,9 +295,6 @@ public:
|
||||
//! Set the file name of this material file. Used in error reporting.
|
||||
MaterialBuilder& fileName(const char* name) noexcept;
|
||||
|
||||
//! Set the commandline parameters of matc. Used for debugging purpose.
|
||||
MaterialBuilder& compilationParameters(const char* params) noexcept;
|
||||
|
||||
//! Set the shading model.
|
||||
MaterialBuilder& shading(Shading shading) noexcept;
|
||||
|
||||
@@ -613,13 +603,6 @@ public:
|
||||
*/
|
||||
MaterialBuilder& optimization(Optimization optimization) noexcept;
|
||||
|
||||
/**
|
||||
* Specifies workarounds to enable during code generation. By default, all workaround are
|
||||
* enabled. These workarounds typically disable important optimizations and in some cases
|
||||
* whole features.
|
||||
*/
|
||||
MaterialBuilder& workarounds(Workarounds workarounds) noexcept;
|
||||
|
||||
// TODO: this is present here for matc's "--print" flag, but ideally does not belong inside MaterialBuilder.
|
||||
//! If true, will output the generated GLSL shader code to stdout.
|
||||
MaterialBuilder& printShaders(bool printShaders) noexcept;
|
||||
@@ -893,7 +876,6 @@ private:
|
||||
|
||||
utils::CString mMaterialName;
|
||||
utils::CString mFileName;
|
||||
utils::CString mCompilationParameters;
|
||||
|
||||
class ShaderCode {
|
||||
public:
|
||||
@@ -1006,15 +988,7 @@ private:
|
||||
|
||||
} // namespace filamat
|
||||
|
||||
template<>
|
||||
struct utils::EnableBitMaskOperators<filamat::MaterialBuilder::TargetApi>
|
||||
: public std::true_type {
|
||||
};
|
||||
|
||||
template<>
|
||||
struct utils::EnableBitMaskOperators<filamat::MaterialBuilder::Workarounds>
|
||||
: public std::true_type {
|
||||
};
|
||||
|
||||
template<> struct utils::EnableBitMaskOperators<filamat::MaterialBuilder::TargetApi>
|
||||
: public std::true_type {};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -283,13 +283,10 @@ static void collectDescriptorSets(const GLSLPostProcessor::Config& config, Descr
|
||||
|
||||
} // namespace msl
|
||||
|
||||
GLSLPostProcessor::GLSLPostProcessor(
|
||||
MaterialBuilder::Optimization optimization,
|
||||
MaterialBuilder::Workarounds workarounds,
|
||||
uint32_t flags)
|
||||
: mOptimization(optimization), mWorkarounds(workarounds),
|
||||
mPrintShaders(flags & PRINT_SHADERS),
|
||||
mGenerateDebugInfo(flags & GENERATE_DEBUG_INFO) {
|
||||
GLSLPostProcessor::GLSLPostProcessor(MaterialBuilder::Optimization optimization, uint32_t flags)
|
||||
: mOptimization(optimization),
|
||||
mPrintShaders(flags & PRINT_SHADERS),
|
||||
mGenerateDebugInfo(flags & GENERATE_DEBUG_INFO) {
|
||||
// This should occur only once, to avoid races.
|
||||
SpirvRemapWrapperSetUp();
|
||||
}
|
||||
@@ -1051,15 +1048,9 @@ void GLSLPostProcessor::fixupClipDistance(
|
||||
void GLSLPostProcessor::registerPerformancePasses(Optimizer& optimizer, Config const& config) {
|
||||
auto RegisterPass = [&](Optimizer::PassToken&& pass,
|
||||
MaterialBuilder::TargetApi apiFilter = MaterialBuilder::TargetApi::ALL) {
|
||||
|
||||
// Workaround management is currently very simple, only two values are possible
|
||||
// ALL and NONE. If the value is anything but NONE, we apply all workarounds.
|
||||
if (config.workarounds != MaterialBuilderBase::Workarounds::NONE) {
|
||||
if (!(config.targetApi & apiFilter)) {
|
||||
return;
|
||||
}
|
||||
if (!(config.targetApi & apiFilter)) {
|
||||
return;
|
||||
}
|
||||
|
||||
optimizer.RegisterPass(std::move(pass));
|
||||
};
|
||||
|
||||
|
||||
@@ -58,10 +58,7 @@ public:
|
||||
GENERATE_DEBUG_INFO = 1 << 1,
|
||||
};
|
||||
|
||||
GLSLPostProcessor(
|
||||
MaterialBuilder::Optimization optimization,
|
||||
MaterialBuilder::Workarounds workarounds,
|
||||
uint32_t flags);
|
||||
GLSLPostProcessor(MaterialBuilder::Optimization optimization, uint32_t flags);
|
||||
|
||||
~GLSLPostProcessor();
|
||||
|
||||
@@ -70,7 +67,6 @@ public:
|
||||
filament::UserVariantFilterMask variantFilter;
|
||||
MaterialBuilder::TargetApi targetApi;
|
||||
MaterialBuilder::TargetLanguage targetLanguage;
|
||||
MaterialBuilder::Workarounds workarounds;
|
||||
filament::backend::ShaderStage shaderType;
|
||||
filament::backend::ShaderModel shaderModel;
|
||||
filament::backend::FeatureLevel featureLevel;
|
||||
@@ -137,7 +133,6 @@ private:
|
||||
void fixupClipDistance(SpirvBlob& spirv, GLSLPostProcessor::Config const& config) const;
|
||||
|
||||
const MaterialBuilder::Optimization mOptimization;
|
||||
const MaterialBuilder::Workarounds mWorkarounds;
|
||||
const bool mPrintShaders;
|
||||
const bool mGenerateDebugInfo;
|
||||
};
|
||||
|
||||
@@ -232,11 +232,6 @@ MaterialBuilder& MaterialBuilder::fileName(const char* name) noexcept {
|
||||
return *this;
|
||||
}
|
||||
|
||||
MaterialBuilder& MaterialBuilder::compilationParameters(const char* params) noexcept {
|
||||
mCompilationParameters = CString(params);
|
||||
return *this;
|
||||
}
|
||||
|
||||
MaterialBuilder& MaterialBuilder::material(const char* code, size_t const line) noexcept {
|
||||
mMaterialFragmentCode.setUnresolved(CString(code));
|
||||
mMaterialFragmentCode.setLineOffset(line);
|
||||
@@ -585,6 +580,7 @@ MaterialBuilder& MaterialBuilder::stereoscopicType(StereoscopicType const stereo
|
||||
mStereoscopicType = stereoscopicType;
|
||||
return *this;
|
||||
}
|
||||
|
||||
MaterialBuilder& MaterialBuilder::stereoscopicEyeCount(uint8_t const eyeCount) noexcept {
|
||||
mStereoscopicEyeCount = eyeCount;
|
||||
return *this;
|
||||
@@ -610,11 +606,6 @@ MaterialBuilder& MaterialBuilder::optimization(Optimization const optimization)
|
||||
return *this;
|
||||
}
|
||||
|
||||
MaterialBuilder& MaterialBuilder::workarounds(Workarounds const workarounds) noexcept {
|
||||
mWorkarounds = workarounds;
|
||||
return *this;
|
||||
}
|
||||
|
||||
MaterialBuilder& MaterialBuilder::printShaders(bool const printShaders) noexcept {
|
||||
mPrintShaders = printShaders;
|
||||
return *this;
|
||||
@@ -933,7 +924,7 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector<Va
|
||||
uint32_t flags = 0;
|
||||
flags |= mPrintShaders ? GLSLPostProcessor::PRINT_SHADERS : 0;
|
||||
flags |= mGenerateDebugInfo ? GLSLPostProcessor::GENERATE_DEBUG_INFO : 0;
|
||||
GLSLPostProcessor postProcessor(mOptimization, mWorkarounds, flags);
|
||||
GLSLPostProcessor postProcessor(mOptimization, flags);
|
||||
|
||||
// Start: must be protected by lock
|
||||
Mutex entriesLock;
|
||||
@@ -1063,7 +1054,6 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector<Va
|
||||
.variantFilter = mVariantFilter,
|
||||
.targetApi = targetApi,
|
||||
.targetLanguage = targetLanguage,
|
||||
.workarounds = mWorkarounds,
|
||||
.shaderType = v.stage,
|
||||
.shaderModel = shaderModel,
|
||||
.featureLevel = featureLevel,
|
||||
@@ -1175,23 +1165,30 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector<Va
|
||||
std::sort(metalEntries.begin(), metalEntries.end(), compare);
|
||||
std::sort(wgslEntries.begin(), wgslEntries.end(), compare);
|
||||
|
||||
|
||||
utils::slog.e <<"----------------------- a" << utils::io::endl;
|
||||
// Generate the dictionaries.
|
||||
for (const auto& s : glslEntries) {
|
||||
textDictionary.addText(s.shader);
|
||||
}
|
||||
utils::slog.e <<"----------------------- b" << utils::io::endl;
|
||||
for (const auto& s : essl1Entries) {
|
||||
textDictionary.addText(s.shader);
|
||||
}
|
||||
utils::slog.e <<"----------------------- c" << utils::io::endl;
|
||||
for (auto& s : spirvEntries) {
|
||||
std::vector const spirv{ std::move(s.data) };
|
||||
s.dictionaryIndex = spirvDictionary.addBlob(spirv);
|
||||
}
|
||||
utils::slog.e <<"----------------------- d" << utils::io::endl;
|
||||
for (const auto& s : metalEntries) {
|
||||
textDictionary.addText(s.shader);
|
||||
}
|
||||
utils::slog.e <<"----------------------- e" << utils::io::endl;
|
||||
for (const auto& s : wgslEntries) {
|
||||
textDictionary.addText(s.shader);
|
||||
}
|
||||
utils::slog.e <<"----------------------- f" << utils::io::endl;
|
||||
|
||||
// Emit dictionary chunk (TextDictionaryReader and DictionaryTextChunk)
|
||||
const auto& dictionaryChunk = container.push<DictionaryTextChunk>(
|
||||
@@ -1604,8 +1601,6 @@ void MaterialBuilder::writeCommonChunks(ChunkContainer& container, MaterialInfo&
|
||||
container.emplace<uint32_t>(MaterialVersion, MATERIAL_VERSION);
|
||||
container.emplace<uint8_t>(MaterialFeatureLevel, (uint8_t)info.featureLevel);
|
||||
container.emplace<const char*>(MaterialName, mMaterialName.c_str_safe());
|
||||
container.emplace<const char*>(MaterialCompilationParameters,
|
||||
mCompilationParameters.c_str_safe());
|
||||
container.emplace<uint32_t>(MaterialShaderModels, mShaderModels.getValue());
|
||||
container.emplace<uint8_t>(ChunkType::MaterialDomain, static_cast<uint8_t>(mMaterialDomain));
|
||||
|
||||
|
||||
@@ -30,17 +30,34 @@
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
namespace filamat {
|
||||
|
||||
namespace {
|
||||
bool isWordChar(char const c) {
|
||||
// Note: isalnum is locale-dependent, which can be problematic.
|
||||
// For our purpose, we define word characters as ASCII alphanumeric characters plus underscore.
|
||||
// This is safe for UTF-8 strings, as any byte of a multi-byte character will not be
|
||||
// in these ranges.
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
|
||||
bool isWordChar(char const c) { return std::isalnum(c) || c == '_'; }
|
||||
|
||||
|
||||
// Function to trim specified characters from both ends of a string_view
|
||||
std::string_view trim(std::string_view sv, std::string_view chars_to_trim = " \t\r\n") {
|
||||
// Find the first character that is not in our set of trim characters
|
||||
const auto start = sv.find_first_not_of(chars_to_trim);
|
||||
|
||||
// If no such character exists, the string is empty or all trim characters
|
||||
if (start == std::string_view::npos) {
|
||||
return ""; // Return an empty view
|
||||
}
|
||||
|
||||
// Find the last character that is not in our set of trim characters
|
||||
const auto end = sv.find_last_not_of(chars_to_trim);
|
||||
|
||||
// Calculate the length of the new view
|
||||
const auto len = end - start + 1;
|
||||
|
||||
// Return a new view representing the trimmed string
|
||||
return sv.substr(start, len);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
LineDictionary::LineDictionary() = default;
|
||||
@@ -68,10 +85,15 @@ std::vector<LineDictionary::index_t> LineDictionary::getIndices(
|
||||
}
|
||||
|
||||
void LineDictionary::addText(std::string_view const text) noexcept {
|
||||
utils::slog.e <<"begin addText l(" << text.length() << ")=" <<
|
||||
trim(text.substr(0, std::min(15, (int)text.length())));
|
||||
|
||||
size_t cur = 0;
|
||||
size_t const len = text.length();
|
||||
const char* s = text.data();
|
||||
size_t trueCount = 0;
|
||||
while (cur < len) {
|
||||
trueCount++;
|
||||
// Start of the current line
|
||||
size_t const pos = cur;
|
||||
// Find the end of the current line or end of text
|
||||
@@ -83,12 +105,22 @@ void LineDictionary::addText(std::string_view const text) noexcept {
|
||||
cur++;
|
||||
}
|
||||
addLine({ s + pos, cur - pos });
|
||||
if (trueCount > 1000000 && trueCount > 0) {
|
||||
utils::slog.e <<"print addText at=" << trim(text.substr(0, std::min(15, (int)text.length()))) <<
|
||||
" count=" << trueCount << " cur=" << cur << utils::io::endl;
|
||||
trueCount = 0;
|
||||
}
|
||||
}
|
||||
utils::slog.e <<" ----- end addText" << utils::io::endl;
|
||||
}
|
||||
|
||||
void LineDictionary::addLine(std::string_view const line) noexcept {
|
||||
utils::slog.e <<"********** begin addline l(" << line.length() << ")=" <<
|
||||
trim(line.substr(0, std::min(15, (int)line.length())));
|
||||
|
||||
auto const lines = splitString(line);
|
||||
for (std::string_view const& subline : lines) {
|
||||
utils::slog.e <<"addline indices=" << mLineIndices.size() << utils::io::endl;
|
||||
// Never add a line twice.
|
||||
auto pos = mLineIndices.find(subline);
|
||||
if (pos != mLineIndices.end()) {
|
||||
@@ -101,6 +133,7 @@ void LineDictionary::addLine(std::string_view const line) noexcept {
|
||||
.index = index_t(mStrings.size() - 1),
|
||||
.count = 1 });
|
||||
}
|
||||
utils::slog.e <<"*********** end addline" << utils::io::endl;
|
||||
}
|
||||
|
||||
std::string_view LineDictionary::ltrim(std::string_view s) {
|
||||
@@ -114,14 +147,26 @@ std::pair<size_t, size_t> LineDictionary::findPattern(
|
||||
// Patterns are ordered from longest to shortest to ensure correct prefix matching.
|
||||
static constexpr std::string_view kPatterns[] = { "hp_copy_", "mp_copy_", "_" };
|
||||
|
||||
utils::slog.e <<"begin findPattern l(" << line.length() << ")=" <<
|
||||
trim(line.substr(0, std::min(15, (int)line.length())));
|
||||
|
||||
if (line.length() == 2) {
|
||||
utils::slog.e <<"begin findPattern l[0]=" << (int) line[0] << " l[1]=" << (int) line[1] <<
|
||||
|
||||
utils::io::endl;
|
||||
|
||||
}
|
||||
|
||||
size_t trueCount = 1;
|
||||
const size_t line_len = line.length();
|
||||
for (size_t i = offset; i < line_len; ++i) {
|
||||
for (size_t i = offset; i < line_len; ++i, ++trueCount) {
|
||||
// A pattern must be a whole word (or at the start of the string).
|
||||
if (i > 0 && isWordChar(line[i - 1])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& prefix : kPatterns) {
|
||||
trueCount++;
|
||||
if (line.size() - i >= prefix.size() && line.substr(i, prefix.size()) == prefix) {
|
||||
// A known prefix has been matched. Now, check for a sequence of digits.
|
||||
size_t const startOfDigits = i + prefix.size();
|
||||
@@ -145,11 +190,19 @@ std::pair<size_t, size_t> LineDictionary::findPattern(
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (trueCount % 10000 == 0 && trueCount > 0) {
|
||||
utils::slog.e <<"print findPattern at=" << trim(line) <<
|
||||
" count=" << trueCount << utils::io::endl;
|
||||
}
|
||||
}
|
||||
utils::slog.e <<" ----- end findPattern" << utils::io::endl;
|
||||
return { std::string_view::npos, 0 }; // No pattern found
|
||||
}
|
||||
|
||||
std::vector<std::string_view> LineDictionary::splitString(std::string_view const line) {
|
||||
utils::slog.e <<"begin splitString l=" << trim(line.substr(0,
|
||||
std::min(15, (int)line.length())));
|
||||
|
||||
std::vector<std::string_view> result;
|
||||
size_t current_pos = 0;
|
||||
|
||||
@@ -158,7 +211,9 @@ std::vector<std::string_view> LineDictionary::splitString(std::string_view const
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t trueCount = 0;
|
||||
while (current_pos < line.length()) {
|
||||
trueCount++;
|
||||
auto const [match_pos, match_len] = findPattern(line, current_pos);
|
||||
|
||||
if (match_pos == std::string_view::npos) {
|
||||
@@ -177,7 +232,13 @@ std::vector<std::string_view> LineDictionary::splitString(std::string_view const
|
||||
|
||||
// Move cursor past the match.
|
||||
current_pos = match_pos + match_len;
|
||||
|
||||
if (trueCount % 10000 == 0 && trueCount > 0) {
|
||||
utils::slog.e <<"print splitString at=" << trim(line) <<
|
||||
" count=" << trueCount << utils::io::endl;
|
||||
}
|
||||
}
|
||||
utils::slog.e <<"------- end splitString" << utils::io::endl;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -17,20 +17,16 @@
|
||||
#ifndef TNT_CONFIG_H
|
||||
#define TNT_CONFIG_H
|
||||
|
||||
#include <filamat/MaterialBuilder.h>
|
||||
|
||||
#include <filament/MaterialEnums.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <filamat/MaterialBuilder.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
|
||||
namespace matp {
|
||||
|
||||
@@ -44,7 +40,6 @@ public:
|
||||
using Platform = filamat::MaterialBuilder::Platform;
|
||||
using TargetApi = filamat::MaterialBuilder::TargetApi;
|
||||
using Optimization = filamat::MaterialBuilder::Optimization;
|
||||
using Workarounds = filamat::MaterialBuilder::Workarounds;
|
||||
|
||||
// For defines, template, and material parameters, we use an ordered map with a transparent comparator.
|
||||
// Even though the key is stored using std::string, this allows you to make lookups using
|
||||
@@ -80,8 +75,6 @@ public:
|
||||
|
||||
virtual std::string toString() const noexcept = 0;
|
||||
|
||||
virtual std::string toPIISafeString() const noexcept = 0;
|
||||
|
||||
bool isDebug() const noexcept {
|
||||
return mDebug;
|
||||
}
|
||||
@@ -102,6 +95,10 @@ public:
|
||||
return mOptimizationLevel;
|
||||
}
|
||||
|
||||
void setOptimizationLevel(Optimization level) noexcept {
|
||||
mOptimizationLevel = level;
|
||||
}
|
||||
|
||||
Metadata getReflectionTarget() const noexcept {
|
||||
return mReflectionTarget;
|
||||
}
|
||||
@@ -150,10 +147,6 @@ public:
|
||||
return mFeatureLevel;
|
||||
}
|
||||
|
||||
Workarounds getWorkarounds() const noexcept {
|
||||
return mWorkarounds;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool mDebug = false;
|
||||
bool mIsValid = true;
|
||||
@@ -171,7 +164,6 @@ protected:
|
||||
StringReplacementMap mTemplateMap;
|
||||
StringReplacementMap mMaterialParameters;
|
||||
filament::UserVariantFilterMask mVariantFilter = 0;
|
||||
Workarounds mWorkarounds = Workarounds::ALL;
|
||||
bool mIncludeEssl1 = true;
|
||||
};
|
||||
|
||||
|
||||
@@ -501,7 +501,6 @@ bool MaterialParser::parse(filamat::MaterialBuilder& builder,
|
||||
.platform(config.getPlatform())
|
||||
.targetApi(config.getTargetApi())
|
||||
.optimization(config.getOptimizationLevel())
|
||||
.workarounds(config.getWorkarounds())
|
||||
.printShaders(config.printShaders())
|
||||
.saveRawVariants(config.saveRawVariants())
|
||||
.generateDebugInfo(config.isDebug())
|
||||
|
||||
@@ -141,10 +141,6 @@ include_directories(${PUBLIC_HDR_DIR})
|
||||
|
||||
add_library(${TARGET} STATIC ${PUBLIC_HDRS} ${SRCS})
|
||||
|
||||
if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
target_link_libraries(${TARGET} PRIVATE dawncpp_headers)
|
||||
endif()
|
||||
|
||||
target_link_libraries(${TARGET} PUBLIC ${LIBS} filamentapp-resources)
|
||||
|
||||
target_include_directories(${TARGET} PUBLIC ${PUBLIC_HDR_DIR})
|
||||
|
||||
@@ -37,15 +37,8 @@ struct Config {
|
||||
int stereoscopicEyeCount = 2;
|
||||
uint8_t samples = 1;
|
||||
|
||||
// Indicate GPU preference for vulkan
|
||||
// Provided to indicate GPU preference for vulkan
|
||||
std::string vulkanGPUHint;
|
||||
|
||||
|
||||
// Note that WebGPU has its own enums for backends, but to avoid leaking webgpu headers to
|
||||
// consumers of FilamentApp, we just overload the Engine::Backend enum.
|
||||
using WebGPUBackend = filament::Engine::Backend;
|
||||
// Force a backend for webgpu.
|
||||
WebGPUBackend forcedWebGPUBackend = WebGPUBackend::DEFAULT;
|
||||
};
|
||||
|
||||
#endif // TNT_FILAMENT_SAMPLE_CONFIG_H
|
||||
|
||||
@@ -49,17 +49,12 @@ class ImGuiHelper;
|
||||
class IBL;
|
||||
class MeshAssimp;
|
||||
|
||||
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
|
||||
// For customizing the vulkan backend
|
||||
namespace filament::backend {
|
||||
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
|
||||
class VulkanPlatform;
|
||||
#endif
|
||||
|
||||
#if defined(FILAMENT_SUPPORTS_WEBGPU)
|
||||
class WebGPUPlatform;
|
||||
#endif
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
class FilamentApp {
|
||||
public:
|
||||
@@ -266,11 +261,6 @@ private:
|
||||
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
|
||||
filament::backend::VulkanPlatform* mVulkanPlatform = nullptr;
|
||||
#endif
|
||||
|
||||
#if defined(FILAMENT_SUPPORTS_WEBGPU)
|
||||
filament::backend::WebGPUPlatform* mWebGPUPlatform = nullptr;
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
#endif // TNT_FILAMENT_SAMPLE_FILAMENTAPP_H
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
@@ -26,7 +27,6 @@
|
||||
#include <imgui.h>
|
||||
|
||||
#include <utils/EntityManager.h>
|
||||
#include <utils/Logger.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/Path.h>
|
||||
|
||||
@@ -48,10 +48,6 @@
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
#endif
|
||||
|
||||
#if defined(FILAMENT_SUPPORTS_WEBGPU)
|
||||
#include <backend/platforms/WebGPUPlatform.h>
|
||||
#endif
|
||||
|
||||
#include <filagui/ImGuiHelper.h>
|
||||
|
||||
#include <filamentapp/Cube.h>
|
||||
@@ -107,36 +103,6 @@ private:
|
||||
};
|
||||
#endif
|
||||
|
||||
#if defined(FILAMENT_SUPPORTS_WEBGPU)
|
||||
class FilamentAppWebGPUPlatform : public WebGPUPlatform {
|
||||
public:
|
||||
FilamentAppWebGPUPlatform(Config::WebGPUBackend backend)
|
||||
: mBackend(backend) {}
|
||||
|
||||
virtual WebGPUPlatform::Configuration getConfiguration() const noexcept override {
|
||||
WebGPUPlatform::Configuration config = {};
|
||||
switch (mBackend) {
|
||||
case Config::WebGPUBackend::VULKAN:
|
||||
config.forceBackendType = wgpu::BackendType::Vulkan;
|
||||
break;
|
||||
case Config::WebGPUBackend::METAL:
|
||||
config.forceBackendType = wgpu::BackendType::Metal;
|
||||
break;
|
||||
case Config::WebGPUBackend::DEFAULT:
|
||||
break;
|
||||
default:
|
||||
LOG(ERROR) << "FilamentApp: Unsupported webgpu backend was selected(="
|
||||
<< (int) mBackend << "). Selection is ignored.";
|
||||
break;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
private:
|
||||
Config::WebGPUBackend const mBackend;
|
||||
};
|
||||
#endif
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
FilamentApp& FilamentApp::get() {
|
||||
@@ -566,13 +532,6 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback,
|
||||
delete mVulkanPlatform;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(FILAMENT_SUPPORTS_WEBGPU)
|
||||
if (mWebGPUPlatform) {
|
||||
delete mWebGPUPlatform;
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
// RELATIVE_ASSET_PATH is set inside samples/CMakeLists.txt and used to support multi-configuration
|
||||
@@ -703,25 +662,21 @@ FilamentApp::Window::Window(FilamentApp* filamentApp,
|
||||
engineConfig.stereoscopicType = Engine::StereoscopicType::NONE;
|
||||
#endif
|
||||
|
||||
Platform* platform = nullptr;
|
||||
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
|
||||
if (backend == Engine::Backend::VULKAN) {
|
||||
platform = mFilamentApp->mVulkanPlatform =
|
||||
new FilamentAppVulkanPlatform(config.vulkanGPUHint.c_str());
|
||||
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
|
||||
mFilamentApp->mVulkanPlatform =
|
||||
new FilamentAppVulkanPlatform(config.vulkanGPUHint.c_str());
|
||||
return Engine::Builder()
|
||||
.backend(backend)
|
||||
.platform(mFilamentApp->mVulkanPlatform)
|
||||
.featureLevel(config.featureLevel)
|
||||
.config(&engineConfig)
|
||||
.build();
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(FILAMENT_SUPPORTS_WEBGPU)
|
||||
if (backend == Engine::Backend::WEBGPU) {
|
||||
platform = mFilamentApp->mWebGPUPlatform =
|
||||
new FilamentAppWebGPUPlatform(config.forcedWebGPUBackend);
|
||||
}
|
||||
#endif
|
||||
|
||||
return Engine::Builder()
|
||||
.backend(backend)
|
||||
.featureLevel(config.featureLevel)
|
||||
.platform(platform)
|
||||
.config(&engineConfig)
|
||||
.build();
|
||||
};
|
||||
|
||||
@@ -107,12 +107,6 @@ static bool printMaterial(ostream& text, const ChunkContainer& container) {
|
||||
text << name.c_str() << endl;
|
||||
}
|
||||
|
||||
CString compilationParameters;
|
||||
if (read(container, MaterialCompilationParameters, &compilationParameters)) {
|
||||
text << " " << setw(alignment) << left << "Compilation Parameters: ";
|
||||
text << compilationParameters.c_str() << endl;
|
||||
}
|
||||
|
||||
text << endl;
|
||||
|
||||
text << "Shading:" << endl;
|
||||
|
||||
+8
-11
@@ -736,7 +736,7 @@ class MaterialSidePanel extends LitElement {
|
||||
};
|
||||
});
|
||||
}
|
||||
if (props.has('currentMaterial') || props.has('currentBackend')) {
|
||||
if (props.has('currentMaterial')) {
|
||||
if (this.currentBackend && this.database && this.currentMaterial) {
|
||||
const material = this.database[this.currentMaterial];
|
||||
const activeVariants = _validDict(this.activeShaders) ? this.activeShaders[this.currentMaterial].variants : [];
|
||||
@@ -833,15 +833,17 @@ class MaterialSidePanel extends LitElement {
|
||||
vstring = `[${vstring}]`;
|
||||
}
|
||||
const shaderModelSymbol = {
|
||||
'desktop': '(desktop)',
|
||||
'mobile': '(mobile)',
|
||||
'desktop': '🄳 ',
|
||||
'mobile': '🄼 ',
|
||||
};
|
||||
|
||||
let languagesDiv = isVariantSelected ? this._buildLanguagesDiv(isActive) : null;
|
||||
const stage = (variant.shader.pipelineStage.indexOf('vertex') >=0 ? '𝕍ert' : '𝔽rag');
|
||||
const shaderModel = (isMatinfo ? shaderModelSymbol[variant.shader.shaderModel] : '');
|
||||
const stage =
|
||||
(isMatinfo ? shaderModelSymbol[variant.shader.shaderModel] : '') +
|
||||
(variant.shader.pipelineStage.indexOf('vertex') >=0 ? '𝕍ert' : '𝔽rag');
|
||||
return html`
|
||||
<div class="${divClass}" @click="${onClickVariant}">
|
||||
<div>${stage}  ${vstring} ${shaderModel} </div>
|
||||
<div>${stage}  ${vstring} </div>
|
||||
</div>
|
||||
${languagesDiv ?? nothing}
|
||||
`
|
||||
@@ -1069,11 +1071,6 @@ class MatdbgViewer extends LitElement {
|
||||
}
|
||||
|
||||
updated(props) {
|
||||
if (props.has('currentShaderModel')) {
|
||||
if (this.currentShaderModel == 'matinfo') {
|
||||
this.hideInactiveVariants = false;
|
||||
}
|
||||
}
|
||||
// Set a language if there hasn't been one set.
|
||||
if (props.has('currentBackend') && this.currentBackend) {
|
||||
if (LANGUAGE_CHOICES[this.currentBackend].indexOf(this.currentLanguage) < 0) {
|
||||
|
||||
@@ -196,9 +196,7 @@ static void printUsage(char* name) {
|
||||
" a substring to match against the device name\n\n"
|
||||
" --screenshot-as-ppm, -d\n"
|
||||
" export PPM as oppose to TIFF screenshots\n\n"
|
||||
" --webgpu-backend=<backend>, -w\n"
|
||||
" You can force WebGPU to select a backend of your choice. Provided that the platform\n"
|
||||
" supports this backend. (See -a for argument options).\n\n"
|
||||
|
||||
);
|
||||
const std::string from("SHOWCASE");
|
||||
for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) {
|
||||
@@ -217,7 +215,7 @@ static std::ifstream::pos_type getFileSize(const char* filename) {
|
||||
}
|
||||
|
||||
static int handleCommandLineArguments(int argc, char* argv[], App* app) {
|
||||
static constexpr const char* OPTSTR = "ha:f:i:usc:rt:b:evg:dw:";
|
||||
static constexpr const char* OPTSTR = "ha:f:i:usc:rt:b:evg:d";
|
||||
static const struct option OPTIONS[] = {
|
||||
{ "help", no_argument, nullptr, 'h' },
|
||||
{ "api", required_argument, nullptr, 'a' },
|
||||
@@ -234,7 +232,6 @@ static int handleCommandLineArguments(int argc, char* argv[], App* app) {
|
||||
{ "split-view", no_argument, nullptr, 'v' },
|
||||
{ "vulkan-gpu-hint", required_argument, nullptr, 'g' },
|
||||
{ "screenshot-as-ppm", no_argument, nullptr, 'd' },
|
||||
{ "webgpu-backend", required_argument, nullptr, 'w' },
|
||||
{ nullptr, 0, nullptr, 0 }
|
||||
};
|
||||
int opt;
|
||||
@@ -316,10 +313,6 @@ static int handleCommandLineArguments(int argc, char* argv[], App* app) {
|
||||
app->screenshotAsPPM = true;
|
||||
break;
|
||||
}
|
||||
case 'w': {
|
||||
app->config.forcedWebGPUBackend = samples::parseArgumentsForBackend(arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (app->config.headless && app->batchFile.empty()) {
|
||||
|
||||
@@ -37,7 +37,7 @@ function start_render_() {
|
||||
# -W enables the webgpu build
|
||||
# -f forces regeneration of cmake build files
|
||||
# -X points to the mesa directory, which contains the compiled gl and vk drivers.
|
||||
CXX=`which clang++` CC=`which clang` ./build.sh -f -W -X ${MESA_DIR} -p desktop debug gltf_viewer
|
||||
CXX=`which clang++` CC=`which clang` ./build.sh -W -f -X ${MESA_DIR} -p desktop debug gltf_viewer
|
||||
}
|
||||
|
||||
function end_render_() {
|
||||
@@ -55,8 +55,7 @@ function end_render_() {
|
||||
start_render_ && \
|
||||
python3 ${RENDERDIFF_TEST_DIR}/src/render.py \
|
||||
--gltf_viewer="$(pwd)/out/cmake-debug/samples/gltf_viewer" \
|
||||
--test="${RENDERDIFF_TEST_DIR}/tests/presubmit.json" \
|
||||
--output_dir="${RENDER_OUTPUT_DIR}" \
|
||||
--opengl_lib="${MESA_LIB_DIR}" \
|
||||
--vk_icd="${MESA_VK_ICD_PATH}" && \
|
||||
--test=${RENDERDIFF_TEST_DIR}/tests/presubmit.json \
|
||||
--output_dir=${RENDER_OUTPUT_DIR} \
|
||||
--opengl_lib=${MESA_LIB_DIR} && \
|
||||
end_render_
|
||||
|
||||
@@ -41,7 +41,7 @@ def _parse_commit(commit_str):
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
RE_STR = rf"{RDIFF_UPDATE_GOLDEN_STR}(?:\s)?\=(?:\s)?([a-zA-Z0-9\s\-\/]+)"
|
||||
RE_STR = rf"{RDIFF_UPDATE_GOLDEN_STR}(?:S)?=[\[]?([a-zA-Z0-9,\s\-\/]+)[\]]?"
|
||||
|
||||
parser = ArgParseImpl()
|
||||
parser.add_argument('--file', help='A file containing the commit message')
|
||||
|
||||
@@ -6,34 +6,13 @@ import json
|
||||
|
||||
from utils import execute, ArgParseImpl, important_print, mkdir_p
|
||||
from image_diff import same_image, output_image_diff
|
||||
from results import RESULT_OK, RESULT_FAILED, RESULT_MISSING, GOLDEN_MISSING
|
||||
from results import RESULT_OK, RESULT_FAILED, RESULT_MISSING
|
||||
|
||||
def _compare_goldens(base_dir, comparison_dir, out_dir=None):
|
||||
all_files = glob.glob(os.path.join(base_dir, "./**/*.tif"), recursive=True)
|
||||
test_dirs = set(os.path.abspath(os.path.dirname(f)).replace(os.path.abspath(base_dir) + '/', '') \
|
||||
for f in all_files)
|
||||
all_results = []
|
||||
|
||||
def single_test(src_dir, dest_dir, src_fname):
|
||||
src_fname = os.path.abspath(src_fname)
|
||||
test_case = src_fname.replace(f'{src_dir}/', '')
|
||||
dest_fname = os.path.join(dest_dir, test_case)
|
||||
result = {
|
||||
'name': test_case,
|
||||
}
|
||||
if not os.path.exists(dest_fname):
|
||||
result['result'] = RESULT_MISSING
|
||||
elif not same_image(src_fname, dest_fname):
|
||||
result['result'] = RESULT_FAILED
|
||||
if output_test_dir:
|
||||
# just the file name
|
||||
diff_fname = f"{test_case.replace('.tif', '_diff.tif')}"
|
||||
output_image_diff(src_fname, dest_fname, os.path.join(output_test_dir, diff_fname))
|
||||
result['diff'] = diff_fname
|
||||
else:
|
||||
result['result'] = RESULT_OK
|
||||
return result
|
||||
|
||||
for test_dir in test_dirs:
|
||||
results = []
|
||||
output_test_dir = None if not out_dir else os.path.abspath(os.path.join(out_dir, test_dir))
|
||||
@@ -41,23 +20,27 @@ def _compare_goldens(base_dir, comparison_dir, out_dir=None):
|
||||
mkdir_p(output_test_dir)
|
||||
base_test_dir = os.path.abspath(os.path.join(base_dir, test_dir))
|
||||
comp_test_dir = os.path.abspath(os.path.join(comparison_dir, test_dir))
|
||||
results = [
|
||||
single_test(base_test_dir, comp_test_dir, golden_file) \
|
||||
for golden_file in glob.glob(os.path.join(base_test_dir, "*.tif"))
|
||||
]
|
||||
seen_test_cases = set([r['name'] for r in results])
|
||||
|
||||
# For files that are rendered but not in the golden directory
|
||||
for base_file in \
|
||||
glob.glob(os.path.join(comp_test_dir, "*.tif")):
|
||||
src_fname = os.path.abspath(base_file)
|
||||
test_case = base_file.replace(f'{comp_test_dir}/', '')
|
||||
if test_case not in seen_test_cases:
|
||||
results.append({
|
||||
'name': test_case,
|
||||
'result': GOLDEN_MISSING,
|
||||
})
|
||||
|
||||
for golden_file in \
|
||||
glob.glob(os.path.join(base_test_dir, "*.tif")):
|
||||
base_fname = os.path.abspath(golden_file)
|
||||
test_case = base_fname.replace(f'{base_test_dir}/', '')
|
||||
comp_fname = os.path.join(comp_test_dir, test_case)
|
||||
result = {
|
||||
'name': test_case,
|
||||
}
|
||||
if not os.path.exists(comp_fname):
|
||||
print(f'file name not found: {comp_fname}')
|
||||
result['result'] = RESULT_MISSING
|
||||
elif not same_image(base_fname, comp_fname):
|
||||
result['result'] = RESULT_FAILED
|
||||
if output_test_dir:
|
||||
# just the file name
|
||||
diff_fname = f"{test_case.replace('.tif', '_diff.tif')}"
|
||||
output_image_diff(base_fname, comp_fname, os.path.join(output_test_dir, diff_fname))
|
||||
result['diff'] = diff_fname
|
||||
else:
|
||||
result['result'] = RESULT_OK
|
||||
results.append(result)
|
||||
if output_test_dir:
|
||||
output_fname = os.path.join(output_test_dir, "compare_results.json")
|
||||
results_meta = {
|
||||
@@ -87,7 +70,7 @@ if __name__ == '__main__':
|
||||
|
||||
results = _compare_goldens(args.src, dest, out_dir=args.out)
|
||||
|
||||
failed = [f" {k['name']} ({k['result']})" for k in results if k['result'] != RESULT_OK]
|
||||
failed = [f" {k['name']}" for k in results if k['result'] != RESULT_OK]
|
||||
success_count = len(results) - len(failed)
|
||||
important_print(f'Successfully compared {success_count} / {len(results)} images' +
|
||||
('\nFailed:\n' + ('\n'.join(failed)) if len(failed) > 0 else ''))
|
||||
|
||||
@@ -29,7 +29,6 @@ if [[ "$os_name" == "Linux" ]]; then
|
||||
MESA_LIB_DIR="${MESA_DIR}lib/x86_64-linux-gnu"
|
||||
elif [[ "$os_name" == "Darwin" ]]; then
|
||||
MESA_LIB_DIR="${MESA_DIR}lib"
|
||||
MESA_VK_ICD_PATH="${MESA_DIR}share/vulkan/icd.d/lvp_icd.aarch64.json"
|
||||
else
|
||||
echo "Unsupported platform for renderdiff tests"
|
||||
exit 1
|
||||
|
||||
@@ -26,24 +26,15 @@ from golden_manager import GoldenManager
|
||||
from image_diff import same_image
|
||||
from results import RESULT_OK, RESULT_FAILED
|
||||
|
||||
def _render_single_model(gltf_viewer, test_json_path, named_output_dir,
|
||||
test_name, backend, model, model_path, opengl_lib, vk_icd):
|
||||
# We need to pass along the old environment because it might include set up from vulkansdk.
|
||||
env = os.environ.copy()
|
||||
def _render_single_model(gltf_viewer, test_json_path, named_output_dir, test_name, backend, model, model_path, opengl_lib):
|
||||
env = None
|
||||
if backend == 'opengl' and opengl_lib and os.path.isdir(opengl_lib):
|
||||
env |= {
|
||||
env = {
|
||||
'LD_LIBRARY_PATH': opengl_lib,
|
||||
# for macOS
|
||||
'DYLD_LIBRARY_PATH': opengl_lib,
|
||||
}
|
||||
|
||||
if backend == 'vulkan' and os.path.exists(vk_icd):
|
||||
env |= {
|
||||
'VK_ICD_FILENAMES': vk_icd,
|
||||
'VK_DRIVER_FILES': vk_icd,
|
||||
'VK_LOADER_DEBUG': 'all',
|
||||
}
|
||||
|
||||
out_name = f'{test_name}.{backend}.{model}'
|
||||
test_desc = out_name
|
||||
|
||||
@@ -52,10 +43,10 @@ def _render_single_model(gltf_viewer, test_json_path, named_output_dir,
|
||||
|
||||
important_print(f'Rendering {test_desc}')
|
||||
|
||||
out_code, output = execute(
|
||||
out_code, _ = execute(
|
||||
f'{gltf_viewer} -a {backend} --batch={test_json_path} -e {model_path} --headless',
|
||||
cwd=working_dir,
|
||||
env=env, capture_output=True
|
||||
env=env, capture_output=False
|
||||
)
|
||||
|
||||
result = ''
|
||||
@@ -65,10 +56,9 @@ def _render_single_model(gltf_viewer, test_json_path, named_output_dir,
|
||||
out_tif_name = f'{named_output_dir}/{out_tif_basename}'
|
||||
mv_f(f'{working_dir}/{test_name}0.tif', out_tif_name)
|
||||
mv_f(f'{working_dir}/{test_name}0.json', f'{named_output_dir}/{test_name}.json')
|
||||
important_print(f'{test_desc} rendering succeeded. output=\n{output}')
|
||||
else:
|
||||
result = RESULT_FAILED
|
||||
important_print(f'{test_desc} rendering failed with error={out_code}output=\n{output}')
|
||||
important_print(f'{test_desc} rendering failed with error={out_code}')
|
||||
|
||||
return {
|
||||
'name': out_name,
|
||||
@@ -101,18 +91,17 @@ def _render_test_config(gltf_viewer,
|
||||
f.write(f'[{test.to_filament_format()}]')
|
||||
|
||||
for backend in test_config.backends:
|
||||
if backend == 'vulkan':
|
||||
assert vk_icd, "VK ICD must be specified when testing vulkan backend"
|
||||
for model in test.models:
|
||||
model_path = os.path.abspath(test_config.models[model])
|
||||
futures.append(
|
||||
executor.submit(_render_single_model, gltf_viewer_abs,
|
||||
test_json_path, named_output_dir,
|
||||
test.name, backend, model, model_path,
|
||||
opengl_lib, vk_icd))
|
||||
opengl_lib))
|
||||
|
||||
for future in concurrent.futures.as_completed(futures):
|
||||
results.append(future.result())
|
||||
print(results)
|
||||
|
||||
return named_output_dir, results
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
RESULT_OK = 'ok'
|
||||
RESULT_FAILED = 'failed'
|
||||
RESULT_MISSING = 'missing'
|
||||
GOLDEN_MISSING = 'golden missing'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user