Compare commits

..
Author SHA1 Message Date
Powei Feng 5a2b46e134 [WIP] Trying to fix issue 2025-08-14 23:48:03 -07:00
109 changed files with 1473 additions and 1898 deletions
+2 -6
View File
@@ -8,8 +8,8 @@ runs:
uses: actions/cache@v3
id: cache-vulkan-sdk
with:
path: ${{ runner.homedir }}/VulkanSDK
key: vulkansdk-${{ env.GITHUB_VULKANSDK_VERSION }}-2-${{ runner.os }}
path: vulkansdk
key: vulkansdk-${{ env.GITHUB_VULKANSDK_VERSION }}-${{ runner.os }}
- name: Download Vulkan SDK
if: steps.cache-vulkan-sdk.outputs.cache-hit != 'true'
run: |
@@ -21,8 +21,4 @@ runs:
run: |
source ${{ github.workspace }}/build/common/get-vulkan-sdk.sh
unpack_vulkan_installer
pushd .
cd ~/VulkanSDK/${GITHUB_VULKANSDK_VERSION}
sudo ./install_vulkan.py
popd
shell: bash
+180 -171
View File
@@ -9,36 +9,6 @@ on:
- main
jobs:
build-desktop-mac:
name: build-mac
runs-on: macos-14-xlarge
steps:
- uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- uses: ./.github/actions/mac-prereq
- name: Run build script
run: |
cd build/mac && printf "y" | ./build.sh presubmit
- name: Test material parser
run: |
out/cmake-release/filament/test/test_material_parser
build-desktop-linux:
name: build-linux
runs-on: ubuntu-22.04-16core
steps:
- uses: actions/checkout@v4.1.6
with:
fetch-depth: 0
- uses: ./.github/actions/linux-prereq
- name: Run build script
run: |
cd build/linux && printf "y" | ./build.sh presubmit
- name: Test material parser
run: |
out/cmake-release/filament/test/test_material_parser
build-windows:
name: build-windows
runs-on: windows-2022-32core
@@ -48,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
+1
View File
@@ -544,6 +544,7 @@ if (FILAMENT_SUPPORTS_VULKAN)
endif()
if (FILAMENT_SUPPORTS_WEBGPU)
message(AUTHOR_WARNING "WebGPU is in development stage and broken at this stage. Its support is very limited.")
add_definitions(-DFILAMENT_SUPPORTS_WEBGPU)
endif()
+3
View File
@@ -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)
+6 -6
View File
@@ -18,7 +18,7 @@ Filament release archives contains host-side tools that are required to generate
Make sure you always use tools from the same release as the runtime library. This is particularly
important for `matc` (material compiler).
If you'd rather build Filament yourself, please refer to our [build manual](/BUILDING.md).
If you'd rather build Filament yourself, please refer to our [build manual](BUILDING.md).
### Android
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.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
-5
View File
@@ -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 -1
View File
@@ -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.
-3
View File
@@ -137,9 +137,6 @@ if [[ "$OS_NAME" == "Darwin" ]]; then
# This is necessary to be able to build vk (lavapipe) on macOS. Doesn't seem like a real dependency.
sed -I '' "s/error('Vulkan drivers require dri3 for X11 support')//g" meson.build
# This is to properly link lib-xcb-present on the mac build (though we won't be drawing to any
# real hardware surface).
sed -I '' "s/dep_xcb_present = null_dep/dep_xcb_present = dependency('xcb-present')/g" meson.build
fi
# -Dosmesa=true => builds OSMesa, which is an offscreen GL context
+6 -1
View File
@@ -109,8 +109,13 @@ function install_mac() {
unzip -t vulkan_sdk.zip
fi
echo "recognized zip layout 'vulkan_sdk.zip' ${InstallVulkan}.app/Contents" >&2
${InstallVulkan}.app/Contents/MacOS/${InstallVulkan} --accept-licenses --default-answer --confirm-command install
local sdk_temp=${VULKAN_SDK_DIR}.tmp
sudo ${InstallVulkan}.app/Contents/MacOS/${InstallVulkan} --root "$sdk_temp" --accept-licenses --default-answer --confirm-command install
du -hs $sdk_temp
test -d $sdk_temp/macOS || { echo "unrecognized dmg folder layout: $sdk_temp" ; ls -l $sdk_temp ; }
cp -r $sdk_temp/macOS/* $VULKAN_SDK_DIR/
if [[ -d ${InstallVulkan}.app/Contents ]] ; then
sudo rm -rf "$sdk_temp"
rm -rf ${InstallVulkan}.app
fi
}
+3 -1
View File
@@ -135,7 +135,9 @@ set build_flags=-j %NUMBER_OF_PROCESSORS%
:: cmake --build . --target sample-resources --config %config% %build_flags% || exit /b
:: cmake --build . --target suzanne-resources --config %config% %build_flags% || exit /b
cmake --build . %INSTALL% --config %config% %build_flags% -- /m || exit /b
cmake --build . --target sample-resources --config %config% %build_flags% -- /m || exit /b
:: cmake --build . %INSTALL% --config %config% %build_flags% -- /m || exit /b
@echo off
echo Disk info after building variant: %variant%
+2 -4
View File
@@ -221,8 +221,6 @@ export is an html with the output captured in a <code>&lt;pre&gt;</code> tag).</
<li>Replace css styling in the exported output as needed (so they don't interfere with the book's css.</li>
<li>Replace resource urls to refer to locations relative to the mdbook structure.</li>
</ul>
<p>Any <code>markdeep</code> doc can be placed in <code>docs_src/src_markdeep/</code> and they will be parsed to html and included
in the book as above.</p>
<h3 id="readmes"><a class="header" href="#readmes">READMEs</a></h3>
<p>Filament depends on a number of libraries, which reside in the directory <code>libs</code>. These individual
libaries often have README.md in their root to describe itself. We collect these descriptions into our
@@ -231,7 +229,7 @@ located in <code>tools</code>. Some of tools also have README.md as description.
<p>The process for copying and processing these READMEs is outlined in <a href="#introductory-doc">Introductory docs</a>.</p>
<h3 id="other-technical-notes"><a class="header" href="#other-technical-notes">Other technical notes</a></h3>
<p>These are technical documents that do not fit into a library, tool, or directory of the
Filament source tree. We collect them into the <code>docs_src/src_mdbook/src/notes</code> directory. No additional
Filament source tree. We collect them into the <code>docs_src/src/notes</code> directory. No additional
processing is needed for these documents.</p>
<h3 id="raw-source-files"><a class="header" href="#raw-source-files">Raw source files</a></h3>
<p>These are files that are not part of the <code>mdbook</code> generation, but should be included output in <code>/docs</code>
@@ -244,7 +242,7 @@ add a link in <code>SUMMARY.md</code>, and perform the steps outlined in
<a href="#how-to-create">how-to create section</a>.</p>
<p>For example, if you are adding a general technical note, then you would</p>
<ul>
<li>Place the document (file with extension <code>.md</code>) in <code>docs_src/src_mdbook/src/notes</code></li>
<li>Place the document (file with extension <code>.md</code>) in <code>docs_src/src/notes</code></li>
<li>Add a link in <a href="https://github.com/google/filament/blob/main/docs_src/src_mdbook/src/SUMMARY.md"><code>docs_src/src_mdbook/src/SUMMARY.md</code></a></li>
<li>Run the commands in the <a href="#how-to-generate">Generate</a> section</li>
</ul>
+9 -9
View File
@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.64.0'
implementation 'com.google.android.filament:filament-android:1.63.1'
}
</code></pre>
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
@@ -196,18 +196,18 @@ dependencies {
</div>
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
<p>iOS projects can use CocoaPods to install the latest release:</p>
<pre><code class="language-shell">pod 'Filament', '~&gt; 1.64.0'
<pre><code class="language-shell">pod 'Filament', '~&gt; 1.63.1'
</code></pre>
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
<ul>
<li><a href="../main/filament.html">Filament</a>, an in-depth explanation of
<li><a href="https://google.github.io/filament/Filament.html">Filament</a>, an in-depth explanation of
real-time physically based rendering, the graphics capabilities and implementation of Filament.
This document explains the math and reasoning behind most of our decisions. This document is a
good introduction to PBR for graphics programmers.</li>
<li><a href="../main/materials.html">Materials</a>, the full reference
<li><a href="https://google.github.io/filament/Materials.html">Materials</a>, the full reference
documentation for our material system. This document explains our different material models, how
to use the material compiler <code>matc</code> and how to write custom materials.</li>
<li><a href="../notes/material_properties.html">Material Properties</a>, a reference
<li><a href="https://google.github.io/filament/notes/material_properties.html">Material Properties</a>, a reference
sheet for the standard material model.</li>
</ul>
<h2 id="examples"><a class="header" href="#examples">Examples</a></h2>
@@ -395,7 +395,7 @@ by <code>matc</code>:</p>
MaterialInstance* materialInstance = material-&gt;createInstance();
</code></pre>
<p>To learn more about materials and <code>matc</code>, please refer to the
<a href="../main/materials.html">materials documentation</a>.</p>
<a href="./docs/Materials.html">materials documentation</a>.</p>
<p>To render, simply pass the <code>View</code> to the <code>Renderer</code>:</p>
<pre><code class="language-c++">// beginFrame() returns false if we need to skip a frame
if (renderer-&gt;beginFrame(swapChain)) {
@@ -408,7 +408,7 @@ if (renderer-&gt;beginFrame(swapChain)) {
in the <code>samples/</code> directory. These samples are all based on <code>libs/filamentapp/</code> which contains the
code that creates a native window with SDL2 and initializes the Filament engine, renderer and views.</p>
<p>For more information on how to prepare environment maps for image-based lighting please refer to
<a href="building.html#running-the-native-samples">BUILDING.md</a>.</p>
<a href="https://github.com/google/filament/blob/main/BUILDING.md#running-the-native-samples">BUILDING.md</a>.</p>
<h3 id="android-1"><a class="header" href="#android-1">Android</a></h3>
<p>See <code>android/samples</code> for examples of how to use Filament on Android.</p>
<p>You must always first initialize Filament by calling <code>Filament.init()</code>.</p>
@@ -430,7 +430,7 @@ OpenGL ES.</p>
<code>third_party/textures</code> and <code>third_party/environments</code>. These assets are under CC0 license. Please
refer to their respective <code>URL.txt</code> files to know more about the original authors.</p>
<p>Environments must be pre-processed using
<a href="building.html#running-the-native-samples"><code>cmgen</code></a> or
<a href="https://github.com/google/filament/blob/main/BUILDING.md#running-the-native-samples"><code>cmgen</code></a> or
using the <code>libiblprefilter</code> library.</p>
<h2 id="how-to-make-contributions"><a class="header" href="#how-to-make-contributions">How to make contributions</a></h2>
<p>Please read and follow the steps in <a href="contributing.html">CONTRIBUTING.md</a>. Make sure you are
@@ -513,7 +513,7 @@ and tools.</p>
<li><code>web</code>: JavaScript bindings, documentation, and samples</li>
</ul>
<h2 id="license"><a class="header" href="#license">License</a></h2>
<p>Please see <a href="https://github.com/google/filament/blob/main/LICENSE">LICENSE</a>.</p>
<p>Please see <a href="/LICENSE">LICENSE</a>.</p>
<h2 id="disclaimer"><a class="header" href="#disclaimer">Disclaimer</a></h2>
<p>This is not an officially supported Google product.</p>
+563 -10
View File
@@ -1,11 +1,564 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Redirecting...</title>
<meta http-equiv="refresh" content="0; url=./dup/intro.html">
</head>
<body>
</body>
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Introduction - Filament</title>
<!-- Custom HTML head -->
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="shortcut icon" href="favicon.png">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" href="css/chrome.css">
<!-- Fonts -->
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="highlight.css">
<link rel="stylesheet" href="tomorrow-night.css">
<link rel="stylesheet" href="ayu-highlight.css">
<!-- Custom theme stylesheets -->
<!-- MathJax -->
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<!-- Provide site root to javascript -->
<script>
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
</script>
<!-- Start loading toc.js asap -->
<script src="toc.js"></script>
</head>
<body>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('light')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div style="display:flex;align-items:center;justify-content:center">
<img class="flogo" src="images/filament_logo_small.png"></img>
</div>
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<!-- Filament: disable themes because the markdeep part does not look good for dark themes -->
<!--
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
-->
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Filament</h1>
<div class="right-buttons">
<a href="https://github.com/google/filament" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="filament"><a class="header" href="#filament">Filament</a></h1>
<p><a href="https://github.com/google/filament/actions?query=workflow%3AAndroid"><img src="https://github.com/google/filament/workflows/Android/badge.svg" alt="Android Build Status" /></a>
<a href="https://github.com/google/filament/actions?query=workflow%3AiOS"><img src="https://github.com/google/filament/workflows/iOS/badge.svg" alt="iOS Build Status" /></a>
<a href="https://github.com/google/filament/actions?query=workflow%3ALinux"><img src="https://github.com/google/filament/workflows/Linux/badge.svg" alt="Linux Build Status" /></a>
<a href="https://github.com/google/filament/actions?query=workflow%3AmacOS"><img src="https://github.com/google/filament/workflows/macOS/badge.svg" alt="macOS Build Status" /></a>
<a href="https://github.com/google/filament/actions?query=workflow%3AWindows"><img src="https://github.com/google/filament/workflows/Windows/badge.svg" alt="Windows Build Status" /></a>
<a href="https://github.com/google/filament/actions?query=workflow%3AWeb"><img src="https://github.com/google/filament/workflows/Web/badge.svg" alt="Web Build Status" /></a></p>
<p>Filament is a real-time physically based rendering engine for Android, iOS, Linux, macOS, Windows,
and WebGL. It is designed to be as small as possible and as efficient as possible on Android.</p>
<h2 id="download"><a class="header" href="#download">Download</a></h2>
<p><a href="https://github.com/google/filament/releases">Download Filament releases</a> to access stable builds.
Filament release archives contains host-side tools that are required to generate assets.</p>
<p>Make sure you always use tools from the same release as the runtime library. This is particularly
important for <code>matc</code> (material compiler).</p>
<p>If you'd rather build Filament yourself, please refer to our <a href="building.html">build manual</a>.</p>
<h3 id="android"><a class="header" href="#android">Android</a></h3>
<p>Android projects can simply declare Filament libraries as Maven dependencies:</p>
<pre><code class="language-gradle">repositories {
// ...
mavenCentral()
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.63.1'
}
</code></pre>
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
<div class="table-wrapper"><table><thead><tr><th>Artifact</th><th>Description</th></tr></thead><tbody>
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android/badge.svg?subject=filament-android" alt="filament-android" /></a></td><td>The Filament rendering engine itself.</td></tr>
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android-debug"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android-debug/badge.svg?subject=filament-android-debug" alt="filament-android-debug" /></a></td><td>Debug version of <code>filament-android</code>.</td></tr>
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/gltfio-android"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/gltfio-android/badge.svg?subject=gltfio-android" alt="gltfio-android" /></a></td><td>A glTF 2.0 loader for Filament, depends on <code>filament-android</code>.</td></tr>
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-utils-android"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-utils-android/badge.svg?subject=filament-utils-android" alt="filament-utils-android" /></a></td><td>KTX loading, Kotlin math, and camera utilities, depends on <code>gltfio-android</code>.</td></tr>
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android/badge.svg?subject=filamat-android" alt="filamat-android" /></a></td><td>A runtime material builder/compiler. This library is large but contains a full shader compiler/validator/optimizer and supports both OpenGL and Vulkan.</td></tr>
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android-lite"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android-lite/badge.svg?subject=filamat-android-lite" alt="filamat-android-lite" /></a></td><td>A much smaller alternative to <code>filamat-android</code> that can only generate OpenGL shaders. It does not provide validation or optimizations.</td></tr>
</tbody></table>
</div>
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
<p>iOS projects can use CocoaPods to install the latest release:</p>
<pre><code class="language-shell">pod 'Filament', '~&gt; 1.63.1'
</code></pre>
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
<ul>
<li><a href="https://google.github.io/filament/Filament.html">Filament</a>, an in-depth explanation of
real-time physically based rendering, the graphics capabilities and implementation of Filament.
This document explains the math and reasoning behind most of our decisions. This document is a
good introduction to PBR for graphics programmers.</li>
<li><a href="https://google.github.io/filament/Materials.html">Materials</a>, the full reference
documentation for our material system. This document explains our different material models, how
to use the material compiler <code>matc</code> and how to write custom materials.</li>
<li><a href="https://google.github.io/filament/notes/material_properties.html">Material Properties</a>, a reference
sheet for the standard material model.</li>
</ul>
<h2 id="examples"><a class="header" href="#examples">Examples</a></h2>
<p><img src="../images/samples/example_bistro1.jpg" alt="Night scene" />
<img src="../images/samples/example_bistro2.jpg" alt="Night scene" />
<img src="../images/samples/example_materials1.jpg" alt="Materials" />
<img src="../images/samples/example_materials2.jpg" alt="Materials" />
<img src="../images/samples/example_helmet.jpg" alt="Helmet" />
<img src="../images/samples/example_ssr.jpg" alt="Screen-space refraction" /></p>
<h2 id="features"><a class="header" href="#features">Features</a></h2>
<h3 id="apis"><a class="header" href="#apis">APIs</a></h3>
<ul>
<li>Native C++ API for Android, iOS, Linux, macOS and Windows</li>
<li>Java/JNI API for Android</li>
<li>JavaScript API</li>
</ul>
<h3 id="backends"><a class="header" href="#backends">Backends</a></h3>
<ul>
<li>OpenGL 4.1+ for Linux, macOS and Windows</li>
<li>OpenGL ES 3.0+ for Android and iOS</li>
<li>Metal for macOS and iOS</li>
<li>Vulkan 1.0 for Android, Linux, macOS, and Windows</li>
<li>WebGL 2.0 for all platforms</li>
</ul>
<h3 id="rendering"><a class="header" href="#rendering">Rendering</a></h3>
<ul>
<li>Clustered forward renderer</li>
<li>Cook-Torrance microfacet specular BRDF</li>
<li>Lambertian diffuse BRDF</li>
<li>Custom lighting/surface shading</li>
<li>HDR/linear lighting</li>
<li>Metallic workflow</li>
<li>Clear coat</li>
<li>Anisotropic lighting</li>
<li>Approximated translucent (subsurface) materials</li>
<li>Cloth/fabric/sheen shading</li>
<li>Normal mapping &amp; ambient occlusion mapping</li>
<li>Image-based lighting</li>
<li>Physically-based camera (shutter speed, sensitivity and aperture)</li>
<li>Physical light units</li>
<li>Point lights, spot lights, and directional light</li>
<li>Specular anti-aliasing</li>
<li>Point, spot, and directional light shadows</li>
<li>Cascaded shadows</li>
<li>EVSM, PCSS, DPCF, or PCF shadows</li>
<li>Transparent shadows</li>
<li>Contact shadows</li>
<li>Screen-space ambient occlusion</li>
<li>Screen-space reflections</li>
<li>Screen-space refraction</li>
<li>Global fog</li>
<li>Dynamic resolution (with support for AMD FidelityFX FSR)</li>
</ul>
<h3 id="post-processing"><a class="header" href="#post-processing">Post processing</a></h3>
<ul>
<li>HDR bloom</li>
<li>Depth of field bokeh</li>
<li>Multiple tone mappers: generic (customizable), ACES, filmic, etc.</li>
<li>Color and tone management: luminance scaling, gamut mapping</li>
<li>Color grading: exposure, night adaptation, white balance, channel mixer,
shadows/mid-tones/highlights, ASC CDL, contrast, saturation, etc.</li>
<li>TAA, FXAA, MSAA</li>
<li>Screen-space lens flares</li>
</ul>
<h3 id="gltf-20"><a class="header" href="#gltf-20">glTF 2.0</a></h3>
<ul>
<li>
<p>Encodings</p>
<ul>
<li><input disabled="" type="checkbox" checked=""/>
Embeded</li>
<li><input disabled="" type="checkbox" checked=""/>
Binary</li>
</ul>
</li>
<li>
<p>Primitive Types</p>
<ul>
<li><input disabled="" type="checkbox" checked=""/>
Points</li>
<li><input disabled="" type="checkbox" checked=""/>
Lines</li>
<li><input disabled="" type="checkbox"/>
Line Loop</li>
<li><input disabled="" type="checkbox" checked=""/>
Line Strip</li>
<li><input disabled="" type="checkbox" checked=""/>
Triangles</li>
<li><input disabled="" type="checkbox" checked=""/>
Triangle Strip</li>
<li><input disabled="" type="checkbox"/>
Triangle Fan</li>
</ul>
</li>
<li>
<p>Animation</p>
<ul>
<li><input disabled="" type="checkbox" checked=""/>
Transform animation</li>
<li><input disabled="" type="checkbox" checked=""/>
Linear interpolation</li>
<li><input disabled="" type="checkbox" checked=""/>
Morph animation
<ul>
<li><input disabled="" type="checkbox" checked=""/>
Sparse accessor</li>
</ul>
</li>
<li><input disabled="" type="checkbox" checked=""/>
Skin animation</li>
<li><input disabled="" type="checkbox" checked=""/>
Joint animation</li>
</ul>
</li>
<li>
<p>Extensions</p>
<ul>
<li><input disabled="" type="checkbox" checked=""/>
KHR_draco_mesh_compression</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_lights_punctual</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_materials_clearcoat</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_materials_emissive_strength</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_materials_ior</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_materials_pbrSpecularGlossiness</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_materials_sheen</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_materials_transmission</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_materials_unlit</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_materials_variants</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_materials_volume</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_materials_specular</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_mesh_quantization</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_texture_basisu</li>
<li><input disabled="" type="checkbox" checked=""/>
KHR_texture_transform</li>
<li><input disabled="" type="checkbox" checked=""/>
EXT_meshopt_compression</li>
</ul>
</li>
</ul>
<h2 id="rendering-with-filament"><a class="header" href="#rendering-with-filament">Rendering with Filament</a></h2>
<h3 id="native-linux-macos-and-windows"><a class="header" href="#native-linux-macos-and-windows">Native Linux, macOS and Windows</a></h3>
<p>You must create an <code>Engine</code>, a <code>Renderer</code> and a <code>SwapChain</code>. The <code>SwapChain</code> is created from a
native window pointer (an <code>NSView</code> on macOS or a <code>HWND</code> on Windows for instance):</p>
<pre><code class="language-c++">Engine* engine = Engine::create();
SwapChain* swapChain = engine-&gt;createSwapChain(nativeWindow);
Renderer* renderer = engine-&gt;createRenderer();
</code></pre>
<p>To render a frame you must then create a <code>View</code>, a <code>Scene</code> and a <code>Camera</code>:</p>
<pre><code class="language-c++">Camera* camera = engine-&gt;createCamera(EntityManager::get().create());
View* view = engine-&gt;createView();
Scene* scene = engine-&gt;createScene();
view-&gt;setCamera(camera);
view-&gt;setScene(scene);
</code></pre>
<p>Renderables are added to the scene:</p>
<pre><code class="language-c++">Entity renderable = EntityManager::get().create();
// build a quad
RenderableManager::Builder(1)
.boundingBox({{ -1, -1, -1 }, { 1, 1, 1 }})
.material(0, materialInstance)
.geometry(0, RenderableManager::PrimitiveType::TRIANGLES, vertexBuffer, indexBuffer, 0, 6)
.culling(false)
.build(*engine, renderable);
scene-&gt;addEntity(renderable);
</code></pre>
<p>The material instance is obtained from a material, itself loaded from a binary blob generated
by <code>matc</code>:</p>
<pre><code class="language-c++">Material* material = Material::Builder()
.package((void*) BAKED_MATERIAL_PACKAGE, sizeof(BAKED_MATERIAL_PACKAGE))
.build(*engine);
MaterialInstance* materialInstance = material-&gt;createInstance();
</code></pre>
<p>To learn more about materials and <code>matc</code>, please refer to the
<a href="./docs/Materials.html">materials documentation</a>.</p>
<p>To render, simply pass the <code>View</code> to the <code>Renderer</code>:</p>
<pre><code class="language-c++">// beginFrame() returns false if we need to skip a frame
if (renderer-&gt;beginFrame(swapChain)) {
// for each View
renderer-&gt;render(view);
renderer-&gt;endFrame();
}
</code></pre>
<p>For complete examples of Linux, macOS and Windows Filament applications, look at the source files
in the <code>samples/</code> directory. These samples are all based on <code>libs/filamentapp/</code> which contains the
code that creates a native window with SDL2 and initializes the Filament engine, renderer and views.</p>
<p>For more information on how to prepare environment maps for image-based lighting please refer to
<a href="https://github.com/google/filament/blob/main/BUILDING.md#running-the-native-samples">BUILDING.md</a>.</p>
<h3 id="android-1"><a class="header" href="#android-1">Android</a></h3>
<p>See <code>android/samples</code> for examples of how to use Filament on Android.</p>
<p>You must always first initialize Filament by calling <code>Filament.init()</code>.</p>
<p>Rendering with Filament on Android is similar to rendering from native code (the APIs are largely
the same across languages). You can render into a <code>Surface</code> by passing a <code>Surface</code> to the
<code>createSwapChain</code> method. This allows you to render to a <code>SurfaceTexture</code>, a <code>TextureView</code> or
a <code>SurfaceView</code>. To make things easier we provide an Android specific API called <code>UiHelper</code> in the
package <code>com.google.android.filament.android</code>. All you need to do is set a render callback on the
helper and attach your <code>SurfaceView</code> or <code>TextureView</code> to it. You are still responsible for
creating the swap chain in the <code>onNativeWindowChanged()</code> callback.</p>
<h3 id="ios-1"><a class="header" href="#ios-1">iOS</a></h3>
<p>Filament is supported on iOS 11.0 and above. See <code>ios/samples</code> for examples of using Filament on
iOS.</p>
<p>Filament on iOS is largely the same as native rendering with C++. A <code>CAEAGLLayer</code> or <code>CAMetalLayer</code>
is passed to the <code>createSwapChain</code> method. Filament for iOS supports both Metal (preferred) and
OpenGL ES.</p>
<h2 id="assets"><a class="header" href="#assets">Assets</a></h2>
<p>To get started you can use the textures and environment maps found respectively in
<code>third_party/textures</code> and <code>third_party/environments</code>. These assets are under CC0 license. Please
refer to their respective <code>URL.txt</code> files to know more about the original authors.</p>
<p>Environments must be pre-processed using
<a href="https://github.com/google/filament/blob/main/BUILDING.md#running-the-native-samples"><code>cmgen</code></a> or
using the <code>libiblprefilter</code> library.</p>
<h2 id="how-to-make-contributions"><a class="header" href="#how-to-make-contributions">How to make contributions</a></h2>
<p>Please read and follow the steps in <a href="contributing.html">CONTRIBUTING.md</a>. Make sure you are
familiar with the <a href="code_style.html">code style</a>.</p>
<h2 id="directory-structure"><a class="header" href="#directory-structure">Directory structure</a></h2>
<p>This repository not only contains the core Filament engine, but also its supporting libraries
and tools.</p>
<ul>
<li><code>android</code>: Android libraries and projects
<ul>
<li><code>filamat-android</code>: Filament material generation library (AAR) for Android</li>
<li><code>filament-android</code>: Filament library (AAR) for Android</li>
<li><code>filament-utils-android</code>: Extra utilities (KTX loader, math types, etc.)</li>
<li><code>gltfio-android</code>: Filament glTF loading library (AAR) for Android</li>
<li><code>samples</code>: Android-specific Filament samples</li>
</ul>
</li>
<li><code>art</code>: Source for various artworks (logos, PDF manuals, etc.)</li>
<li><code>assets</code>: 3D assets to use with sample applications</li>
<li><code>build</code>: CMake build scripts</li>
<li><code>docs</code>: Documentation
<ul>
<li><code>math</code>: Mathematica notebooks used to explore BRDFs, equations, etc.</li>
</ul>
</li>
<li><code>filament</code>: Filament rendering engine (minimal dependencies)
<ul>
<li><code>backend</code>: Rendering backends/drivers (Vulkan, Metal, OpenGL/ES)</li>
</ul>
</li>
<li><code>ide</code>: Configuration files for IDEs (CLion, etc.)</li>
<li><code>ios</code>: Sample projects for iOS</li>
<li><code>libs</code>: Libraries
<ul>
<li><code>bluegl</code>: OpenGL bindings for macOS, Linux and Windows</li>
<li><code>bluevk</code>: Vulkan bindings for macOS, Linux, Windows and Android</li>
<li><code>camutils</code>: Camera manipulation utilities</li>
<li><code>filabridge</code>: Library shared by the Filament engine and host tools</li>
<li><code>filaflat</code>: Serialization/deserialization library used for materials</li>
<li><code>filagui</code>: Helper library for <a href="https://github.com/ocornut/imgui">Dear ImGui</a></li>
<li><code>filamat</code>: Material generation library</li>
<li><code>filamentapp</code>: SDL2 skeleton to build sample apps</li>
<li><code>filameshio</code>: Tiny filamesh parsing library (see also <code>tools/filamesh</code>)</li>
<li><code>geometry</code>: Mesh-related utilities</li>
<li><code>gltfio</code>: Loader for glTF 2.0</li>
<li><code>ibl</code>: IBL generation tools</li>
<li><code>image</code>: Image filtering and simple transforms</li>
<li><code>imageio</code>: Image file reading / writing, only intended for internal use</li>
<li><code>matdbg</code>: DebugServer for inspecting shaders at run-time (debug builds only)</li>
<li><code>math</code>: Math library</li>
<li><code>mathio</code>: Math types support for output streams</li>
<li><code>utils</code>: Utility library (threads, memory, data structures, etc.)</li>
<li><code>viewer</code>: glTF viewer library (requires gltfio)</li>
</ul>
</li>
<li><code>samples</code>: Sample desktop applications</li>
<li><code>shaders</code>: Shaders used by <code>filamat</code> and <code>matc</code></li>
<li><code>third_party</code>: External libraries and assets
<ul>
<li><code>environments</code>: Environment maps under CC0 license that can be used with <code>cmgen</code></li>
<li><code>models</code>: Models under permissive licenses</li>
<li><code>textures</code>: Textures under CC0 license</li>
</ul>
</li>
<li><code>tools</code>: Host tools
<ul>
<li><code>cmgen</code>: Image-based lighting asset generator</li>
<li><code>filamesh</code>: Mesh converter</li>
<li><code>glslminifier</code>: Minifies GLSL source code</li>
<li><code>matc</code>: Material compiler</li>
<li><code>filament-matp</code>: Material parser</li>
<li><code>matinfo</code> Displays information about materials compiled with <code>matc</code></li>
<li><code>mipgen</code> Generates a series of miplevels from a source image</li>
<li><code>normal-blending</code>: Tool to blend normal maps</li>
<li><code>resgen</code> Aggregates binary blobs into embeddable resources</li>
<li><code>roughness-prefilter</code>: Pre-filters a roughness map from a normal map to reduce aliasing</li>
<li><code>specular-color</code>: Computes the specular color of conductors based on spectral data</li>
</ul>
</li>
<li><code>web</code>: JavaScript bindings, documentation, and samples</li>
</ul>
<h2 id="license"><a class="header" href="#license">License</a></h2>
<p>Please see <a href="/LICENSE">LICENSE</a>.</p>
<h2 id="disclaimer"><a class="header" href="#disclaimer">Disclaimer</a></h2>
<p>This is not an officially supported Google product.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="next prefetch" href="dup/building.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="next prefetch" href="dup/building.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="elasticlunr.min.js"></script>
<script src="mark.min.js"></script>
<script src="searcher.js"></script>
<script src="clipboard.min.js"></script>
<script src="highlight.js"></script>
<script src="book.js"></script>
<!-- Custom JS scripts -->
</div>
</body>
</html>
+2 -2
View File
@@ -160,8 +160,8 @@
<main>
<h1 id="core-concepts"><a class="header" href="#core-concepts">Core Concepts</a></h1>
<ul>
<li><a href="filament.html">Filament</a> - High-level designs; Filament's PBR/math assumptions; implementation details.</li>
<li><a href="materials.html">Materials</a> - A guide to Filament's material definition.</li>
<li><a href="main/filament.html">Filament</a> - High-level designs; Filament's PBR/math assumptions; implementation details.</li>
<li><a href="main/materials.html">Materials</a> - A guide to Filament's material definition.</li>
</ul>
</main>
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -5
View File
@@ -65,9 +65,6 @@ We describe step 1 in detail for the sake of record:
- Replace css styling in the exported output as needed (so they don't interfere with the book's css.
- Replace resource urls to refer to locations relative to the mdbook structure.
Any `markdeep` doc can be placed in `docs_src/src_markdeep/` and they will be parsed to html and included
in the book as above.
### READMEs
Filament depends on a number of libraries, which reside in the directory `libs`. These individual
libaries often have README.md in their root to describe itself. We collect these descriptions into our
@@ -78,7 +75,7 @@ The process for copying and processing these READMEs is outlined in [Introductor
### Other technical notes
These are technical documents that do not fit into a library, tool, or directory of the
Filament source tree. We collect them into the `docs_src/src_mdbook/src/notes` directory. No additional
Filament source tree. We collect them into the `docs_src/src/notes` directory. No additional
processing is needed for these documents.
### Raw source files
@@ -93,7 +90,7 @@ add a link in `SUMMARY.md`, and perform the steps outlined in
[how-to create section](#how-to-create).
For example, if you are adding a general technical note, then you would
- Place the document (file with extension `.md`) in `docs_src/src_mdbook/src/notes`
- Place the document (file with extension `.md`) in `docs_src/src/notes`
- Add a link in [`docs_src/src_mdbook/src/SUMMARY.md`]
- Run the commands in the [Generate](#how-to-generate) section
+1 -6
View File
@@ -2,14 +2,9 @@
"README.md": {
"dest": "dup/intro.md",
"link_transforms": {
"/BUILDING.md#running-the-native-samples": "building.md#running-the-native-samples",
"/BUILDING.md": "building.md",
"BUILDING.md": "building.md",
"/CONTRIBUTING.md": "contributing.md",
"/CODE_STYLE.md": "code_style.md",
"/LICENSE": "https://github.com/google/filament/blob/main/LICENSE",
"https://google.github.io/filament/Filament.html": "../main/filament.md",
"https://google.github.io/filament/Materials.html": "../main/materials.md",
"https://google.github.io/filament/notes/material_properties.html": "../notes/material_properties.md",
"docs/images/samples": "../images/samples"
}
},
+2 -2
View File
@@ -1,5 +1,5 @@
# Core Concepts
- [Filament](filament.md) - High-level designs; Filament's PBR/math assumptions; implementation details.
- [Materials](materials.md) - A guide to Filament's material definition.
- [Filament](main/filament.md) - High-level designs; Filament's PBR/math assumptions; implementation details.
- [Materials](main/materials.md) - A guide to Filament's material definition.
-11
View File
@@ -1,11 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Redirecting...</title>
<meta http-equiv="refresh" content="0; url=./dup/intro.html">
</head>
<body>
</body>
</html>
-7
View File
@@ -258,8 +258,6 @@ if (FILAMENT_SUPPORTS_WEBGPU)
include/backend/platforms/WebGPUPlatform.h
src/webgpu/platform/WebGPUPlatform.cpp
src/webgpu/SpdMipmapGenerator/SpdMipmapGenerator.cpp
src/webgpu/utils/AsyncTaskCounter.cpp
src/webgpu/utils/AsyncTaskCounter.h
src/webgpu/utils/StringPlaceholderTemplateProcessor.cpp
src/webgpu/utils/StringPlaceholderTemplateProcessor.h
src/webgpu/WebGPUBlitter.cpp
@@ -414,7 +412,6 @@ endif()
if (APPLE AND NOT IOS)
target_link_libraries(${TARGET} PRIVATE "-framework Cocoa")
target_link_libraries(${TARGET} PRIVATE "-framework QuartzCore")
endif()
target_link_libraries(${TARGET} PUBLIC math)
@@ -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()
+1 -1
View File
@@ -156,7 +156,7 @@ public:
* Set to `true` to forcibly disable amortized shader compilation in the backend.
* Currently only honored by the GL backend.
*/
bool disableAmortizedShaderCompile = true;
bool disableAmortizedShaderCompile = false;
/**
* Disable backend handles use-after-free checks.
@@ -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;
+5 -7
View File
@@ -97,13 +97,11 @@ void CommandStream::execute(void* buffer) {
}
}
Driver& UTILS_RESTRICT driver = mDriver;
CommandBase* UTILS_RESTRICT base = static_cast<CommandBase*>(buffer);
mDriver.execute([&driver, base] {
auto& d = driver;
auto p = base;
while (UTILS_LIKELY(p)) {
p = p->execute(d);
mDriver.execute([this, buffer]() {
Driver& UTILS_RESTRICT driver = mDriver;
CommandBase* UTILS_RESTRICT base = static_cast<CommandBase*>(buffer);
while (UTILS_LIKELY(base)) {
base = base->execute(driver);
}
});
@@ -118,10 +118,6 @@ MetalSwapChain::MetalSwapChain(
layerDrawableMutex(std::make_shared<std::mutex>()),
type(SwapChainType::CAMETALLAYER) {
FILAMENT_CHECK_PRECONDITION([nativeWindow isKindOfClass:[CAMetalLayer class]])
<< "nativeWindow pointer of class "
<< [NSStringFromClass([nativeWindow class]) UTF8String] << " is not a CAMetalLayer";
if (!(flags & SwapChain::CONFIG_TRANSPARENT) && !nativeWindow.opaque) {
LOG(WARNING) << "Warning: Filament SwapChain has no CONFIG_TRANSPARENT flag, but the "
"CAMetaLayer("
+1 -1
View File
@@ -1948,7 +1948,7 @@ void OpenGLDriver::destroyTexture(Handle<HwTexture> th) {
if (UTILS_UNLIKELY(t->hwStream)) {
detachStream(t);
}
if (UTILS_UNLIKELY(t->externalTexture)) {
if (UTILS_UNLIKELY(t->target == SamplerType::SAMPLER_EXTERNAL)) {
mPlatform.destroyExternalImageTexture(t->externalTexture);
} else {
glDeleteTextures(1, &t->gl.id);
@@ -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);
}
+1 -6
View File
@@ -308,10 +308,7 @@ struct VulkanRenderTarget : private HwRenderTarget, fvkmemory::Resource {
// Creates a special "default" render target (i.e. associated with the swap chain)
explicit VulkanRenderTarget();
VulkanRenderTarget(VulkanRenderTarget&& target)
: HwRenderTarget(0, 0),
mOffscreen(false),
mProtected(false) {
VulkanRenderTarget(VulkanRenderTarget&& target) {
swap(std::move(target));
}
@@ -368,8 +365,6 @@ struct VulkanRenderTarget : private HwRenderTarget, fvkmemory::Resource {
private:
void swap(VulkanRenderTarget&& target) {
std::swap(width, target.width);
std::swap(height, target.height);
std::swap(mOffscreen, target.mOffscreen);
std::swap(mProtected, target.mProtected);
std::swap(mInfo, target.mInfo);
+24 -50
View File
@@ -58,10 +58,10 @@ namespace {
areCopyCompatible(sourceFormat, destinationFormat) &&
(sourceExtent.width == destinationExtent.width) &&
(sourceExtent.height == destinationExtent.height) &&
((sourceOrigin.x % sourceBlockSize.width) == 0) &&
((sourceOrigin.y % sourceBlockSize.height) == 0) &&
((destinationOrigin.x % destinationBlockSize.width) == 0) &&
((destinationOrigin.y % destinationBlockSize.height) == 0);
((sourceOrigin.x & sourceBlockSize.width) == 0) &&
((sourceOrigin.y & sourceBlockSize.height) == 0) &&
((destinationOrigin.x & destinationBlockSize.width) == 0) &&
((destinationOrigin.y & destinationBlockSize.height) == 0);
}
struct BlitFragmentShaderArgs final {
@@ -206,20 +206,9 @@ constexpr std::string_view FRAGMENT_SHADER_SNIPPET_2D_INPUT_TEMPLATE{ R"(
} // namespace
// It can perform a direct memory copy if the formats are compatible and no scaling or format
// conversion is needed. Otherwise, it uses a render pass with a custom shader to perform
// the blit. This allows for scaling, format conversion, and resolving multisampled textures.
// The blitter caches pipelines, layouts, and shaders to avoid recompilation.
WebGPUBlitter::WebGPUBlitter(wgpu::Device const& device)
: mDevice{ device } {}
// Performs a blit operation from a source to a destination texture.
// This function first checks if a direct texture-to-texture copy can be performed.
// A direct copy is possible if the source and destination textures have the same sample count,
// compatible formats, and the copy extents match. If a direct copy is not possible, this
// function falls back to a render pass-based blit. The render pass uses a shader to read from
// the source texture and write to the destination texture, allowing for format conversion,
// scaling, and MSAA resolves.
void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& commandEncoder,
BlitArgs const& args) {
// current assumptions/simplifications made in this implementation:
@@ -278,20 +267,22 @@ void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& c
args.destination.texture.GetUsage() & wgpu::TextureUsage::CopyDst)
<< "destination texture usage doesn't have wgpu::TextureUsage::CopyDst";
copyByteByByte(commandEncoder, args);
copy(commandEncoder, args);
return;
}
// need to do a render pass blit...
FILAMENT_CHECK_PRECONDITION(args.destination.texture.GetSampleCount() == 1)
<< "Blitting does not currently support writing to multisampled output textures.";
FILAMENT_CHECK_PRECONDITION(args.source.texture.GetUsage() & wgpu::TextureUsage::TextureBinding)
<< "Source texture usage doesn't have wgpu::TextureUsage::TextureBinding";
<< "source texture usage doesn't have wgpu::TextureUsage::TextureBinding";
FILAMENT_CHECK_PRECONDITION(
args.destination.texture.GetUsage() & wgpu::TextureUsage::RenderAttachment)
<< "Destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
<< "destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
// create input/output texture views...
const wgpu::TextureViewDimension sourceDimension{ args.source.texture.GetDimension() ==
wgpu::TextureDimension::e3D
? wgpu::TextureViewDimension::e3D
@@ -336,6 +327,7 @@ void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& c
<< destinationViewDescriptor.baseArrayLayer << " and mip level "
<< destinationViewDescriptor.baseMipLevel << " for render pass blit?";
// create uniform buffer (for shader args)...
const BlitFragmentShaderArgs blitFragmentShaderArgs{
.depthPlane = args.source.layerOrDepth,
.scale = { static_cast<float>(args.source.extent.width) / args.destination.extent.width,
@@ -446,7 +438,7 @@ void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& c
const wgpu::RenderPassEncoder renderPassEncoder{ commandEncoder.BeginRenderPass(
&renderPassDescriptor) };
FILAMENT_CHECK_POSTCONDITION(renderPassEncoder)
<< "Failed to create render pass encoder for blit.";
<< "Failed to create render pass encoder for blit?";
renderPassEncoder.SetPipeline(getOrCreateRenderPipeline(args.filter, sourceDimension,
args.source.texture.GetSampleCount(), depthSource,
args.destination.texture.GetFormat()));
@@ -456,7 +448,7 @@ void WebGPUBlitter::blit(wgpu::Queue const& queue, wgpu::CommandEncoder const& c
renderPassEncoder.End();
}
void WebGPUBlitter::copyByteByByte(wgpu::CommandEncoder const& commandEncoder, BlitArgs const& args) {
void WebGPUBlitter::copy(wgpu::CommandEncoder const& commandEncoder, BlitArgs const& args) {
const wgpu::TexelCopyTextureInfo sourceCopyInfo{
.texture = args.source.texture,
.mipLevel = args.source.mipLevel,
@@ -513,7 +505,7 @@ void WebGPUBlitter::createSampler(const SamplerMagFilter filter) {
.maxAnisotropy = 1, // should not matter, just being consistently defined
};
const wgpu::Sampler sampler{ mDevice.CreateSampler(&descriptor) };
FILAMENT_CHECK_POSTCONDITION(sampler) << "Failed to create blit sampler.";
FILAMENT_CHECK_POSTCONDITION(sampler) << "Failed to create blit sampler?";
switch (filter) {
case SamplerMagFilter::NEAREST:
mNearestSampler = sampler;
@@ -524,8 +516,6 @@ void WebGPUBlitter::createSampler(const SamplerMagFilter filter) {
}
}
// Caches and returns a render pipeline for a given blit configuration.
// If a pipeline for the given configuration does not exist, it creates and caches one.
wgpu::RenderPipeline const& WebGPUBlitter::getOrCreateRenderPipeline(
const SamplerMagFilter filterType, const wgpu::TextureViewDimension sourceDimension,
const uint32_t sourceSampleCount, const bool depthSource,
@@ -539,9 +529,6 @@ wgpu::RenderPipeline const& WebGPUBlitter::getOrCreateRenderPipeline(
return mRenderPipelines[key];
}
// Creates a render pipeline for a blit operation.
// The pipeline is configured with the appropriate vertex and fragment shaders,
// pipeline layout, and render target state based on the blit parameters.
wgpu::RenderPipeline WebGPUBlitter::createRenderPipeline(const SamplerMagFilter filterType,
const wgpu::TextureViewDimension sourceDimension, const uint32_t sourceSampleCount,
const bool depthSource, const wgpu::TextureFormat destinationTextureFormat) {
@@ -593,7 +580,7 @@ wgpu::RenderPipeline WebGPUBlitter::createRenderPipeline(const SamplerMagFilter
.fragment = &fragmentState,
};
const wgpu::RenderPipeline pipeline{ mDevice.CreateRenderPipeline(&pipelineDescriptor) };
FILAMENT_CHECK_POSTCONDITION(pipeline) << "Failed to create pipeline for render pass blit.";
FILAMENT_CHECK_POSTCONDITION(pipeline) << "Failed to create pipeline for render pass blit?";
return pipeline;
}
@@ -608,8 +595,6 @@ size_t WebGPUBlitter::hashRenderPipelineKey(const SamplerMagFilter filterType,
return seed;
}
// Caches and returns a pipeline layout for a given blit configuration.
// If a layout for the given configuration does not exist, it creates and caches one.
wgpu::PipelineLayout const& WebGPUBlitter::getOrCreatePipelineLayout(
const SamplerMagFilter filterType, const wgpu::TextureViewDimension sourceDimension,
const bool multisampledSource, const bool depthSource) {
@@ -622,8 +607,6 @@ wgpu::PipelineLayout const& WebGPUBlitter::getOrCreatePipelineLayout(
return mPipelineLayouts[key];
}
// Creates a pipeline layout for a blit operation.
// The layout defines the bind group layouts used by the blit pipeline.
wgpu::PipelineLayout WebGPUBlitter::createPipelineLayout(const SamplerMagFilter filterType,
const wgpu::TextureViewDimension sourceDimension, const bool multisampledSource,
const bool depthSource) {
@@ -636,7 +619,7 @@ wgpu::PipelineLayout WebGPUBlitter::createPipelineLayout(const SamplerMagFilter
const wgpu::PipelineLayout pipelineLayout{ mDevice.CreatePipelineLayout(
&pipelineLayoutDescriptor) };
FILAMENT_CHECK_POSTCONDITION(pipelineLayout)
<< "Failed to create pipeline layout for render pass blit.";
<< "Failed to create pipeline layout for render pass blit?";
return pipelineLayout;
}
@@ -650,8 +633,6 @@ size_t WebGPUBlitter::hashPipelineLayoutKey(const SamplerMagFilter filterType,
return seed;
}
// Caches and returns a bind group layout for a given blit configuration.
// If a layout for the given configuration does not exist, it creates and caches one.
wgpu::BindGroupLayout const& WebGPUBlitter::getOrCreateTextureBindGroupLayout(
const SamplerMagFilter filterType, const wgpu::TextureViewDimension sourceDimension,
const bool multisampledSource, const bool depthSource) {
@@ -664,9 +645,6 @@ wgpu::BindGroupLayout const& WebGPUBlitter::getOrCreateTextureBindGroupLayout(
return mTextureBindGroupLayouts[key];
}
// Creates a bind group layout for the blit operation's texture resources.
// This layout specifies the bindings for the source texture, a uniform buffer with blit parameters,
// and optionally a sampler.
wgpu::BindGroupLayout WebGPUBlitter::createTextureBindGroupLayout(const SamplerMagFilter filterType,
const wgpu::TextureViewDimension sourceDimension, const bool multisampledSource,
const bool depthSource) {
@@ -677,11 +655,11 @@ wgpu::BindGroupLayout WebGPUBlitter::createTextureBindGroupLayout(const SamplerM
.texture = {
.sampleType = depthSource
? wgpu::TextureSampleType::Depth
: (multisampledSource
? wgpu::TextureSampleType::UnfilterableFloat
: wgpu::TextureSampleType::Float), // only F32 scalar sample
// type supported for now
// (aside from depth)
: wgpu::TextureSampleType::Float, // only F32 scalar sample
// type supported for now
// (aside from depth)
// see assumptions listed
// in blit function
.viewDimension = sourceDimension,
.multisampled = multisampledSource,
},
@@ -715,7 +693,7 @@ wgpu::BindGroupLayout WebGPUBlitter::createTextureBindGroupLayout(const SamplerM
const wgpu::BindGroupLayout textureBindGroupLayout{ mDevice.CreateBindGroupLayout(
&textureBindGroupLayoutDescriptor) };
FILAMENT_CHECK_POSTCONDITION(textureBindGroupLayout)
<< "Failed to create texture bind group layout for render pass blit.";
<< "Failed to create texture bind group layout for render pass blit?";
return textureBindGroupLayout;
}
@@ -729,8 +707,6 @@ size_t WebGPUBlitter::hashTextureBindGroupLayoutKey(const SamplerMagFilter filte
return seed;
}
// Caches and returns a shader module for a given blit configuration.
// If a shader module for the given configuration does not exist, it creates and caches one.
wgpu::ShaderModule const& WebGPUBlitter::getOrCreateShaderModule(
const wgpu::TextureViewDimension sourceDimension, const bool multisampledSource,
const bool depthSource, const bool depthDestination) {
@@ -743,9 +719,6 @@ wgpu::ShaderModule const& WebGPUBlitter::getOrCreateShaderModule(
return mShaderModules[key];
}
// Creates a shader module containing the vertex and fragment shaders for the blit operation.
// The shader source is generated from a template, with placeholders filled in based on the
// blit configuration (e.g., texture type, sample count).
wgpu::ShaderModule WebGPUBlitter::createShaderModule(
const wgpu::TextureViewDimension sourceDimension, const bool multisampledSource,
const bool depthSource, const bool depthDestination) {
@@ -757,8 +730,9 @@ wgpu::ShaderModule WebGPUBlitter::createShaderModule(
textureType = "texture_depth_2d";
}
} else {
// non-depth
if (multisampledSource) {
textureType = "texture_multisampled_2d<f32>";
textureType = "texture_multisampled_2d";
} else {
if (sourceDimension == wgpu::TextureViewDimension::e3D) {
textureType = "texture_3d<f32>";
@@ -811,7 +785,7 @@ wgpu::ShaderModule WebGPUBlitter::createShaderModule(
};
const wgpu::ShaderModule shaderModule{ mDevice.CreateShaderModule(&shaderModuleDescriptor) };
FILAMENT_CHECK_POSTCONDITION(shaderModule)
<< "Failed to create shader module for render pass blit.";
<< "Failed to create shader module for render pass blit?";
return shaderModule;
}
+1 -17
View File
@@ -26,17 +26,6 @@
namespace filament::backend {
/**
* A blit essentially writes pixels to a region of one texture given a region of another texture.
* Such a process can be a byte-by-byte copy when possible, while other scenarios involve various
* transformations, such as pixel format conversion, image scaling, filtering/sampling
* (such as down sampling), or even multi-sample resolves. Some blits can be faster than others,
* depending on what is needed; for example, a byte-by-byte copy is generally faster than other
* operations.
*
* This class is responsible for performing blits throughout the lifecycle of its owner/caller,
* e.g. the WebGPU backend/driver, where it can leverage state to optimize such blit calls over time.
*/
class WebGPUBlitter final {
public:
struct BlitArgs final {
@@ -69,15 +58,10 @@ private:
/**
* ONLY should be called if canDoDirectCopy(...) is true (see it in WebGPUBlitter.cpp)
*/
void copyByteByByte(wgpu::CommandEncoder const&, BlitArgs const&);
void copy(wgpu::CommandEncoder const&, BlitArgs const&);
void createSampler(SamplerMagFilter);
// The following methods are used to create and cache WebGPU objects.
// The pattern is to have a `getOrCreate...` method that looks up the object in a cache,
// and if it's not found, it calls a `create...` method to create it and then stores it in the
// cache. A `hash...` method is used to generate a key for the cache.
[[nodiscard]] wgpu::RenderPipeline const& getOrCreateRenderPipeline(SamplerMagFilter,
wgpu::TextureViewDimension sourceDimension, uint32_t sourceSampleCount,
bool depthSource, wgpu::TextureFormat destinationTextureFormat);
@@ -33,9 +33,6 @@ namespace filament::backend {
namespace {
// Creates a wgpu::Buffer, ensuring its size is a multiple of FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS.
// WebGPU's WriteBuffer requires the write size to be a multiple of 4. By ensuring the buffer
// size is also a multiple of 4, we simplify the update logic.
[[nodiscard]] wgpu::Buffer createBuffer(wgpu::Device const& device, const wgpu::BufferUsage usage,
uint32_t size, const char* const label) {
// Write size must be divisible by WEBGPU_BUFFER_SIZE_MODULUS (e.g. 4).
@@ -58,11 +55,6 @@ WebGPUBufferBase::WebGPUBufferBase(wgpu::Device const& device, const wgpu::Buffe
const uint32_t size, char const* const label)
: mBuffer{ createBuffer(device, usage, size, label) } {}
// Updates the GPU buffer with data from a BufferDescriptor.
// WebGPU requires that the size of the data written to a buffer is a multiple of 4.
// This function handles cases where the buffer descriptor's size is not a multiple of 4
// by writing the bulk of the data first, and then copying the remaining bytes into a
// padded temporary chunk which is then written to the buffer.
void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
const uint32_t byteOffset, wgpu::Queue const& queue) {
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.buffer)
@@ -28,10 +28,6 @@ namespace filament::backend {
class BufferDescriptor;
/**
* A base class for WebGPU buffer objects, providing common functionality for creating and
* updating GPU buffers.
*/
class WebGPUBufferBase /* intended to be extended */ {
public:
/**
@@ -50,8 +46,7 @@ protected:
private:
const wgpu::Buffer mBuffer;
// WebGPU requires that the source buffer of a writeBuffer call has a size that is a multiple
// of 4. This member is used to pad the data if the source size is not a multiple of 4.
// FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS (e.g. 4) bytes to hold any extra chunk we need.
std::array<uint8_t, FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS> mRemainderChunk{};
};
@@ -40,8 +40,6 @@ namespace {
} // namespace
// The usage flags are determined by the binding type, and always include CopyDst to allow for
// updating the buffer.
WebGPUBufferObject::WebGPUBufferObject(wgpu::Device const& device,
const BufferObjectBinding bindingType, const uint32_t byteCount)
: HwBufferObject{ byteCount },
@@ -31,12 +31,6 @@ namespace filament::backend {
enum class BufferObjectBinding : uint8_t;
/**
* A WebGPU-specific implementation of BufferObject.
* A BufferObject is a GPU buffer that can be used for a variety of purposes, such as storing
* uniform data, vertex attributes, or pixel data. The specific usage is determined by the
* BufferObjectBinding.
*/
class WebGPUBufferObject final : public HwBufferObject, public WebGPUBufferBase {
public:
WebGPUBufferObject(wgpu::Device const&, BufferObjectBinding, uint32_t byteCount);
@@ -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 {
+106 -272
View File
@@ -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, &copySize);
mCommandBuffer = mCommandEncoder.Finish();
assert_invariant(mCommandBuffer);
mCommandEncoder = nullptr;
mQueue.Submit(1, &mCommandBuffer);
mCommandBuffer = nullptr;
.depthOrArrayLayers = 1 };
// TODO consider removing encoder finish + submit once we have test coverage and can
// safely verify doing so will not result in strange errors. We don't think this
// submit is necessary, as the encoder should get finished and the command
// submitted in commit(), finish(), and/or flush() in this frame
// or the next anyway. And, the read/copy bits will be encoded before other things.
// Thus, not submitting here _seems_ ok. Nonetheless, doing the submit here likely
// represents unnecessary inefficiency and higher bandwidth
// between the CPU and GPU, but we are worried about functional correctness at this
// stage.
encoder.CopyTextureToBuffer(&source, &destination, &copySize);
wgpu::CommandBuffer commandBuffer = encoder.Finish();
mQueue.Submit(1, &commandBuffer);
// Map the buffer to read the data
struct UserData final {
struct UserData {
PixelBufferDescriptor pixelBufferDescriptor;
wgpu::Buffer buffer;
size_t unpaddedBytesPerRow;
size_t paddedBytesPerRow;
uint32_t height;
WebGPUDriver *driver;
WebGPUDriver* driver;
};
auto userData = std::make_unique<UserData>(UserData{
.pixelBufferDescriptor = std::move(pixelBufferDescriptor),
.buffer = std::move(stagingBuffer),
.unpaddedBytesPerRow = unpaddedBytesPerRow,
.paddedBytesPerRow = paddedBytesPerRow,
.height = actualHeight,
.driver = this,
.pixelBufferDescriptor = std::move(pixelBufferDescriptor),
.buffer = std::move(stagingBuffer),
.unpaddedBytesPerRow = unpaddedBytesPerRow,
.paddedBytesPerRow = paddedBytesPerRow,
.height = height,
.driver = this,
});
mReadPixelMapsCounter.startTask();
userData->buffer.MapAsync(
wgpu::MapMode::Read, 0, bufferSize, wgpu::CallbackMode::AllowSpontaneous,
[](wgpu::MapAsyncStatus status, const char* message, UserData* userdata) {
std::unique_ptr<UserData> data(static_cast<UserData*>(userdata));
if (UTILS_LIKELY(status == wgpu::MapAsyncStatus::Success)) {
const char *src {static_cast<const char *>(
data->buffer.GetConstMappedRange(0, data->buffer.GetSize()))};
char* dst{ static_cast<char*>(data->pixelBufferDescriptor.buffer) };
// If padding was added for alignment, we need to copy row by row.
if (data->paddedBytesPerRow == data->unpaddedBytesPerRow) {
memcpy(dst, src, data->unpaddedBytesPerRow * data->height);
} else {
for (uint32_t i{ 0 }; i < data->height; ++i) {
memcpy(dst + i * data->unpaddedBytesPerRow,
src + i * data->paddedBytesPerRow, data->unpaddedBytesPerRow);
}
const char* src = static_cast<const char*>(
data->buffer.GetConstMappedRange(0, data->buffer.GetSize()));
char* dst = static_cast<char*>(data->pixelBufferDescriptor.buffer);
for (uint32_t i = 0; i < data->height; ++i) {
memcpy(dst + i * data->unpaddedBytesPerRow,
src + i * data->paddedBytesPerRow, data->unpaddedBytesPerRow);
}
data->buffer.Unmap();
} else {
FWGPU_LOGE << "Failed to map staging buffer for readPixels: " << message;
}
data->driver->scheduleDestroy(std::move(data->pixelBufferDescriptor));
data->driver->mReadPixelMapsCounter.finishTask();
},
userData.release());
}
@@ -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
+3 -9
View File
@@ -24,7 +24,6 @@
#include "webgpu/WebGPUPipelineCache.h"
#include "webgpu/WebGPUPipelineLayoutCache.h"
#include "webgpu/WebGPURenderPassMipmapGenerator.h"
#include "webgpu/utils/AsyncTaskCounter.h"
#include <backend/platforms/WebGPUPlatform.h>
#include "DriverBase.h"
@@ -48,12 +47,8 @@
namespace filament::backend {
class WebGPUSwapChain;
/**
* Implements the private backend driver API for WebGPU specifically (that API is essentially
* expressed in private/backend/DriverAPI.inc)
*
* It manages all the WebGPU resources necessary to accomplish this.
* WebGPU backend (driver) implementation
*/
class WebGPUDriver final : public DriverBase {
public:
@@ -69,8 +64,8 @@ private:
[[nodiscard]] wgpu::Sampler makeSampler(SamplerParams const& params);
[[nodiscard]] static wgpu::AddressMode fWrapModeToWAddressMode(const filament::backend::SamplerWrapMode& fUsage);
// The platform (e.g. OS) specific aspects of the WebGPU backend are strictly only
// handled in the WebGPUPlatform.
// the platform (e.g. OS) specific aspects of the WebGPU backend are strictly only
// handled in the WebGPUPlatform
WebGPUPlatform& mPlatform;
wgpu::Adapter mAdapter = nullptr;
wgpu::Device mDevice = nullptr;
@@ -91,7 +86,6 @@ private:
spd::MipmapGenerator mSpdComputePassMipmapGenerator;
WebGPUMsaaTextureResolver mMsaaTextureResolver{};
WebGPUBlitter mBlitter;
webgpuutils::AsyncTaskCounter mReadPixelMapsCounter{};
struct DescriptorSetBindingInfo{
wgpu::BindGroup bindGroup;
+3 -2
View File
@@ -28,11 +28,12 @@ namespace filament::backend {
FenceStatus WebGPUFence::getStatus() { return mStatus.load(); }
void WebGPUFence::addMarkerToQueueState(wgpu::Queue const& queue) {
// The lambda function is called when the work is done. It updates the fence status based on the
// result of the work.
queue.OnSubmittedWorkDone(
wgpu::CallbackMode::AllowSpontaneous,
[this](const wgpu::QueueWorkDoneStatus status, wgpu::StringView message) {
// Note: The 'message' parameter is required by the function signature
// but can be ignored if not needed.
switch (status) {
case wgpu::QueueWorkDoneStatus::Success:
mStatus.store(FenceStatus::CONDITION_SATISFIED);
@@ -28,11 +28,6 @@ class Queue;
namespace filament::backend {
/**
* A WebGPU-specific implementation of the HwFence.
* This class is used to synchronize the CPU and GPU. It allows the CPU to know when the GPU has
* finished a set of commands.
*/
class WebGPUFence final : public HwFence {
public:
[[nodiscard]] FenceStatus getStatus();
@@ -40,9 +35,6 @@ public:
void addMarkerToQueueState(wgpu::Queue const&);
private:
// The current status of the fence.
// This is atomic because it can be updated by a WebGPU callback thread and read from the main
// thread.
std::atomic<FenceStatus> mStatus{ FenceStatus::TIMEOUT_EXPIRED };
};
@@ -30,10 +30,6 @@ enum class IndexFormat : uint32_t;
namespace filament::backend {
/**
* A WebGPU implementation of the HwIndexBuffer.
* This class represents a GPU buffer that stores indices for indexed drawing.
*/
class WebGPUIndexBuffer final : public HwIndexBuffer, public WebGPUBufferBase {
public:
WebGPUIndexBuffer(wgpu::Device const&, uint8_t elementSize, uint32_t indexCount);
@@ -48,8 +48,8 @@ void resolveColorTextures(wgpu::CommandEncoder const& commandEncoder,
const wgpu::RenderPassEncoder renderPassEncoder{ commandEncoder.BeginRenderPass(
&renderPassDescriptor) };
FILAMENT_CHECK_POSTCONDITION(renderPassEncoder)
<< "Failed to create wgpu::RenderPassEncoder for MSAA resolve.";
renderPassEncoder.End(); // The resolve is implicit in the pass.
<< "Failed to create wgpu::RenderPassEncoder for WebGPUDriver::resolve";
renderPassEncoder.End(); // only the implicit resolve is happening in the pass
}
} // namespace
@@ -59,29 +59,30 @@ void WebGPUMsaaTextureResolver::resolve(ResolveRequest const& request) {
ResolveRequest::TextureInfo const& destination{ request.destination };
FILAMENT_CHECK_PRECONDITION(destination.texture.GetWidth() == source.texture.GetWidth() &&
destination.texture.GetHeight() == source.texture.GetHeight())
<< "Invalid resolve: source and destination sizes don't match.";
<< "invalid resolve: source and destination sizes don't match";
FILAMENT_CHECK_PRECONDITION(
source.texture.GetSampleCount() > 1 && destination.texture.GetSampleCount() == 1)
<< "Invalid resolve: source.samples=" << source.texture.GetSampleCount()
<< "invalid resolve: source.samples=" << source.texture.GetSampleCount()
<< ", destination.samples=" << destination.texture.GetSampleCount();
FILAMENT_CHECK_PRECONDITION(source.texture.GetFormat() == destination.texture.GetFormat())
<< "Source and destination texture format don't match.";
<< "source and destination texture format don't match";
const wgpu::TextureFormat format{ source.texture.GetFormat() };
FILAMENT_CHECK_PRECONDITION(!hasDepth(format)) << "Can't resolve depth formats.";
FILAMENT_CHECK_PRECONDITION(!hasStencil(format)) << "Can't resolve stencil formats.";
FILAMENT_CHECK_PRECONDITION(!hasDepth(format)) << "can't resolve depth formats";
FILAMENT_CHECK_PRECONDITION(!hasStencil(format)) << "can't resolve stencil formats";
FILAMENT_CHECK_PRECONDITION(source.texture.GetUsage() & wgpu::TextureUsage::RenderAttachment)
<< "Source texture usage doesn't have wgpu::TextureUsage::RenderAttachment.";
<< "source texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
FILAMENT_CHECK_PRECONDITION(
destination.texture.GetUsage() & wgpu::TextureUsage::RenderAttachment)
<< "Destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment.";
<< "destination texture usage doesn't have wgpu::TextureUsage::RenderAttachment";
FILAMENT_CHECK_PRECONDITION(destination.texture.GetUsage() & wgpu::TextureUsage::TextureBinding)
<< "Destination texture usage doesn't have wgpu::TextureUsage::TextureBinding.";
<< "destination texture usage doesn't have wgpu::TextureUsage::TextureBinding";
const wgpu::TextureViewDescriptor sourceTextureViewDescriptor{
.label = "resolve_source_texture_view",
@@ -115,7 +116,7 @@ void WebGPUMsaaTextureResolver::resolve(ResolveRequest const& request) {
<< "Failed to create wgpu::TextureView destinationTextureView.";
if (hasDepth(format)) {
PANIC_PRECONDITION("Depth resolve not yet implemented.");
PANIC_PRECONDITION("DEPTH RESOLVE NOT IMPLEMENTED YET");
} else {
resolveColorTextures(request.commandEncoder, sourceTextureView, destinationTextureView);
}
@@ -31,10 +31,6 @@ namespace filament::backend {
class WebGPUTexture;
/**
* A utility class for resolving multisampled textures in WebGPU.
* This is necessary because WebGPU does not have an implicit resolve step like other APIs.
*/
class WebGPUMsaaTextureResolver final {
public:
struct ResolveRequest final {
@@ -51,9 +47,6 @@ public:
TextureInfo destination;
};
/**
* Resolves a multisampled texture into a single-sampled texture.
*/
void resolve(ResolveRequest const&);
};
@@ -54,7 +54,7 @@ namespace {
case CullingMode::FRONT: return wgpu::CullMode::Front;
case CullingMode::BACK: return wgpu::CullMode::Back;
case CullingMode::FRONT_AND_BACK:
// WebGPU does not support culling both front and back faces simultaneously.
// no WegGPU equivalent of front and back
FILAMENT_CHECK_POSTCONDITION(false)
<< "WebGPU does not support CullingMode::FRONT_AND_BACK";
return wgpu::CullMode::Undefined;
@@ -220,7 +220,7 @@ wgpu::RenderPipeline WebGPUPipelineCache::createRenderPipeline(
const bool requestedDepth{ any(request.targetRenderFlags & TargetBufferFlags::DEPTH) };
const bool requestedStencil{ any(request.targetRenderFlags & TargetBufferFlags::STENCIL) };
const bool depthOrStencilRequested{ requestedDepth || requestedStencil };
// depth/stencil...
if (depthOrStencilRequested) {
FILAMENT_CHECK_PRECONDITION(request.depthStencilFormat != wgpu::TextureFormat::Undefined)
<< "Depth or Stencil requested for pipeline, but depthStencilFormat is "
@@ -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,
+81 -5
View File
@@ -107,6 +107,84 @@ namespace {
}
}
/**
* @param fUsage Filament's requested texture usage
* @param samples How many samples to use for MSAA
* @param needsComputeStorageSupport if we need to use this texture as storage binding in something
* like a compute shader
* @param needsRenderAttachmentSupport if we need to use this texture as a render pass attachment
* in something like a render pass blit (e.g. mipmap generation)
* @return The appropriate texture usage flags for the underlying texture
*/
[[nodiscard]] wgpu::TextureUsage fToWGPUTextureUsage(TextureUsage const& fUsage,
const uint8_t samples, const bool needsComputeStorageSupport,
const bool needsRenderAttachmentSupport) {
wgpu::TextureUsage retUsage = wgpu::TextureUsage::None;
// if needsComputeStorageSupport we need to read and write to the texture in a shader and thus
// require CopySrc, CopyDst, TextureBinding, & StorageBinding
// Basing this mapping off of VulkanTexture.cpp's getUsage func and suggestions from Gemini
// TODO Validate assumptions, revisit if issues.
if (needsComputeStorageSupport || any(TextureUsage::BLIT_SRC & fUsage)) {
retUsage |= wgpu::TextureUsage::CopySrc;
}
if (needsComputeStorageSupport ||
any((TextureUsage::BLIT_DST | TextureUsage::UPLOADABLE) & fUsage)) {
retUsage |= wgpu::TextureUsage::CopyDst;
}
if (needsComputeStorageSupport || any(TextureUsage::SAMPLEABLE & fUsage)) {
retUsage |= wgpu::TextureUsage::TextureBinding;
}
if (needsComputeStorageSupport) {
retUsage |= wgpu::TextureUsage::StorageBinding;
}
if (needsRenderAttachmentSupport) {
retUsage |= wgpu::TextureUsage::RenderAttachment;
}
if (any(TextureUsage::BLIT_SRC & fUsage)) {
retUsage |= wgpu::TextureUsage::TextureBinding;
}
if (any(TextureUsage::BLIT_DST & fUsage)) {
retUsage |= wgpu::TextureUsage::RenderAttachment;
}
if (any(TextureUsage::GEN_MIPMAPPABLE & fUsage)) {
retUsage |= (wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TextureBinding);
}
// WGPU Render attachment covers either color or stencil situation dependant
// NOTE: Depth attachment isn't used this way in Vulkan but logically maps to WGPU docs. If
// issues, investigate here.
if (any((TextureUsage::COLOR_ATTACHMENT | TextureUsage::STENCIL_ATTACHMENT |
TextureUsage::DEPTH_ATTACHMENT) &
fUsage)) {
retUsage |= wgpu::TextureUsage::RenderAttachment;
}
// This is from Vulkan logic- if there are any issues try disabling this first, allows perf
// benefit though
const bool useTransientAttachment =
// Usage consists of attachment flags only.
none(fUsage & ~TextureUsage::ALL_ATTACHMENTS) &&
// Usage contains at least one attachment flag.
any(fUsage & TextureUsage::ALL_ATTACHMENTS) &&
// Depth resolve cannot use transient attachment because it uses a custom shader.
// TODO: see VulkanDriver::isDepthStencilResolveSupported() to know when to remove this
// restriction.
// Note that the custom shader does not resolve stencil. We do need to move to vk 1.2
// and above to be able to support stencil resolve (along with depth).
!(any(fUsage & TextureUsage::DEPTH_ATTACHMENT) && samples > 1);
if (useTransientAttachment) {
retUsage |= wgpu::TextureUsage::TransientAttachment;
}
// NOTE: Unused wgpu flags:
// StorageAttachment
// NOTE: Unused Filament flags:
// SUBPASS_INPUT VK goes to input attachment which we don't support right now
// PROTECTED
return retUsage;
}
[[nodiscard]] wgpu::TextureAspect fToWGPUTextureViewAspect(TextureUsage const& fUsage,
TextureFormat const& fFormat) {
@@ -208,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 },
+2 -2
View File
@@ -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
+1 -1
View File
@@ -49,7 +49,7 @@ Backend parseArgumentsForBackend(int argc, char* argv[]) {
} else if (arg == "webgpu") {
backend = Backend::WEBGPU;
} else {
std::cerr << "Unrecognized target API. Must be 'opengl'|'vulkan'|'metal'|'webgpu'."
std::cerr << "Unrecognized target API. Must be 'opengl'|'vulkan'|'metal'."
<< std::endl
<< "Defaulting to OpenGL."
<< std::endl;
+7 -40
View File
@@ -85,18 +85,6 @@ std::filesystem::path ScreenshotParams::expectedFilePath() const {
return expectedDirectoryPath().append(expectedFileName());
}
std::filesystem::path ScreenshotParams::diffFilePath() const {
return diffDirectoryPath().append(diffFileName());
}
std::filesystem::path ScreenshotParams::diffDirectoryPath() {
return BackendTest::binaryDirectory().append("images/diff_images");
}
std::string ScreenshotParams::diffFileName() const {
return absl::StrFormat("%s_diff.png", mFileName);
}
const std::string ScreenshotParams::filePrefix() const {
// TODO(b/422804941): If there are platform specific goldens, when on those platforms append a
// unique platform identifying string to this.
@@ -140,6 +128,8 @@ void ImageExpectation::compareImage() const {
if (bytesFilled) {
#ifndef FILAMENT_IOS
LoadedPng loadedImage(mParams.expectedFilePath());
// Bytewise compare.
EXPECT_EQ(loadedImage.bytes().size(), mResult.bytes().size());
if (loadedImage.bytes().size() != mResult.bytes().size()) {
// Something is wrong with the size of the expected result, which usually means the file
// is missing. Fail the test and early return so later steps can assume the image is
@@ -147,41 +137,18 @@ void ImageExpectation::compareImage() const {
BackendTest::markImageAsFailure(mParams.filePrefix());
return;
}
// Initialize the vector to full saturation on all channels, bad pixels will have
// color channels but not alpha set to 0 to produce highly contrasting black pixels.
std::vector<unsigned char> imageDiff( loadedImage.bytes().size(), 255 );
int pixelDeviations = 0;
for (int i = 0; i < mResult.bytes().size(); i += 4) {
// In order to handle color channels propoerly for the output, stride 4 bytes at a time.
// A failure of any byte in a pixel counts as a failure of the whole pixel.
for (int j = 0; j < 4; ++j) {
if( std::abs( mResult.bytes()[i+j] - loadedImage.bytes()[i+j] ) >
mParams.pixelMatchThreshold() ) {
pixelDeviations++;
imageDiff[i] = 0;
imageDiff[i+1] = 0;
imageDiff[i+2] = 0;
break;
}
for (int i = 0; i < mResult.bytes().size(); ++i) {
if (std::abs( mResult.bytes()[i] - loadedImage.bytes()[i] ) >
mParams.pixelMatchThreshold()) {
pixelDeviations++;
}
}
EXPECT_LE(pixelDeviations, mParams.allowedPixelDeviations());
if (pixelDeviations > mParams.allowedPixelDeviations()) {
BackendTest::markImageAsFailure(mParams.filePrefix());
image::LinearImage image;
image = image::toLinearWithAlpha<uint8_t>(
mParams.width(), mParams.height(),
mParams.width() * 4, (uint8_t *)imageDiff.data(),
[](uint8_t value) -> float { return value; },
[](filament::math::float4 rgba) -> filament::math::float4 { return rgba; });
std::string filePath = mParams.diffFilePath();
std::ofstream pngStream(filePath, std::ios::binary | std::ios::trunc);
// To avoid going from linear -> sRGB -> linear save the PNG as linear.
image::ImageEncoder::encode(pngStream, image::ImageEncoder::Format::PNG_LINEAR, image, "",
filePath);
}
EXPECT_LE(pixelDeviations, mParams.allowedPixelDeviations());
// TODO: Add better debug output, such as generating a diff image.
#else
// For builds that can't load PNGs (currently iOS only) use the expected hash.
uint32_t actualHash = mResult.hash();
@@ -58,9 +58,6 @@ public:
static std::filesystem::path expectedDirectoryPath();
std::string expectedFileName() const;
std::filesystem::path expectedFilePath() const;
static std::filesystem::path diffDirectoryPath();
std::string diffFileName() const;
std::filesystem::path diffFilePath() const;
const std::string filePrefix() const;
int allowedPixelDeviations() const;
int pixelMatchThreshold() const;
-2
View File
@@ -19,8 +19,6 @@ code. When cmake is run it will copy this directory to the build output creating
The unit tests will then write their resulting images into `images/actual_images` in order to make
the tests easier to debug.
If an image comparison test fails, it writes a diff image to `images/diff_images` where each pixel of the diff is white (255,255,255) if the comparison for the corresponding pixel succeeds and black (0,0,0) of the comparison for the corresponding pixel fails.
### Python utility for updating golden images and comparing results
Inside the unit test source code directory there is a python script called
-44
View File
@@ -1,44 +0,0 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_BACKEND_TEST_WORKAROUNDS_H
#define TNT_BACKEND_TEST_WORKAROUNDS_H
#if defined(FILAMENT_SUPPORTS_WEBGPU)
// consider, if needed, adding (as Vulkan also has transient attachments): || defined(FILAMENT_SUPPORTS_VULKAN)
// TODO: support TextureUsage::READ_PIXELS (or something like that)
// and search-and-replace TEXTURE_USAGE_READ_PIXELS with
// "| TextureUsage::READ_PIXELS" when it is available.
// Until then, this serves as a workaround for WebGPU testing, such that
// it can easily be swapped out for the "right" thing when that is available.
// The problem this is trying to address is that the WebGPU backend will
// assign a texture with wgpu::TextureUsage::TransientAttachment if the Filament
// usage flags don't indicate the pixels are needed CPU-side. If transient attachment is
// assigned to the texture, the texture will not be available later for readPixels, which
// is often needed for the unit tests. If we suggest that
// the texture needs to support being a blit source, then TransientAttachment will
// not get assigned. However, this is not quite right in the test, because a test
// may not really want to do a blit with the texture, just read the pixels from it later for
// image comparison (renderdiff).
// Ideally, the test could express this with a TextureUsage::READ_PIXELS flag, but that
// does not yet exist. So, a hack would be to explicitly set the BLIT_SRC flag instead in
// its place.
#define TEXTURE_USAGE_READ_PIXELS | TextureUsage::BLIT_SRC
#else
#define TEXTURE_USAGE_READ_PIXELS
#endif
#endif // TNT_BACKEND_TEST_WORKAROUNDS_H
+28 -31
View File
@@ -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(); });
+5 -4
View File
@@ -22,7 +22,6 @@
#include "SharedShaders.h"
#include "Skip.h"
#include "TrianglePrimitive.h"
#include "Workarounds.h"
namespace test {
@@ -156,6 +155,8 @@ TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
// This test renders two triangles in two separate draw calls. Between the draw calls, a uniform
// buffer object is partially updated.
TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"All values including alpha are written as 0, see b/417254943");
auto& api = getDriverApi();
Cleanup cleanup(api);
@@ -178,9 +179,9 @@ TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
shader.bindUniform<SimpleMaterialParams>(api, ubuffer, kBindingConfig);
// Create a render target.
auto colorTexture = cleanup.add(
api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, screenWidth(),
screenHeight(), 1, TextureUsage::COLOR_ATTACHMENT TEXTURE_USAGE_READ_PIXELS));
auto colorTexture =
cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1,
screenWidth(), screenHeight(), 1, TextureUsage::COLOR_ATTACHMENT));
auto renderTarget = cleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR0, screenWidth(),
screenHeight(), 1, 0, { { colorTexture } }, {}, {}));
+6 -12
View File
@@ -48,7 +48,7 @@ layout(location = 0) out vec4 fragColor;
// Filament's Vulkan backend requires a descriptor set index of 1 for all samplers.
// This parameter is ignored for other backends.
layout(binding = 0, set = 0) uniform {samplerType} test_tex;
layout(location = 0, set = 0) uniform {samplerType} test_tex;
void main() {
vec2 fbsize = vec2({texSize});
@@ -66,7 +66,7 @@ std::string fragmentUpdateImage3DTemplate (R"(#version 450 core
layout(location = 0) out vec4 fragColor;
// Filament's Vulkan backend requires a descriptor set index of 1 for all samplers.
layout(binding = 0, set = 0) uniform {samplerType} test_tex;
layout(location = 0, set = 0) uniform {samplerType} test_tex;
float getLayer(in sampler3D s) { return 2.5f / 4.0f; }
float getLayer(in sampler2DArray s) { return 2.0f; }
@@ -87,7 +87,7 @@ std::string fragmentUpdateImageMip (R"(#version 450 core
layout(location = 0) out vec4 fragColor;
// Filament's Vulkan backend requires a descriptor set index of 1 for all samplers.
layout(binding = 0, set = 0) uniform sampler2D test_tex;
layout(location = 0, set = 0) uniform sampler2D test_tex;
void main() {
vec2 fbsize = vec2({texSize});
@@ -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
+1 -13
View File
@@ -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;
}
+1 -1
View File
@@ -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;
+1 -3
View File
@@ -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)) {
+2 -2
View File
@@ -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
+3 -29
View File
@@ -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
+6 -15
View File
@@ -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));
};
+1 -6
View File
@@ -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;
};
+9 -14
View File
@@ -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));
+68 -7
View File
@@ -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())
-4
View File
@@ -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
+11 -56
View File
@@ -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();
};
-6
View File
@@ -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
View File
@@ -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}&nbsp ${vstring} ${shaderModel} </div>
<div>${stage}&nbsp ${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) {
+2 -9
View File
@@ -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()) {
+4 -5
View File
@@ -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_
+1 -1
View File
@@ -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')
+23 -40
View File
@@ -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 ''))
-1
View File
@@ -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
+8 -19
View File
@@ -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
View File
@@ -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