Compare commits
301 Commits
MapAsyncEx
...
zm/fix-upd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe98b8be64 | ||
|
|
73c343635e | ||
|
|
432e672022 | ||
|
|
b56b04c5f8 | ||
|
|
99816d67c2 | ||
|
|
d6d4f92922 | ||
|
|
6a59a68622 | ||
|
|
4580f57987 | ||
|
|
38f7e579f1 | ||
|
|
9b1c8a2bf5 | ||
|
|
4504471021 | ||
|
|
37c316fa03 | ||
|
|
14960f7118 | ||
|
|
1deb657442 | ||
|
|
45c0d1b34f | ||
|
|
1ddd10f326 | ||
|
|
308668a705 | ||
|
|
1cd48619e3 | ||
|
|
89c3b3f40b | ||
|
|
e830ec28e4 | ||
|
|
b58ffb87e0 | ||
|
|
385d8969cf | ||
|
|
53bc372876 | ||
|
|
58f6d77e78 | ||
|
|
3769d0a9d3 | ||
|
|
2bc71240cf | ||
|
|
e1fb3f7442 | ||
|
|
e832805faf | ||
|
|
2ce71d6d98 | ||
|
|
26c51e0d9a | ||
|
|
510ae15867 | ||
|
|
d6caa9dc0b | ||
|
|
19209a00e6 | ||
|
|
188113bad6 | ||
|
|
5916837318 | ||
|
|
27aa517c48 | ||
|
|
4622e88a6b | ||
|
|
9bdb6acd63 | ||
|
|
751d213145 | ||
|
|
0c3ae457a6 | ||
|
|
92d4be6923 | ||
|
|
ad8c188f58 | ||
|
|
9716b3924b | ||
|
|
ae9b951b08 | ||
|
|
78a0d8f4f6 | ||
|
|
675d8bc5be | ||
|
|
a90019baa2 | ||
|
|
72997ee71e | ||
|
|
5b631056b1 | ||
|
|
caa334730a | ||
|
|
261f74a1e9 | ||
|
|
f10a7d9bbc | ||
|
|
358d594f34 | ||
|
|
b06b6b5c42 | ||
|
|
ac41a15191 | ||
|
|
ef42c55f56 | ||
|
|
bd67c9c67e | ||
|
|
8f19826fe4 | ||
|
|
afd0e67fb0 | ||
|
|
f1b14d6f65 | ||
|
|
09b5172962 | ||
|
|
39f0ea1706 | ||
|
|
ec4b9113df | ||
|
|
2a51b70a74 | ||
|
|
4ba2c7d65c | ||
|
|
3af28968ed | ||
|
|
2f36ab71c9 | ||
|
|
b40530ad3c | ||
|
|
0131949aff | ||
|
|
b85d52f727 | ||
|
|
53e6cd3126 | ||
|
|
69ae8c491b | ||
|
|
c35ae6571f | ||
|
|
4c621b83e9 | ||
|
|
4abf7cdaba | ||
|
|
9808aa5460 | ||
|
|
5c15d56cf5 | ||
|
|
ef24164464 | ||
|
|
a1abfa30b8 | ||
|
|
b5abcd9bc1 | ||
|
|
8d34af2004 | ||
|
|
db0524d59b | ||
|
|
6193f489a3 | ||
|
|
fb31759c27 | ||
|
|
375e3a03ec | ||
|
|
11bf3a4493 | ||
|
|
7c64fb9cf3 | ||
|
|
d3de9efc33 | ||
|
|
e9dcf2a63a | ||
|
|
8008d21782 | ||
|
|
852ecf048a | ||
|
|
3c91c74232 | ||
|
|
8d20d7abec | ||
|
|
2f1266f7dd | ||
|
|
05be4b0acc | ||
|
|
9d9c3d34f8 | ||
|
|
491531c76b | ||
|
|
bf4ea771be | ||
|
|
dd5882760b | ||
|
|
1707dda62a | ||
|
|
c4d3eded72 | ||
|
|
ca4b0650fa | ||
|
|
33d22b3146 | ||
|
|
e2dd47bf42 | ||
|
|
60b1951f90 | ||
|
|
6ce06b1b60 | ||
|
|
e21d4a5326 | ||
|
|
7fd9e728ae | ||
|
|
b66736b8cc | ||
|
|
ea8f6a3c92 | ||
|
|
adcdbb45f9 | ||
|
|
cff958587d | ||
|
|
9ab2326f15 | ||
|
|
8785acd352 | ||
|
|
57767b7f27 | ||
|
|
febcfc8f2d | ||
|
|
c04c387a89 | ||
|
|
78ac9467f8 | ||
|
|
73b60d4db8 | ||
|
|
7266fd67db | ||
|
|
469f2c94da | ||
|
|
d4e0d051b1 | ||
|
|
df74a5a352 | ||
|
|
21fd32266a | ||
|
|
cd7c7b369b | ||
|
|
65ed7099c0 | ||
|
|
af34572883 | ||
|
|
1f6a67b2a2 | ||
|
|
7701e7a65a | ||
|
|
65a91832a8 | ||
|
|
acb47ffed5 | ||
|
|
13c0304d84 | ||
|
|
febdf3974c | ||
|
|
390acec928 | ||
|
|
ff806f8c3e | ||
|
|
06c4faabc3 | ||
|
|
92aec16fa3 | ||
|
|
f1ffb783d8 | ||
|
|
b708300586 | ||
|
|
f88d757d06 | ||
|
|
4d96f188d1 | ||
|
|
2d943f40bb | ||
|
|
92c058a736 | ||
|
|
ba819bcfca | ||
|
|
e22f1adfa7 | ||
|
|
f2bca03360 | ||
|
|
5f07d701f0 | ||
|
|
496a50bc89 | ||
|
|
6ccb6c4cce | ||
|
|
42c78fd0a9 | ||
|
|
3d0e2923ae | ||
|
|
408b2371f0 | ||
|
|
6ea08b40a8 | ||
|
|
5eb0f7d3ec | ||
|
|
d465d59c3a | ||
|
|
af9a60d175 | ||
|
|
8e0f0c92ce | ||
|
|
5a14fb4b5d | ||
|
|
22a1aacb21 | ||
|
|
e5b6c11399 | ||
|
|
85ebd67a28 | ||
|
|
f33a779668 | ||
|
|
1b5916b655 | ||
|
|
d8720cd858 | ||
|
|
44636bddaa | ||
|
|
1741723687 | ||
|
|
ad2c291d4a | ||
|
|
52ffd80041 | ||
|
|
44dde744ea | ||
|
|
c18ddf929e | ||
|
|
5661365248 | ||
|
|
bcb9d258c2 | ||
|
|
53a94c4547 | ||
|
|
928ffe94a7 | ||
|
|
dcb78c7144 | ||
|
|
43dd8fa1cc | ||
|
|
91a5f00d30 | ||
|
|
0eff54b807 | ||
|
|
68eb0ce3f1 | ||
|
|
023318c08d | ||
|
|
c0e3aa2b0a | ||
|
|
a6d1b9188f | ||
|
|
43193c5ac5 | ||
|
|
35c58047ee | ||
|
|
2190f3544f | ||
|
|
fd8ea78e04 | ||
|
|
7fc336a6d9 | ||
|
|
3cc6fae5c0 | ||
|
|
f4f75da016 | ||
|
|
bdeba1aa86 | ||
|
|
8991be4eea | ||
|
|
940b93ceb8 | ||
|
|
00eb3d0d4f | ||
|
|
7f61eb7a0a | ||
|
|
53f506670c | ||
|
|
9fe7ab31ed | ||
|
|
008534b8a2 | ||
|
|
2c5842f908 | ||
|
|
a8ee9d473e | ||
|
|
1e3ab83e6a | ||
|
|
9720da2c68 | ||
|
|
ee74e2d50c | ||
|
|
6171009add | ||
|
|
d299a444fa | ||
|
|
107276e9cc | ||
|
|
fe00cbf106 | ||
|
|
02d0ddbcfe | ||
|
|
c4802f7683 | ||
|
|
b582b2d64e | ||
|
|
9c4edf56ca | ||
|
|
e24fdb3a9d | ||
|
|
05f24516dd | ||
|
|
a3708b2616 | ||
|
|
ee514df722 | ||
|
|
924c416644 | ||
|
|
597cf9a3ae | ||
|
|
316c8376d3 | ||
|
|
672ddddafc | ||
|
|
a8ee48c4b7 | ||
|
|
c300f77151 | ||
|
|
16734fc767 | ||
|
|
f701c1e1b5 | ||
|
|
98cec207ce | ||
|
|
3ecdf71917 | ||
|
|
ebba65e558 | ||
|
|
177d23b3ac | ||
|
|
cfd402f424 | ||
|
|
b7e492d674 | ||
|
|
d7e0202f37 | ||
|
|
4227d04eb7 | ||
|
|
272ed14737 | ||
|
|
223a754200 | ||
|
|
af480a58c4 | ||
|
|
192738ecb9 | ||
|
|
27c268f865 | ||
|
|
32fcc1a81b | ||
|
|
621eca340e | ||
|
|
ee6c8c3e76 | ||
|
|
dcf7621c35 | ||
|
|
6a8193ea80 | ||
|
|
d4efef9a9b | ||
|
|
cdffc9eaa0 | ||
|
|
a162a65dce | ||
|
|
369bab4744 | ||
|
|
c98038b2d7 | ||
|
|
849c4bb8c5 | ||
|
|
ee64322d76 | ||
|
|
79d65a768a | ||
|
|
37610dc8f3 | ||
|
|
2e31dc20e4 | ||
|
|
ae3d98fb47 | ||
|
|
a938a790fa | ||
|
|
71d556dddf | ||
|
|
d61227b2fd | ||
|
|
612a6b00e4 | ||
|
|
b3353b518c | ||
|
|
0b44761d43 | ||
|
|
c7979e4023 | ||
|
|
e00904e100 | ||
|
|
96e2366569 | ||
|
|
e76624862d | ||
|
|
c5c83a61d3 | ||
|
|
ccf0d6205f | ||
|
|
f482c1d702 | ||
|
|
a4c056e822 | ||
|
|
f5071b2aae | ||
|
|
0fa13ee43d | ||
|
|
1d2b8a08fa | ||
|
|
0c06744450 | ||
|
|
4ada29fbc3 | ||
|
|
76182231dd | ||
|
|
8cd6915b9d | ||
|
|
f90de26bc2 | ||
|
|
cc2e661aab | ||
|
|
3a92cdab3a | ||
|
|
b367982c23 | ||
|
|
a4f4dc617e | ||
|
|
2eb0f11d05 | ||
|
|
6694df991c | ||
|
|
8b2bfa7e9d | ||
|
|
7386220325 | ||
|
|
000faeaca7 | ||
|
|
3f9783ac02 | ||
|
|
4e6dc67ae8 | ||
|
|
666140bb73 | ||
|
|
56050de5cd | ||
|
|
772136decd | ||
|
|
f664601c51 | ||
|
|
f06b27b7fb | ||
|
|
ef53ce88d4 | ||
|
|
ef18030e1a | ||
|
|
f07176c0a2 | ||
|
|
15db141c7a | ||
|
|
d4bbb7c591 | ||
|
|
92e620d2ad | ||
|
|
311104da97 | ||
|
|
3127632f96 | ||
|
|
59f611bfde | ||
|
|
2d556bdca2 | ||
|
|
65e7dba8b8 | ||
|
|
6550a63056 |
2
.github/actions/mac-prereq/action.yml
vendored
2
.github/actions/mac-prereq/action.yml
vendored
@@ -15,7 +15,7 @@ runs:
|
||||
uses: actions/cache@v4 # Use a specific version
|
||||
with:
|
||||
path: ~/Library/Caches/Homebrew
|
||||
key: ${{ runner.os }}-brew-20250424
|
||||
key: ${{ runner.os }}-brew-20251211
|
||||
- name: Install Mac Prerequisites
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
25
.github/workflows/android-continuous.yml
vendored
25
.github/workflows/android-continuous.yml
vendored
@@ -1,25 +0,0 @@
|
||||
name: Android
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- release
|
||||
- rc/**
|
||||
|
||||
jobs:
|
||||
build-android:
|
||||
name: build-android
|
||||
# We intentially use a larger runner here to enable larger disk space
|
||||
# (standard linux runner will fail on disk space and faster build time).
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/linux-prereq
|
||||
- name: Run Android Continuous
|
||||
uses: ./.github/actions/android-continuous
|
||||
with:
|
||||
build-abi: armeabi-v7a,arm64-v8a,x86_64
|
||||
29
.github/workflows/ios-continuous.yml
vendored
29
.github/workflows/ios-continuous.yml
vendored
@@ -1,29 +0,0 @@
|
||||
name: iOS
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- release
|
||||
- rc/**
|
||||
|
||||
jobs:
|
||||
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 continuous
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: filament-ios
|
||||
path: out/filament-release-ios.tgz
|
||||
- name: Build iOS samples
|
||||
run: |
|
||||
cd build/ios && ./build-samples.sh continuous
|
||||
26
.github/workflows/linux-continuous.yml
vendored
26
.github/workflows/linux-continuous.yml
vendored
@@ -1,26 +0,0 @@
|
||||
name: Linux
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- release
|
||||
- rc/**
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
name: build-linux
|
||||
runs-on: 'ubuntu-24.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 continuous
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: filament-linux
|
||||
path: out/filament-release-linux.tgz
|
||||
29
.github/workflows/mac-continuous.yml
vendored
29
.github/workflows/mac-continuous.yml
vendored
@@ -1,29 +0,0 @@
|
||||
name: macOS
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- release
|
||||
- rc/**
|
||||
|
||||
jobs:
|
||||
build-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 continuous
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: filament-mac
|
||||
path: out/filament-release-darwin.tgz
|
||||
- name: Check public headers
|
||||
run: |
|
||||
test/check-headers/test.sh out/release/filament/include
|
||||
99
.github/workflows/postsubmit-main.yml
vendored
Normal file
99
.github/workflows/postsubmit-main.yml
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
name: 'Postsubmit Tasks'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
# Update the renderdiff goldens in filament-assets. This will add or merge the new goldens from
|
||||
# a branch on filament-assets.
|
||||
update-renderdiff-goldens:
|
||||
name: update-renderdiff-goldens
|
||||
runs-on: 'ubuntu-24.04-4core'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/linux-prereq
|
||||
- id: get_commit_msg
|
||||
uses: ./.github/actions/get-commit-msg
|
||||
- name: Build diffimg
|
||||
run: ./build.sh release diffimg
|
||||
- name: Run update script
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.FILAMENTBOT_TOKEN }}
|
||||
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
|
||||
run: |
|
||||
GOLDEN_BRANCH=$(echo "${COMMIT_MESSAGE}" | python3 test/renderdiff/src/commit_msg.py)
|
||||
COMMIT_HASH="${{ steps.get_commit_msg.outputs.hash }}"
|
||||
if [[ "${GOLDEN_BRANCH}" != "main" ]]; then
|
||||
git config --global user.email "filament.bot@gmail.com"
|
||||
git config --global user.name "Filament Bot"
|
||||
git config --global credential.helper cache
|
||||
echo "branch==${GOLDEN_BRANCH}"
|
||||
echo "hash==${COMMIT_HASH}"
|
||||
python3 test/renderdiff/src/update_golden.py --branch=${GOLDEN_BRANCH} \
|
||||
--merge-to-main --filament-tag=${COMMIT_HASH} --golden-repo-token=${GH_TOKEN}
|
||||
fi
|
||||
|
||||
# Update the /docs (offiicla github-hosted Filament site) if necessary
|
||||
update-docs:
|
||||
name: update-docs
|
||||
runs-on: 'ubuntu-24.04-4core'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/linux-prereq
|
||||
- id: get_commit_msg
|
||||
uses: ./.github/actions/get-commit-msg
|
||||
- name: Prerequisites
|
||||
run: pip install selenium
|
||||
- name: Run update script
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.FILAMENTBOT_TOKEN }}
|
||||
run: |
|
||||
bash docs_src/build/install_mdbook.sh && source ~/.bashrc
|
||||
COMMIT_HASH="${{ steps.get_commit_msg.outputs.hash }}"
|
||||
git config --global user.email "filament.bot@gmail.com"
|
||||
git config --global user.name "Filament Bot"
|
||||
git config --global credential.helper cache
|
||||
bash docs_src/build/postsubmit.sh ${COMMIT_HASH} ${GH_TOKEN}
|
||||
|
||||
# Produce a json that describes the android artifact sizes, and will push that json to a folder in
|
||||
# filament-assets
|
||||
update-sizeguard:
|
||||
name: update-sizeguard
|
||||
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'
|
||||
- id: get_commit_msg
|
||||
uses: ./.github/actions/get-commit-msg
|
||||
- name: Build and generate size report
|
||||
run: |
|
||||
cd build/android && printf "y" | ./build.sh release all
|
||||
cd ../..
|
||||
COMMIT_HASH="${{ steps.get_commit_msg.outputs.hash }}"
|
||||
python3 test/sizeguard/dump_artifact_size.py out/*.tgz out/*.aar > "${COMMIT_HASH}.json"
|
||||
- name: Push to filament-assets
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.FILAMENTBOT_TOKEN }}
|
||||
run: |
|
||||
COMMIT_HASH="${{ steps.get_commit_msg.outputs.hash }}"
|
||||
git config --global user.email "filament.bot@gmail.com"
|
||||
git config --global user.name "Filament Bot"
|
||||
git clone https://x-access-token:${GH_TOKEN}@github.com/google/filament-assets.git filament-assets
|
||||
mkdir -p filament-assets/sizeguard
|
||||
mv "${COMMIT_HASH}.json" filament-assets/sizeguard/
|
||||
cd filament-assets
|
||||
git add sizeguard/"${COMMIT_HASH}.json"
|
||||
git commit -m "Update sizeguard for filament@${COMMIT_HASH}" || echo "No changes to commit"
|
||||
git push https://x-access-token:${GH_TOKEN}@github.com/google/filament-assets.git main
|
||||
145
.github/workflows/postsubmit.yml
vendored
145
.github/workflows/postsubmit.yml
vendored
@@ -1,58 +1,115 @@
|
||||
name: 'Post-submit tasks'
|
||||
name: 'Postsubmit CI'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- release
|
||||
- rc/**
|
||||
|
||||
jobs:
|
||||
update-renderdiff-goldens:
|
||||
name: update-renderdiff-goldens
|
||||
runs-on: 'ubuntu-24.04-4core'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/linux-prereq
|
||||
- id: get_commit_msg
|
||||
uses: ./.github/actions/get-commit-msg
|
||||
- name: Prerequisites
|
||||
run: pip install tifffile numpy
|
||||
- name: Run update script
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.FILAMENTBOT_TOKEN }}
|
||||
run: |
|
||||
GOLDEN_BRANCH=$(echo "${{ steps.get_commit_msg.outputs.msg }}" | python3 test/renderdiff/src/commit_msg.py)
|
||||
COMMIT_HASH="${{ steps.get_commit_msg.outputs.hash }}"
|
||||
if [[ "${GOLDEN_BRANCH}" != "main" ]]; then
|
||||
git config --global user.email "filament.bot@gmail.com"
|
||||
git config --global user.name "Filament Bot"
|
||||
git config --global credential.helper cache
|
||||
echo "branch==${GOLDEN_BRANCH}"
|
||||
echo "hash==${COMMIT_HASH}"
|
||||
python3 test/renderdiff/src/update_golden.py --branch=${GOLDEN_BRANCH} \
|
||||
--merge-to-main --filament-tag=${COMMIT_HASH} --golden-repo-token=${GH_TOKEN}
|
||||
fi
|
||||
build-android:
|
||||
name: build-android
|
||||
# We intentially use a larger runner here to enable larger disk space
|
||||
# (standard linux runner will fail on disk space and faster build time).
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
|
||||
update-docs:
|
||||
name: update-docs
|
||||
runs-on: 'ubuntu-24.04-4core'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/linux-prereq
|
||||
- id: get_commit_msg
|
||||
uses: ./.github/actions/get-commit-msg
|
||||
- name: Prerequisites
|
||||
run: pip install selenium
|
||||
- name: Run update script
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.FILAMENTBOT_TOKEN }}
|
||||
- name: Run Android Continuous
|
||||
uses: ./.github/actions/android-continuous
|
||||
with:
|
||||
build-abi: armeabi-v7a,arm64-v8a,x86_64
|
||||
|
||||
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: |
|
||||
bash docs_src/build/install_mdbook.sh && source ~/.bashrc
|
||||
COMMIT_HASH="${{ steps.get_commit_msg.outputs.hash }}"
|
||||
git config --global user.email "filament.bot@gmail.com"
|
||||
git config --global user.name "Filament Bot"
|
||||
git config --global credential.helper cache
|
||||
bash docs_src/build/postsubmit.sh ${COMMIT_HASH} ${GH_TOKEN}
|
||||
cd build/ios && printf "y" | ./build.sh continuous
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: filament-ios
|
||||
path: out/filament-release-ios.tgz
|
||||
- name: Build iOS samples
|
||||
run: |
|
||||
cd build/ios && ./build-samples.sh continuous
|
||||
|
||||
build-linux:
|
||||
name: build-linux
|
||||
runs-on: 'ubuntu-24.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 continuous
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: filament-linux
|
||||
path: out/filament-release-linux.tgz
|
||||
|
||||
build-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 continuous
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: filament-mac
|
||||
path: out/filament-release-darwin.tgz
|
||||
- name: Check public headers
|
||||
run: |
|
||||
test/check-headers/test.sh out/release/filament/include
|
||||
|
||||
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 continuous
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: filament-web
|
||||
path: out/filament-release-web.tgz
|
||||
|
||||
build-windows:
|
||||
name: build-windows
|
||||
runs-on: windows-2022-32core
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- name: Run build script
|
||||
run: |
|
||||
build\windows\build-github.bat continuous
|
||||
shell: cmd
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: filament-windows
|
||||
path: out/filament-windows.tgz
|
||||
|
||||
35
.github/workflows/presubmit.yml
vendored
35
.github/workflows/presubmit.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Presubmit
|
||||
name: 'Presubmit CI'
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
- uses: ./.github/actions/mac-prereq
|
||||
- name: Run build script
|
||||
run: |
|
||||
cd build/mac && printf "y" | ./build.sh presubmit
|
||||
cd build/mac && printf "y" | ./build.sh presubmit-with-test
|
||||
- name: Test material parser
|
||||
run: |
|
||||
out/cmake-release/filament/test/test_material_parser
|
||||
@@ -67,7 +67,16 @@ jobs:
|
||||
# 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
|
||||
pushd .
|
||||
cd build/android && printf "y" | ./build.sh presubmit-with-archive arm64-v8a
|
||||
popd
|
||||
- name: Check artifact sizes
|
||||
run: |
|
||||
python3 test/sizeguard/dump_artifact_size.py out/*.aar > current_size.json
|
||||
python3 test/sizeguard/check_size.py current_size.json \
|
||||
--target-branch origin/main \
|
||||
--threshold 20480 \
|
||||
--artifacts filament-android-release.aar/jni/arm64-v8a/libfilament-jni.so
|
||||
|
||||
build-ios:
|
||||
name: build-iOS
|
||||
@@ -125,7 +134,6 @@ jobs:
|
||||
uses: ./.github/actions/get-commit-msg
|
||||
- name: Prerequisites
|
||||
run: |
|
||||
pip install tifffile numpy
|
||||
# Must have at least clang-16 for a webgpu/dawn build.
|
||||
sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer
|
||||
shell: bash
|
||||
@@ -134,23 +142,27 @@ jobs:
|
||||
env:
|
||||
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
|
||||
run: |
|
||||
ls ./gltf/Models
|
||||
TEST_DIR=test/renderdiff
|
||||
source ${TEST_DIR}/src/preamble.sh
|
||||
start_
|
||||
set -eux
|
||||
GOLDEN_BRANCH=$(echo "${COMMIT_MESSAGE}" | python3 ${TEST_DIR}/src/commit_msg.py)
|
||||
bash ${TEST_DIR}/generate.sh && \
|
||||
python3 ${TEST_DIR}/src/golden_manager.py \
|
||||
bash ${TEST_DIR}/generate.sh
|
||||
# Build diffimg tool
|
||||
./build.sh release diffimg
|
||||
|
||||
python3 ${TEST_DIR}/src/golden_manager.py \
|
||||
--branch=${GOLDEN_BRANCH} \
|
||||
--output=${GOLDEN_OUTPUT_DIR}
|
||||
|
||||
# Note that we need to upload the output even if comparison fails, so we undo `set -ex`
|
||||
end_
|
||||
# Note that we need to upload the output even if comparison fails, so we undo `set -eux`
|
||||
set +eux
|
||||
|
||||
python3 ${TEST_DIR}/src/compare.py \
|
||||
--src=${GOLDEN_OUTPUT_DIR} \
|
||||
--dest=${RENDER_OUTPUT_DIR} \
|
||||
--out=${DIFF_OUTPUT_DIR} 2>&1 | tee compare_output.txt
|
||||
--out=${DIFF_OUTPUT_DIR} \
|
||||
--diffimg="$(pwd)/out/cmake-release/tools/diffimg/diffimg" \
|
||||
--test="${TEST_DIR}/tests/presubmit.json" 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
|
||||
@@ -158,6 +170,7 @@ jobs:
|
||||
cat compare_output.txt >> "$GITHUB_OUTPUT"
|
||||
echo "$DELIMITER" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
shell: bash
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: presubmit-renderdiff-result
|
||||
|
||||
7
.github/workflows/release.yml
vendored
7
.github/workflows/release.yml
vendored
@@ -163,9 +163,7 @@ jobs:
|
||||
mv out/filamat-android-release.aar out/filamat-${TAG}-android.aar
|
||||
mv out/gltfio-android-release.aar out/gltfio-${TAG}-android.aar
|
||||
mv out/filament-utils-android-release.aar out/filament-utils-${TAG}-android.aar
|
||||
cd out/android-release/filament
|
||||
tar -czf ../../filament-${TAG}-android-native.tgz .
|
||||
cd ../../..
|
||||
mv out/filament-android-release-linux.tgz out/filament-${TAG}-android-native.tgz
|
||||
- name: Sign sample-gltf-viewer
|
||||
run: |
|
||||
echo "${APK_KEYSTORE_BASE64}" > filament.jks.base64
|
||||
@@ -245,7 +243,8 @@ jobs:
|
||||
env:
|
||||
TAG: ${{ steps.git_ref.outputs.tag }}
|
||||
run: |
|
||||
build\windows\build-github.bat release
|
||||
@REMARK 'call' is required to ensure control returns to this script after the batch file finishes.
|
||||
call build\windows\build-github.bat release
|
||||
echo on
|
||||
move out\filament-windows.tgz out\filament-%TAG%-windows.tgz
|
||||
shell: cmd
|
||||
|
||||
27
.github/workflows/web-continuous.yml
vendored
27
.github/workflows/web-continuous.yml
vendored
@@ -1,27 +0,0 @@
|
||||
name: Web
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- release
|
||||
- rc/**
|
||||
|
||||
jobs:
|
||||
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 continuous
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: filament-web
|
||||
path: out/filament-release-web.tgz
|
||||
24
.github/workflows/windows-continuous.yml
vendored
24
.github/workflows/windows-continuous.yml
vendored
@@ -1,24 +0,0 @@
|
||||
name: Windows
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- release
|
||||
- rc/**
|
||||
|
||||
jobs:
|
||||
build-windows:
|
||||
name: build-windows
|
||||
runs-on: windows-2022-32core
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- name: Run build script
|
||||
run: |
|
||||
build\windows\build-github.bat continuous
|
||||
shell: cmd
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: filament-windows
|
||||
path: out/filament-windows.tgz
|
||||
75
BUILDING.md
75
BUILDING.md
@@ -1,6 +1,6 @@
|
||||
## Building Filament
|
||||
# Building Filament
|
||||
|
||||
### Prerequisites
|
||||
## Prerequisites
|
||||
|
||||
To build Filament, you must first install the following tools:
|
||||
|
||||
@@ -18,7 +18,7 @@ To build Filament for Android you must also install the following:
|
||||
- Android NDK 25.1 or higher
|
||||
- Java 17
|
||||
|
||||
### Environment variables
|
||||
## Environment variables
|
||||
|
||||
To build Filament for Android, make sure the environment variable `ANDROID_HOME` points to the
|
||||
location of your Android SDK.
|
||||
@@ -30,7 +30,7 @@ When building for WebGL, you'll also need to set `EMSDK`. See [WebAssembly](#web
|
||||
We recommend using CLion to develop for Filament. Simply open the root directory's CMakeLists.txt
|
||||
in CLion to obtain a usable project.
|
||||
|
||||
### Easy build
|
||||
## Easy build
|
||||
|
||||
Once the required OS specific dependencies listed below are installed, you can use the script
|
||||
located in `build.sh` to build Filament easily on macOS and Linux.
|
||||
@@ -67,7 +67,7 @@ For more specialized options, please also consider the following pages:
|
||||
- `-t`: [`fgviewer`](https://google.github.io/filament/dup/fgviewer.html)
|
||||
- `-b` and `-y`: [ASAN/UBSAN builds](https://google.github.io/filament/notes/asan_ubsan.html)
|
||||
|
||||
### Filament-specific CMake Options
|
||||
## Filament-specific CMake Options
|
||||
|
||||
The following CMake options are boolean options specific to Filament:
|
||||
|
||||
@@ -89,7 +89,7 @@ cmake . -DOPTION=ON # Replace OPTION with the option name, set to ON / OFF
|
||||
|
||||
Options can also be set with the CMake GUI.
|
||||
|
||||
### Linux
|
||||
## Linux
|
||||
|
||||
Make sure you've installed the following dependencies:
|
||||
|
||||
@@ -148,7 +148,7 @@ ninja
|
||||
|
||||
This will build Filament, its tests and samples, and various host tools.
|
||||
|
||||
### macOS
|
||||
## macOS
|
||||
|
||||
To compile Filament you must have the most recent version of Xcode installed and you need to
|
||||
make sure the command line tools are setup by running:
|
||||
@@ -169,7 +169,7 @@ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../release/fila
|
||||
ninja
|
||||
```
|
||||
|
||||
### iOS
|
||||
## iOS
|
||||
|
||||
The easiest way to build Filament for iOS is to use `build.sh` and the
|
||||
`-p ios` flag. For instance to build the debug target:
|
||||
@@ -180,9 +180,9 @@ The easiest way to build Filament for iOS is to use `build.sh` and the
|
||||
|
||||
See [ios/samples/README.md](./ios/samples/README.md) for more information.
|
||||
|
||||
### Windows
|
||||
## Windows
|
||||
|
||||
#### Building on Windows with Visual Studio 2019 or later
|
||||
### Building on Windows with Visual Studio 2019 or later
|
||||
|
||||
Install the following components:
|
||||
|
||||
@@ -225,7 +225,7 @@ You can also use CMake to invoke the build without opening Visual Studio. For ex
|
||||
cmake --build . --target gltf_viewer --config Release
|
||||
```
|
||||
|
||||
### Android
|
||||
## Android
|
||||
|
||||
Before building Filament for Android, make sure to build Filament for your host. Some of the
|
||||
host tools are required to successfully build for Android.
|
||||
@@ -242,7 +242,13 @@ foremost for `arm64-v8a`.
|
||||
|
||||
To build Android on Windows machines, see [android/Windows.md](android/Windows.md).
|
||||
|
||||
#### Easy Android build
|
||||
### Important: SDK location
|
||||
|
||||
Either ensure your `ANDROID_HOME` environment variable is set or make sure the root project
|
||||
contains a `local.properties` file with the `sdk.dir` property pointing to your installation of
|
||||
the Android SDK.
|
||||
|
||||
### Easy Android build
|
||||
|
||||
The easiest way to build Filament for Android is to use `build.sh` and the
|
||||
`-p android` flag. For instance to build the release target:
|
||||
@@ -251,9 +257,46 @@ The easiest way to build Filament for Android is to use `build.sh` and the
|
||||
./build.sh -p android release
|
||||
```
|
||||
|
||||
To build a sample (such as `android/samples/sample-hello-triangle`) for an ARM 64-bit phone, you would run
|
||||
```shell
|
||||
./build.sh -p android -q arm64-v8a -k sample-hello-triangle release
|
||||
```
|
||||
|
||||
The output APK can be found in `android/samples/sample-hello-triangle/build/outputs/apk/release/sample-hello-triangle-release-unsigned.apk`
|
||||
|
||||
Run `build.sh -h` for more information.
|
||||
|
||||
#### Manual builds
|
||||
### Android Studio
|
||||
|
||||
You must use the latest stable release of Android Studio.
|
||||
|
||||
The Android build of filament is separated into java/kotlin client APIs, a layer of jni bindings
|
||||
that bridges java/kotlin with native code, and Filament and other component code that have been compiled
|
||||
into architecture-specific libraries. Our default Android Studio gradle setup can compile java/kotlin and
|
||||
the jni bindings for you, but it will treat the filament libraries as already compiled and present on
|
||||
the system.
|
||||
|
||||
Therefore, before compiling the sample app or any other targets, you must
|
||||
make sure that the native filament libraries have been compiled and are located at a prescribed location
|
||||
so that the jni bindings can link against them. You can do so by using the easy build script
|
||||
|
||||
```shell
|
||||
./build.sh -p android release -q arm64-v8a
|
||||
```
|
||||
|
||||
Note that the above step will also install host machine tools into prescribed locations. These tools are
|
||||
required for compiling Filament assets such as materials and environment maps.
|
||||
|
||||
Now we are ready to compile the apps. To open the project, point Studio to the `android` folder.
|
||||
After opening the project and syncing with Gradle, select the sample of your choice
|
||||
using the drop-down widget in the toolbar. Additionally, you will need to select a deployment target.
|
||||
By doing so, Android Studio will automatically try to compile the app only for that specific
|
||||
device's architecture. So if you are targeting a new Pixel phone, make sure that the step above
|
||||
(compiling the library) is targeting ARM 64-bit (`-q arm64-v8a` ), and if you are running the app on
|
||||
an emulator on a Linux machine with an x86 64-bit chipset, you would indicate (`-q x86_64`) in the above step.
|
||||
|
||||
|
||||
### Manual builds
|
||||
|
||||
Invoke CMake in a build directory of your choice, inside of filament's directory. The commands
|
||||
below show how to build Filament for ARM 64-bit (`aarch64`).
|
||||
@@ -281,7 +324,7 @@ This will generate Filament's Android binaries in `out/android-release`. This lo
|
||||
to build the Android Studio projects located in `filament/android`. After install, the library
|
||||
binaries should be found in `out/android-release/filament/lib/arm64-v8a`.
|
||||
|
||||
#### AAR
|
||||
### AAR
|
||||
|
||||
Before you attempt to build the AAR, make sure you've compiled and installed the native libraries
|
||||
as explained in the sections above. You must have the following ABIs built in
|
||||
@@ -313,7 +356,7 @@ Alternatively you can build the AAR from the command line by executing the follo
|
||||
The `-Pcom.google.android.filament.dist-dir` can be used to specify a different installation
|
||||
directory (it must match the CMake install prefix used in the previous steps).
|
||||
|
||||
#### Using Filament's AAR
|
||||
### Using Filament's AAR
|
||||
|
||||
Create a new module in your project and select _Import .JAR or .AAR Package_ when prompted. Make
|
||||
sure to add the newly created module as a dependency to your application.
|
||||
@@ -354,7 +397,7 @@ productFlavors {
|
||||
}
|
||||
```
|
||||
|
||||
### WebAssembly
|
||||
## WebAssembly
|
||||
|
||||
The core Filament library can be cross-compiled to WebAssembly from either macOS or Linux. To get
|
||||
started, follow the instructions for building Filament on your platform ([macOS](#macos) or
|
||||
|
||||
@@ -10,6 +10,7 @@ cmake_minimum_required(VERSION 3.22.1)
|
||||
# ==================================================================================================
|
||||
# Toolchain configuration
|
||||
# ==================================================================================================
|
||||
# On iOS, the deployment target is set inside third_party/clang/ios.cmake
|
||||
if (APPLE AND NOT IOS)
|
||||
# This must be set before project() is called
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15 CACHE STRING "")
|
||||
@@ -64,6 +65,8 @@ option(FILAMENT_SUPPORTS_WEBP_TEXTURES "Enable webp texture support for Filament
|
||||
# On the regular filament build (where size is of less concern), we enable GTAO by default.
|
||||
option(FILAMENT_DISABLE_GTAO "Disable GTAO" OFF)
|
||||
|
||||
option(FILAMENT_BUILD_TESTING "Build tests" ON)
|
||||
|
||||
set(FILAMENT_NDK_VERSION "" CACHE STRING
|
||||
"Android NDK version or version prefix to be used when building for Android."
|
||||
)
|
||||
@@ -367,14 +370,22 @@ endif()
|
||||
|
||||
if (LINUX)
|
||||
option(USE_STATIC_LIBCXX "Link against the static runtime libraries." ON)
|
||||
|
||||
# Add this step to support both glibc-based Linux distributions (e.g., Ubuntu, Debian)
|
||||
# and musl-based distributions (e.g., Alpine).
|
||||
include(CheckSymbolExists)
|
||||
check_symbol_exists(__GLIBC__ "features.h" CLANG_WITH_GLIBC)
|
||||
|
||||
if (${USE_STATIC_LIBCXX})
|
||||
if (FILAMENT_USING_GCC)
|
||||
link_libraries("-static-libgcc -static-libstdc++")
|
||||
else ()
|
||||
elseif (CLANG_WITH_GLIBC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
||||
link_libraries("-static-libgcc -static-libstdc++")
|
||||
link_libraries(libc++.a)
|
||||
link_libraries(libc++abi.a)
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -473,7 +484,7 @@ endif()
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${EXTRA_SANITIZE_OPTIONS}")
|
||||
|
||||
if (FILAMENT_ENABLE_COVERAGE)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fprofile-instr-generate -fcoverage-mapping")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-instr-generate -fcoverage-mapping -Wno-pass-failed")
|
||||
endif()
|
||||
|
||||
# Disable the stack check for macOS to workaround a known issue in clang 11.0.0.
|
||||
@@ -563,6 +574,10 @@ if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
add_definitions(-DFILAMENT_SUPPORTS_WEBGPU)
|
||||
endif()
|
||||
|
||||
if (FILAMENT_SUPPORTS_WEBP_TEXTURES)
|
||||
add_definitions(-DFILAMENT_SUPPORTS_WEBP_TEXTURES)
|
||||
endif()
|
||||
|
||||
# Build with Metal support on non-WebGL Apple platforms.
|
||||
if (APPLE AND NOT WEBGL)
|
||||
option(FILAMENT_SUPPORTS_METAL "Include the Metal backend" ON)
|
||||
@@ -574,7 +589,7 @@ if (FILAMENT_SUPPORTS_METAL)
|
||||
endif()
|
||||
|
||||
# Building filamat increases build times and isn't required for web, so turn it off by default.
|
||||
if (NOT WEBGL AND NOT IOS)
|
||||
if (NOT WEBGL)
|
||||
option(FILAMENT_BUILD_FILAMAT "Build filamat and JNI buildings" ON)
|
||||
else()
|
||||
option(FILAMENT_BUILD_FILAMAT "Build filamat and JNI buildings" OFF)
|
||||
@@ -864,6 +879,7 @@ add_subdirectory(${LIBRARIES}/gltfio)
|
||||
add_subdirectory(${LIBRARIES}/ibl)
|
||||
add_subdirectory(${LIBRARIES}/iblprefilter)
|
||||
add_subdirectory(${LIBRARIES}/image)
|
||||
add_subdirectory(${LIBRARIES}/imagediff)
|
||||
add_subdirectory(${LIBRARIES}/ktxreader)
|
||||
add_subdirectory(${LIBRARIES}/math)
|
||||
add_subdirectory(${LIBRARIES}/mathio)
|
||||
@@ -872,7 +888,9 @@ add_subdirectory(${LIBRARIES}/utils)
|
||||
add_subdirectory(${LIBRARIES}/viewer)
|
||||
add_subdirectory(${FILAMENT}/shaders)
|
||||
add_subdirectory(${EXTERNAL}/abseil/tnt)
|
||||
add_subdirectory(${EXTERNAL}/basisu/tnt)
|
||||
# Add zstd before basisu to force it to use the external zstd target,
|
||||
# preventing a duplicate symbol conflict with its bundled version.
|
||||
add_subdirectory(${EXTERNAL}/zstd/tnt)
|
||||
add_subdirectory(${EXTERNAL}/civetweb/tnt)
|
||||
add_subdirectory(${EXTERNAL}/imgui/tnt)
|
||||
add_subdirectory(${EXTERNAL}/robin-map/tnt)
|
||||
@@ -886,8 +904,10 @@ add_subdirectory(${EXTERNAL}/jsmn/tnt)
|
||||
add_subdirectory(${EXTERNAL}/stb/tnt)
|
||||
add_subdirectory(${EXTERNAL}/getopt)
|
||||
add_subdirectory(${EXTERNAL}/perfetto/tnt)
|
||||
add_subdirectory(${EXTERNAL}/zstd/tnt)
|
||||
add_subdirectory(${EXTERNAL}/basisu/tnt)
|
||||
|
||||
# imageio-lite is needed for viewer
|
||||
add_subdirectory(${LIBRARIES}/imageio-lite)
|
||||
|
||||
# Note that this has to be placed after mikktspace in order for combine_static_libs to work.
|
||||
add_subdirectory(${LIBRARIES}/geometry)
|
||||
@@ -913,7 +933,6 @@ endif()
|
||||
|
||||
if (FILAMENT_SUPPORTS_WEBP_TEXTURES)
|
||||
add_subdirectory(${EXTERNAL}/libwebp/tnt)
|
||||
add_definitions(-DFILAMENT_SUPPORTS_WEBP_TEXTURES)
|
||||
endif()
|
||||
|
||||
if (FILAMENT_SUPPORTS_VULKAN)
|
||||
@@ -957,6 +976,7 @@ if (IS_HOST_PLATFORM)
|
||||
|
||||
add_subdirectory(${TOOLS}/cmgen)
|
||||
add_subdirectory(${TOOLS}/cso-lut)
|
||||
add_subdirectory(${TOOLS}/diffimg)
|
||||
add_subdirectory(${TOOLS}/filamesh)
|
||||
add_subdirectory(${TOOLS}/glslminifier)
|
||||
add_subdirectory(${TOOLS}/matc)
|
||||
@@ -971,6 +991,7 @@ if (IS_HOST_PLATFORM)
|
||||
add_subdirectory(${TOOLS}/roughness-prefilter)
|
||||
add_subdirectory(${TOOLS}/specular-color)
|
||||
add_subdirectory(${TOOLS}/uberz)
|
||||
add_subdirectory(${TOOLS}/specgen)
|
||||
endif()
|
||||
|
||||
# Generate exported executables for cross-compiled builds (Android, WebGL, and iOS)
|
||||
|
||||
@@ -6,7 +6,3 @@
|
||||
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||
|
||||
## Release notes for next branch cut
|
||||
|
||||
- engine: add `View::getLastDynamicResolutionScale()` (b/457753622)
|
||||
|
||||
- materials: Make Material Instances' UBO descriptor use dynamic offsets. [⚠️ **Recompile Materials**]
|
||||
|
||||
25
README.md
25
README.md
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.67.0'
|
||||
implementation 'com.google.android.filament:filament-android:1.69.3'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -39,19 +39,18 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
||||
|
||||
| Artifact | Description |
|
||||
| ------------- | ------------- |
|
||||
| [](https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android) | The Filament rendering engine itself. |
|
||||
| [](https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android-debug) | Debug version of `filament-android`. |
|
||||
| [](https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/gltfio-android) | A glTF 2.0 loader for Filament, depends on `filament-android`. |
|
||||
| [](https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-utils-android) | KTX loading, Kotlin math, and camera utilities, depends on `gltfio-android`. |
|
||||
| [](https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android) | A runtime material builder/compiler. This library is large but contains a full shader compiler/validator/optimizer and supports both OpenGL and Vulkan. |
|
||||
| [](https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android-lite) | A much smaller alternative to `filamat-android` that can only generate OpenGL shaders. It does not provide validation or optimizations. |
|
||||
| [](https://mvnrepository.com/artifact/com.google.android.filament/filament-android) | The Filament rendering engine itself. |
|
||||
| [](https://mvnrepository.com/artifact/com.google.android.filament/filament-android-debug) | Debug version of `filament-android`. |
|
||||
| [](https://mvnrepository.com/artifact/com.google.android.filament/gltfio-android) | A glTF 2.0 loader for Filament, depends on `filament-android`. |
|
||||
| [](https://mvnrepository.com/artifact/com.google.android.filament/filament-utils-android) | KTX loading, Kotlin math, and camera utilities, depends on `gltfio-android`. |
|
||||
| [](https://mvnrepository.com/artifact/com.google.android.filament/filamat-android) | A runtime material builder/compiler. This library is large but contains a full shader compiler/validator/optimizer and supports both OpenGL and Vulkan. |
|
||||
|
||||
### iOS
|
||||
|
||||
iOS projects can use CocoaPods to install the latest release:
|
||||
|
||||
```shell
|
||||
pod 'Filament', '~> 1.67.0'
|
||||
pod 'Filament', '~> 1.69.3'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
@@ -89,7 +88,8 @@ pod 'Filament', '~> 1.67.0'
|
||||
- OpenGL ES 3.0+ for Android and iOS
|
||||
- Metal for macOS and iOS
|
||||
- Vulkan 1.0 for Android, Linux, macOS, and Windows
|
||||
- WebGL 2.0 for all platforms
|
||||
- WebGPU for Android, Linux, macOS, and Windows
|
||||
- WebGL 2.0 for all browsers supporting it
|
||||
|
||||
### Rendering
|
||||
|
||||
@@ -124,7 +124,7 @@ pod 'Filament', '~> 1.67.0'
|
||||
|
||||
- HDR bloom
|
||||
- Depth of field bokeh
|
||||
- Multiple tone mappers: generic (customizable), ACES, filmic, etc.
|
||||
- Multiple tone mappers: PBR Neutral, AgX, generic (customizable), ACES, filmic, etc.
|
||||
- Color and tone management: luminance scaling, gamut mapping
|
||||
- Color grading: exposure, night adaptation, white balance, channel mixer,
|
||||
shadows/mid-tones/highlights, ASC CDL, contrast, saturation, etc.
|
||||
@@ -158,15 +158,16 @@ pod 'Filament', '~> 1.67.0'
|
||||
- [x] KHR_draco_mesh_compression
|
||||
- [x] KHR_lights_punctual
|
||||
- [x] KHR_materials_clearcoat
|
||||
- [x] KHR_materials_dispersion
|
||||
- [x] KHR_materials_emissive_strength
|
||||
- [x] KHR_materials_ior
|
||||
- [x] KHR_materials_pbrSpecularGlossiness
|
||||
- [x] KHR_materials_sheen
|
||||
- [x] KHR_materials_specular
|
||||
- [x] KHR_materials_transmission
|
||||
- [x] KHR_materials_unlit
|
||||
- [x] KHR_materials_variants
|
||||
- [x] KHR_materials_volume
|
||||
- [x] KHR_materials_specular
|
||||
- [x] KHR_mesh_quantization
|
||||
- [x] KHR_texture_basisu
|
||||
- [x] KHR_texture_transform
|
||||
@@ -331,7 +332,7 @@ and tools.
|
||||
- `filamesh`: Mesh converter
|
||||
- `glslminifier`: Minifies GLSL source code
|
||||
- `matc`: Material compiler
|
||||
- `filament-matp`: Material parser
|
||||
- `matedit`: Material editor for compiled materials
|
||||
- `matinfo` Displays information about materials compiled with `matc`
|
||||
- `mipgen` Generates a series of miplevels from a source image
|
||||
- `normal-blending`: Tool to blend normal maps
|
||||
|
||||
@@ -7,6 +7,50 @@ 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.69.4
|
||||
|
||||
|
||||
## v1.69.3
|
||||
|
||||
|
||||
## v1.69.2
|
||||
|
||||
- engine: fix shader compilation failure in TAA material
|
||||
- engine: fix stereo & parallel shader compilation
|
||||
|
||||
## v1.69.1
|
||||
|
||||
|
||||
## v1.69.0
|
||||
|
||||
- engine: Support custom attributes morphing, and allow for omitting position and/or normal data. [⚠️ **Recompile Materials**]
|
||||
|
||||
## v1.68.5
|
||||
|
||||
- engine: "native" Streams are officially deprecated. Use "acquired" streams instead.
|
||||
- engine: add "engine.skip_frame_when_cpu_ahead_of_display" feature [b/474599530]
|
||||
|
||||
## v1.68.4
|
||||
|
||||
- gltfio: Add optional support for webp textures (EXT_texture_webp), controlled via FILAMENT_SUPPORTS_WEBP_TEXTURES cmake option
|
||||
|
||||
## v1.68.3
|
||||
|
||||
- materials: added support for the glTF `KHR_materials_dispersion` extension, which adds dispersion for refractive objects
|
||||
|
||||
## v1.68.2
|
||||
|
||||
- Support `setPresentationTime` with the Metal backend.
|
||||
|
||||
## v1.68.1
|
||||
|
||||
|
||||
## v1.68.0
|
||||
|
||||
- engine: add `View::getLastDynamicResolutionScale()` (b/457753622)
|
||||
- Metal: report GPU errors to the platform via `debugUpdateStat` (b/431665753).
|
||||
- materials: Make Material Instances' UBO descriptor use dynamic offsets. [⚠️ **Recompile Materials**]
|
||||
|
||||
## v1.67.1
|
||||
|
||||
- Metal: Add support for the `SwapChain::CONFIG_MSAA_4_SAMPLES` flag.
|
||||
|
||||
@@ -100,7 +100,7 @@ buildscript {
|
||||
'kotlin': '2.0.21',
|
||||
'kotlin_coroutines': '1.9.0',
|
||||
'buildTools': '35.0.0',
|
||||
'ndk': '27.0.11718014',
|
||||
'ndk': '29.0.14206865',
|
||||
'androidx_core': '1.13.1',
|
||||
'androidx_annotations': '1.9.0'
|
||||
]
|
||||
@@ -124,6 +124,7 @@ buildscript {
|
||||
|
||||
ext.cmakeArgs = [
|
||||
"--no-warn-unused-cli",
|
||||
"-DANDROID_WEAK_API_DEFS=ON",
|
||||
"-DANDROID_PIE=ON",
|
||||
"-DANDROID_PLATFORM=21",
|
||||
"-DANDROID_STL=c++_static",
|
||||
@@ -193,7 +194,7 @@ subprojects {
|
||||
google()
|
||||
}
|
||||
|
||||
if (!name.startsWith("sample")) {
|
||||
if (!name.startsWith("sample") && name != "filament-tools") {
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
|
||||
120
android/buildSrc/README.md
Normal file
120
android/buildSrc/README.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# Filament Tools Gradle Plugin
|
||||
|
||||
## About
|
||||
|
||||
The **Filament Tools Gradle Plugin** helps integrate Filament into your Android project. It
|
||||
automates the use of Filament's command-line tools (`matc`, `cmgen`, and `filamesh`).
|
||||
|
||||
This plugin handles:
|
||||
- **Material Compilation**: Compiles `.mat` material definitions into `.filamat` binaries.
|
||||
- **IBL Generation**: Generates Image-Based Lighting assets from HDR environment maps.
|
||||
- **Mesh Compilation**: Converts models into Filament's efficient `.filamesh` binary format. *Note:
|
||||
This tool is no longer recommended; instead, use glTF and Filament's `gltfio` library for model
|
||||
loading.*
|
||||
|
||||
The plugin hooks directly into the Android build lifecycle (via `preBuild`) and supports incremental
|
||||
builds, so assets are only recompiled when source files change.
|
||||
|
||||
## Usage
|
||||
|
||||
Apply the plugin in your module's `build.gradle` file and configure the `filament` block. You can
|
||||
specify inputs and outputs for any combination of materials, IBLs, or meshes. If a path is not
|
||||
configured, the corresponding task will be disabled.
|
||||
|
||||
### Example Configuration
|
||||
|
||||
```groovy
|
||||
plugins {
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
filament {
|
||||
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
||||
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
||||
|
||||
iblInputFile = project.layout.projectDirectory.file("path/to/environment.hdr")
|
||||
iblOutputDir = project.layout.projectDirectory.dir("src/main/assets/envs")
|
||||
|
||||
meshInputFile = project.layout.projectDirectory.file("path/to/model.obj")
|
||||
meshOutputDir = project.layout.projectDirectory.dir("src/main/assets/models")
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration Details
|
||||
|
||||
- **materialInputDir**: The directory containing your source material definitions (`.mat` files).
|
||||
- **materialOutputDir**: The directory where the compiled material files (`.filamat`) will be
|
||||
generated.
|
||||
- **iblInputFile**: The source high-dynamic-range image file (e.g., `.hdr` or `.exr`) used to
|
||||
generate Image Based Lighting assets.
|
||||
- **iblOutputDir**: The directory where the generated IBL assets (typically `.ktx` files) will be
|
||||
placed.
|
||||
- **meshInputFile**: The source mesh file (e.g., `.obj`) to be compiled.
|
||||
- **meshOutputDir**: The directory where the compiled mesh file (`.filamesh`) will be generated.
|
||||
|
||||
Automatically adds tasks to your Android build to compile materials, generate an IBL, and compile a
|
||||
mesh. The plugin hooks into `preBuild` to ensure assets are generated before the application is
|
||||
built.
|
||||
|
||||
### Configuration Flags
|
||||
|
||||
You can control specific compilation options using Gradle properties (e.g., in `gradle.properties`
|
||||
or via command line `-P`).
|
||||
|
||||
- **`com.google.android.filament.exclude-vulkan`** When set to `true`, the Vulkan backend is
|
||||
excluded from the compiled materials. This can be useful to reduce the size of the generated
|
||||
assets if your application does not target Vulkan. *Default: `false` (Vulkan is included)*
|
||||
|
||||
- **`com.google.android.filament.include-webgpu`** When set to `true`, the WebGPU backend is
|
||||
included in the compiled materials. Use this if you intend to use the materials in a context
|
||||
supporting WebGPU. *Default: `false`*
|
||||
|
||||
## Tools Configuration
|
||||
|
||||
The Filament Tools plugin requires some binary tools to be available: `matc`, `cmgen`, and
|
||||
`filamesh`.
|
||||
|
||||
There are three ways to configure Filament tools:
|
||||
|
||||
1. **Point to a local path directly**
|
||||
|
||||
```groovy
|
||||
filament {
|
||||
matc {
|
||||
path = "/path/to/matc"
|
||||
}
|
||||
|
||||
cmgen {
|
||||
path = "/path/to/cmgen"
|
||||
}
|
||||
|
||||
filamesh {
|
||||
path = "/path/to/filamesh"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **Point to a Maven artifact**
|
||||
|
||||
```groovy
|
||||
filament {
|
||||
matc {
|
||||
// The minor version (the middle number) must match the Filament dependency's.
|
||||
artifact = 'com.google.android.filament:matc:1.68.5'
|
||||
}
|
||||
|
||||
cmgen {
|
||||
artifact = 'com.google.android.filament:cmgen:1.68.5'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
*Note that the `filamesh` artifact is not hosted on Maven Central, so it must be provided locally or
|
||||
via another mechanism if needed.*
|
||||
|
||||
Gradle will automatically handle downloading the tool appropriate for your machine (MacOS/Linux/Windows) from Maven.
|
||||
|
||||
3. **Set the `com.google.android.filament.tools-dir` Gradle property**
|
||||
|
||||
This will override any other configuration. Gradle will attempt to locate the tools under
|
||||
`<tools-dir>/bin` (e.g., `.../bin/matc`, `.../bin/cmgen`, `.../bin/filamesh`).
|
||||
@@ -4,13 +4,18 @@ plugins {
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
create("filament-tools-plugin") {
|
||||
id = "filament-tools-plugin"
|
||||
implementationClass = "FilamentToolsPlugin"
|
||||
create("filament-plugin") {
|
||||
id = "filament-plugin"
|
||||
implementationClass = "com.google.android.filament.gradle.FilamentPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "com.google.gradle:osdetector-gradle-plugin:1.7.3"
|
||||
}
|
||||
|
||||
@@ -1,359 +0,0 @@
|
||||
// This plugin accepts the following parameters:
|
||||
//
|
||||
// com.google.android.filament.tools-dir
|
||||
// Path to the Filament distribution/install directory for desktop.
|
||||
// This directory must contain bin/matc.
|
||||
//
|
||||
// com.google.android.filament.exclude-vulkan
|
||||
// When set, support for Vulkan will be excluded.
|
||||
//
|
||||
// Example:
|
||||
// ./gradlew -Pcom.google.android.filament.tools-dir=../../dist-release assembleDebug
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.provider.ProviderFactory
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.file.FileSystemOperations
|
||||
import org.gradle.api.file.FileType
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.logging.LogLevel
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.InputDirectory
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.gradle.api.tasks.OutputDirectory
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.api.tasks.incremental.InputFileDetails
|
||||
import org.gradle.api.model.ObjectFactory
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
import org.gradle.process.ExecOperations
|
||||
import org.gradle.work.ChangeType
|
||||
import org.gradle.work.Incremental
|
||||
import org.gradle.work.InputChanges
|
||||
|
||||
import java.nio.file.Paths
|
||||
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class TaskWithBinary extends DefaultTask {
|
||||
private final String binaryName
|
||||
private Property<String> binaryPath = null
|
||||
|
||||
TaskWithBinary(String name) {
|
||||
binaryName = name
|
||||
}
|
||||
|
||||
@Inject abstract ObjectFactory getObjects()
|
||||
@Inject abstract ProviderFactory getProviders()
|
||||
|
||||
@Input
|
||||
Property<String> getBinary() {
|
||||
if (binaryPath == null) {
|
||||
def tool = ["/bin/${binaryName}.exe", "/bin/${binaryName}"]
|
||||
def fullPath = tool.collect { path ->
|
||||
def filamentToolsPath = providers
|
||||
.gradleProperty("com.google.android.filament.tools-dir")
|
||||
.forUseAtConfigurationTime().get()
|
||||
def directory = objects.fileProperty()
|
||||
.fileValue(new File(filamentToolsPath)).getAsFile().get()
|
||||
Paths.get(directory.absolutePath, path).toFile()
|
||||
}
|
||||
|
||||
binaryPath = objects.property(String.class)
|
||||
binaryPath.set(
|
||||
(OperatingSystem.current().isWindows() ? fullPath[0] : fullPath[1]).toString())
|
||||
}
|
||||
return binaryPath
|
||||
}
|
||||
}
|
||||
|
||||
class LogOutputStream extends ByteArrayOutputStream {
|
||||
private final Logger logger
|
||||
private final LogLevel level
|
||||
|
||||
LogOutputStream(Logger logger, LogLevel level) {
|
||||
this.logger = logger
|
||||
this.level = level
|
||||
}
|
||||
|
||||
@Override
|
||||
void flush() {
|
||||
logger.log(level, toString())
|
||||
reset()
|
||||
}
|
||||
}
|
||||
|
||||
// Custom task to compile material files using matc
|
||||
// This task handles incremental builds
|
||||
abstract class MaterialCompiler extends TaskWithBinary {
|
||||
@Incremental
|
||||
@InputDirectory
|
||||
abstract DirectoryProperty getInputDir()
|
||||
|
||||
@OutputDirectory
|
||||
abstract DirectoryProperty getOutputDir()
|
||||
|
||||
@Inject abstract FileSystemOperations getFs()
|
||||
@Inject abstract ExecOperations getExec()
|
||||
@Inject abstract ObjectFactory getObjects()
|
||||
@Inject abstract ProviderFactory getProviders()
|
||||
|
||||
MaterialCompiler() {
|
||||
super("matc")
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void execute(InputChanges inputs) {
|
||||
if (!inputs.incremental) {
|
||||
fs.delete({
|
||||
delete(objects.fileTree().from(outputDir).matching { include '*.filamat' })
|
||||
})
|
||||
}
|
||||
|
||||
inputs.getFileChanges(inputDir).each { InputFileDetails change ->
|
||||
if (change.fileType == FileType.DIRECTORY) return
|
||||
|
||||
def file = change.file
|
||||
|
||||
if (change.changeType == ChangeType.REMOVED) {
|
||||
getOutputFile(file).delete()
|
||||
} else {
|
||||
def out = new LogOutputStream(logger, LogLevel.LIFECYCLE)
|
||||
def err = new LogOutputStream(logger, LogLevel.ERROR)
|
||||
|
||||
def header = ("Compiling material " + file + "\n").getBytes()
|
||||
out.write(header)
|
||||
out.flush()
|
||||
|
||||
if (!new File(binary.get()).exists()) {
|
||||
throw new GradleException("Could not find ${binary.get()}." +
|
||||
" Ensure Filament has been built/installed before building this app.")
|
||||
}
|
||||
|
||||
def matcArgs = []
|
||||
def exclude_vulkan = providers
|
||||
.gradleProperty("com.google.android.filament.exclude-vulkan")
|
||||
.forUseAtConfigurationTime().present
|
||||
if (!exclude_vulkan) {
|
||||
matcArgs += ['-a', 'vulkan']
|
||||
}
|
||||
def include_webgpu = providers
|
||||
.gradleProperty("com.google.android.filament.include-webgpu")
|
||||
.forUseAtConfigurationTime().present
|
||||
if (include_webgpu) {
|
||||
matcArgs += ['-a', 'webgpu', '--variant-filter=stereo']
|
||||
}
|
||||
|
||||
def mat_no_opt = providers
|
||||
.gradleProperty("com.google.android.filament.matnopt")
|
||||
.forUseAtConfigurationTime().present
|
||||
if (mat_no_opt) {
|
||||
matcArgs += ['-g']
|
||||
}
|
||||
|
||||
matcArgs += ['-a', 'opengl', '-p', 'mobile', '-o', getOutputFile(file), file]
|
||||
|
||||
exec.exec {
|
||||
standardOutput out
|
||||
errorOutput err
|
||||
executable "${binary.get()}"
|
||||
args matcArgs
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File getOutputFile(final File file) {
|
||||
return outputDir.file(file.name[0..file.name.lastIndexOf('.')] + 'filamat').get().asFile
|
||||
}
|
||||
}
|
||||
|
||||
// Custom task to process IBLs using cmgen
|
||||
// This task handles incremental builds
|
||||
abstract class IblGenerator extends TaskWithBinary {
|
||||
@Input
|
||||
@Optional
|
||||
abstract Property<String> getCmgenArgs()
|
||||
|
||||
@Incremental
|
||||
@InputFile
|
||||
abstract RegularFileProperty getInputFile()
|
||||
|
||||
@OutputDirectory
|
||||
abstract DirectoryProperty getOutputDir()
|
||||
|
||||
@Inject abstract FileSystemOperations getFs()
|
||||
@Inject abstract ExecOperations getExec()
|
||||
@Inject abstract ObjectFactory getObjects()
|
||||
|
||||
IblGenerator() {
|
||||
super("cmgen")
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void execute(InputChanges inputs) {
|
||||
if (!inputs.incremental) {
|
||||
fs.delete({
|
||||
delete(objects.fileTree().from(outputDir).matching { include '*' })
|
||||
})
|
||||
}
|
||||
|
||||
inputs.getFileChanges(inputFile).each { InputFileDetails change ->
|
||||
if (change.fileType == FileType.DIRECTORY) return
|
||||
|
||||
def file = change.file
|
||||
|
||||
if (change.changeType == ChangeType.REMOVED) {
|
||||
getOutputFile(file).delete()
|
||||
} else {
|
||||
def out = new LogOutputStream(logger, LogLevel.LIFECYCLE)
|
||||
def err = new LogOutputStream(logger, LogLevel.ERROR)
|
||||
|
||||
def header = ("Generating IBL " + file + "\n").getBytes()
|
||||
out.write(header)
|
||||
out.flush()
|
||||
|
||||
if (!new File(binary.get()).exists()) {
|
||||
throw new GradleException("Could not find ${binary.get()}." +
|
||||
" Ensure Filament has been built/installed before building this app.")
|
||||
}
|
||||
|
||||
def outputPath = outputDir.get().asFile
|
||||
def commandArgs = cmgenArgs.getOrNull()
|
||||
if (commandArgs == null) {
|
||||
commandArgs =
|
||||
'-q -x ' + outputPath + ' --format=rgb32f ' +
|
||||
'--extract-blur=0.08 --extract=' + outputPath.absolutePath
|
||||
}
|
||||
commandArgs = commandArgs + " " + file
|
||||
|
||||
exec.exec {
|
||||
standardOutput out
|
||||
errorOutput err
|
||||
executable "${binary.get()}"
|
||||
args(commandArgs.split())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File getOutputFile(final File file) {
|
||||
return outputDir.file(file.name[0..file.name.lastIndexOf('.') - 1]).get().asFile
|
||||
}
|
||||
}
|
||||
|
||||
// Custom task to compile mesh files using filamesh
|
||||
// This task handles incremental builds
|
||||
abstract class MeshCompiler extends TaskWithBinary {
|
||||
@Incremental
|
||||
@InputFile
|
||||
abstract RegularFileProperty getInputFile()
|
||||
|
||||
@OutputDirectory
|
||||
abstract DirectoryProperty getOutputDir()
|
||||
|
||||
@Inject abstract FileSystemOperations getFs()
|
||||
@Inject abstract ExecOperations getExec()
|
||||
|
||||
MeshCompiler() {
|
||||
super("filamesh")
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void execute(InputChanges inputs) {
|
||||
if (!inputs.incremental) {
|
||||
fs.delete({
|
||||
delete(objects.fileTree().from(outputDir).matching { include '*.filamesh' })
|
||||
})
|
||||
}
|
||||
|
||||
inputs.getFileChanges(inputFile).each { InputFileDetails change ->
|
||||
if (change.fileType == FileType.DIRECTORY) return
|
||||
|
||||
def file = change.file
|
||||
|
||||
if (change.changeType == ChangeType.REMOVED) {
|
||||
getOutputFile(file).delete()
|
||||
} else {
|
||||
def out = new LogOutputStream(logger, LogLevel.LIFECYCLE)
|
||||
def err = new LogOutputStream(logger, LogLevel.ERROR)
|
||||
|
||||
def header = ("Compiling mesh " + file + "\n").getBytes()
|
||||
out.write(header)
|
||||
out.flush()
|
||||
|
||||
if (!new File(binary.get()).exists()) {
|
||||
throw new GradleException("Could not find ${binary.get()}." +
|
||||
" Ensure Filament has been built/installed before building this app.")
|
||||
}
|
||||
|
||||
exec.exec {
|
||||
standardOutput out
|
||||
errorOutput err
|
||||
executable "${binary.get()}"
|
||||
args(file, getOutputFile(file))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File getOutputFile(final File file) {
|
||||
return outputDir.file(file.name[0..file.name.lastIndexOf('.')] + 'filamesh').get().asFile
|
||||
}
|
||||
}
|
||||
|
||||
class FilamentToolsPluginExtension {
|
||||
DirectoryProperty materialInputDir
|
||||
DirectoryProperty materialOutputDir
|
||||
|
||||
String cmgenArgs
|
||||
RegularFileProperty iblInputFile
|
||||
DirectoryProperty iblOutputDir
|
||||
|
||||
RegularFileProperty meshInputFile
|
||||
DirectoryProperty meshOutputDir
|
||||
}
|
||||
|
||||
class FilamentToolsPlugin implements Plugin<Project> {
|
||||
void apply(Project project) {
|
||||
def extension = project.extensions.create('filamentTools', FilamentToolsPluginExtension)
|
||||
extension.materialInputDir = project.objects.directoryProperty()
|
||||
extension.materialOutputDir = project.objects.directoryProperty()
|
||||
extension.iblInputFile = project.objects.fileProperty()
|
||||
extension.iblOutputDir = project.objects.directoryProperty()
|
||||
extension.meshInputFile = project.objects.fileProperty()
|
||||
extension.meshOutputDir = project.objects.directoryProperty()
|
||||
|
||||
project.tasks.register("filamentCompileMaterials", MaterialCompiler) {
|
||||
enabled =
|
||||
extension.materialInputDir.isPresent() &&
|
||||
extension.materialOutputDir.isPresent()
|
||||
inputDir.set(extension.materialInputDir.getOrNull())
|
||||
outputDir.set(extension.materialOutputDir.getOrNull())
|
||||
}
|
||||
|
||||
project.preBuild.dependsOn "filamentCompileMaterials"
|
||||
|
||||
project.tasks.register("filamentGenerateIbl", IblGenerator) {
|
||||
enabled = extension.iblInputFile.isPresent() && extension.iblOutputDir.isPresent()
|
||||
cmgenArgs = extension.cmgenArgs
|
||||
inputFile = extension.iblInputFile.getOrNull()
|
||||
outputDir = extension.iblOutputDir.getOrNull()
|
||||
}
|
||||
|
||||
project.preBuild.dependsOn "filamentGenerateIbl"
|
||||
|
||||
project.tasks.register("filamentCompileMesh", MeshCompiler) {
|
||||
enabled = extension.meshInputFile.isPresent() && extension.meshOutputDir.isPresent()
|
||||
inputFile = extension.meshInputFile.getOrNull()
|
||||
outputDir = extension.meshOutputDir.getOrNull()
|
||||
}
|
||||
|
||||
project.preBuild.dependsOn "filamentCompileMesh"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.gradle
|
||||
|
||||
import org.gradle.api.Action
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.provider.Property
|
||||
|
||||
class FilamentExtension {
|
||||
final ToolsLocator tools
|
||||
final DirectoryProperty materialInputDir
|
||||
final DirectoryProperty materialOutputDir
|
||||
final Property<String> cmgenArgs
|
||||
final RegularFileProperty iblInputFile
|
||||
final DirectoryProperty iblOutputDir
|
||||
final RegularFileProperty meshInputFile
|
||||
final DirectoryProperty meshOutputDir
|
||||
|
||||
FilamentExtension(Project project) {
|
||||
this.tools = new ToolsLocator(project)
|
||||
this.materialInputDir = project.objects.directoryProperty()
|
||||
this.materialOutputDir = project.objects.directoryProperty()
|
||||
this.cmgenArgs = project.objects.property(String)
|
||||
this.iblInputFile = project.objects.fileProperty()
|
||||
this.iblOutputDir = project.objects.directoryProperty()
|
||||
this.meshInputFile = project.objects.fileProperty()
|
||||
this.meshOutputDir = project.objects.directoryProperty()
|
||||
}
|
||||
|
||||
void matc(Action<ToolsLocator.ToolConfig> action) {
|
||||
action.execute(tools.matc)
|
||||
}
|
||||
|
||||
void cmgen(Action<ToolsLocator.ToolConfig> action) {
|
||||
action.execute(tools.cmgen)
|
||||
}
|
||||
|
||||
void filamesh(Action<ToolsLocator.ToolConfig> action) {
|
||||
action.execute(tools.filamesh)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.gradle
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
|
||||
class FilamentPlugin implements Plugin<Project> {
|
||||
@Override
|
||||
void apply(Project project) {
|
||||
project.pluginManager.apply("com.google.osdetector")
|
||||
|
||||
FilamentExtension extension = project.extensions.create("filament", FilamentExtension, project)
|
||||
|
||||
project.afterEvaluate {
|
||||
extension.tools.resolve(project)
|
||||
|
||||
project.tasks.register("filamentCompileMaterials", MaterialCompileTask) {
|
||||
enabled = extension.materialInputDir.isPresent() && extension.materialOutputDir.isPresent()
|
||||
inputDir.set(extension.materialInputDir.getOrNull())
|
||||
outputDir.set(extension.materialOutputDir.getOrNull())
|
||||
matcTool.from(extension.tools.matcToolFiles)
|
||||
}
|
||||
|
||||
project.tasks.register("filamentGenerateIbl", IblGenerateTask) {
|
||||
enabled = extension.iblInputFile.isPresent() && extension.iblOutputDir.isPresent()
|
||||
cmgenArgs = extension.cmgenArgs
|
||||
inputFile.set(extension.iblInputFile.getOrNull())
|
||||
outputDir.set(extension.iblOutputDir.getOrNull())
|
||||
cmgenTool.from(extension.tools.cmgenToolFiles)
|
||||
}
|
||||
|
||||
project.tasks.register("filamentCompileMesh", MeshCompileTask) {
|
||||
enabled = extension.meshInputFile.isPresent() && extension.meshOutputDir.isPresent()
|
||||
inputFile = extension.meshInputFile.getOrNull()
|
||||
outputDir = extension.meshOutputDir.getOrNull()
|
||||
filameshTool.from(extension.tools.filameshToolFiles)
|
||||
}
|
||||
|
||||
project.preBuild.dependsOn "filamentCompileMaterials"
|
||||
project.preBuild.dependsOn "filamentGenerateIbl"
|
||||
project.preBuild.dependsOn "filamentCompileMesh"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.gradle
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.file.FileSystemOperations
|
||||
import org.gradle.api.file.FileType
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.model.ObjectFactory
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.gradle.api.tasks.OutputDirectory
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.process.ExecOperations
|
||||
import org.gradle.work.ChangeType
|
||||
import org.gradle.work.Incremental
|
||||
import org.gradle.work.InputChanges
|
||||
import org.gradle.api.tasks.incremental.InputFileDetails
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class IblGenerateTask extends DefaultTask {
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
abstract Property<String> getCmgenArgs()
|
||||
|
||||
@Incremental
|
||||
@InputFile
|
||||
abstract RegularFileProperty getInputFile()
|
||||
|
||||
@OutputDirectory
|
||||
abstract DirectoryProperty getOutputDir()
|
||||
|
||||
@InputFiles
|
||||
abstract ConfigurableFileCollection getCmgenTool()
|
||||
|
||||
@Inject
|
||||
abstract FileSystemOperations getFileSystemOperations()
|
||||
|
||||
@Inject
|
||||
abstract ExecOperations getExecOperations()
|
||||
|
||||
@Inject
|
||||
abstract ObjectFactory getObjectFactory()
|
||||
|
||||
@TaskAction
|
||||
void execute(InputChanges inputs) {
|
||||
if (cmgenTool.empty) {
|
||||
throw new IllegalStateException(
|
||||
"cmgen executable not configured. Please configure the 'cmgen' block in the " +
|
||||
"'filament' extension or set the 'com.google.android.filament.tools-dir' " +
|
||||
"property."
|
||||
)
|
||||
}
|
||||
|
||||
File cmgen = getCmgenTool().singleFile
|
||||
if (!cmgen.exists()) {
|
||||
throw new IllegalStateException("cmgen executable does not exist: ${cmgen.absolutePath}")
|
||||
}
|
||||
|
||||
if (!cmgen.canExecute()) {
|
||||
cmgen.setExecutable(true)
|
||||
}
|
||||
|
||||
if (!inputs.incremental) {
|
||||
getFileSystemOperations().delete {
|
||||
delete(getObjectFactory().fileTree().from(getOutputDir()).matching { include '*' })
|
||||
}
|
||||
}
|
||||
|
||||
inputs.getFileChanges(getInputFile()).each { InputFileDetails change ->
|
||||
if (change.fileType == FileType.DIRECTORY) return
|
||||
|
||||
def file = change.file
|
||||
|
||||
if (change.changeType == ChangeType.REMOVED) {
|
||||
computeOutputFile(file).delete()
|
||||
} else {
|
||||
println "Generating IBL: ${file.name}"
|
||||
|
||||
def outputPath = getOutputDir().get().asFile
|
||||
def commandArgs = getCmgenArgs().getOrNull()
|
||||
if (commandArgs == null) {
|
||||
// Default args if not provided
|
||||
commandArgs = '-q -x ' + outputPath + ' --format=rgb32f ' +
|
||||
'--extract-blur=0.08 --extract=' + outputPath.absolutePath
|
||||
}
|
||||
|
||||
def argsList = commandArgs.split(' ').toList()
|
||||
argsList.add(file.absolutePath)
|
||||
|
||||
getExecOperations().exec { spec ->
|
||||
spec.executable(cmgen)
|
||||
spec.args(argsList)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File computeOutputFile(final File file) {
|
||||
String name = file.name
|
||||
int dotIndex = name.lastIndexOf('.')
|
||||
String baseName = dotIndex > 0 ? name.substring(0, dotIndex) : name
|
||||
return getOutputDir().file(baseName).get().asFile
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.gradle
|
||||
|
||||
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.file.FileSystemOperations
|
||||
import org.gradle.api.file.FileType
|
||||
import org.gradle.api.model.ObjectFactory
|
||||
import org.gradle.api.provider.ProviderFactory
|
||||
import org.gradle.api.tasks.InputDirectory
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.gradle.api.tasks.OutputDirectory
|
||||
import org.gradle.api.tasks.PathSensitive
|
||||
import org.gradle.api.tasks.PathSensitivity
|
||||
import org.gradle.api.tasks.SkipWhenEmpty
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.process.ExecOperations
|
||||
import org.gradle.work.ChangeType
|
||||
import org.gradle.work.Incremental
|
||||
import org.gradle.work.InputChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class MaterialCompileTask extends DefaultTask {
|
||||
|
||||
@Incremental
|
||||
@InputDirectory
|
||||
@PathSensitive(PathSensitivity.RELATIVE)
|
||||
abstract DirectoryProperty getInputDir()
|
||||
|
||||
@OutputDirectory
|
||||
abstract DirectoryProperty getOutputDir()
|
||||
|
||||
@InputFiles
|
||||
@PathSensitive(PathSensitivity.NONE)
|
||||
abstract ConfigurableFileCollection getMatcTool()
|
||||
|
||||
@Inject
|
||||
abstract ExecOperations getExecOperations()
|
||||
|
||||
@Inject
|
||||
abstract FileSystemOperations getFileSystemOperations()
|
||||
|
||||
@Inject
|
||||
abstract ObjectFactory getObjectFactory()
|
||||
|
||||
@Inject
|
||||
abstract ProviderFactory getProviderFactory()
|
||||
|
||||
@TaskAction
|
||||
void compile(InputChanges inputs) {
|
||||
if (matcTool.empty) {
|
||||
throw new IllegalStateException(
|
||||
"matc executable not configured. Please configure the 'matc' block in the " +
|
||||
"'filament' extension or set the 'com.google.android.filament.tools-dir' " +
|
||||
"property."
|
||||
)
|
||||
}
|
||||
|
||||
File matc = matcTool.singleFile
|
||||
if (!matc.exists()) {
|
||||
throw new IllegalStateException("matc executable does not exist: ${matc.absolutePath}")
|
||||
}
|
||||
|
||||
if (!matc.canExecute()) {
|
||||
matc.setExecutable(true)
|
||||
}
|
||||
|
||||
if (!inputs.incremental) {
|
||||
getFileSystemOperations().delete {
|
||||
delete(getObjectFactory().fileTree().from(getOutputDir()).matching {
|
||||
include '*.filamat'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
def pf = getProviderFactory()
|
||||
def excludeVulkanProperty = pf.gradleProperty("com.google.android.filament.exclude-vulkan")
|
||||
def includeWebGpuProperty = pf.gradleProperty("com.google.android.filament.include-webgpu")
|
||||
def matNoOptProperty = pf.gradleProperty("com.google.android.filament.matnopt")
|
||||
def excludeVulkan = excludeVulkanProperty.orNull == "true"
|
||||
def includeWebGpu = includeWebGpuProperty.orNull == "true"
|
||||
def matNoOpt = matNoOptProperty.orNull == "true"
|
||||
|
||||
inputs.getFileChanges(getInputDir()).each { change ->
|
||||
if (change.fileType == FileType.DIRECTORY) return
|
||||
|
||||
File file = change.file
|
||||
File outputFile = computeOutputFile(file)
|
||||
|
||||
if (change.changeType == ChangeType.REMOVED) {
|
||||
outputFile.delete()
|
||||
} else {
|
||||
println "Compiling material: ${file.name}"
|
||||
|
||||
def args = []
|
||||
if (!excludeVulkan) {
|
||||
args += ['-a', 'vulkan']
|
||||
}
|
||||
|
||||
if (includeWebGpu) {
|
||||
args += ['-a', 'webgpu', '--variant-filter=stereo']
|
||||
}
|
||||
|
||||
if (matNoOpt) {
|
||||
args += ['-g']
|
||||
}
|
||||
|
||||
args += [
|
||||
'-a', 'opengl', '-p', 'mobile',
|
||||
'-o', outputFile.absolutePath,
|
||||
file.absolutePath
|
||||
]
|
||||
|
||||
getExecOperations().exec { spec ->
|
||||
spec.executable(matc)
|
||||
spec.args(args)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File computeOutputFile(File inputFile) {
|
||||
String baseName = inputFile.name
|
||||
int dotIndex = baseName.lastIndexOf('.')
|
||||
if (dotIndex > 0) {
|
||||
baseName = baseName.substring(0, dotIndex)
|
||||
}
|
||||
return getOutputDir().file("${baseName}.filamat").get().asFile
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.gradle
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.file.FileSystemOperations
|
||||
import org.gradle.api.file.FileType
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.model.ObjectFactory
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.gradle.api.tasks.OutputDirectory
|
||||
import org.gradle.api.tasks.PathSensitive
|
||||
import org.gradle.api.tasks.PathSensitivity
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.process.ExecOperations
|
||||
import org.gradle.work.ChangeType
|
||||
import org.gradle.work.Incremental
|
||||
import org.gradle.work.InputChanges
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class MeshCompileTask extends DefaultTask {
|
||||
|
||||
@Incremental
|
||||
@InputFile
|
||||
@PathSensitive(PathSensitivity.RELATIVE)
|
||||
abstract RegularFileProperty getInputFile()
|
||||
|
||||
@OutputDirectory
|
||||
abstract DirectoryProperty getOutputDir()
|
||||
|
||||
@InputFiles
|
||||
@PathSensitive(PathSensitivity.NONE)
|
||||
abstract ConfigurableFileCollection getFilameshTool()
|
||||
|
||||
@Inject
|
||||
abstract ExecOperations getExecOperations()
|
||||
|
||||
@Inject
|
||||
abstract FileSystemOperations getFileSystemOperations()
|
||||
|
||||
@Inject
|
||||
abstract ObjectFactory getObjectFactory()
|
||||
|
||||
@TaskAction
|
||||
void compile(InputChanges inputs) {
|
||||
if (filameshTool.empty) {
|
||||
throw new IllegalStateException(
|
||||
"filamesh executable not configured. Please configure the 'filamesh' block in the " +
|
||||
"'filament' extension or set the 'com.google.android.filament.tools-dir' " +
|
||||
"property."
|
||||
)
|
||||
}
|
||||
|
||||
File filamesh = filameshTool.singleFile
|
||||
if (!filamesh.exists()) {
|
||||
throw new IllegalStateException("filamesh executable does not exist: ${filamesh.absolutePath}")
|
||||
}
|
||||
|
||||
if (!filamesh.canExecute()) {
|
||||
filamesh.setExecutable(true)
|
||||
}
|
||||
|
||||
if (!inputs.incremental) {
|
||||
getFileSystemOperations().delete {
|
||||
delete(getObjectFactory().fileTree().from(getOutputDir()).matching {
|
||||
include '*.filamesh'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
inputs.getFileChanges(inputFile).each { change ->
|
||||
if (change.fileType == FileType.DIRECTORY) return
|
||||
|
||||
File file = change.file
|
||||
File outputFile = computeOutputFile(file)
|
||||
|
||||
if (change.changeType == ChangeType.REMOVED) {
|
||||
outputFile.delete()
|
||||
} else {
|
||||
println "Compiling mesh: ${file.name}"
|
||||
|
||||
getExecOperations().exec { spec ->
|
||||
spec.executable(filamesh)
|
||||
spec.args(file.absolutePath, outputFile.absolutePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File computeOutputFile(File inputFile) {
|
||||
String baseName = inputFile.name
|
||||
int dotIndex = baseName.lastIndexOf('.')
|
||||
if (dotIndex > 0) {
|
||||
baseName = baseName.substring(0, dotIndex)
|
||||
}
|
||||
return getOutputDir().file("${baseName}.filamesh").get().asFile
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.gradle
|
||||
|
||||
import org.gradle.api.Action
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.artifacts.Configuration
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
|
||||
import java.nio.file.Paths
|
||||
|
||||
class ToolsLocator {
|
||||
static class ToolConfig {
|
||||
String artifact
|
||||
String path
|
||||
FileCollection files
|
||||
}
|
||||
|
||||
final ToolConfig matc = new ToolConfig()
|
||||
final ToolConfig cmgen = new ToolConfig()
|
||||
final ToolConfig filamesh = new ToolConfig()
|
||||
private final Project project
|
||||
|
||||
ToolsLocator(Project project) {
|
||||
this.project = project
|
||||
}
|
||||
|
||||
void resolve(Project project) {
|
||||
resolveTool(matc, "matc")
|
||||
resolveTool(cmgen, "cmgen")
|
||||
resolveTool(filamesh, "filamesh")
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a specific tool by its name and sets the {@link ToolConfig#files} property of the
|
||||
* provided {@link ToolConfig} object. It first attempts to locate the tool based on a Gradle
|
||||
* property `com.google.android.filament.tools-dir` if present, otherwise it resolves the tool
|
||||
* through a Gradle configuration.
|
||||
*
|
||||
* @param tool The {@link ToolConfig} object whose {@code files} property will be set.
|
||||
* @param name The name of the tool (e.g., "matc", "cmgen").
|
||||
*/
|
||||
private void resolveTool(ToolConfig tool, String name) {
|
||||
// Find the OS classifier, e.g. 'osx-aarch_64'.
|
||||
def classifier =
|
||||
project.extensions.getByType(com.google.gradle.osdetector.OsDetector).classifier
|
||||
|
||||
// If com.google.android.filament.tools-dir is set, we'll use it as the tool's base path.
|
||||
def toolsDirProp = project.providers.gradleProperty("com.google.android.filament.tools-dir")
|
||||
if (toolsDirProp.isPresent()) {
|
||||
def toolsDir = toolsDirProp.get()
|
||||
def path = OperatingSystem.current().isWindows() ?
|
||||
"${toolsDir}/bin/${name}.exe" :
|
||||
"${toolsDir}/bin/${name}"
|
||||
tool.files = project.files(path)
|
||||
return
|
||||
}
|
||||
|
||||
// If an explicit path for the tool is provided in ToolConfig
|
||||
// (e.g. matc { path = 'path/to/tool' }), use it directly.
|
||||
if (tool.path) {
|
||||
tool.files = project.files(tool.path)
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, if an artifact is provided
|
||||
// (e.g. matc { artifact = 'com.google.android.filament:matc:1.68.5' }), resolve it.
|
||||
if (tool.artifact) {
|
||||
String depString = tool.artifact
|
||||
|
||||
// In Gradle, a configuration is a named, manageable group of dependencies.
|
||||
// Resolve the tool artifact using a detached configuration. A detached configuration
|
||||
// is a temporary, isolated configuration that is not part of the project's regular
|
||||
// configuration hierarchy.
|
||||
Configuration config = project.configurations.detachedConfiguration()
|
||||
config.setTransitive(false) // We only want the tool itself, not its dependencies
|
||||
|
||||
def dep = project.dependencies.create("${depString}:${classifier}@exe")
|
||||
config.dependencies.add(dep)
|
||||
|
||||
// A Gradle Configuration implements FileCollection. When treated as a FileCollection,
|
||||
// it represents the resolved files of its dependencies.
|
||||
tool.files = config
|
||||
}
|
||||
}
|
||||
|
||||
FileCollection getMatcToolFiles() {
|
||||
return matc.files ?: project.files()
|
||||
}
|
||||
|
||||
FileCollection getCmgenToolFiles() {
|
||||
return cmgen.files ?: project.files()
|
||||
}
|
||||
|
||||
FileCollection getFilameshToolFiles() {
|
||||
return filamesh.files ?: project.files()
|
||||
}
|
||||
}
|
||||
@@ -335,6 +335,13 @@ Java_com_google_android_filament_filamat_MaterialBuilder_nMaterialBuilderRefract
|
||||
builder->refractionMode((MaterialBuilder::RefractionMode) mode);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_filamat_MaterialBuilder_nMaterialBuilderReflectionMode(JNIEnv* env,
|
||||
jclass, jlong nativeBuilder, jint mode) {
|
||||
auto builder = (MaterialBuilder*) nativeBuilder;
|
||||
builder->reflectionMode((MaterialBuilder::ReflectionMode) mode);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_filamat_MaterialBuilder_nMaterialBuilderRefractionType(JNIEnv* env,
|
||||
jclass, jlong nativeBuilder, jint type) {
|
||||
|
||||
@@ -177,6 +177,11 @@ public class MaterialBuilder {
|
||||
SCREEN_SPACE
|
||||
}
|
||||
|
||||
public enum ReflectionMode {
|
||||
DEFAULT,
|
||||
SCREEN_SPACE
|
||||
}
|
||||
|
||||
public enum RefractionType {
|
||||
SOLID,
|
||||
THIN
|
||||
@@ -403,6 +408,12 @@ public class MaterialBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public MaterialBuilder reflectionMode(ReflectionMode mode) {
|
||||
nMaterialBuilderReflectionMode(mNativeObject, mode.ordinal());
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public MaterialBuilder refractionType(RefractionType type) {
|
||||
nMaterialBuilderRefractionType(mNativeObject, type.ordinal());
|
||||
@@ -604,6 +615,7 @@ public class MaterialBuilder {
|
||||
private static native void nMaterialBuilderSpecularAntiAliasingThreshold(long mNativeObject,
|
||||
float threshold);
|
||||
private static native void nMaterialBuilderRefractionMode(long nativeBuilder, int mode);
|
||||
private static native void nMaterialBuilderReflectionMode(long nativeBuilder, int mode);
|
||||
private static native void nMaterialBuilderRefractionType(long nativeBuilder, int type);
|
||||
private static native void nMaterialBuilderClearCoatIorChange(long mNativeObject,
|
||||
boolean clearCoatIorChange);
|
||||
|
||||
@@ -134,6 +134,7 @@ target_include_directories(filament-jni PRIVATE
|
||||
../../filament/backend/include
|
||||
../../third_party/robin-map
|
||||
../../third_party/perfetto
|
||||
../../libs/bluevk/include
|
||||
../../libs/utils/include)
|
||||
|
||||
# Ordering is significant in the following list. The PRIVATE qualifier prevents transitive deps.
|
||||
@@ -167,3 +168,9 @@ target_link_libraries(filament-jni
|
||||
|
||||
# Force a relink when the version script is changed:
|
||||
set_target_properties(filament-jni PROPERTIES LINK_DEPENDS ${VERSION_SCRIPT})
|
||||
|
||||
if (FILAMENT_SUPPORTS_VULKAN)
|
||||
add_definitions(-DFILAMENT_SUPPORTS_VULKAN=1)
|
||||
else()
|
||||
add_definitions(-DFILAMENT_SUPPORTS_VULKAN=0)
|
||||
endif()
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include <filament/Camera.h>
|
||||
|
||||
|
||||
#include <utils/Entity.h>
|
||||
|
||||
#include <math/mat4.h>
|
||||
@@ -40,6 +41,13 @@ Java_com_google_android_filament_Camera_nSetProjectionFov(JNIEnv*, jclass ,
|
||||
camera->setProjection(fovInDegrees, aspect, near, far, (Camera::Fov) fov);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jdouble JNICALL
|
||||
Java_com_google_android_filament_Camera_nGetFieldOfViewInDegrees(JNIEnv*, jclass,
|
||||
jlong nativeCamera, jint direction) {
|
||||
Camera *camera = (Camera *) nativeCamera;
|
||||
return camera->getFieldOfViewInDegrees((Camera::Fov) direction);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_Camera_nSetLensProjection(JNIEnv*, jclass,
|
||||
jlong nativeCamera, jdouble focalLength, jdouble aspect, jdouble near, jdouble far) {
|
||||
@@ -62,6 +70,21 @@ Java_com_google_android_filament_Camera_nSetCustomProjection(JNIEnv *env, jclass
|
||||
env->ReleaseDoubleArrayElements(inProjectionForCulling_, inProjectionForCulling, JNI_ABORT);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_Camera_nSetCustomEyeProjection(JNIEnv *env, jclass,
|
||||
jlong nativeCamera, jdoubleArray inProjection_, jint count, jdoubleArray inProjectionForCulling_,
|
||||
jdouble near, jdouble far) {
|
||||
Camera *camera = (Camera *) nativeCamera;
|
||||
jdouble *inProjection = env->GetDoubleArrayElements(inProjection_, NULL);
|
||||
jdouble *inProjectionForCulling = env->GetDoubleArrayElements(inProjectionForCulling_, NULL);
|
||||
camera->setCustomEyeProjection(
|
||||
reinterpret_cast<const filament::math::mat4 *>(inProjection), (size_t) count,
|
||||
*reinterpret_cast<const filament::math::mat4 *>(inProjectionForCulling),
|
||||
near, far);
|
||||
env->ReleaseDoubleArrayElements(inProjection_, inProjection, JNI_ABORT);
|
||||
env->ReleaseDoubleArrayElements(inProjectionForCulling_, inProjectionForCulling, JNI_ABORT);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_Camera_nSetScaling(JNIEnv* env, jclass,
|
||||
jlong nativeCamera, jdouble x, jdouble y) {
|
||||
@@ -76,6 +99,17 @@ Java_com_google_android_filament_Camera_nSetShift(JNIEnv* env, jclass,
|
||||
camera->setShift({(double)x, (double)y});
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_Camera_nGetShift(JNIEnv* env, jclass,
|
||||
jlong nativeCamera, jdoubleArray out_) {
|
||||
Camera *camera = (Camera *) nativeCamera;
|
||||
jdouble *out = env->GetDoubleArrayElements(out_, NULL);
|
||||
filament::math::double2 s = camera->getShift();
|
||||
out[0] = s.x;
|
||||
out[1] = s.y;
|
||||
env->ReleaseDoubleArrayElements(out_, out, 0);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_Camera_nLookAt(JNIEnv*, jclass, jlong nativeCamera,
|
||||
jdouble eye_x, jdouble eye_y, jdouble eye_z, jdouble center_x, jdouble center_y,
|
||||
@@ -115,6 +149,15 @@ Java_com_google_android_filament_Camera_nSetModelMatrixFp64(JNIEnv *env, jclass,
|
||||
env->ReleaseDoubleArrayElements(in_, in, JNI_ABORT);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_Camera_nSetEyeModelMatrix(JNIEnv *env, jclass,
|
||||
jlong nativeCamera, jint eyeId, jdoubleArray model_) {
|
||||
Camera* camera = (Camera *) nativeCamera;
|
||||
jdouble *model = env->GetDoubleArrayElements(model_, NULL);
|
||||
camera->setEyeModelMatrix((uint8_t)eyeId, *reinterpret_cast<const filament::math::mat4*>(model));
|
||||
env->ReleaseDoubleArrayElements(model_, model, JNI_ABORT);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_Camera_nGetProjectionMatrix(JNIEnv *env, jclass,
|
||||
jlong nativeCamera, jdoubleArray out_) {
|
||||
@@ -280,3 +323,5 @@ Java_com_google_android_filament_Camera_nComputeEffectiveFov(JNIEnv*, jclass,
|
||||
jdouble fovInDegrees, jdouble focusDistance) {
|
||||
return Camera::computeEffectiveFov(fovInDegrees, focusDistance);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include <filament/Camera.h>
|
||||
#include <filament/Engine.h>
|
||||
#include <filament/MorphTargetBuffer.h>
|
||||
|
||||
#include <utils/Entity.h>
|
||||
#include <utils/EntityManager.h>
|
||||
@@ -207,6 +208,14 @@ Java_com_google_android_filament_Engine_nDestroySkinningBuffer(JNIEnv*, jclass,
|
||||
return engine->destroy(skinningBuffer);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nDestroyMorphTargetBuffer(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jlong nativeMorphTargetBuffer) {
|
||||
Engine* engine = (Engine*) nativeEngine;
|
||||
MorphTargetBuffer* mtb = (MorphTargetBuffer*) nativeMorphTargetBuffer;
|
||||
return engine->destroy(mtb);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nDestroyIndirectLight(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jlong nativeIndirectLight) {
|
||||
@@ -328,6 +337,13 @@ Java_com_google_android_filament_Engine_nIsValidSkinningBuffer(JNIEnv*, jclass,
|
||||
return (jboolean)engine->isValid((SkinningBuffer*)nativeSkinningBuffer);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nIsValidMorphTargetBuffer(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jlong nativeMorphTargetBuffer) {
|
||||
Engine* engine = (Engine*) nativeEngine;
|
||||
return (jboolean) engine->isValid((MorphTargetBuffer*) nativeMorphTargetBuffer);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nIsValidIndirectLight(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jlong nativeIndirectLight) {
|
||||
|
||||
@@ -25,7 +25,7 @@ using namespace filament;
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_Material_nBuilderBuild(JNIEnv *env, jclass,
|
||||
jlong nativeEngine, jobject buffer_, jint size, jint shBandCount, jint shadowQuality) {
|
||||
jlong nativeEngine, jobject buffer_, jint size, jint shBandCount, jint shadowQuality, jint uboBatchingMode) {
|
||||
Engine* engine = (Engine*) nativeEngine;
|
||||
AutoBuffer buffer(env, buffer_, size);
|
||||
auto builder = Material::Builder();
|
||||
@@ -33,6 +33,7 @@ Java_com_google_android_filament_Material_nBuilderBuild(JNIEnv *env, jclass,
|
||||
builder.sphericalHarmonicsBandCount(shBandCount);
|
||||
}
|
||||
builder.shadowSamplingQuality((Material::Builder::ShadowSamplingQuality)shadowQuality);
|
||||
builder.uboBatching((Material::UboBatchingMode)uboBatchingMode);
|
||||
Material* material = builder
|
||||
.package(buffer.getData(), buffer.getSize())
|
||||
.build(*engine);
|
||||
@@ -95,6 +96,14 @@ Java_com_google_android_filament_Material_nGetBlendingMode(JNIEnv*, jclass,
|
||||
return (jint) material->getBlendingMode();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_Material_nGetTransparencyMode(JNIEnv*, jclass,
|
||||
jlong nativeMaterial) {
|
||||
Material* material = (Material*) nativeMaterial;
|
||||
return (jint) material->getTransparencyMode();
|
||||
}
|
||||
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint JNICALL
|
||||
|
||||
@@ -564,3 +564,19 @@ Java_com_google_android_filament_MaterialInstance_nGetDepthFunc(JNIEnv* env, jcl
|
||||
MaterialInstance* instance = (MaterialInstance*)nativeMaterialInstance;
|
||||
return (jint)instance->getDepthFunc();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_MaterialInstance_nSetTransparencyMode(JNIEnv*, jclass,
|
||||
jlong nativeMaterialInstance, jint mode) {
|
||||
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
|
||||
instance->setTransparencyMode((MaterialInstance::TransparencyMode) mode);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_MaterialInstance_nGetTransparencyMode(JNIEnv*, jclass,
|
||||
jlong nativeMaterialInstance) {
|
||||
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
|
||||
return (jint) instance->getTransparencyMode();
|
||||
}
|
||||
|
||||
@@ -58,6 +58,27 @@ Java_com_google_android_filament_MorphTargetBuffer_nBuilderCount(JNIEnv*, jclass
|
||||
builder->count((size_t) count);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_MorphTargetBuffer_nBuilderWithPositions(JNIEnv*, jclass,
|
||||
jlong nativeBuilder, jboolean enabled) {
|
||||
MorphTargetBuffer::Builder* builder = (MorphTargetBuffer::Builder*) nativeBuilder;
|
||||
builder->withPositions(enabled);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_MorphTargetBuffer_nBuilderWithTangents(JNIEnv*, jclass,
|
||||
jlong nativeBuilder, jboolean enabled) {
|
||||
MorphTargetBuffer::Builder* builder = (MorphTargetBuffer::Builder*) nativeBuilder;
|
||||
builder->withTangents(enabled);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_MorphTargetBuffer_nBuilderEnableCustomMorphing(JNIEnv*,
|
||||
jclass, jlong nativeBuilder, jboolean enabled) {
|
||||
MorphTargetBuffer::Builder* builder = (MorphTargetBuffer::Builder*) nativeBuilder;
|
||||
builder->enableCustomMorphing(enabled);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_MorphTargetBuffer_nBuilderBuild(JNIEnv*, jclass,
|
||||
@@ -112,3 +133,24 @@ Java_com_google_android_filament_MorphTargetBuffer_nGetCount(JNIEnv*, jclass,
|
||||
MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeObject;
|
||||
return (jint)morphTargetBuffer->getCount();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_MorphTargetBuffer_nHasPositions(JNIEnv*, jclass,
|
||||
jlong nativeObject) {
|
||||
MorphTargetBuffer* morphTargetBuffer = (MorphTargetBuffer*) nativeObject;
|
||||
return (jboolean) morphTargetBuffer->hasPositions();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_MorphTargetBuffer_nHasTangents(JNIEnv*, jclass,
|
||||
jlong nativeObject) {
|
||||
MorphTargetBuffer* morphTargetBuffer = (MorphTargetBuffer*) nativeObject;
|
||||
return (jboolean) morphTargetBuffer->hasTangents();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_MorphTargetBuffer_nIsCustomMorphingEnabled(JNIEnv*, jclass,
|
||||
jlong nativeObject) {
|
||||
MorphTargetBuffer* morphTargetBuffer = (MorphTargetBuffer*) nativeObject;
|
||||
return (jboolean) morphTargetBuffer->isCustomMorphingEnabled();
|
||||
}
|
||||
|
||||
@@ -366,6 +366,13 @@ Java_com_google_android_filament_RenderableManager_nSetPriority(JNIEnv*, jclass,
|
||||
rm->setPriority((RenderableManager::Instance) i, (uint8_t) priority);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_RenderableManager_nGetPriority(JNIEnv*, jclass,
|
||||
jlong nativeRenderableManager, jint i) {
|
||||
RenderableManager *rm = (RenderableManager *) nativeRenderableManager;
|
||||
return (jint) rm->getPriority((RenderableManager::Instance) i);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_RenderableManager_nSetChannel(JNIEnv*, jclass,
|
||||
jlong nativeRenderableManager, jint i, jint channel) {
|
||||
@@ -373,6 +380,13 @@ Java_com_google_android_filament_RenderableManager_nSetChannel(JNIEnv*, jclass,
|
||||
rm->setChannel((RenderableManager::Instance) i, (uint8_t) channel);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_RenderableManager_nGetChannel(JNIEnv*, jclass,
|
||||
jlong nativeRenderableManager, jint i) {
|
||||
RenderableManager *rm = (RenderableManager *) nativeRenderableManager;
|
||||
return (jint) rm->getChannel((RenderableManager::Instance) i);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_RenderableManager_nSetCulling(JNIEnv*, jclass,
|
||||
jlong nativeRenderableManager, jint i, jboolean enabled) {
|
||||
@@ -380,6 +394,13 @@ Java_com_google_android_filament_RenderableManager_nSetCulling(JNIEnv*, jclass,
|
||||
rm->setCulling((RenderableManager::Instance) i, enabled);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_RenderableManager_nIsCullingEnabled(JNIEnv*, jclass,
|
||||
jlong nativeRenderableManager, jint i) {
|
||||
RenderableManager *rm = (RenderableManager *) nativeRenderableManager;
|
||||
return (jboolean) rm->isCullingEnabled((RenderableManager::Instance) i);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_RenderableManager_nSetFogEnabled(JNIEnv*, jclass,
|
||||
jlong nativeRenderableManager, jint i, jboolean enabled) {
|
||||
@@ -429,6 +450,13 @@ Java_com_google_android_filament_RenderableManager_nIsShadowReceiver(JNIEnv*, jc
|
||||
return (jboolean) rm->isShadowReceiver((RenderableManager::Instance) i);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_RenderableManager_nIsScreenSpaceContactShadowsEnabled(JNIEnv*, jclass,
|
||||
jlong nativeRenderableManager, jint i) {
|
||||
RenderableManager *rm = (RenderableManager *) nativeRenderableManager;
|
||||
return (jboolean) rm->isScreenSpaceContactShadowsEnabled((RenderableManager::Instance) i);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_RenderableManager_nGetAxisAlignedBoundingBox(JNIEnv* env,
|
||||
jclass, jlong nativeRenderableManager, jint i, jfloatArray center_,
|
||||
@@ -500,6 +528,13 @@ Java_com_google_android_filament_RenderableManager_nSetBlendOrderAt(JNIEnv*, jcl
|
||||
(uint16_t) blendOrder);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_RenderableManager_nGetBlendOrderAt(JNIEnv*, jclass,
|
||||
jlong nativeRenderableManager, jint i, jint primitiveIndex) {
|
||||
RenderableManager *rm = (RenderableManager *) nativeRenderableManager;
|
||||
return (jint) rm->getBlendOrderAt((RenderableManager::Instance) i, (size_t) primitiveIndex);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_RenderableManager_nSetGlobalBlendOrderEnabledAt(JNIEnv*, jclass,
|
||||
jlong nativeRenderableManager, jint i, jint primitiveIndex, jboolean enabled) {
|
||||
@@ -508,6 +543,13 @@ Java_com_google_android_filament_RenderableManager_nSetGlobalBlendOrderEnabledAt
|
||||
(bool) enabled);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_RenderableManager_nIsGlobalBlendOrderEnabledAt(JNIEnv*, jclass,
|
||||
jlong nativeRenderableManager, jint i, jint primitiveIndex) {
|
||||
RenderableManager *rm = (RenderableManager *) nativeRenderableManager;
|
||||
return (jboolean) rm->isGlobalBlendOrderEnabledAt((RenderableManager::Instance) i, (size_t) primitiveIndex);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_RenderableManager_nGetEnabledAttributesAt(JNIEnv*, jclass,
|
||||
jlong nativeRenderableManager, jint i, jint primitiveIndex) {
|
||||
|
||||
@@ -63,6 +63,14 @@ Java_com_google_android_filament_Skybox_nBuilderColor(JNIEnv *, jclass,
|
||||
builder->color({r, g, b, a});
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_Skybox_nBuilderPriority(JNIEnv *, jclass,
|
||||
jlong nativeSkyBoxBuilder, jint priority) {
|
||||
Skybox::Builder *builder = (Skybox::Builder *) nativeSkyBoxBuilder;
|
||||
builder->priority(uint8_t(priority));
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_Skybox_nBuilderBuild(JNIEnv *env, jclass type,
|
||||
jlong nativeSkyBoxBuilder, jlong nativeEngine) {
|
||||
|
||||
@@ -21,6 +21,11 @@
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <android/bitmap.h>
|
||||
#include <android/hardware_buffer_jni.h>
|
||||
#include <backend/platforms/PlatformEGLAndroid.h>
|
||||
# if FILAMENT_SUPPORTS_VULKAN
|
||||
# include <backend/platforms/VulkanPlatformAndroid.h>
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include <filament/Engine.h>
|
||||
@@ -168,6 +173,13 @@ Java_com_google_android_filament_Texture_nBuilderSwizzle(JNIEnv *, jclass ,
|
||||
(Texture::Swizzle)r, (Texture::Swizzle)g, (Texture::Swizzle)b, (Texture::Swizzle)a);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_Texture_nBuilderSamples(JNIEnv*, jclass,
|
||||
jlong nativeBuilder, jint samples) {
|
||||
Texture::Builder *builder = (Texture::Builder *) nativeBuilder;
|
||||
builder->samples((uint8_t) samples);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_Texture_nBuilderImportTexture(JNIEnv*, jclass, jlong nativeBuilder, jlong id) {
|
||||
@@ -388,6 +400,57 @@ Java_com_google_android_filament_Texture_nSetExternalImage(JNIEnv*, jclass, jlon
|
||||
texture->setExternalImage(*engine, (void*)eglImage);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Texture_nSetExternalImageByAHB(JNIEnv *env, jclass clazz,
|
||||
jlong nativeTexture, jlong nativeEngine, jobject ahb) {
|
||||
Texture *texture = (Texture *) nativeTexture;
|
||||
Engine *engine = (Engine *) nativeEngine;
|
||||
|
||||
#ifdef __ANDROID__
|
||||
Platform* platform = engine->getPlatform();
|
||||
AHardwareBuffer* nativeBuffer = nullptr;
|
||||
if (__builtin_available(android 26, *)) {
|
||||
nativeBuffer = AHardwareBuffer_fromHardwareBuffer(env, ahb);
|
||||
}
|
||||
if (!nativeBuffer) {
|
||||
// either we're not on Android 26, or ahb wasn't a AHardwareBuffer
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
if (engine->getBackend() == Backend::OPENGL) {
|
||||
// CAVEAT: we assume that Backend::OPENGL on Android implies PlatformEGLAndroid.
|
||||
#if UTILS_HAS_RTTI
|
||||
if (!dynamic_cast<PlatformEGLAndroid*>(platform)) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
#endif
|
||||
auto* eglPlatform = (PlatformEGLAndroid*) platform;
|
||||
auto ref = eglPlatform->createExternalImage(nativeBuffer, false);
|
||||
texture->setExternalImage(*engine, ref);
|
||||
}
|
||||
|
||||
#if FILAMENT_SUPPORTS_VULKAN
|
||||
else if (engine->getBackend() == Backend::VULKAN) {
|
||||
// CAVEAT: we assume that Backend::VULKAN on Android implies VulkanPlatformAndroid.
|
||||
#if UTILS_HAS_RTTI
|
||||
if (!dynamic_cast<VulkanPlatformAndroid*>(platform)) {
|
||||
return JNI_FALSE;
|
||||
}
|
||||
#endif
|
||||
auto* vulkanPlatform = (VulkanPlatformAndroid*) platform;
|
||||
auto ref = vulkanPlatform->createExternalImage(nativeBuffer, false);
|
||||
texture->setExternalImage(*engine, ref);
|
||||
}
|
||||
#endif // FILAMENT_SUPPORTS_VULKAN
|
||||
// success!
|
||||
return JNI_TRUE;
|
||||
#else
|
||||
// other platforms could come here
|
||||
return JNI_FALSE;
|
||||
#endif // __ANDROID__
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_Texture_nSetExternalStream(JNIEnv*, jclass,
|
||||
jlong nativeTexture, jlong nativeEngine, jlong nativeStream) {
|
||||
@@ -607,3 +670,4 @@ Java_com_google_android_filament_android_TextureHelper_nSetBitmapWithCallback(JN
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -52,6 +52,11 @@ Java_com_google_android_filament_ToneMapper_nCreatePBRNeutralToneMapper(JNIEnv*,
|
||||
return (jlong) new PBRNeutralToneMapper();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_ToneMapper_nCreateGT7ToneMapper(JNIEnv*, jclass) {
|
||||
return (jlong) new GT7ToneMapper();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_ToneMapper_nCreateAgxToneMapper(JNIEnv*, jclass, jint look) {
|
||||
return (jlong) new AgxToneMapper(AgxToneMapper::AgxLook(look));
|
||||
|
||||
@@ -76,6 +76,12 @@ Java_com_google_android_filament_View_nSetVisibleLayers(JNIEnv*, jclass, jlong n
|
||||
view->setVisibleLayers((uint8_t) select, (uint8_t) value);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_View_nGetVisibleLayers(JNIEnv*, jclass, jlong nativeView) {
|
||||
View* view = (View*) nativeView;
|
||||
return view->getVisibleLayers();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_View_nSetShadowingEnabled(JNIEnv*, jclass, jlong nativeView, jboolean enabled) {
|
||||
View* view = (View*) nativeView;
|
||||
@@ -440,6 +446,18 @@ Java_com_google_android_filament_View_nIsShadowingEnabled(JNIEnv *, jclass, jlon
|
||||
return (jboolean)view->isShadowingEnabled();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_View_nSetFrustumCullingEnabled(JNIEnv*, jclass, jlong nativeView, jboolean enabled) {
|
||||
View* view = (View*) nativeView;
|
||||
view->setFrustumCullingEnabled(enabled);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_View_nIsFrustumCullingEnabled(JNIEnv*, jclass, jlong nativeView) {
|
||||
View* view = (View*) nativeView;
|
||||
return (jboolean)view->isFrustumCullingEnabled();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_View_nSetScreenSpaceRefractionEnabled(JNIEnv *, jclass,
|
||||
|
||||
@@ -136,4 +136,13 @@ final class Asserts {
|
||||
throw new ArrayIndexOutOfBoundsException("Array length must be at least 4");
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull @Size(min = 2)
|
||||
static double[] assertDouble2(@Nullable double[] out) {
|
||||
if (out == null) out = new double[2];
|
||||
else if (out.length < 2) {
|
||||
throw new ArrayIndexOutOfBoundsException("Array length must be at least 2");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,6 +343,27 @@ public class Camera {
|
||||
nSetScaling(getNativeObject(), xscaling, yscaling);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a custom projection matrix for each eye.
|
||||
*
|
||||
* @param inProjection An array of projection matrices, one for each eye.
|
||||
* Must have at least 16 * count elements.
|
||||
* @param count Number of eyes to set.
|
||||
* @param inProjectionForCulling Custom projection matrix for culling, must encompass all eyes.
|
||||
* @param near Distance to the near plane.
|
||||
* @param far Distance to the far plane.
|
||||
*/
|
||||
public void setCustomEyeProjection(
|
||||
@NonNull double[] inProjection, int count,
|
||||
@NonNull @Size(min = 16) double[] inProjectionForCulling,
|
||||
double near, double far) {
|
||||
Asserts.assertMat4dIn(inProjectionForCulling);
|
||||
if (inProjection.length < 16 * count) {
|
||||
throw new IllegalArgumentException("inProjection array too small for the given count");
|
||||
}
|
||||
nSetCustomEyeProjection(getNativeObject(), inProjection, count, inProjectionForCulling, near, far);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an additional matrix that scales the projection matrix.
|
||||
*
|
||||
@@ -399,6 +420,31 @@ public class Camera {
|
||||
nSetShift(getNativeObject(), xshift, yshift);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the shift amount used to translate the projection matrix.
|
||||
*
|
||||
* @param out A 2-double array where the shift will be stored, or null.
|
||||
* @return A 2-double array containing the x and y shift.
|
||||
*/
|
||||
@NonNull @Size(min = 2)
|
||||
public double[] getShift(@Nullable @Size(min = 2) double[] out) {
|
||||
out = Asserts.assertDouble2(out);
|
||||
nGetShift(getNativeObject(), out);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the camera's field of view in degrees.
|
||||
*
|
||||
* @param direction The direction of the FOV (VERTICAL or HORIZONTAL).
|
||||
* @return The field of view in degrees.
|
||||
*/
|
||||
public double getFieldOfViewInDegrees(@NonNull Fov direction) {
|
||||
return nGetFieldOfViewInDegrees(getNativeObject(), direction.ordinal());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sets the camera's model matrix.
|
||||
* <p>
|
||||
@@ -745,6 +791,17 @@ public class Camera {
|
||||
return mEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the model matrix for a specific eye.
|
||||
*
|
||||
* @param eyeId The index of the eye.
|
||||
* @param model The model matrix for the eye.
|
||||
*/
|
||||
public void setEyeModelMatrix(int eyeId, @NonNull @Size(min = 16) double[] model) {
|
||||
Asserts.assertMat4dIn(model);
|
||||
nSetEyeModelMatrix(getNativeObject(), eyeId, model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to compute the effective focal length taking into account the focus distance
|
||||
*
|
||||
@@ -784,8 +841,13 @@ public class Camera {
|
||||
private static native void nSetCustomProjection(long nativeCamera, double[] inProjection, double[] inProjectionForCulling, double near, double far);
|
||||
private static native void nSetScaling(long nativeCamera, double x, double y);
|
||||
private static native void nSetShift(long nativeCamera, double x, double y);
|
||||
private static native void nGetShift(long nativeCamera, double[] out);
|
||||
private static native void nSetModelMatrix(long nativeCamera, float[] in);
|
||||
private static native void nSetModelMatrixFp64(long nativeCamera, double[] in);
|
||||
private static native void nSetEyeModelMatrix(long nativeCamera, int eyeId, double[] model);
|
||||
private static native void nSetCustomEyeProjection(long nativeCamera, double[] inProjection, int count, double[] inProjectionForCulling, double near, double far);
|
||||
private static native double nGetFieldOfViewInDegrees(long nativeCamera, int direction);
|
||||
|
||||
private static native void nLookAt(long nativeCamera, double eyeX, double eyeY, double eyeZ, double centerX, double centerY, double centerZ, double upX, double upY, double upZ);
|
||||
private static native double nGetNear(long nativeCamera);
|
||||
private static native double nGetCullingFar(long nativeCamera);
|
||||
|
||||
@@ -939,6 +939,15 @@ public class Engine {
|
||||
return nIsValidSkinningBuffer(getNativeObject(), object.getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the object is valid.
|
||||
* @param object Object to check for validity
|
||||
* @return returns true if the specified object is valid.
|
||||
*/
|
||||
public boolean isValidMorphTargetBuffer(@NonNull MorphTargetBuffer object) {
|
||||
return nIsValidMorphTargetBuffer(getNativeObject(), object.getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the object is valid.
|
||||
* @param object Object to check for validity
|
||||
@@ -1192,6 +1201,15 @@ public class Engine {
|
||||
skinningBuffer.clearNativeObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys a {@link MorphTargetBuffer} and frees all its associated resources.
|
||||
* @param morphTargetBuffer the {@link MorphTargetBuffer} to destroy
|
||||
*/
|
||||
public void destroyMorphTargetBuffer(@NonNull MorphTargetBuffer morphTargetBuffer) {
|
||||
assertDestroy(nDestroyMorphTargetBuffer(getNativeObject(), morphTargetBuffer.getNativeObject()));
|
||||
morphTargetBuffer.clearNativeObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys a {@link IndirectLight} and frees all its associated resources.
|
||||
* @param ibl the {@link IndirectLight} to destroy
|
||||
@@ -1483,6 +1501,7 @@ public class Engine {
|
||||
private static native boolean nDestroyIndexBuffer(long nativeEngine, long nativeIndexBuffer);
|
||||
private static native boolean nDestroyVertexBuffer(long nativeEngine, long nativeVertexBuffer);
|
||||
private static native boolean nDestroySkinningBuffer(long nativeEngine, long nativeSkinningBuffer);
|
||||
private static native boolean nDestroyMorphTargetBuffer(long nativeEngine, long nativeMorphTargetBuffer);
|
||||
private static native boolean nDestroyIndirectLight(long nativeEngine, long nativeIndirectLight);
|
||||
private static native boolean nDestroyMaterial(long nativeEngine, long nativeMaterial);
|
||||
private static native boolean nDestroyMaterialInstance(long nativeEngine, long nativeMaterialInstance);
|
||||
@@ -1499,6 +1518,7 @@ public class Engine {
|
||||
private static native boolean nIsValidIndexBuffer(long nativeEngine, long nativeIndexBuffer);
|
||||
private static native boolean nIsValidVertexBuffer(long nativeEngine, long nativeVertexBuffer);
|
||||
private static native boolean nIsValidSkinningBuffer(long nativeEngine, long nativeSkinningBuffer);
|
||||
private static native boolean nIsValidMorphTargetBuffer(long nativeEngine, long nativeMorphTargetBuffer);
|
||||
private static native boolean nIsValidIndirectLight(long nativeEngine, long nativeIndirectLight);
|
||||
private static native boolean nIsValidMaterial(long nativeEngine, long nativeMaterial);
|
||||
private static native boolean nIsValidMaterialInstance(long nativeEngine, long nativeMaterial, long nativeMaterialInstance);
|
||||
|
||||
@@ -54,6 +54,7 @@ public class Material {
|
||||
static final CullingMode[] sCullingModeValues = CullingMode.values();
|
||||
static final VertexBuffer.VertexAttribute[] sVertexAttributeValues =
|
||||
VertexBuffer.VertexAttribute.values();
|
||||
static final TransparencyMode[] sTransparencyModeValues = TransparencyMode.values();
|
||||
}
|
||||
|
||||
private long mNativeObject;
|
||||
@@ -160,6 +161,31 @@ public class Material {
|
||||
SCREEN,
|
||||
}
|
||||
|
||||
/**
|
||||
* How transparent objects are handled
|
||||
*
|
||||
* @see
|
||||
* <a href="https://google.github.io/filament/Materials.html#materialdefinitions/materialblock/blendingandtransparency:transparencymode">
|
||||
* Blending and transparency: transparencyMode</a>
|
||||
*/
|
||||
public enum TransparencyMode {
|
||||
/** The transparent object is drawn honoring the raster state. */
|
||||
DEFAULT,
|
||||
|
||||
/**
|
||||
* The transparent object is first drawn in the depth buffer,
|
||||
* then in the color buffer, honoring the culling mode, but ignoring the depth test function.
|
||||
*/
|
||||
TWO_PASSES_ONE_SIDE,
|
||||
|
||||
/**
|
||||
* The transparent object is drawn twice in the color buffer,
|
||||
* first with back faces only, then with front faces; the culling
|
||||
* mode is ignored. Can be combined with two-sided lighting.
|
||||
*/
|
||||
TWO_PASSES_TWO_SIDES
|
||||
}
|
||||
|
||||
/**
|
||||
* Supported refraction modes
|
||||
*
|
||||
@@ -261,6 +287,20 @@ public class Material {
|
||||
LOW
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines whether a material instance should use UBO batching or not.
|
||||
*/
|
||||
public enum UboBatchingMode {
|
||||
/**
|
||||
* For default, it follows the engine settings.
|
||||
* If UBO batching is enabled on the engine and the material domain is SURFACE, it
|
||||
* turns on the UBO batching. Otherwise, it turns off the UBO batching.
|
||||
*/
|
||||
DEFAULT,
|
||||
/** Disable the Ubo Batching for this material */
|
||||
DISABLED
|
||||
}
|
||||
|
||||
public static class UserVariantFilterBit {
|
||||
/** Directional lighting */
|
||||
public static int DIRECTIONAL_LIGHTING = 0x01;
|
||||
@@ -372,6 +412,7 @@ public class Material {
|
||||
private int mSize;
|
||||
private int mShBandCount = 0;
|
||||
private ShadowSamplingQuality mShadowSamplingQuality = ShadowSamplingQuality.LOW;
|
||||
private UboBatchingMode mUboBatchingMode = UboBatchingMode.DEFAULT;
|
||||
|
||||
|
||||
/**
|
||||
@@ -416,6 +457,17 @@ public class Material {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the batching mode of the instances created from this material.
|
||||
* @param uboBatchingMode
|
||||
* @return Reference to this Builder for chaining calls.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder uboBatching(UboBatchingMode mode) {
|
||||
mUboBatchingMode = mode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns the Material object.
|
||||
*
|
||||
@@ -428,7 +480,7 @@ public class Material {
|
||||
@NonNull
|
||||
public Material build(@NonNull Engine engine) {
|
||||
long nativeMaterial = nBuilderBuild(engine.getNativeObject(),
|
||||
mBuffer, mSize, mShBandCount, mShadowSamplingQuality.ordinal());
|
||||
mBuffer, mSize, mShBandCount, mShadowSamplingQuality.ordinal(), mUboBatchingMode.ordinal());
|
||||
if (nativeMaterial == 0) throw new IllegalStateException("Couldn't create Material");
|
||||
return new Material(nativeMaterial);
|
||||
}
|
||||
@@ -561,6 +613,18 @@ public class Material {
|
||||
return EnumCache.sBlendingModeValues[nGetBlendingMode(getNativeObject())];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the transparency mode of this material.
|
||||
* This value only makes sense when the blending mode is transparent or fade.
|
||||
*
|
||||
* @see
|
||||
* <a href="https://google.github.io/filament/Materials.html#materialdefinitions/materialblock/blendingandtransparency:transparencymode">
|
||||
* Blending and transparency: transparencyMode</a>
|
||||
*/
|
||||
public TransparencyMode getTransparencyMode() {
|
||||
return EnumCache.sTransparencyModeValues[nGetTransparencyMode(getNativeObject())];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the refraction mode of this material.
|
||||
*
|
||||
@@ -1094,7 +1158,7 @@ public class Material {
|
||||
mNativeObject = 0;
|
||||
}
|
||||
|
||||
private static native long nBuilderBuild(long nativeEngine, @NonNull Buffer buffer, int size, int shBandCount, int shadowQuality);
|
||||
private static native long nBuilderBuild(long nativeEngine, @NonNull Buffer buffer, int size, int shBandCount, int shadowQuality, int uboBatchingMode);
|
||||
private static native long nCreateInstance(long nativeMaterial);
|
||||
private static native long nCreateInstanceWithName(long nativeMaterial, @NonNull String name);
|
||||
private static native long nGetDefaultInstance(long nativeMaterial);
|
||||
@@ -1104,6 +1168,7 @@ public class Material {
|
||||
private static native int nGetShading(long nativeMaterial);
|
||||
private static native int nGetInterpolation(long nativeMaterial);
|
||||
private static native int nGetBlendingMode(long nativeMaterial);
|
||||
private static native int nGetTransparencyMode(long nativeMaterial);
|
||||
private static native int nGetVertexDomain(long nativeMaterial);
|
||||
private static native int nGetCullingMode(long nativeMaterial);
|
||||
private static native boolean nIsColorWriteEnabled(long nativeMaterial);
|
||||
|
||||
@@ -537,6 +537,14 @@ public class MaterialInstance {
|
||||
nSetDoubleSided(getNativeObject(), doubleSided);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the transparency mode for this material instance.
|
||||
* @see Material.TransparencyMode
|
||||
*/
|
||||
public void setTransparencyMode(@NonNull Material.TransparencyMode mode) {
|
||||
nSetTransparencyMode(getNativeObject(), mode.ordinal());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether double-sided lighting is enabled when the parent Material has double-sided
|
||||
* capability.
|
||||
@@ -545,6 +553,14 @@ public class MaterialInstance {
|
||||
return nIsDoubleSided(getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the transparency mode.
|
||||
*/
|
||||
@NonNull
|
||||
public Material.TransparencyMode getTransparencyMode() {
|
||||
return Material.EnumCache.sTransparencyModeValues[nGetTransparencyMode(getNativeObject())];
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the default triangle culling state that was set on the material.
|
||||
*
|
||||
@@ -982,4 +998,6 @@ public class MaterialInstance {
|
||||
private static native boolean nIsStencilWriteEnabled(long nativeMaterialInstance);
|
||||
private static native boolean nIsDepthCullingEnabled(long nativeMaterialInstance);
|
||||
private static native int nGetDepthFunc(long nativeMaterialInstance);
|
||||
private static native void nSetTransparencyMode(long nativeMaterialInstance, int mode);
|
||||
private static native int nGetTransparencyMode(long nativeMaterialInstance);
|
||||
}
|
||||
|
||||
@@ -64,6 +64,45 @@ public class MorphTargetBuffer {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method to enable or disable the built-in position morphing buffer.
|
||||
* Default is true.
|
||||
*
|
||||
* @param enabled true to enable, false to disable
|
||||
* @return this <code>Builder</code> object for chaining calls
|
||||
*/
|
||||
@NonNull
|
||||
public Builder withPositions(boolean enabled) {
|
||||
nBuilderWithPositions(mNativeBuilder, enabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method to enable or disable the built-in tangent morphing buffer.
|
||||
* Default is true.
|
||||
*
|
||||
* @param enabled true to enable, false to disable
|
||||
* @return this <code>Builder</code> object for chaining calls
|
||||
*/
|
||||
@NonNull
|
||||
public Builder withTangents(boolean enabled) {
|
||||
nBuilderWithTangents(mNativeBuilder, enabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method to enable or disable custom morphing.
|
||||
* Default is false.
|
||||
*
|
||||
* @param enabled true to enable, false to disable
|
||||
* @return this <code>Builder</code> object for chaining calls
|
||||
*/
|
||||
@NonNull
|
||||
public Builder enableCustomMorphing(boolean enabled) {
|
||||
nBuilderEnableCustomMorphing(mNativeBuilder, enabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns the <code>MorphTargetBuffer</code> object.
|
||||
*
|
||||
@@ -156,6 +195,27 @@ public class MorphTargetBuffer {
|
||||
return nGetCount(mNativeObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this MorphTargetBuffer has a position buffer.
|
||||
*/
|
||||
public boolean hasPositions() {
|
||||
return nHasPositions(mNativeObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this MorphTargetBuffer has a tangent buffer.
|
||||
*/
|
||||
public boolean hasTangents() {
|
||||
return nHasTangents(mNativeObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if custom morphing is enabled.
|
||||
*/
|
||||
public boolean isCustomMorphingEnabled() {
|
||||
return nIsCustomMorphingEnabled(mNativeObject);
|
||||
}
|
||||
|
||||
public long getNativeObject() {
|
||||
if (mNativeObject == 0) {
|
||||
throw new IllegalStateException("Calling method on destroyed MorphTargetBuffer");
|
||||
@@ -171,10 +231,16 @@ public class MorphTargetBuffer {
|
||||
private static native void nDestroyBuilder(long nativeBuilder);
|
||||
private static native void nBuilderVertexCount(long nativeBuilder, int vertexCount);
|
||||
private static native void nBuilderCount(long nativeBuilder, int count);
|
||||
private static native void nBuilderWithPositions(long nativeBuilder, boolean enabled);
|
||||
private static native void nBuilderWithTangents(long nativeBuilder, boolean enabled);
|
||||
private static native void nBuilderEnableCustomMorphing(long nativeBuilder, boolean enabled);
|
||||
private static native long nBuilderBuild(long nativeBuilder, long nativeEngine);
|
||||
|
||||
private static native int nSetPositionsAt(long nativeObject, long nativeEngine, int targetIndex, float[] positions, int count);
|
||||
private static native int nSetTangentsAt(long nativeObject, long nativeEngine, int targetIndex, short[] tangents, int count);
|
||||
private static native int nGetVertexCount(long nativeObject);
|
||||
private static native int nGetCount(long nativeObject);
|
||||
private static native boolean nHasPositions(long nativeObject);
|
||||
private static native boolean nHasTangents(long nativeObject);
|
||||
private static native boolean nIsCustomMorphingEnabled(long nativeObject);
|
||||
}
|
||||
|
||||
@@ -346,8 +346,8 @@ public class RenderableManager {
|
||||
*
|
||||
* @return Builder reference for chaining calls.
|
||||
*
|
||||
* @see Builder::blendOrder()
|
||||
* @see Builder::priority()
|
||||
* @see Builder#blendOrder()
|
||||
* @see Builder#priority()
|
||||
* @see RenderableManager::setBlendOrderAt()
|
||||
*/
|
||||
@NonNull
|
||||
@@ -725,6 +725,10 @@ public class RenderableManager {
|
||||
nSetPriority(mNativeObject, i, priority);
|
||||
}
|
||||
|
||||
public int getPriority(@EntityInstance int i) {
|
||||
return nGetPriority(mNativeObject, i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the channel of a renderable
|
||||
*
|
||||
@@ -734,6 +738,10 @@ public class RenderableManager {
|
||||
nSetChannel(mNativeObject, i, channel);
|
||||
}
|
||||
|
||||
public int getChannel(@EntityInstance int i) {
|
||||
return nGetChannel(mNativeObject, i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes whether or not frustum culling is on.
|
||||
*
|
||||
@@ -743,6 +751,10 @@ public class RenderableManager {
|
||||
nSetCulling(mNativeObject, i, enabled);
|
||||
}
|
||||
|
||||
public boolean isCullingEnabled(@EntityInstance int i) {
|
||||
return nIsCullingEnabled(mNativeObject, i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes whether or not the large-scale fog is applied to this renderable
|
||||
* @see Builder#fog
|
||||
@@ -812,6 +824,10 @@ public class RenderableManager {
|
||||
nSetScreenSpaceContactShadows(mNativeObject, i, enabled);
|
||||
}
|
||||
|
||||
public boolean isScreenSpaceContactShadowsEnabled(@EntityInstance int i) {
|
||||
return nIsScreenSpaceContactShadowsEnabled(mNativeObject, i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the renderable can cast shadows.
|
||||
*
|
||||
@@ -932,6 +948,10 @@ public class RenderableManager {
|
||||
nSetBlendOrderAt(mNativeObject, instance, primitiveIndex, blendOrder);
|
||||
}
|
||||
|
||||
public int getBlendOrderAt(@EntityInstance int instance, @IntRange(from = 0) int primitiveIndex) {
|
||||
return nGetBlendOrderAt(mNativeObject, instance, primitiveIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes whether the blend order is global or local to this Renderable (by default).
|
||||
*
|
||||
@@ -946,6 +966,10 @@ public class RenderableManager {
|
||||
nSetGlobalBlendOrderEnabledAt(mNativeObject, instance, primitiveIndex, enabled);
|
||||
}
|
||||
|
||||
public boolean isGlobalBlendOrderEnabledAt(@EntityInstance int instance, @IntRange(from = 0) int primitiveIndex) {
|
||||
return nIsGlobalBlendOrderEnabledAt(mNativeObject, instance, primitiveIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the set of enabled attribute slots in the given primitive's VertexBuffer.
|
||||
*/
|
||||
@@ -1013,8 +1037,11 @@ public class RenderableManager {
|
||||
private static native void nSetAxisAlignedBoundingBox(long nativeRenderableManager, int i, float cx, float cy, float cz, float ex, float ey, float ez);
|
||||
private static native void nSetLayerMask(long nativeRenderableManager, int i, int select, int value);
|
||||
private static native void nSetPriority(long nativeRenderableManager, int i, int priority);
|
||||
private static native int nGetPriority(long nativeRenderableManager, int i);
|
||||
private static native void nSetChannel(long nativeRenderableManager, int i, int channel);
|
||||
private static native int nGetChannel(long nativeRenderableManager, int i);
|
||||
private static native void nSetCulling(long nativeRenderableManager, int i, boolean enabled);
|
||||
private static native boolean nIsCullingEnabled(long nativeRenderableManager, int i);
|
||||
private static native void nSetFogEnabled(long nativeRenderableManager, int i, boolean enabled);
|
||||
private static native boolean nGetFogEnabled(long nativeRenderableManager, int i);
|
||||
private static native void nSetLightChannel(long nativeRenderableManager, int i, int channel, boolean enable);
|
||||
@@ -1022,6 +1049,7 @@ public class RenderableManager {
|
||||
private static native void nSetCastShadows(long nativeRenderableManager, int i, boolean enabled);
|
||||
private static native void nSetReceiveShadows(long nativeRenderableManager, int i, boolean enabled);
|
||||
private static native void nSetScreenSpaceContactShadows(long nativeRenderableManager, int i, boolean enabled);
|
||||
private static native boolean nIsScreenSpaceContactShadowsEnabled(long nativeRenderableManager, int i);
|
||||
private static native boolean nIsShadowCaster(long nativeRenderableManager, int i);
|
||||
private static native boolean nIsShadowReceiver(long nativeRenderableManager, int i);
|
||||
private static native void nGetAxisAlignedBoundingBox(long nativeRenderableManager, int i, float[] center, float[] halfExtent);
|
||||
@@ -1032,6 +1060,8 @@ public class RenderableManager {
|
||||
private static native long nGetMaterialInstanceAt(long nativeRenderableManager, int i, int primitiveIndex);
|
||||
private static native void nSetGeometryAt(long nativeRenderableManager, int i, int primitiveIndex, int primitiveType, long nativeVertexBuffer, long nativeIndexBuffer, int offset, int count);
|
||||
private static native void nSetBlendOrderAt(long nativeRenderableManager, int i, int primitiveIndex, int blendOrder);
|
||||
private static native int nGetBlendOrderAt(long nativeRenderableManager, int i, int primitiveIndex);
|
||||
private static native void nSetGlobalBlendOrderEnabledAt(long nativeRenderableManager, int i, int primitiveIndex, boolean enabled);
|
||||
private static native boolean nIsGlobalBlendOrderEnabledAt(long nativeRenderableManager, int i, int primitiveIndex);
|
||||
private static native int nGetEnabledAttributesAt(long nativeRenderableManager, int i, int primitiveIndex);
|
||||
}
|
||||
|
||||
@@ -155,6 +155,24 @@ public class Skybox {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the rendering priority of the Skybox. By default, it is set to the lowest
|
||||
* priority (7) such that the Skybox is always rendered after the opaque objects,
|
||||
* to reduce overdraw when depth culling is enabled.
|
||||
*
|
||||
* @param priority clamped to the range [0..7], defaults to 4; 7 is lowest priority
|
||||
* (rendered last).
|
||||
*
|
||||
* @return Builder reference for chaining calls.
|
||||
*
|
||||
* @see RenderableManager.Builder#priority
|
||||
*/
|
||||
@NonNull
|
||||
public Builder priority(@IntRange(from = 0, to = 7) int priority) {
|
||||
nBuilderPriority(mNativeBuilder, priority);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a <code>Skybox</code> object
|
||||
*
|
||||
@@ -262,6 +280,7 @@ public class Skybox {
|
||||
private static native void nBuilderShowSun(long nativeSkyboxBuilder, boolean show);
|
||||
private static native void nBuilderIntensity(long nativeSkyboxBuilder, float intensity);
|
||||
private static native void nBuilderColor(long nativeSkyboxBuilder, float r, float g, float b, float a);
|
||||
private static native void nBuilderPriority(long nativeSkyboxBuilder, int priority);
|
||||
private static native long nBuilderBuild(long nativeSkyboxBuilder, long nativeEngine);
|
||||
private static native void nSetLayerMask(long nativeSkybox, int select, int value);
|
||||
private static native int nGetLayerMask(long nativeSkybox);
|
||||
|
||||
@@ -71,6 +71,16 @@ import static com.google.android.filament.Texture.Type.COMPRESSED;
|
||||
* @see MaterialInstance#setParameter(String, Texture, TextureSampler)
|
||||
*/
|
||||
public class Texture {
|
||||
|
||||
private static Class<?> HardwareBufferClass = null;
|
||||
|
||||
static {
|
||||
try {
|
||||
HardwareBufferClass = Class.forName("android.hardware.HardwareBuffer");
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private static final Sampler[] sSamplerValues = Sampler.values();
|
||||
private static final InternalFormat[] sInternalFormatValues = InternalFormat.values();
|
||||
|
||||
@@ -785,6 +795,17 @@ public class Texture {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the number of samples for multisample anti-aliasing.
|
||||
* @param samples number of samples, must be at least 1. Default is 1.
|
||||
* @return This Builder, for chaining calls.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder samples(@IntRange(from = 1) int samples) {
|
||||
nBuilderSamples(mNativeBuilder, samples);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the texture's internal format.
|
||||
* <p>The internal format specifies how texels are stored (which may be different from how
|
||||
@@ -1172,6 +1193,38 @@ public class Texture {
|
||||
nSetExternalImage(getNativeObject(), engine.getNativeObject(), eglImage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the external image to associate with this <code>Texture</code>.
|
||||
*
|
||||
* <p>Typically, the external image is OS specific, and can be a video or camera frame.
|
||||
* There are many restrictions when using an external image as a texture, such as:</p>
|
||||
* <ul>
|
||||
* <li> only the level of detail (lod) 0 can be specified</li>
|
||||
* <li> only nearest or linear filtering is supported</li>
|
||||
* <li> the size and format of the texture is defined by the external image</li>
|
||||
* <li> only the CLAMP_TO_EDGE wrap mode is supported</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param engine {@link Engine} this texture is associated to. Must be the
|
||||
* instance passed to {@link Builder#build Builder.build()}.
|
||||
* @param externalImageRef An OS specific Object. On Android it must be a
|
||||
* <code>android.hardware.HardwareBuffer</code>
|
||||
*/
|
||||
public void setExternalImage(@NonNull Engine engine, Object externalImageRef) {
|
||||
if (HardwareBufferClass != null) {
|
||||
if (!HardwareBufferClass.isInstance(externalImageRef)) {
|
||||
throw new IllegalArgumentException("externalImageRef must be a AHardwareBuffer");
|
||||
}
|
||||
if (!nSetExternalImageByAHB(getNativeObject(), engine.getNativeObject(), externalImageRef)) {
|
||||
throw new IllegalStateException("Error setting AHardwareBuffer as external image");
|
||||
}
|
||||
} else {
|
||||
throw new UnsupportedOperationException(
|
||||
"setExternalImage(Engine, Object) not supported on this platform");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Specifies the external stream to associate with this <code>Texture</code>.
|
||||
*
|
||||
@@ -1328,6 +1381,7 @@ public class Texture {
|
||||
private static native void nBuilderFormat(long nativeBuilder, int format);
|
||||
private static native void nBuilderUsage(long nativeBuilder, int flags);
|
||||
private static native void nBuilderSwizzle(long nativeBuilder, int r, int g, int b, int a);
|
||||
private static native void nBuilderSamples(long nativeBuilder, int samples);
|
||||
private static native void nBuilderImportTexture(long nativeBuilder, long id);
|
||||
private static native void nBuilderExternal(long nativeBuilder);
|
||||
private static native long nBuilderBuild(long nativeBuilder, long nativeEngine);
|
||||
@@ -1364,6 +1418,8 @@ public class Texture {
|
||||
private static native void nSetExternalImage(
|
||||
long nativeObject, long nativeEngine, long eglImage);
|
||||
|
||||
private static native boolean nSetExternalImageByAHB(long nativeTexture, long nativeObject, Object ahb);
|
||||
|
||||
private static native void nSetExternalStream(long nativeTexture,
|
||||
long nativeEngine, long nativeStream);
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ package com.google.android.filament;
|
||||
* <li>ACESLegacyToneMapper</li>
|
||||
* <li>FilmicToneMapper</li>
|
||||
* <li>PBRNeutralToneMapper</li>
|
||||
* <li>GT7ToneMapper</li>
|
||||
* </ul>
|
||||
* <li>Debug/validation tone mapping operators</li>
|
||||
* <ul>
|
||||
@@ -111,6 +112,19 @@ public class ToneMapper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gran Turismo 7 tone mapping operator. This tone mapper was designed
|
||||
* to preserve the appearance of materials across lighting conditions while
|
||||
* avoiding artifacts in the highlights in high dynamic range conditions.
|
||||
* This tone mapper targets an SDR paper white value of 250 nits, with a
|
||||
* reference luminance of 100 cd/m^2 (a value of 1.0 in the HDR framebuffer).
|
||||
*/
|
||||
public static class GT7ToneMapper extends ToneMapper {
|
||||
public GT7ToneMapper() {
|
||||
super(nCreateGT7ToneMapper());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AgX tone mapping operator.
|
||||
*/
|
||||
@@ -244,6 +258,7 @@ public class ToneMapper {
|
||||
private static native long nCreateACESLegacyToneMapper();
|
||||
private static native long nCreateFilmicToneMapper();
|
||||
private static native long nCreatePBRNeutralToneMapper();
|
||||
private static native long nCreateGT7ToneMapper();
|
||||
private static native long nCreateAgxToneMapper(int look);
|
||||
private static native long nCreateGenericToneMapper(
|
||||
float contrast, float midGrayIn, float midGrayOut, float hdrMax);
|
||||
|
||||
@@ -350,6 +350,26 @@ public class View {
|
||||
nSetVisibleLayers(getNativeObject(), select & 0xFF, values & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the visible layers.
|
||||
*
|
||||
* @return a bitmask specifying which layer is visible.
|
||||
*/
|
||||
public int getVisibleLayers() {
|
||||
return nGetVisibleLayers(getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables a specific layer.
|
||||
*
|
||||
* @param layer Index of the layer to enable or disable, must be between 0 and 7.
|
||||
* @param enabled True to enable the layer, false to disable it.
|
||||
*/
|
||||
public void setLayerEnabled(@IntRange(from = 0, to = 7) int layer, boolean enabled) {
|
||||
int mask = 1 << layer;
|
||||
setVisibleLayers(mask, enabled ? mask : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables shadow mapping. Enabled by default.
|
||||
*
|
||||
@@ -368,6 +388,22 @@ public class View {
|
||||
return nIsShadowingEnabled(getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables frustum culling. Enabled by default.
|
||||
*
|
||||
* @param enabled true enables frustum culling, false disables it.
|
||||
*/
|
||||
public void setFrustumCullingEnabled(boolean enabled) {
|
||||
nSetFrustumCullingEnabled(getNativeObject(), enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether frustum culling is enabled
|
||||
*/
|
||||
public boolean isFrustumCullingEnabled() {
|
||||
return nIsFrustumCullingEnabled(getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables screen space refraction. Enabled by default.
|
||||
*
|
||||
@@ -1322,6 +1358,9 @@ public class View {
|
||||
private static native boolean nHasCamera(long nativeView);
|
||||
private static native void nSetViewport(long nativeView, int left, int bottom, int width, int height);
|
||||
private static native void nSetVisibleLayers(long nativeView, int select, int value);
|
||||
private static native int nGetVisibleLayers(long nativeView);
|
||||
private static native void nSetFrustumCullingEnabled(long nativeView, boolean enabled);
|
||||
private static native boolean nIsFrustumCullingEnabled(long nativeView);
|
||||
private static native void nSetShadowingEnabled(long nativeView, boolean enabled);
|
||||
private static native void nSetRenderTarget(long nativeView, long nativeRenderTarget);
|
||||
private static native void nSetSampleCount(long nativeView, int count);
|
||||
@@ -1406,65 +1445,59 @@ public class View {
|
||||
* by lowering the resolution of a View, or to increase the quality when the
|
||||
* rendering is faster than the target frame rate.
|
||||
*
|
||||
* This structure can be used to specify the minimum scale factor used when
|
||||
* <p>This structure can be used to specify the minimum scale factor used when
|
||||
* lowering the resolution of a View, and the maximum scale factor used when
|
||||
* increasing the resolution for higher quality rendering. The scale factors
|
||||
* can be controlled on each X and Y axis independently. By default, all scale
|
||||
* factors are set to 1.0.
|
||||
* factors are set to 1.0.</p>
|
||||
*
|
||||
* enabled: enable or disables dynamic resolution on a View
|
||||
* <ul>
|
||||
* <li>enabled: enable or disables dynamic resolution on a View</li>
|
||||
*
|
||||
* homogeneousScaling: by default the system scales the major axis first. Set this to true
|
||||
* to force homogeneous scaling.
|
||||
* <li>homogeneousScaling: by default the system scales the major axis first. Set this to true
|
||||
* to force homogeneous scaling.</li>
|
||||
*
|
||||
* minScale: the minimum scale in X and Y this View should use
|
||||
* <li>minScale: the minimum scale in X and Y this View should use</li>
|
||||
*
|
||||
* maxScale: the maximum scale in X and Y this View should use
|
||||
* <li>maxScale: the maximum scale in X and Y this View should use</li>
|
||||
*
|
||||
* quality: upscaling quality.
|
||||
* LOW: 1 bilinear tap, Medium: 4 bilinear taps, High: 9 bilinear taps (tent)
|
||||
* <li>quality: upscaling quality.
|
||||
* LOW: 1 bilinear tap, Medium: 4 bilinear taps, High: 9 bilinear taps (tent)</li>
|
||||
* </ul>
|
||||
*
|
||||
* \note
|
||||
* <p>Note:
|
||||
* Dynamic resolution is only supported on platforms where the time to render
|
||||
* a frame can be measured accurately. On platforms where this is not supported,
|
||||
* Dynamic Resolution can't be enabled unless minScale == maxScale.
|
||||
* Dynamic Resolution can't be enabled unless <code>minScale == maxScale</code>.</p>
|
||||
*
|
||||
* @see Renderer::FrameRateOptions
|
||||
* @see Renderer.FrameRateOptions
|
||||
*
|
||||
*/
|
||||
public static class DynamicResolutionOptions {
|
||||
/**
|
||||
* minimum scale factors in x and y
|
||||
*/
|
||||
/** minimum scale factors in x and y */
|
||||
public float minScale = 0.5f;
|
||||
/**
|
||||
* maximum scale factors in x and y
|
||||
*/
|
||||
/** maximum scale factors in x and y */
|
||||
public float maxScale = 1.0f;
|
||||
/**
|
||||
* sharpness when QualityLevel::MEDIUM or higher is used [0 (disabled), 1 (sharpest)]
|
||||
*/
|
||||
/** sharpness when QualityLevel::MEDIUM or higher is used [0 (disabled), 1 (sharpest)] */
|
||||
public float sharpness = 0.9f;
|
||||
/**
|
||||
* enable or disable dynamic resolution
|
||||
*/
|
||||
/** enable or disable dynamic resolution */
|
||||
public boolean enabled = false;
|
||||
/**
|
||||
* set to true to force homogeneous scaling
|
||||
*/
|
||||
/** set to true to force homogeneous scaling */
|
||||
public boolean homogeneousScaling = false;
|
||||
/**
|
||||
* Upscaling quality
|
||||
* LOW: bilinear filtered blit. Fastest, poor quality
|
||||
* MEDIUM: Qualcomm Snapdragon Game Super Resolution (SGSR) 1.0
|
||||
* HIGH: AMD FidelityFX FSR1 w/ mobile optimizations
|
||||
* ULTRA: AMD FidelityFX FSR1
|
||||
* <ul>
|
||||
* <li>LOW: bilinear filtered blit. Fastest, poor quality</li>
|
||||
* <li>MEDIUM: Qualcomm Snapdragon Game Super Resolution (SGSR) 1.0</li>
|
||||
* <li>HIGH: AMD FidelityFX FSR1 w/ mobile optimizations</li>
|
||||
* <li>ULTRA: AMD FidelityFX FSR1</li>
|
||||
* </ul>
|
||||
* FSR1 and SGSR require a well anti-aliased (MSAA or TAA), noise free scene.
|
||||
* Avoid FXAA and dithering.
|
||||
*
|
||||
* The default upscaling quality is set to LOW.
|
||||
* <p>The default upscaling quality is set to LOW.</p>
|
||||
*
|
||||
* caveat: currently, 'quality' is always set to LOW if the View is TRANSLUCENT.
|
||||
* <p>caveat: currently, <code>quality</code> is always set to LOW if the View is TRANSLUCENT.</p>
|
||||
*/
|
||||
@NonNull
|
||||
public QualityLevel quality = QualityLevel.LOW;
|
||||
@@ -1473,134 +1506,98 @@ public class View {
|
||||
/**
|
||||
* Options to control the bloom effect
|
||||
*
|
||||
* enabled: Enable or disable the bloom post-processing effect. Disabled by default.
|
||||
* <ul>
|
||||
* <li>enabled: Enable or disable the bloom post-processing effect. Disabled by default.</li>
|
||||
*
|
||||
* levels: Number of successive blurs to achieve the blur effect, the minimum is 3 and the
|
||||
* <li>levels: Number of successive blurs to achieve the blur effect, the minimum is 3 and the
|
||||
* maximum is 12. This value together with resolution influences the spread of the
|
||||
* blur effect. This value can be silently reduced to accommodate the original
|
||||
* image size.
|
||||
* image size.</li>
|
||||
*
|
||||
* resolution: Resolution of bloom's minor axis. The minimum value is 2^levels and the
|
||||
* <li>resolution: Resolution of bloom's minor axis. The minimum value is 2^levels and the
|
||||
* the maximum is lower of the original resolution and 4096. This parameter is
|
||||
* silently clamped to the minimum and maximum.
|
||||
* It is highly recommended that this value be smaller than the target resolution
|
||||
* after dynamic resolution is applied (horizontally and vertically).
|
||||
* after dynamic resolution is applied (horizontally and vertically).</li>
|
||||
*
|
||||
* strength: how much of the bloom is added to the original image. Between 0 and 1.
|
||||
* <li>strength: how much of the bloom is added to the original image. Between 0 and 1.</li>
|
||||
*
|
||||
* blendMode: Whether the bloom effect is purely additive (false) or mixed with the original
|
||||
* image (true).
|
||||
* <li>blendMode: Whether the bloom effect is purely additive (false) or mixed with the original
|
||||
* image (true).</li>
|
||||
*
|
||||
* threshold: When enabled, a threshold at 1.0 is applied on the source image, this is
|
||||
* useful for artistic reasons and is usually needed when a dirt texture is used.
|
||||
* <li>threshold: When enabled, a threshold at 1.0 is applied on the source image, this is
|
||||
* useful for artistic reasons and is usually needed when a dirt texture is used.</li>
|
||||
*
|
||||
* dirt: A dirt/scratch/smudges texture (that can be RGB), which gets added to the
|
||||
* <li>dirt: A dirt/scratch/smudges texture (that can be RGB), which gets added to the
|
||||
* bloom effect. Smudges are visible where bloom occurs. Threshold must be
|
||||
* enabled for the dirt effect to work properly.
|
||||
* enabled for the dirt effect to work properly.</li>
|
||||
*
|
||||
* dirtStrength: Strength of the dirt texture.
|
||||
* <li>dirtStrength: Strength of the dirt texture.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public static class BloomOptions {
|
||||
public enum BlendMode {
|
||||
/**
|
||||
* Bloom is modulated by the strength parameter and added to the scene
|
||||
*/
|
||||
/** Bloom is modulated by the strength parameter and added to the scene */
|
||||
ADD,
|
||||
/**
|
||||
* Bloom is interpolated with the scene using the strength parameter
|
||||
*/
|
||||
/** Bloom is interpolated with the scene using the strength parameter */
|
||||
INTERPOLATE,
|
||||
}
|
||||
|
||||
/**
|
||||
* user provided dirt texture
|
||||
*/
|
||||
/** user provided dirt texture */
|
||||
@Nullable
|
||||
public Texture dirt = null;
|
||||
/**
|
||||
* strength of the dirt texture
|
||||
*/
|
||||
/** strength of the dirt texture */
|
||||
public float dirtStrength = 0.2f;
|
||||
/**
|
||||
* bloom's strength between 0.0 and 1.0
|
||||
*/
|
||||
/** bloom's strength between 0.0 and 1.0 */
|
||||
public float strength = 0.10f;
|
||||
/**
|
||||
* resolution of vertical axis (2^levels to 2048)
|
||||
*/
|
||||
/** resolution of vertical axis (2^levels to 2048) */
|
||||
public int resolution = 384;
|
||||
/**
|
||||
* number of blur levels (1 to 11)
|
||||
*/
|
||||
/** number of blur levels (1 to 11) */
|
||||
public int levels = 6;
|
||||
/**
|
||||
* how the bloom effect is applied
|
||||
*/
|
||||
/** how the bloom effect is applied */
|
||||
@NonNull
|
||||
public BloomOptions.BlendMode blendMode = BloomOptions.BlendMode.ADD;
|
||||
/**
|
||||
* whether to threshold the source
|
||||
*/
|
||||
/** whether to threshold the source */
|
||||
public boolean threshold = true;
|
||||
/**
|
||||
* enable or disable bloom
|
||||
*/
|
||||
/** enable or disable bloom */
|
||||
public boolean enabled = false;
|
||||
/**
|
||||
* limit highlights to this value before bloom [10, +inf]
|
||||
*/
|
||||
/** limit highlights to this value before bloom [10, +inf] */
|
||||
public float highlight = 1000.0f;
|
||||
/**
|
||||
* Bloom quality level.
|
||||
* LOW (default): use a more optimized down-sampling filter, however there can be artifacts
|
||||
* with dynamic resolution, this can be alleviated by using the homogenous mode.
|
||||
* MEDIUM: Good balance between quality and performance.
|
||||
* HIGH: In this mode the bloom resolution is automatically increased to avoid artifacts.
|
||||
* <ul>
|
||||
* <li>LOW (default): use a more optimized down-sampling filter, however there can be artifacts
|
||||
* with dynamic resolution, this can be alleviated by using the homogenous mode.</li>
|
||||
* <li>MEDIUM: Good balance between quality and performance.</li>
|
||||
* <li>HIGH: In this mode the bloom resolution is automatically increased to avoid artifacts.
|
||||
* This mode can be significantly slower on mobile, especially at high resolution.
|
||||
* This mode greatly improves the anamorphic bloom.
|
||||
* This mode greatly improves the anamorphic bloom.</li>
|
||||
* </ul>
|
||||
*/
|
||||
@NonNull
|
||||
public QualityLevel quality = QualityLevel.LOW;
|
||||
/**
|
||||
* enable screen-space lens flare
|
||||
*/
|
||||
/** enable screen-space lens flare */
|
||||
public boolean lensFlare = false;
|
||||
/**
|
||||
* enable starburst effect on lens flare
|
||||
*/
|
||||
/** enable starburst effect on lens flare */
|
||||
public boolean starburst = true;
|
||||
/**
|
||||
* amount of chromatic aberration
|
||||
*/
|
||||
/** amount of chromatic aberration */
|
||||
public float chromaticAberration = 0.005f;
|
||||
/**
|
||||
* number of flare "ghosts"
|
||||
*/
|
||||
/** number of flare "ghosts" */
|
||||
public int ghostCount = 4;
|
||||
/**
|
||||
* spacing of the ghost in screen units [0, 1[
|
||||
*/
|
||||
/** spacing of the ghost in screen units [0, 1[ */
|
||||
public float ghostSpacing = 0.6f;
|
||||
/**
|
||||
* hdr threshold for the ghosts
|
||||
*/
|
||||
/** hdr threshold for the ghosts */
|
||||
public float ghostThreshold = 10.0f;
|
||||
/**
|
||||
* thickness of halo in vertical screen units, 0 to disable
|
||||
*/
|
||||
/** thickness of halo in vertical screen units, 0 to disable */
|
||||
public float haloThickness = 0.1f;
|
||||
/**
|
||||
* radius of halo in vertical screen units [0, 0.5]
|
||||
*/
|
||||
/** radius of halo in vertical screen units [0, 0.5] */
|
||||
public float haloRadius = 0.4f;
|
||||
/**
|
||||
* hdr threshold for the halo
|
||||
*/
|
||||
/** hdr threshold for the halo */
|
||||
public float haloThreshold = 10.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options to control large-scale fog in the scene. Materials can enable the `linearFog` property,
|
||||
* Options to control large-scale fog in the scene. Materials can enable the <code>linearFog</code> property,
|
||||
* which uses a simplified, linear equation for fog calculation; in this mode, the heightFalloff
|
||||
* is ignored as well as the mipmap selection in IBL or skyColor mode.
|
||||
*/
|
||||
@@ -1614,12 +1611,12 @@ public class View {
|
||||
* This can be used to exclude the skybox, which is desirable if it already contains clouds or
|
||||
* fog. The default value is +infinity which applies the fog to everything.
|
||||
*
|
||||
* Note: The SkyBox is typically at a distance of 1e19 in world space (depending on the near
|
||||
* plane distance and projection used though).
|
||||
* <p>Note: The SkyBox is typically at a distance of 1e19 in world space (depending on the near
|
||||
* plane distance and projection used though).</p>
|
||||
*/
|
||||
public float cutOffDistance = Float.POSITIVE_INFINITY;
|
||||
/**
|
||||
* fog's maximum opacity between 0 and 1. Ignored in `linearFog` mode.
|
||||
* fog's maximum opacity between 0 and 1. Ignored in <code>linearFog</code> mode.
|
||||
*/
|
||||
public float maximumOpacity = 1.0f;
|
||||
/**
|
||||
@@ -1631,11 +1628,11 @@ public class View {
|
||||
* It can be expressed as 1/H, where H is the altitude change in world units [m] that causes a
|
||||
* factor 2.78 (e) change in fog density.
|
||||
*
|
||||
* A falloff of 0 means the fog density is constant everywhere and may result is slightly
|
||||
* faster computations.
|
||||
* <p>A falloff of 0 means the fog density is constant everywhere and may result is slightly
|
||||
* faster computations.</p>
|
||||
*
|
||||
* In `linearFog` mode, only use to compute the slope of the linear equation. Completely
|
||||
* ignored if set to 0.
|
||||
* <p>In <code>linearFog</code> mode, only use to compute the slope of the linear equation. Completely
|
||||
* ignored if set to 0.</p>
|
||||
*/
|
||||
public float heightFalloff = 1.0f;
|
||||
/**
|
||||
@@ -1645,11 +1642,11 @@ public class View {
|
||||
* above one are allowed but could create a non energy-conservative fog (this is dependant
|
||||
* on the IBL's intensity as well).
|
||||
*
|
||||
* We assume that our fog has no absorption and therefore all the light it scatters out
|
||||
* <p>We assume that our fog has no absorption and therefore all the light it scatters out
|
||||
* becomes ambient light in-scattering and has lost all directionality, i.e.: scattering is
|
||||
* isotropic. This somewhat simulates Rayleigh scattering.
|
||||
* isotropic. This somewhat simulates Rayleigh scattering.</p>
|
||||
*
|
||||
* This value is used as a tint instead, when fogColorFromIbl is enabled.
|
||||
* <p>This value is used as a tint instead, when fogColorFromIbl is enabled.</p>
|
||||
*
|
||||
* @see #fogColorFromIbl
|
||||
*/
|
||||
@@ -1660,20 +1657,20 @@ public class View {
|
||||
* light is absorbed and out-scattered per unit of distance. Each unit of extinction reduces
|
||||
* the incoming light to 37% of its original value.
|
||||
*
|
||||
* Note: The extinction factor is related to the fog density, it's usually some constant K times
|
||||
* <p>Note: The extinction factor is related to the fog density, it's usually some constant K times
|
||||
* the density at sea level (more specifically at fog height). The constant K depends on
|
||||
* the composition of the fog/atmosphere.
|
||||
* the composition of the fog/atmosphere.</p>
|
||||
*
|
||||
* For historical reason this parameter is called `density`.
|
||||
* <p>For historical reason this parameter is called <code>density</code>.</p>
|
||||
*
|
||||
* In `linearFog` mode this is the slope of the linear equation if heightFalloff is set to 0.
|
||||
* <p>In <code>linearFog</code> mode this is the slope of the linear equation if heightFalloff is set to 0.
|
||||
* Otherwise, heightFalloff affects the slope calculation such that it matches the slope of
|
||||
* the standard equation at the camera height.
|
||||
* the standard equation at the camera height.</p>
|
||||
*/
|
||||
public float density = 0.1f;
|
||||
/**
|
||||
* Distance in world units [m] from the camera where the Sun in-scattering starts.
|
||||
* Ignored in `linearFog` mode.
|
||||
* Ignored in <code>linearFog</code> mode.
|
||||
*/
|
||||
public float inScatteringStart = 0.0f;
|
||||
/**
|
||||
@@ -1681,16 +1678,16 @@ public class View {
|
||||
* is scattered (by the fog) towards the camera.
|
||||
* Size of the Sun in-scattering (>0 to activate). Good values are >> 1 (e.g. ~10 - 100).
|
||||
* Smaller values result is a larger scattering size.
|
||||
* Ignored in `linearFog` mode.
|
||||
* Ignored in <code>linearFog</code> mode.
|
||||
*/
|
||||
public float inScatteringSize = -1.0f;
|
||||
/**
|
||||
* The fog color will be sampled from the IBL in the view direction and tinted by `color`.
|
||||
* The fog color will be sampled from the IBL in the view direction and tinted by <code>color</code>.
|
||||
* Depending on the scene this can produce very convincing results.
|
||||
*
|
||||
* This simulates a more anisotropic phase-function.
|
||||
* <p>This simulates a more anisotropic phase-function.</p>
|
||||
*
|
||||
* `fogColorFromIbl` is ignored when skyTexture is specified.
|
||||
* <p><code>fogColorFromIbl</code> is ignored when skyTexture is specified.</p>
|
||||
*
|
||||
* @see #skyColor
|
||||
*/
|
||||
@@ -1703,11 +1700,11 @@ public class View {
|
||||
* level with a strong gaussian filter or even an irradiance filter and then generate mip
|
||||
* levels as usual. How blurred the base level is somewhat of an artistic decision.
|
||||
*
|
||||
* This simulates a more anisotropic phase-function.
|
||||
* <p>This simulates a more anisotropic phase-function.</p>
|
||||
*
|
||||
* `fogColorFromIbl` is ignored when skyTexture is specified.
|
||||
* <p><code>fogColorFromIbl</code> is ignored when skyTexture is specified.</p>
|
||||
*
|
||||
* In `linearFog` mode mipmap level 0 is always used.
|
||||
* <p>In <code>linearFog</code> mode mipmap level 0 is always used.</p>
|
||||
*
|
||||
* @see Texture
|
||||
* @see #fogColorFromIbl
|
||||
@@ -1723,9 +1720,9 @@ public class View {
|
||||
/**
|
||||
* Options to control Depth of Field (DoF) effect in the scene.
|
||||
*
|
||||
* cocScale can be used to set the depth of field blur independently of the camera
|
||||
* <p>cocScale can be used to set the depth of field blur independently of the camera
|
||||
* aperture, e.g. for artistic reasons. This can be achieved by setting:
|
||||
* cocScale = cameraAperture / desiredDoFAperture
|
||||
* cocScale = cameraAperture / desiredDoFAperture</p>
|
||||
*
|
||||
* @see Camera
|
||||
*/
|
||||
@@ -1736,59 +1733,24 @@ public class View {
|
||||
MEDIAN,
|
||||
}
|
||||
|
||||
/**
|
||||
* circle of confusion scale factor (amount of blur)
|
||||
*/
|
||||
/** circle of confusion scale factor (amount of blur) */
|
||||
public float cocScale = 1.0f;
|
||||
/**
|
||||
* width/height aspect ratio of the circle of confusion (simulate anamorphic lenses)
|
||||
*/
|
||||
/** width/height aspect ratio of the circle of confusion (simulate anamorphic lenses) */
|
||||
public float cocAspectRatio = 1.0f;
|
||||
/**
|
||||
* maximum aperture diameter in meters (zero to disable rotation)
|
||||
*/
|
||||
/** maximum aperture diameter in meters (zero to disable rotation) */
|
||||
public float maxApertureDiameter = 0.01f;
|
||||
/**
|
||||
* enable or disable depth of field effect
|
||||
*/
|
||||
/** enable or disable depth of field effect */
|
||||
public boolean enabled = false;
|
||||
/**
|
||||
* filter to use for filling gaps in the kernel
|
||||
*/
|
||||
/** filter to use for filling gaps in the kernel */
|
||||
@NonNull
|
||||
public DepthOfFieldOptions.Filter filter = DepthOfFieldOptions.Filter.MEDIAN;
|
||||
/**
|
||||
* perform DoF processing at native resolution
|
||||
*/
|
||||
/** perform DoF processing at native resolution */
|
||||
public boolean nativeResolution = false;
|
||||
/**
|
||||
* Number of of rings used by the gather kernels. The number of rings affects quality
|
||||
* and performance. The actual number of sample per pixel is defined
|
||||
* as (ringCount * 2 - 1)^2. Here are a few commonly used values:
|
||||
* 3 rings : 25 ( 5x 5 grid)
|
||||
* 4 rings : 49 ( 7x 7 grid)
|
||||
* 5 rings : 81 ( 9x 9 grid)
|
||||
* 17 rings : 1089 (33x33 grid)
|
||||
*
|
||||
* With a maximum circle-of-confusion of 32, it is never necessary to use more than 17 rings.
|
||||
*
|
||||
* Usually all three settings below are set to the same value, however, it is often
|
||||
* acceptable to use a lower ring count for the "fast tiles", which improves performance.
|
||||
* Fast tiles are regions of the screen where every pixels have a similar
|
||||
* circle-of-confusion radius.
|
||||
*
|
||||
* A value of 0 means default, which is 5 on desktop and 3 on mobile.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
/** number of kernel rings for foreground tiles */
|
||||
public int foregroundRingCount = 0;
|
||||
/**
|
||||
* number of kernel rings for background tiles
|
||||
*/
|
||||
/** number of kernel rings for background tiles */
|
||||
public int backgroundRingCount = 0;
|
||||
/**
|
||||
* number of kernel rings for fast tiles
|
||||
*/
|
||||
/** number of kernel rings for fast tiles */
|
||||
public int fastGatherRingCount = 0;
|
||||
/**
|
||||
* maximum circle-of-confusion in pixels for the foreground, must be in [0, 32] range.
|
||||
@@ -1806,26 +1768,16 @@ public class View {
|
||||
* Options to control the vignetting effect.
|
||||
*/
|
||||
public static class VignetteOptions {
|
||||
/**
|
||||
* high values restrict the vignette closer to the corners, between 0 and 1
|
||||
*/
|
||||
/** high values restrict the vignette closer to the corners, between 0 and 1 */
|
||||
public float midPoint = 0.5f;
|
||||
/**
|
||||
* controls the shape of the vignette, from a rounded rectangle (0.0), to an oval (0.5), to a circle (1.0)
|
||||
*/
|
||||
/** controls the shape of the vignette, from a rounded rectangle (0.0), to an oval (0.5), to a circle (1.0) */
|
||||
public float roundness = 0.5f;
|
||||
/**
|
||||
* softening amount of the vignette effect, between 0 and 1
|
||||
*/
|
||||
/** softening amount of the vignette effect, between 0 and 1 */
|
||||
public float feather = 0.5f;
|
||||
/**
|
||||
* color of the vignette effect, alpha is currently ignored
|
||||
*/
|
||||
/** color of the vignette effect, alpha is currently ignored */
|
||||
@NonNull @Size(min = 4)
|
||||
public float[] color = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
/**
|
||||
* enables or disables the vignette effect
|
||||
*/
|
||||
/** enables or disables the vignette effect */
|
||||
public boolean enabled = false;
|
||||
}
|
||||
|
||||
@@ -1839,11 +1791,11 @@ public class View {
|
||||
/**
|
||||
* Sets the quality of the HDR color buffer.
|
||||
*
|
||||
* A quality of HIGH or ULTRA means using an RGB16F or RGBA16F color buffer. This means
|
||||
* <p>A quality of HIGH or ULTRA means using an RGB16F or RGBA16F color buffer. This means
|
||||
* colors in the LDR range (0..1) have a 10 bit precision. A quality of LOW or MEDIUM means
|
||||
* using an R11G11B10F opaque color buffer or an RGBA16F transparent color buffer. With
|
||||
* R11G11B10F colors in the LDR range have a precision of either 6 bits (red and green
|
||||
* channels) or 5 bits (blue channel).
|
||||
* channels) or 5 bits (blue channel).</p>
|
||||
*/
|
||||
@NonNull
|
||||
public QualityLevel hdrColorBuffer = QualityLevel.HIGH;
|
||||
@@ -1855,72 +1807,44 @@ public class View {
|
||||
*/
|
||||
public static class AmbientOcclusionOptions {
|
||||
public enum AmbientOcclusionType {
|
||||
/**
|
||||
* use Scalable Ambient Occlusion
|
||||
*/
|
||||
/** use Scalable Ambient Occlusion */
|
||||
SAO,
|
||||
/**
|
||||
* use Ground Truth-Based Ambient Occlusion
|
||||
*/
|
||||
/** use Ground Truth-Based Ambient Occlusion */
|
||||
GTAO,
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of ambient occlusion algorithm.
|
||||
*/
|
||||
/** Type of ambient occlusion algorithm. */
|
||||
@NonNull
|
||||
public AmbientOcclusionOptions.AmbientOcclusionType aoType = AmbientOcclusionOptions.AmbientOcclusionType.SAO;
|
||||
/**
|
||||
* Ambient Occlusion radius in meters, between 0 and ~10.
|
||||
*/
|
||||
/** Ambient Occlusion radius in meters, between 0 and ~10. */
|
||||
public float radius = 0.3f;
|
||||
/**
|
||||
* Controls ambient occlusion's contrast. Must be positive.
|
||||
*/
|
||||
/** Controls ambient occlusion's contrast. Must be positive. */
|
||||
public float power = 1.0f;
|
||||
/**
|
||||
* Self-occlusion bias in meters. Use to avoid self-occlusion.
|
||||
* Between 0 and a few mm. No effect when aoType set to GTAO
|
||||
*/
|
||||
public float bias = 0.0005f;
|
||||
/**
|
||||
* How each dimension of the AO buffer is scaled. Must be either 0.5 or 1.0.
|
||||
*/
|
||||
/** How each dimension of the AO buffer is scaled. Must be either 0.5 or 1.0. */
|
||||
public float resolution = 0.5f;
|
||||
/**
|
||||
* Strength of the Ambient Occlusion effect.
|
||||
*/
|
||||
/** Strength of the Ambient Occlusion effect. */
|
||||
public float intensity = 1.0f;
|
||||
/**
|
||||
* depth distance that constitute an edge for filtering
|
||||
*/
|
||||
/** depth distance that constitute an edge for filtering */
|
||||
public float bilateralThreshold = 0.05f;
|
||||
/**
|
||||
* affects # of samples used for AO and params for filtering
|
||||
*/
|
||||
/** affects # of samples used for AO and params for filtering */
|
||||
@NonNull
|
||||
public QualityLevel quality = QualityLevel.LOW;
|
||||
/**
|
||||
* affects AO smoothness. Recommend setting to HIGH when aoType set to GTAO.
|
||||
*/
|
||||
/** affects AO smoothness. Recommend setting to HIGH when aoType set to GTAO. */
|
||||
@NonNull
|
||||
public QualityLevel lowPassFilter = QualityLevel.MEDIUM;
|
||||
/**
|
||||
* affects AO buffer upsampling quality
|
||||
*/
|
||||
/** affects AO buffer upsampling quality */
|
||||
@NonNull
|
||||
public QualityLevel upsampling = QualityLevel.LOW;
|
||||
/**
|
||||
* enables or disables screen-space ambient occlusion
|
||||
*/
|
||||
/** enables or disables screen-space ambient occlusion */
|
||||
public boolean enabled = false;
|
||||
/**
|
||||
* enables bent normals computation from AO, and specular AO
|
||||
*/
|
||||
/** enables bent normals computation from AO, and specular AO */
|
||||
public boolean bentNormals = false;
|
||||
/**
|
||||
* min angle in radian to consider. No effect when aoType set to GTAO.
|
||||
*/
|
||||
/** min angle in radian to consider. No effect when aoType set to GTAO. */
|
||||
public float minHorizonAngleRad = 0.0f;
|
||||
/**
|
||||
* Screen Space Cone Tracing (SSCT) options
|
||||
@@ -2006,12 +1930,10 @@ public class View {
|
||||
* @see #setMultiSampleAntiAliasingOptions
|
||||
*/
|
||||
public static class MultiSampleAntiAliasingOptions {
|
||||
/**
|
||||
* enables or disables msaa
|
||||
*/
|
||||
/** enables or disables msaa */
|
||||
public boolean enabled = false;
|
||||
/**
|
||||
* sampleCount number of samples to use for multi-sampled anti-aliasing.\n
|
||||
* sampleCount number of samples to use for multi-sampled anti-aliasing.<br>
|
||||
* 0: treated as 1
|
||||
* 1: no anti-aliasing
|
||||
* n: sample count. Effective sample could be different depending on the
|
||||
@@ -2030,106 +1952,75 @@ public class View {
|
||||
* shaders to be recompiled. These options should be changed or set during initialization.
|
||||
* `filterWidth`, `feedback` and `jitterPattern`, however, can be changed at any time.
|
||||
*
|
||||
* `feedback` of 0.1 effectively accumulates a maximum of 19 samples in steady state.
|
||||
* see "A Survey of Temporal Antialiasing Techniques" by Lei Yang and all for more information.
|
||||
* <p><code>feedback</code> of 0.1 effectively accumulates a maximum of 19 samples in steady state.
|
||||
* see "A Survey of Temporal Antialiasing Techniques" by Lei Yang and all for more information.</p>
|
||||
*
|
||||
* @see #setTemporalAntiAliasingOptions
|
||||
*/
|
||||
public static class TemporalAntiAliasingOptions {
|
||||
public enum BoxType {
|
||||
/**
|
||||
* use an AABB neighborhood
|
||||
*/
|
||||
/** use an AABB neighborhood */
|
||||
AABB,
|
||||
/**
|
||||
* use the variance of the neighborhood (not recommended)
|
||||
*/
|
||||
VARIANCE,
|
||||
/**
|
||||
* use both AABB and variance
|
||||
*/
|
||||
/** use both AABB and variance */
|
||||
AABB_VARIANCE,
|
||||
}
|
||||
|
||||
public enum BoxClipping {
|
||||
/**
|
||||
* Accurate box clipping
|
||||
*/
|
||||
/** Accurate box clipping */
|
||||
ACCURATE,
|
||||
/**
|
||||
* clamping
|
||||
*/
|
||||
/** clamping */
|
||||
CLAMP,
|
||||
/**
|
||||
* no rejections (use for debugging)
|
||||
*/
|
||||
/** no rejections (use for debugging) */
|
||||
NONE,
|
||||
}
|
||||
|
||||
public enum JitterPattern {
|
||||
/** 4-samples, rotated grid sampling */
|
||||
RGSS_X4,
|
||||
/** 4-samples, uniform grid in helix sequence */
|
||||
UNIFORM_HELIX_X4,
|
||||
/** 8-samples of halton 2,3 */
|
||||
HALTON_23_X8,
|
||||
/** 16-samples of halton 2,3 */
|
||||
HALTON_23_X16,
|
||||
/** 32-samples of halton 2,3 */
|
||||
HALTON_23_X32,
|
||||
}
|
||||
|
||||
/**
|
||||
* reconstruction filter width typically between 1 (sharper) and 2 (smoother)
|
||||
*/
|
||||
/** @deprecated has no effect. */
|
||||
public float filterWidth = 1.0f;
|
||||
/**
|
||||
* history feedback, between 0 (maximum temporal AA) and 1 (no temporal AA).
|
||||
*/
|
||||
/** history feedback, between 0 (maximum temporal AA) and 1 (no temporal AA). */
|
||||
public float feedback = 0.12f;
|
||||
/**
|
||||
* texturing lod bias (typically -1 or -2)
|
||||
*/
|
||||
/** texturing lod bias (typically -1 or -2) */
|
||||
public float lodBias = -1.0f;
|
||||
/**
|
||||
* post-TAA sharpen, especially useful when upscaling is true.
|
||||
*/
|
||||
/** post-TAA sharpen, especially useful when upscaling is true. */
|
||||
public float sharpness = 0.0f;
|
||||
/**
|
||||
* enables or disables temporal anti-aliasing
|
||||
*/
|
||||
/** enables or disables temporal anti-aliasing */
|
||||
public boolean enabled = false;
|
||||
/**
|
||||
* 4x TAA upscaling. Disables Dynamic Resolution. [BETA]
|
||||
*/
|
||||
public boolean upscaling = false;
|
||||
/**
|
||||
* whether to filter the history buffer
|
||||
*/
|
||||
/** Upscaling factor. Disables Dynamic Resolution. [BETA] */
|
||||
public float upscaling = 1.0f;
|
||||
/** whether to filter the history buffer */
|
||||
public boolean filterHistory = true;
|
||||
/**
|
||||
* whether to apply the reconstruction filter to the input
|
||||
*/
|
||||
/** whether to apply the reconstruction filter to the input */
|
||||
public boolean filterInput = true;
|
||||
/**
|
||||
* whether to use the YcoCg color-space for history rejection
|
||||
*/
|
||||
/** whether to use the YcoCg color-space for history rejection */
|
||||
public boolean useYCoCg = false;
|
||||
/**
|
||||
* type of color gamut box
|
||||
*/
|
||||
/** set to true for HDR content */
|
||||
public boolean hdr = true;
|
||||
/** type of color gamut box */
|
||||
@NonNull
|
||||
public TemporalAntiAliasingOptions.BoxType boxType = TemporalAntiAliasingOptions.BoxType.AABB;
|
||||
/**
|
||||
* clipping algorithm
|
||||
*/
|
||||
/** clipping algorithm */
|
||||
@NonNull
|
||||
public TemporalAntiAliasingOptions.BoxClipping boxClipping = TemporalAntiAliasingOptions.BoxClipping.ACCURATE;
|
||||
/** Jitter Pattern */
|
||||
@NonNull
|
||||
public TemporalAntiAliasingOptions.JitterPattern jitterPattern = TemporalAntiAliasingOptions.JitterPattern.HALTON_23_X16;
|
||||
/** High values increases ghosting artefact, lower values increases jittering, range [0.75, 1.25] */
|
||||
public float varianceGamma = 1.0f;
|
||||
/**
|
||||
* adjust the feedback dynamically to reduce flickering
|
||||
*/
|
||||
/** adjust the feedback dynamically to reduce flickering */
|
||||
public boolean preventFlickering = false;
|
||||
/**
|
||||
* whether to apply history reprojection (debug option)
|
||||
*/
|
||||
/** whether to apply history reprojection (debug option) */
|
||||
public boolean historyReprojection = true;
|
||||
}
|
||||
|
||||
@@ -2138,30 +2029,22 @@ public class View {
|
||||
* @see #setScreenSpaceReflectionsOptions
|
||||
*/
|
||||
public static class ScreenSpaceReflectionsOptions {
|
||||
/**
|
||||
* ray thickness, in world units
|
||||
*/
|
||||
/** ray thickness, in world units */
|
||||
public float thickness = 0.1f;
|
||||
/**
|
||||
* bias, in world units, to prevent self-intersections
|
||||
*/
|
||||
/** bias, in world units, to prevent self-intersections */
|
||||
public float bias = 0.01f;
|
||||
/**
|
||||
* maximum distance, in world units, to raycast
|
||||
*/
|
||||
/** maximum distance, in world units, to raycast */
|
||||
public float maxDistance = 3.0f;
|
||||
/**
|
||||
* stride, in texels, for samples along the ray.
|
||||
*/
|
||||
/** stride, in texels, for samples along the ray. */
|
||||
public float stride = 2.0f;
|
||||
public boolean enabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the screen-space guard band.
|
||||
* A guard band can be enabled to avoid some artifacts towards the edge of the screen when
|
||||
* <p>A guard band can be enabled to avoid some artifacts towards the edge of the screen when
|
||||
* using screen-space effects such as SSAO. Enabling the guard band reduces performance slightly.
|
||||
* Currently the guard band can only be enabled or disabled.
|
||||
* Currently the guard band can only be enabled or disabled.</p>
|
||||
*/
|
||||
public static class GuardBandOptions {
|
||||
public boolean enabled = false;
|
||||
@@ -2174,13 +2057,9 @@ public class View {
|
||||
* @see #setSampleCount
|
||||
*/
|
||||
public enum AntiAliasing {
|
||||
/**
|
||||
* no anti aliasing performed as part of post-processing
|
||||
*/
|
||||
/** no anti aliasing performed as part of post-processing */
|
||||
NONE,
|
||||
/**
|
||||
* FXAA is a low-quality but very efficient type of anti-aliasing. (default).
|
||||
*/
|
||||
/** FXAA is a low-quality but very efficient type of anti-aliasing. (default). */
|
||||
FXAA,
|
||||
}
|
||||
|
||||
@@ -2188,13 +2067,9 @@ public class View {
|
||||
* List of available post-processing dithering techniques.
|
||||
*/
|
||||
public enum Dithering {
|
||||
/**
|
||||
* No dithering
|
||||
*/
|
||||
/** No dithering */
|
||||
NONE,
|
||||
/**
|
||||
* Temporal dithering (default)
|
||||
*/
|
||||
/** Temporal dithering (default) */
|
||||
TEMPORAL,
|
||||
}
|
||||
|
||||
@@ -2203,21 +2078,13 @@ public class View {
|
||||
* @see #setShadowType
|
||||
*/
|
||||
public enum ShadowType {
|
||||
/**
|
||||
* percentage-closer filtered shadows (default)
|
||||
*/
|
||||
/** percentage-closer filtered shadows (default) */
|
||||
PCF,
|
||||
/**
|
||||
* variance shadows
|
||||
*/
|
||||
/** variance shadows */
|
||||
VSM,
|
||||
/**
|
||||
* PCF with contact hardening simulation
|
||||
*/
|
||||
/** PCF with contact hardening simulation */
|
||||
DPCF,
|
||||
/**
|
||||
* PCF with soft shadows and contact hardening
|
||||
*/
|
||||
/** PCF with soft shadows and contact hardening */
|
||||
PCSS,
|
||||
PCFd,
|
||||
}
|
||||
@@ -2225,14 +2092,14 @@ public class View {
|
||||
/**
|
||||
* View-level options for VSM Shadowing.
|
||||
* @see #setVsmShadowOptions
|
||||
* @warning This API is still experimental and subject to change.
|
||||
* <b>Warning:</b> This API is still experimental and subject to change.
|
||||
*/
|
||||
public static class VsmShadowOptions {
|
||||
/**
|
||||
* Sets the number of anisotropic samples to use when sampling a VSM shadow map. If greater
|
||||
* than 0, mipmaps will automatically be generated each frame for all lights.
|
||||
*
|
||||
* The number of anisotropic samples = 2 ^ vsmAnisotropy.
|
||||
* <p>The number of anisotropic samples = 2 ^ vsmAnisotropy.</p>
|
||||
*/
|
||||
public int anisotropy = 0;
|
||||
/**
|
||||
@@ -2266,7 +2133,7 @@ public class View {
|
||||
/**
|
||||
* View-level options for DPCF and PCSS Shadowing.
|
||||
* @see #setSoftShadowOptions
|
||||
* @warning This API is still experimental and subject to change.
|
||||
* <b>Warning:</b> This API is still experimental and subject to change.
|
||||
*/
|
||||
public static class SoftShadowOptions {
|
||||
/**
|
||||
|
||||
12
android/filament-tools/.gitignore
vendored
Normal file
12
android/filament-tools/.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
/.idea/libraries
|
||||
/.idea/caches
|
||||
/.idea/gradle.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
/.cxx
|
||||
64
android/filament-tools/build.gradle
Normal file
64
android/filament-tools/build.gradle
Normal file
@@ -0,0 +1,64 @@
|
||||
plugins {
|
||||
id "de.undercouch.download" version "5.6.0"
|
||||
}
|
||||
|
||||
apply from: rootProject.file('gradle/gradle-mvn-push.gradle')
|
||||
|
||||
def tools = ['matc', 'cmgen']
|
||||
|
||||
def platforms = [
|
||||
'mac': [classifier: 'osx-aarch_64', archive: "filament-v${VERSION_NAME}-mac.tgz", path: { t -> "filament/bin/${t}" }],
|
||||
'linux': [classifier: 'linux-x86_64', archive: "filament-v${VERSION_NAME}-linux.tgz", path: { t -> "filament/bin/${t}" }],
|
||||
'windows': [classifier: 'windows-x86_64', archive: "filament-v${VERSION_NAME}-windows.tgz", path: { t -> "bin/${t}.exe" }]
|
||||
]
|
||||
|
||||
platforms.each { platform, config ->
|
||||
def platformName = platform.capitalize()
|
||||
def remoteUrl = "https://github.com/google/filament/releases/download/v${VERSION_NAME}/${config.archive}"
|
||||
def downloadFile = file("${buildDir}/downloads/${config.archive}")
|
||||
def extractDir = file("${buildDir}/extracted/filament-v${VERSION_NAME}-${platform}")
|
||||
|
||||
task "downloadRelease${platformName}"(type: Download) {
|
||||
src remoteUrl
|
||||
dest downloadFile
|
||||
overwrite false
|
||||
}
|
||||
|
||||
def extractionTask = task "extractTools${platformName}"(dependsOn: "downloadRelease${platformName}", type: Copy) {
|
||||
group = "setup"
|
||||
from tarTree(resources.gzip(downloadFile))
|
||||
|
||||
// Include specific tools based on platform pattern
|
||||
include tools.collect { tool -> config.path(tool) }
|
||||
|
||||
// Flatten the path so it lands directly in 'into'
|
||||
eachFile { fcd ->
|
||||
fcd.relativePath = new RelativePath(true, fcd.name)
|
||||
}
|
||||
|
||||
into extractDir
|
||||
includeEmptyDirs = false
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
tools.each { toolName ->
|
||||
create(toolName, MavenPublication) {
|
||||
artifactId = toolName
|
||||
|
||||
platforms.each { platform, config ->
|
||||
def extractDir = file("${buildDir}/extracted/filament-v${VERSION_NAME}-${platform}")
|
||||
def archivePath = config.path(toolName)
|
||||
def exeName = new File(archivePath).name
|
||||
|
||||
artifact(new File(extractDir, exeName)) {
|
||||
classifier = config.classifier
|
||||
extension = "exe"
|
||||
builtBy tasks.named("extractTools${platform.capitalize()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
android/filament-tools/gradle.properties
Normal file
2
android/filament-tools/gradle.properties
Normal file
@@ -0,0 +1,2 @@
|
||||
POM_NAME=Filament
|
||||
POM_PACKAGING=exe
|
||||
@@ -22,6 +22,10 @@ add_library(image STATIC IMPORTED)
|
||||
set_target_properties(image PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libimage.a)
|
||||
|
||||
add_library(imageio-lite STATIC IMPORTED)
|
||||
set_target_properties(imageio-lite PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libimageio-lite.a)
|
||||
|
||||
add_library(ktxreader STATIC IMPORTED)
|
||||
set_target_properties(ktxreader PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libktxreader.a)
|
||||
@@ -30,6 +34,10 @@ add_library(viewer STATIC IMPORTED)
|
||||
set_target_properties(viewer PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libviewer.a)
|
||||
|
||||
add_library(imagediff STATIC IMPORTED)
|
||||
set_target_properties(imagediff PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libimagediff.a)
|
||||
|
||||
add_library(civetweb STATIC IMPORTED)
|
||||
set_target_properties(civetweb PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libcivetweb.a)
|
||||
@@ -57,6 +65,7 @@ add_library(filament-utils-jni SHARED
|
||||
src/main/cpp/IBLPrefilterContext.cpp
|
||||
src/main/cpp/Utils.cpp
|
||||
src/main/cpp/Manipulator.cpp
|
||||
src/main/cpp/ImageDiff.cpp
|
||||
src/main/cpp/RemoteServer.cpp
|
||||
|
||||
${IMAGEIO_DIR}/include/imageio/ImageDecoder.h
|
||||
@@ -74,6 +83,7 @@ target_include_directories(filament-utils-jni PRIVATE
|
||||
${FILAMENT_DIR}/include
|
||||
../../filament/backend/include
|
||||
${IMAGEIO_DIR}/include
|
||||
../../libs/imagediff/include
|
||||
../../libs/utils/include)
|
||||
|
||||
set_target_properties(filament-utils-jni PROPERTIES LINK_DEPENDS ${VERSION_SCRIPT})
|
||||
@@ -85,10 +95,13 @@ target_link_libraries(filament-utils-jni
|
||||
PRIVATE camutils
|
||||
PRIVATE iblprefilter
|
||||
PRIVATE image
|
||||
PRIVATE imageio-lite
|
||||
PRIVATE filament-jni
|
||||
PRIVATE ktxreader
|
||||
PRIVATE viewer
|
||||
PRIVATE imagediff
|
||||
PRIVATE log
|
||||
PRIVATE utils
|
||||
PRIVATE perfetto # needed only when FILAMENT_ENABLE_PERFETTO is defined
|
||||
PRIVATE jnigraphics # needed for AndroidBitmap_* functions in ImageDiff
|
||||
)
|
||||
|
||||
@@ -71,8 +71,10 @@ Java_com_google_android_filament_utils_AutomationEngine_nStartBatchMode(JNIEnv*
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_utils_AutomationEngine_nTick(JNIEnv* env, jclass klass,
|
||||
jlong nativeAutomation, jlong nativeEngine,
|
||||
jlong view, jlongArray materials, jlong renderer, jfloat deltaTime) {
|
||||
jlong view, jlongArray materials, jlong renderer, jlong nativeIbl, jint sunlightEntity,
|
||||
jintArray assetLights, jlong nativeLm, jlong scene, jfloat deltaTime) {
|
||||
using MaterialPointer = MaterialInstance*;
|
||||
|
||||
jsize materialCount = 0;
|
||||
jlong* longMaterials = nullptr;
|
||||
MaterialPointer* ptrMaterials = nullptr;
|
||||
@@ -84,12 +86,28 @@ Java_com_google_android_filament_utils_AutomationEngine_nTick(JNIEnv* env, jclas
|
||||
ptrMaterials[i] = (MaterialPointer) longMaterials[i];
|
||||
}
|
||||
}
|
||||
|
||||
jsize lightCount = 0;
|
||||
jint* intLights = nullptr;
|
||||
if (assetLights) {
|
||||
lightCount = env->GetArrayLength(assetLights);
|
||||
intLights = env->GetIntArrayElements(assetLights, nullptr);
|
||||
}
|
||||
|
||||
static_assert(sizeof(jint) == sizeof(Entity));
|
||||
|
||||
AutomationEngine* automation = (AutomationEngine*) nativeAutomation;
|
||||
AutomationEngine::ViewerContent content = {
|
||||
.view = (View*) view,
|
||||
.renderer = (Renderer*) renderer,
|
||||
.materials = ptrMaterials,
|
||||
.materialCount = (size_t) materialCount,
|
||||
.lightManager = (LightManager*) nativeLm,
|
||||
.scene = (Scene*) scene,
|
||||
.indirectLight = (IndirectLight*) nativeIbl,
|
||||
.sunlight = (Entity&) sunlightEntity,
|
||||
.assetLights = (Entity*) intLights,
|
||||
.assetLightCount = (size_t) lightCount,
|
||||
};
|
||||
Engine* engine = (Engine*)nativeEngine;
|
||||
automation->tick(engine, content, deltaTime);
|
||||
@@ -97,6 +115,9 @@ Java_com_google_android_filament_utils_AutomationEngine_nTick(JNIEnv* env, jclas
|
||||
env->ReleaseLongArrayElements(materials, longMaterials, 0);
|
||||
delete[] ptrMaterials;
|
||||
}
|
||||
if (intLights) {
|
||||
env->ReleaseIntArrayElements(assetLights, intLights, 0);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
@@ -159,37 +180,50 @@ extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_utils_AutomationEngine_nGetViewerOptions(JNIEnv* env, jclass,
|
||||
jlong nativeObject, jobject result) {
|
||||
AutomationEngine* automation = (AutomationEngine*) nativeObject;
|
||||
auto options = automation->getViewerOptions();
|
||||
const auto& settings = automation->getSettings();
|
||||
const auto& options = settings.viewer;
|
||||
|
||||
const jclass klass = env->GetObjectClass(result);
|
||||
|
||||
const jfieldID cameraAperture = env->GetFieldID(klass, "cameraAperture", "F");
|
||||
const jfieldID cameraSpeed = env->GetFieldID(klass, "cameraSpeed", "F");
|
||||
const jfieldID cameraISO = env->GetFieldID(klass, "cameraISO", "F");
|
||||
const jfieldID cameraNear = env->GetFieldID(klass, "cameraNear", "F");
|
||||
const jfieldID cameraFar = env->GetFieldID(klass, "cameraFar", "F");
|
||||
const jfieldID groundShadowStrength = env->GetFieldID(klass, "groundShadowStrength", "F");
|
||||
const jfieldID groundPlaneEnabled = env->GetFieldID(klass, "groundPlaneEnabled", "Z");
|
||||
const jfieldID skyboxEnabled = env->GetFieldID(klass, "skyboxEnabled", "Z");
|
||||
const jfieldID cameraFocalLength = env->GetFieldID(klass, "cameraFocalLength", "F");
|
||||
const jfieldID cameraFocusDistance = env->GetFieldID(klass, "cameraFocusDistance", "F");
|
||||
const jfieldID autoScaleEnabled = env->GetFieldID(klass, "autoScaleEnabled", "Z");
|
||||
const jfieldID autoInstancingEnabled = env->GetFieldID(klass, "autoInstancingEnabled", "Z");
|
||||
|
||||
env->SetFloatField(result, cameraAperture, options.cameraAperture);
|
||||
env->SetFloatField(result, cameraSpeed, options.cameraSpeed);
|
||||
env->SetFloatField(result, cameraISO, options.cameraISO);
|
||||
env->SetFloatField(result, cameraNear, options.cameraNear);
|
||||
env->SetFloatField(result, cameraFar, options.cameraFar);
|
||||
env->SetFloatField(result, groundShadowStrength, options.groundShadowStrength);
|
||||
env->SetBooleanField(result, groundPlaneEnabled, options.groundPlaneEnabled);
|
||||
env->SetBooleanField(result, skyboxEnabled, options.skyboxEnabled);
|
||||
env->SetFloatField(result, cameraFocalLength, options.cameraFocalLength);
|
||||
env->SetFloatField(result, cameraFocusDistance, options.cameraFocusDistance);
|
||||
env->SetBooleanField(result, autoScaleEnabled, options.autoScaleEnabled);
|
||||
env->SetBooleanField(result, autoInstancingEnabled, options.autoInstancingEnabled);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_utils_AutomationEngine_nGetCameraSettings(JNIEnv* env, jclass,
|
||||
jlong nativeObject, jobject result) {
|
||||
AutomationEngine* automation = (AutomationEngine*) nativeObject;
|
||||
const auto& settings = automation->getSettings();
|
||||
const auto& camera = settings.camera;
|
||||
|
||||
const jclass klass = env->GetObjectClass(result);
|
||||
|
||||
const jfieldID aperture = env->GetFieldID(klass, "aperture", "F");
|
||||
const jfieldID shutterSpeed = env->GetFieldID(klass, "shutterSpeed", "F");
|
||||
const jfieldID sensitivity = env->GetFieldID(klass, "sensitivity", "F");
|
||||
const jfieldID near = env->GetFieldID(klass, "near", "F");
|
||||
const jfieldID far = env->GetFieldID(klass, "far", "F");
|
||||
const jfieldID focalLength = env->GetFieldID(klass, "focalLength", "F");
|
||||
const jfieldID focusDistance = env->GetFieldID(klass, "focusDistance", "F");
|
||||
|
||||
env->SetFloatField(result, aperture, camera.aperture);
|
||||
env->SetFloatField(result, shutterSpeed, camera.shutterSpeed);
|
||||
env->SetFloatField(result, sensitivity, camera.sensitivity);
|
||||
env->SetFloatField(result, near, camera.near);
|
||||
env->SetFloatField(result, far, camera.far);
|
||||
env->SetFloatField(result, focalLength, camera.focalLength);
|
||||
env->SetFloatField(result, focusDistance, camera.focusDistance);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_utils_AutomationEngine_nGetColorGrading(JNIEnv*, jclass,
|
||||
jlong nativeObject, jlong nativeEngine) {
|
||||
@@ -215,6 +249,18 @@ Java_com_google_android_filament_utils_AutomationEngine_nShouldClose(JNIEnv*, jc
|
||||
return automation->shouldClose();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_utils_AutomationEngine_nGetTestCount(JNIEnv*, jclass, jlong native) {
|
||||
AutomationEngine* automation = (AutomationEngine*) native;
|
||||
return (jint) automation->testCount();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_utils_AutomationEngine_nGetCurrentTest(JNIEnv*, jclass, jlong native) {
|
||||
AutomationEngine* automation = (AutomationEngine*) native;
|
||||
return (jint) automation->currentTest();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_utils_AutomationEngine_nDestroy(JNIEnv*, jclass, jlong native) {
|
||||
AutomationEngine* automation = (AutomationEngine*) native;
|
||||
|
||||
217
android/filament-utils-android/src/main/cpp/ImageDiff.cpp
Normal file
217
android/filament-utils-android/src/main/cpp/ImageDiff.cpp
Normal file
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright (C) 2026 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 <jni.h>
|
||||
#include <android/bitmap.h>
|
||||
|
||||
#include <imagediff/ImageDiff.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace imagediff;
|
||||
using namespace utils;
|
||||
|
||||
namespace {
|
||||
|
||||
struct BitmapLock {
|
||||
JNIEnv* env;
|
||||
jobject bitmap;
|
||||
void* pixels;
|
||||
AndroidBitmapInfo info;
|
||||
|
||||
BitmapLock(JNIEnv* env, jobject bitmap) : env(env), bitmap(bitmap), pixels(nullptr) {
|
||||
if (!bitmap) return;
|
||||
if (AndroidBitmap_getInfo(env, bitmap, &info) < 0) {
|
||||
return;
|
||||
}
|
||||
if (AndroidBitmap_lockPixels(env, bitmap, &pixels) < 0) {
|
||||
pixels = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
~BitmapLock() {
|
||||
if (pixels) {
|
||||
AndroidBitmap_unlockPixels(env, bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
bool isValid() const { return pixels != nullptr; }
|
||||
|
||||
imagediff::Bitmap toBitmap() const {
|
||||
return {
|
||||
.width = (uint32_t) info.width,
|
||||
.height = (uint32_t) info.height,
|
||||
.stride = (size_t) info.stride,
|
||||
.data = pixels
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
|
||||
// Helper to convert C++ ImageDiffResult to Java Result
|
||||
jobject createResult(JNIEnv* env, ImageDiffResult const& result, bool generateDiff) {
|
||||
// Create Result class/objects
|
||||
jclass resultClass = env->FindClass("com/google/android/filament/utils/ImageDiff$Result");
|
||||
jmethodID resultCtor = env->GetMethodID(resultClass, "<init>", "()V");
|
||||
jobject resultObj = env->NewObject(resultClass, resultCtor);
|
||||
jfieldID statusField = env->GetFieldID(resultClass, "status", "Lcom/google/android/filament/utils/ImageDiff$Result$Status;");
|
||||
jfieldID failingCountField = env->GetFieldID(resultClass, "failingPixelCount", "J");
|
||||
jfieldID maxDiffField = env->GetFieldID(resultClass, "maxDiffFound", "[F");
|
||||
jfieldID diffImageField = env->GetFieldID(resultClass, "diffImage", "Landroid/graphics/Bitmap;");
|
||||
|
||||
// Map Status enum
|
||||
jclass statusEnum = env->FindClass("com/google/android/filament/utils/ImageDiff$Result$Status");
|
||||
jobject statusObj = nullptr;
|
||||
jfieldID enumField = nullptr;
|
||||
switch (result.status) {
|
||||
case ImageDiffResult::Status::PASSED:
|
||||
enumField = env->GetStaticFieldID(statusEnum, "PASSED", "Lcom/google/android/filament/utils/ImageDiff$Result$Status;");
|
||||
break;
|
||||
case ImageDiffResult::Status::SIZE_MISMATCH:
|
||||
enumField = env->GetStaticFieldID(statusEnum, "SIZE_MISMATCH", "Lcom/google/android/filament/utils/ImageDiff$Result$Status;");
|
||||
break;
|
||||
case ImageDiffResult::Status::PIXEL_DIFFERENCE:
|
||||
enumField = env->GetStaticFieldID(statusEnum, "PIXEL_DIFFERENCE", "Lcom/google/android/filament/utils/ImageDiff$Result$Status;");
|
||||
break;
|
||||
}
|
||||
statusObj = env->GetStaticObjectField(statusEnum, enumField);
|
||||
env->SetObjectField(resultObj, statusField, statusObj);
|
||||
|
||||
env->SetLongField(resultObj, failingCountField, (jlong) result.failingPixelCount);
|
||||
|
||||
jfloatArray maxDiffArray = env->NewFloatArray(4);
|
||||
env->SetFloatArrayRegion(maxDiffArray, 0, 4, result.maxDiffFound);
|
||||
env->SetObjectField(resultObj, maxDiffField, maxDiffArray);
|
||||
|
||||
if (generateDiff && result.diffImage.getWidth() > 0) {
|
||||
jclass bitmapClass = env->FindClass("android/graphics/Bitmap");
|
||||
jmethodID createBitmap = env->GetStaticMethodID(bitmapClass, "createBitmap",
|
||||
"(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
|
||||
|
||||
jclass configClass = env->FindClass("android/graphics/Bitmap$Config");
|
||||
jfieldID argb8888 = env->GetStaticFieldID(configClass, "ARGB_8888", "Landroid/graphics/Bitmap$Config;");
|
||||
jobject configObj = env->GetStaticObjectField(configClass, argb8888);
|
||||
|
||||
uint32_t width = result.diffImage.getWidth();
|
||||
uint32_t height = result.diffImage.getHeight();
|
||||
jobject diffBitmap = env->CallStaticObjectMethod(bitmapClass, createBitmap, (jint)width, (jint)height, configObj);
|
||||
|
||||
if (diffBitmap) {
|
||||
void* diffPixels;
|
||||
if (AndroidBitmap_lockPixels(env, diffBitmap, &diffPixels) == 0) {
|
||||
float const* src = result.diffImage.getPixelRef();
|
||||
uint8_t* dst = (uint8_t*) diffPixels;
|
||||
uint32_t channels = result.diffImage.getChannels(); // usually 4
|
||||
|
||||
for (size_t i = 0; i < width * height; ++i) {
|
||||
for (int c = 0; c < 4; ++c) {
|
||||
float v = 0.0f;
|
||||
if (c < channels) v = src[i * channels + c];
|
||||
if (c == 3 && channels < 4) v = 1.0f; // Alpha 1.0 if missing
|
||||
dst[i * 4 + c] = (uint8_t) std::min(255.0f, std::max(0.0f, v * 255.0f));
|
||||
}
|
||||
}
|
||||
AndroidBitmap_unlockPixels(env, diffBitmap);
|
||||
env->SetObjectField(resultObj, diffImageField, diffBitmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resultObj;
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jobject JNICALL
|
||||
Java_com_google_android_filament_utils_ImageDiff_nCompareBasic(JNIEnv* env, jclass,
|
||||
jobject refBitmap, jobject candBitmap, jint mode, jint swizzle, jint channelMask,
|
||||
jfloat maxAbsDiff, jfloat maxFailingPixelsFraction, jobject maskBitmap) {
|
||||
|
||||
BitmapLock refArg(env, refBitmap);
|
||||
BitmapLock candArg(env, candBitmap);
|
||||
BitmapLock maskArg(env, maskBitmap);
|
||||
|
||||
if (!refArg.isValid() || !candArg.isValid()) {
|
||||
ImageDiffResult emptyResult;
|
||||
emptyResult.status = ImageDiffResult::Status::SIZE_MISMATCH; // or ERROR
|
||||
return createResult(env, emptyResult, false);
|
||||
}
|
||||
|
||||
ImageDiffConfig config;
|
||||
config.mode = (ImageDiffConfig::Mode) mode;
|
||||
config.swizzle = (ImageDiffConfig::Swizzle) swizzle;
|
||||
config.channelMask = (uint8_t) channelMask;
|
||||
config.maxAbsDiff = maxAbsDiff;
|
||||
config.maxFailingPixelsFraction = maxFailingPixelsFraction;
|
||||
|
||||
imagediff::Bitmap const* maskPtr = nullptr;
|
||||
imagediff::Bitmap maskVal;
|
||||
if (maskBitmap && maskArg.isValid()) {
|
||||
maskVal = maskArg.toBitmap();
|
||||
maskPtr = &maskVal;
|
||||
}
|
||||
|
||||
bool generateDiff = true;
|
||||
ImageDiffResult result = compare(refArg.toBitmap(), candArg.toBitmap(), config, maskPtr, generateDiff);
|
||||
|
||||
return createResult(env, result, generateDiff);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jobject JNICALL
|
||||
Java_com_google_android_filament_utils_ImageDiff_nCompareJson(JNIEnv* env, jclass,
|
||||
jobject refBitmap, jobject candBitmap, jstring jsonConfig, jobject maskBitmap) {
|
||||
|
||||
BitmapLock refArg(env, refBitmap);
|
||||
BitmapLock candArg(env, candBitmap);
|
||||
BitmapLock maskArg(env, maskBitmap);
|
||||
|
||||
if (!refArg.isValid() || !candArg.isValid()) {
|
||||
ImageDiffResult emptyResult;
|
||||
emptyResult.status = ImageDiffResult::Status::SIZE_MISMATCH; // or ERROR
|
||||
return createResult(env, emptyResult, false);
|
||||
}
|
||||
|
||||
ImageDiffConfig config;
|
||||
const char* nativeJson = env->GetStringUTFChars(jsonConfig, 0);
|
||||
size_t length = env->GetStringUTFLength(jsonConfig);
|
||||
|
||||
bool parsed = parseConfig(nativeJson, length, &config);
|
||||
env->ReleaseStringUTFChars(jsonConfig, nativeJson);
|
||||
|
||||
if (!parsed) {
|
||||
// Fallback to default or error?
|
||||
// We could log error.
|
||||
utils::slog.e << "ImageDiff JNI: Failed to parse JSON config" << utils::io::endl;
|
||||
ImageDiffResult errResult;
|
||||
errResult.status = ImageDiffResult::Status::PIXEL_DIFFERENCE; // assume fail
|
||||
return createResult(env, errResult, false);
|
||||
}
|
||||
|
||||
imagediff::Bitmap const* maskPtr = nullptr;
|
||||
imagediff::Bitmap maskVal;
|
||||
if (maskBitmap && maskArg.isValid()) {
|
||||
maskVal = maskArg.toBitmap();
|
||||
maskPtr = &maskVal;
|
||||
}
|
||||
|
||||
bool generateDiff = true;
|
||||
ImageDiffResult result = compare(refArg.toBitmap(), candArg.toBitmap(), config, maskPtr, generateDiff);
|
||||
|
||||
return createResult(env, result, generateDiff);
|
||||
}
|
||||
|
||||
@@ -94,20 +94,23 @@ public class AutomationEngine {
|
||||
* Allows remote control for the viewer.
|
||||
*/
|
||||
public static class ViewerOptions {
|
||||
public float cameraAperture = 16.0f;
|
||||
public float cameraSpeed = 125.0f;
|
||||
public float cameraISO = 100.0f;
|
||||
public float cameraNear = 0.1f;
|
||||
public float cameraFar = 100.0f;
|
||||
public float groundShadowStrength = 0.75f;
|
||||
public boolean groundPlaneEnabled = false;
|
||||
public boolean skyboxEnabled = true;
|
||||
public float cameraFocalLength = 28.0f;
|
||||
public float cameraFocusDistance = 0.0f;
|
||||
public boolean autoScaleEnabled = true;
|
||||
public boolean autoInstancingEnabled = false;
|
||||
}
|
||||
|
||||
public static class CameraSettings {
|
||||
public float aperture = 16.0f;
|
||||
public float shutterSpeed = 125.0f;
|
||||
public float sensitivity = 100.0f;
|
||||
public float near = 0.1f;
|
||||
public float far = 100.0f;
|
||||
public float focalLength = 28.0f;
|
||||
public float focusDistance = 10.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an automation engine from a JSON specification.
|
||||
*
|
||||
@@ -175,7 +178,12 @@ public class AutomationEngine {
|
||||
}
|
||||
long nativeView = content.view.getNativeObject();
|
||||
long nativeRenderer = content.renderer.getNativeObject();
|
||||
nTick(mNativeObject, engine.getNativeObject(), nativeView, nativeMaterialInstances, nativeRenderer, deltaTime);
|
||||
long nativeIbl = content.indirectLight == null ? 0 : content.indirectLight.getNativeObject();
|
||||
long nativeLm = content.lightManager == null ? 0 : content.lightManager.getNativeObject();
|
||||
long nativeScene = content.scene == null ? 0 : content.scene.getNativeObject();
|
||||
nTick(mNativeObject, engine.getNativeObject(), nativeView, nativeMaterialInstances,
|
||||
nativeRenderer, nativeIbl, content.sunlight, content.assetLights, nativeLm,
|
||||
nativeScene, deltaTime);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,6 +237,13 @@ public class AutomationEngine {
|
||||
return result;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public CameraSettings getCameraSettings() {
|
||||
CameraSettings result = new CameraSettings();
|
||||
nGetCameraSettings(mNativeObject, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a color grading object that corresponds to the latest settings.
|
||||
*
|
||||
@@ -261,6 +276,9 @@ public class AutomationEngine {
|
||||
*/
|
||||
public boolean shouldClose() { return nShouldClose(mNativeObject); }
|
||||
|
||||
public int getTestCount() { return nGetTestCount(mNativeObject); }
|
||||
public int getCurrentTest() { return nGetCurrentTest(mNativeObject); }
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
nDestroy(mNativeObject);
|
||||
@@ -274,15 +292,19 @@ public class AutomationEngine {
|
||||
private static native void nStartRunning(long nativeObject);
|
||||
private static native void nStartBatchMode(long nativeObject);
|
||||
private static native void nTick(long nativeObject, long nativeEngine,
|
||||
long view, long[] materials, long renderer, float deltaTime);
|
||||
long view, long[] materials, long renderer, long ibl, int sunlight, int[] assetLights,
|
||||
long lightManager, long scene, float deltaTime);
|
||||
private static native void nApplySettings(long nativeObject, long nativeEngine,
|
||||
String jsonSettings, long view,
|
||||
long[] materials, long ibl, int sunlight, int[] assetLights, long lightManager,
|
||||
long scene, long renderer);
|
||||
private static native void nGetViewerOptions(long nativeObject, Object result);
|
||||
private static native void nGetCameraSettings(long nativeObject, Object result);
|
||||
private static native long nGetColorGrading(long nativeObject, long nativeEngine);
|
||||
private static native void nSignalBatchMode(long nativeObject);
|
||||
private static native void nStopRunning(long nativeObject);
|
||||
private static native boolean nShouldClose(long nativeObject);
|
||||
private static native int nGetTestCount(long nativeObject);
|
||||
private static native int nGetCurrentTest(long nativeObject);
|
||||
private static native void nDestroy(long nativeObject);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.utils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
public class ImageDiff {
|
||||
public enum Mode {
|
||||
LEAF, AND, OR
|
||||
}
|
||||
|
||||
public enum Swizzle {
|
||||
RGBA, BGRA
|
||||
}
|
||||
|
||||
public static class Config {
|
||||
@NonNull
|
||||
public Mode mode = Mode.LEAF;
|
||||
@NonNull
|
||||
public Swizzle swizzle = Swizzle.RGBA;
|
||||
public int channelMask = 0xF;
|
||||
public float maxAbsDiff = 0.0f;
|
||||
public float maxFailingPixelsFraction = 0.0f;
|
||||
// Children not supported in this simple wrapper for now, can be added if needed
|
||||
}
|
||||
|
||||
public static class Result {
|
||||
public enum Status {
|
||||
PASSED,
|
||||
SIZE_MISMATCH,
|
||||
PIXEL_DIFFERENCE
|
||||
}
|
||||
|
||||
public Status status;
|
||||
public long failingPixelCount;
|
||||
public float[] maxDiffFound; // [R, G, B, A]
|
||||
public Bitmap diffImage; // Null if not generated
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two bitmaps using a configuration object.
|
||||
*
|
||||
* @param reference Golden image
|
||||
* @param candidate Actual image
|
||||
* @param config Comparison configuration
|
||||
* @param mask Optional mask (grayscale)
|
||||
* @return Result of comparison
|
||||
*/
|
||||
@NonNull
|
||||
public static Result compareBasic(@NonNull Bitmap reference, @NonNull Bitmap candidate,
|
||||
@NonNull Config config, @Nullable Bitmap mask) {
|
||||
return nCompareBasic(reference, candidate, config.mode.ordinal(), config.swizzle.ordinal(),
|
||||
config.channelMask, config.maxAbsDiff, config.maxFailingPixelsFraction, mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two bitmaps using a JSON configuration string.
|
||||
*
|
||||
* @param reference Golden image
|
||||
* @param candidate Actual image
|
||||
* @param jsonConfig Comparison configuration in JSON format
|
||||
* @param mask Optional mask (grayscale)
|
||||
* @return Result of comparison
|
||||
*/
|
||||
@NonNull
|
||||
public static Result compare(@NonNull Bitmap reference, @NonNull Bitmap candidate,
|
||||
@NonNull String jsonConfig, @Nullable Bitmap mask) {
|
||||
return nCompareJson(reference, candidate, jsonConfig, mask);
|
||||
}
|
||||
|
||||
private static native Result nCompareBasic(Bitmap reference, Bitmap candidate, int mode, int swizzle,
|
||||
int channelMask, float maxAbsDiff, float maxFailingPixelsFraction, Bitmap mask);
|
||||
|
||||
private static native Result nCompareJson(Bitmap reference, Bitmap candidate, String jsonConfig, Bitmap mask);
|
||||
}
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package com.google.android.filament.utils
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.MotionEvent
|
||||
import android.view.Surface
|
||||
import android.view.SurfaceView
|
||||
@@ -26,6 +29,7 @@ import com.google.android.filament.android.UiHelper
|
||||
import com.google.android.filament.gltfio.*
|
||||
import kotlinx.coroutines.*
|
||||
import java.nio.Buffer
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
private const val kNearPlane = 0.05f // 5 cm
|
||||
private const val kFarPlane = 1000.0f // 1 km
|
||||
@@ -119,6 +123,8 @@ class ModelViewer(
|
||||
private val target = DoubleArray(3)
|
||||
private val upward = DoubleArray(3)
|
||||
|
||||
private var debugFrameCallback: ((Bitmap) -> Unit)? = null
|
||||
|
||||
init {
|
||||
renderer = engine.createRenderer()
|
||||
scene = engine.createScene()
|
||||
@@ -305,10 +311,39 @@ class ModelViewer(
|
||||
// Render the scene, unless the renderer wants to skip the frame.
|
||||
if (renderer.beginFrame(swapChain!!, frameTimeNanos)) {
|
||||
renderer.render(view)
|
||||
|
||||
debugFrameCallback?.let {
|
||||
val viewport = view.viewport
|
||||
val bitmap = Bitmap.createBitmap(viewport.width, viewport.height,
|
||||
Bitmap.Config.ARGB_8888)
|
||||
val buffer = ByteBuffer.allocateDirect(viewport.width * viewport.height * 4)
|
||||
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
val pixelBufferDescriptor = Texture.PixelBufferDescriptor(buffer,
|
||||
Texture.Format.RGBA, Texture.Type.UBYTE, 1, 0, 0, 0, handler) {
|
||||
buffer.rewind()
|
||||
bitmap.copyPixelsFromBuffer(buffer)
|
||||
it(bitmap)
|
||||
}
|
||||
renderer.readPixels(viewport.left, viewport.bottom, viewport.width,
|
||||
viewport.height, pixelBufferDescriptor)
|
||||
debugFrameCallback = null
|
||||
}
|
||||
|
||||
renderer.endFrame()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets a callback that will be invoked with the next rendered frame as a Bitmap. Note that this
|
||||
* is a one-time callback.
|
||||
*
|
||||
* @param callback callback to be invoked with a rendered frame as [Bitmap]
|
||||
*/
|
||||
fun debugGetNextFrameCallback(callback: (Bitmap) -> Unit) {
|
||||
debugFrameCallback = callback
|
||||
}
|
||||
|
||||
private fun populateScene(asset: FilamentAsset) {
|
||||
val rcm = engine.renderableManager
|
||||
var count = 0
|
||||
|
||||
@@ -6,6 +6,7 @@ option(FILAMENT_ENABLE_FGVIEWER "Enables Frame Graph Viewer" OFF)
|
||||
option(FILAMENT_ENABLE_MATDBG "Enables Material debugger" OFF)
|
||||
option(FILAMENT_DISABLE_MATOPT "Disables material optimizations" OFF)
|
||||
option(FILAMENT_SUPPORTS_WEBGPU "Enables WebGPU on Android" OFF)
|
||||
option(FILAMENT_SUPPORTS_WEBP_TEXTURES "Enable webp texture support on Android" OFF)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
||||
@@ -30,6 +31,12 @@ add_library(stb STATIC IMPORTED)
|
||||
set_target_properties(stb PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libstb.a)
|
||||
|
||||
if (FILAMENT_SUPPORTS_WEBP_TEXTURES)
|
||||
add_library(webpdecoder STATIC IMPORTED)
|
||||
set_target_properties(webpdecoder PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libwebpdecoder.a)
|
||||
endif()
|
||||
|
||||
add_library(basis_transcoder STATIC IMPORTED)
|
||||
set_target_properties(basis_transcoder PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libbasis_transcoder.a)
|
||||
@@ -81,6 +88,10 @@ set(GLTFIO_INCLUDE_DIRS
|
||||
../../libs/ktxreader/include
|
||||
)
|
||||
|
||||
if (FILAMENT_SUPPORTS_WEBP_TEXTURES)
|
||||
list(APPEND GLTFIO_INCLUDE_DIRS ../../third_party/libwebp/src)
|
||||
endif()
|
||||
|
||||
add_library(gltfio-jni SHARED ${GLTFIO_SRCS})
|
||||
|
||||
target_compile_definitions(gltfio-jni PUBLIC GLTFIO_DRACO_SUPPORTED=1)
|
||||
@@ -107,3 +118,6 @@ target_link_libraries(gltfio-jni
|
||||
PRIVATE perfetto # needed only when FILAMENT_ENABLE_PERFETTO is defined
|
||||
PRIVATE log # needed only when perfetto above is used
|
||||
)
|
||||
if (FILAMENT_SUPPORTS_WEBP_TEXTURES)
|
||||
target_link_libraries(gltfio-jni PRIVATE webpdecoder)
|
||||
endif()
|
||||
|
||||
@@ -128,6 +128,18 @@ Java_com_google_android_filament_gltfio_ResourceLoader_nCreateKtx2Provider(JNIEn
|
||||
return (jlong) createKtx2Provider(engine);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_gltfio_ResourceLoader_nIsWebpSupported(JNIEnv*, jclass) {
|
||||
return (jboolean) isWebpSupported();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_gltfio_ResourceLoader_nCreateWebpProvider(JNIEnv*, jclass,
|
||||
jlong nativeEngine) {
|
||||
Engine* engine = (Engine*) nativeEngine;
|
||||
return (jlong) createWebpProvider(engine);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_gltfio_ResourceLoader_nDestroyTextureProvider(JNIEnv*, jclass,
|
||||
jlong nativeProvider) {
|
||||
|
||||
@@ -37,6 +37,7 @@ public class ResourceLoader {
|
||||
private final long mNativeObject;
|
||||
private final long mNativeStbProvider;
|
||||
private final long mNativeKtx2Provider;
|
||||
private final long mNativeWebpProvider;
|
||||
|
||||
/**
|
||||
* Constructs a resource loader tied to the given Filament engine.
|
||||
@@ -50,9 +51,17 @@ public class ResourceLoader {
|
||||
mNativeObject = nCreateResourceLoader(nativeEngine, false);
|
||||
mNativeStbProvider = nCreateStbProvider(nativeEngine);
|
||||
mNativeKtx2Provider = nCreateKtx2Provider(nativeEngine);
|
||||
|
||||
nAddTextureProvider(mNativeObject, "image/jpeg", mNativeStbProvider);
|
||||
nAddTextureProvider(mNativeObject, "image/png", mNativeStbProvider);
|
||||
nAddTextureProvider(mNativeObject, "image/ktx2", mNativeKtx2Provider);
|
||||
if (nIsWebpSupported()) {
|
||||
mNativeWebpProvider = nCreateWebpProvider(nativeEngine);
|
||||
nAddTextureProvider(mNativeObject, "image/webp", mNativeWebpProvider);
|
||||
}
|
||||
else {
|
||||
mNativeWebpProvider = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,9 +77,17 @@ public class ResourceLoader {
|
||||
mNativeObject = nCreateResourceLoader(nativeEngine, normalizeSkinningWeights);
|
||||
mNativeStbProvider = nCreateStbProvider(nativeEngine);
|
||||
mNativeKtx2Provider = nCreateKtx2Provider(nativeEngine);
|
||||
|
||||
nAddTextureProvider(mNativeObject, "image/jpeg", mNativeStbProvider);
|
||||
nAddTextureProvider(mNativeObject, "image/png", mNativeStbProvider);
|
||||
nAddTextureProvider(mNativeObject, "image/ktx2", mNativeKtx2Provider);
|
||||
if (nIsWebpSupported()) {
|
||||
mNativeWebpProvider = nCreateWebpProvider(nativeEngine);
|
||||
nAddTextureProvider(mNativeObject, "image/webp", mNativeWebpProvider);
|
||||
}
|
||||
else {
|
||||
mNativeWebpProvider = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,6 +97,9 @@ public class ResourceLoader {
|
||||
nDestroyResourceLoader(mNativeObject);
|
||||
nDestroyTextureProvider(mNativeStbProvider);
|
||||
nDestroyTextureProvider(mNativeKtx2Provider);
|
||||
if (nIsWebpSupported()) {
|
||||
nDestroyTextureProvider(mNativeWebpProvider);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,6 +211,9 @@ public class ResourceLoader {
|
||||
|
||||
private static native long nCreateStbProvider(long nativeEngine);
|
||||
private static native long nCreateKtx2Provider(long nativeEngine);
|
||||
private static native boolean nIsWebpSupported();
|
||||
private static native long nCreateWebpProvider(long nativeEngine);
|
||||
|
||||
private static native void nAddTextureProvider(long nativeLoader, String url, long nativeProvider);
|
||||
private static native void nDestroyTextureProvider(long nativeProvider);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.67.0
|
||||
VERSION_NAME=1.69.3
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -80,54 +80,12 @@ frame and the external texture are perfectly synchronized.
|
||||
|
||||

|
||||
|
||||
## Prerequisites
|
||||
## Building Samples
|
||||
|
||||
Before you start, make sure to read [Filament's README](../../README.md). You need to be able to
|
||||
compile Filament's native library and Filament's AAR for this project. The easiest way to proceed
|
||||
is to install all the required dependencies and to run the following commands at the root of the
|
||||
source tree:
|
||||
source tree.
|
||||
|
||||
```shell
|
||||
./build.sh -p desktop -i release
|
||||
./build.sh -p android release
|
||||
```
|
||||
To build the samples, please follow the steps described in [BUILDING.md](../../BUILDING.md#android)
|
||||
|
||||
This will build all the native components and the AAR required by this sample application.
|
||||
|
||||
If you do not use the build script, you must set the `filament_tools_dir` property when invoking
|
||||
Gradle, either from the command line or from `local.properties`. This property must point to the
|
||||
distribution/install directory for desktop (produced by make/ninja install). This directory must
|
||||
contain `bin/matc` and `bin/cmgen`.
|
||||
|
||||
Example:
|
||||
```shell
|
||||
./gradlew -Pfilament_tools_dir=../../dist-release assembleDebug
|
||||
```
|
||||
|
||||
## Important: SDK location
|
||||
|
||||
Either ensure your `ANDROID_HOME` environment variable is set or make sure the root project
|
||||
contains a `local.properties` file with the `sdk.dir` property pointing to your installation of
|
||||
the Android SDK.
|
||||
|
||||
## Compiling
|
||||
|
||||
### Android Studio
|
||||
|
||||
You must use the latest stable release of Android Studio. To open the project, point Studio to the
|
||||
`android` folder. After opening the project and syncing to gradle, select the sample of your choice
|
||||
using the drop-down widget in the toolbar.
|
||||
|
||||
To compile and run each sample make sure you have selected the appropriate build variant
|
||||
(arm7, arm8, x86 or x86_64). If you are not sure you can simply select the "universal"
|
||||
variant which includes all the other ones.
|
||||
|
||||
### Command Line
|
||||
|
||||
From the `android` directory in the project root:
|
||||
|
||||
```shell
|
||||
./gradlew :samples:sample-hello-triangle:installDebug
|
||||
```
|
||||
|
||||
Replace `sample-hello-triangle` with your preferred project.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'filament-tools-plugin'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
@@ -10,7 +10,7 @@ kotlin {
|
||||
jvmToolchain(versions.jdk)
|
||||
}
|
||||
|
||||
filamentTools {
|
||||
filament {
|
||||
cmgenArgs = "-q --format=ktx --size=256 --extract-blur=0.1 --deploy=src/main/assets/envs/default_env"
|
||||
iblInputFile = project.layout.projectDirectory.file("../../../third_party/environments/lightroom_14b.hdr")
|
||||
iblOutputDir = project.layout.projectDirectory.dir("src/main/assets/envs")
|
||||
|
||||
@@ -389,9 +389,9 @@ class MainActivity : Activity() {
|
||||
viewerContent.assetLights = modelViewer.asset?.lightEntities
|
||||
automation.applySettings(modelViewer.engine, json, viewerContent)
|
||||
modelViewer.view.colorGrading = automation.getColorGrading(modelViewer.engine)
|
||||
modelViewer.cameraFocalLength = automation.viewerOptions.cameraFocalLength
|
||||
modelViewer.cameraNear = automation.viewerOptions.cameraNear
|
||||
modelViewer.cameraFar = automation.viewerOptions.cameraFar
|
||||
modelViewer.cameraFocalLength = automation.cameraSettings.focalLength
|
||||
modelViewer.cameraNear = automation.cameraSettings.near
|
||||
modelViewer.cameraFar = automation.cameraSettings.far
|
||||
updateRootTransform()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'filament-tools-plugin'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
@@ -10,7 +10,7 @@ kotlin {
|
||||
jvmToolchain(versions.jdk)
|
||||
}
|
||||
|
||||
filamentTools {
|
||||
filament {
|
||||
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
||||
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'filament-tools-plugin'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
@@ -10,7 +10,7 @@ kotlin {
|
||||
jvmToolchain(versions.jdk)
|
||||
}
|
||||
|
||||
filamentTools {
|
||||
filament {
|
||||
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
||||
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'filament-tools-plugin'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
@@ -10,7 +10,7 @@ kotlin {
|
||||
jvmToolchain(versions.jdk)
|
||||
}
|
||||
|
||||
filamentTools {
|
||||
filament {
|
||||
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
||||
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'filament-tools-plugin'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
@@ -10,7 +10,7 @@ kotlin {
|
||||
jvmToolchain(versions.jdk)
|
||||
}
|
||||
|
||||
filamentTools {
|
||||
filament {
|
||||
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
||||
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'filament-tools-plugin'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
@@ -10,7 +10,7 @@ kotlin {
|
||||
jvmToolchain(versions.jdk)
|
||||
}
|
||||
|
||||
filamentTools {
|
||||
filament {
|
||||
meshInputFile = project.layout.projectDirectory.file("../../../third_party/models/shader_ball/shader_ball.obj")
|
||||
meshOutputDir = project.layout.projectDirectory.dir("src/main/assets/models")
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'filament-tools-plugin'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
@@ -10,7 +10,7 @@ kotlin {
|
||||
jvmToolchain(versions.jdk)
|
||||
}
|
||||
|
||||
filamentTools {
|
||||
filament {
|
||||
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
||||
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'filament-tools-plugin'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
@@ -10,7 +10,7 @@ kotlin {
|
||||
jvmToolchain(versions.jdk)
|
||||
}
|
||||
|
||||
filamentTools {
|
||||
filament {
|
||||
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
||||
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'filament-tools-plugin'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
@@ -10,7 +10,7 @@ kotlin {
|
||||
jvmToolchain(versions.jdk)
|
||||
}
|
||||
|
||||
filamentTools {
|
||||
filament {
|
||||
iblInputFile = project.layout.projectDirectory.file("../../../third_party/environments/studio_small_02_2k.hdr")
|
||||
iblOutputDir = project.layout.projectDirectory.dir("src/main/assets/envs")
|
||||
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
||||
|
||||
55
android/samples/sample-render-validation/build.gradle
Normal file
55
android/samples/sample-render-validation/build.gradle
Normal file
@@ -0,0 +1,55 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(versions.jdk)
|
||||
}
|
||||
|
||||
filament {
|
||||
cmgenArgs = "-q --format=ktx --size=256 --extract-blur=0.1 --deploy=src/main/assets/envs/default_env"
|
||||
iblInputFile = project.layout.projectDirectory.file("../../../third_party/environments/lightroom_14b.hdr")
|
||||
iblOutputDir = project.layout.projectDirectory.dir("src/main/assets/envs")
|
||||
}
|
||||
|
||||
// don't forget to update MainACtivity.kt when/if changing this.
|
||||
tasks.register('copyDamagedHelmetGltf', Copy) {
|
||||
from file("../../../third_party/models/DamagedHelmet/DamagedHelmet.glb")
|
||||
into file("src/main/assets/models")
|
||||
rename {String fileName -> "helmet.glb"}
|
||||
}
|
||||
|
||||
preBuild.dependsOn copyDamagedHelmetGltf
|
||||
|
||||
clean.doFirst {
|
||||
delete "src/main/assets"
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.google.android.filament.validation'
|
||||
|
||||
compileSdkVersion versions.compileSdk
|
||||
defaultConfig {
|
||||
applicationId "com.google.android.filament.validation"
|
||||
minSdkVersion versions.minSdk
|
||||
targetSdkVersion versions.targetSdk
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility versions.jdk
|
||||
targetCompatibility versions.jdk
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation deps.kotlin
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation deps.coroutines.core
|
||||
implementation project(':filament-android')
|
||||
implementation project(':gltfio-android')
|
||||
implementation project(':filament-utils-android')
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:label="Filament Validation"
|
||||
android:supportsRtl="true"
|
||||
android:largeHeap="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:theme="@android:style/Theme.NoTitleBar">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
|
||||
android:screenOrientation="fullSensor">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,350 @@
|
||||
/*
|
||||
* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.validation
|
||||
|
||||
import android.app.Activity
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.Choreographer
|
||||
import android.view.SurfaceView
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ScrollView
|
||||
import android.widget.TextView
|
||||
import com.google.android.filament.utils.ModelViewer
|
||||
import com.google.android.filament.utils.Utils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import com.google.android.filament.utils.KTX1Loader
|
||||
import com.google.android.filament.IndirectLight
|
||||
import com.google.android.filament.Skybox
|
||||
import android.graphics.Color
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.nio.ByteBuffer
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Button
|
||||
import android.widget.Spinner
|
||||
import android.widget.AdapterView
|
||||
|
||||
class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
|
||||
companion object {
|
||||
init {
|
||||
Utils.init()
|
||||
System.loadLibrary("filament-utils-jni")
|
||||
}
|
||||
private const val TAG = "FilamentValidation"
|
||||
}
|
||||
|
||||
private lateinit var surfaceView: SurfaceView
|
||||
private lateinit var choreographer: Choreographer
|
||||
private lateinit var modelViewer: ModelViewer
|
||||
private lateinit var statusTextView: TextView
|
||||
private lateinit var resultsContainer: LinearLayout
|
||||
private lateinit var inputManager: ValidationInputManager
|
||||
private var currentInput: ValidationInputManager.ValidationInput? = null
|
||||
private lateinit var modeSpinner: Spinner
|
||||
private lateinit var runButton: Button
|
||||
private var resultManager: ValidationResultManager? = null
|
||||
|
||||
private var validationRunner: ValidationRunner? = null
|
||||
|
||||
// Frame callback
|
||||
private val frameScheduler = object : Choreographer.FrameCallback {
|
||||
override fun doFrame(frameTimeNanos: Long) {
|
||||
choreographer.postFrameCallback(this)
|
||||
modelViewer.render(frameTimeNanos)
|
||||
validationRunner?.onFrame(frameTimeNanos)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
// SurfaceView container
|
||||
surfaceView = findViewById(R.id.surface_view)
|
||||
surfaceView.holder.setFixedSize(512, 512)
|
||||
|
||||
statusTextView = findViewById(R.id.status_text)
|
||||
modeSpinner = findViewById(R.id.mode_spinner)
|
||||
runButton = findViewById(R.id.run_button)
|
||||
resultsContainer = findViewById(R.id.results_container)
|
||||
|
||||
// Setup Spinner
|
||||
val modes = arrayOf("Run Validation", "Generate Goldens")
|
||||
val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, modes)
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
modeSpinner.adapter = adapter
|
||||
|
||||
// Setup Run Button
|
||||
runButton.setOnClickListener {
|
||||
currentInput?.let { input ->
|
||||
val generateGoldens = modeSpinner.selectedItemPosition == 1
|
||||
val newInput = input.copy(generateGoldens = generateGoldens)
|
||||
startValidation(newInput)
|
||||
}
|
||||
}
|
||||
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
|
||||
choreographer = Choreographer.getInstance()
|
||||
modelViewer = ModelViewer(surfaceView)
|
||||
inputManager = ValidationInputManager(this)
|
||||
|
||||
// Initialize IBL
|
||||
createIndirectLight()
|
||||
|
||||
handleIntent()
|
||||
}
|
||||
|
||||
private fun createIndirectLight() {
|
||||
try {
|
||||
val engine = modelViewer.engine
|
||||
val scene = modelViewer.scene
|
||||
val iblName = "default_env"
|
||||
|
||||
fun readAsset(path: String): ByteBuffer {
|
||||
val input = assets.open(path)
|
||||
val bytes = input.readBytes()
|
||||
return ByteBuffer.wrap(bytes)
|
||||
}
|
||||
|
||||
readAsset("envs/$iblName/${iblName}_ibl.ktx").let {
|
||||
val bundle = KTX1Loader.createIndirectLight(engine, it)
|
||||
scene.indirectLight = bundle.indirectLight
|
||||
modelViewer.indirectLightCubemap = bundle.cubemap
|
||||
scene.indirectLight!!.intensity = 30_000.0f
|
||||
}
|
||||
|
||||
readAsset("envs/$iblName/${iblName}_skybox.ktx").let {
|
||||
val bundle = KTX1Loader.createSkybox(engine, it)
|
||||
scene.skybox = bundle.skybox
|
||||
modelViewer.skyboxCubemap = bundle.cubemap
|
||||
}
|
||||
Log.i(TAG, "IBL loaded successfully")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to load IBL", e)
|
||||
statusTextView.text = "Warning: Failed to load IBL"
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleIntent() {
|
||||
statusTextView.text = "Resolving configuration..."
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
try {
|
||||
val input = inputManager.resolveConfig(intent)
|
||||
currentInput = input
|
||||
|
||||
// Sync spinner with intent
|
||||
modeSpinner.setSelection(if (input.generateGoldens) 1 else 0)
|
||||
|
||||
startValidation(input)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to resolve config", e)
|
||||
statusTextView.text = "Error: ${e.message}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startValidation(input: ValidationInputManager.ValidationInput) {
|
||||
try {
|
||||
resultsContainer.removeAllViews()
|
||||
Log.i(TAG, "Starting validation with config: ${input.config.name}")
|
||||
Log.i(TAG, "Output dir: ${input.outputDir.absolutePath}")
|
||||
|
||||
resultManager = ValidationResultManager(input.outputDir)
|
||||
|
||||
validationRunner = ValidationRunner(this, modelViewer, input.config, resultManager!!)
|
||||
validationRunner?.callback = this
|
||||
validationRunner?.generateGoldens = input.generateGoldens
|
||||
validationRunner?.start()
|
||||
|
||||
// Sync spinner in case it was called programmatically or changed implicitly
|
||||
modeSpinner.setSelection(if (input.generateGoldens) 1 else 0)
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to start validation", e)
|
||||
statusTextView.text = "Error: ${e.message}"
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
choreographer.postFrameCallback(frameScheduler)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
choreographer.removeFrameCallback(frameScheduler)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
choreographer.removeFrameCallback(frameScheduler)
|
||||
}
|
||||
|
||||
private var currentRenderedBitmap: Bitmap? = null
|
||||
private var currentGoldenBitmap: Bitmap? = null
|
||||
private var currentDiffBitmap: Bitmap? = null
|
||||
|
||||
override fun onTestFinished(result: ValidationResult) {
|
||||
runOnUiThread {
|
||||
val status = "Test ${result.testName} finished: ${if(result.passed) "PASS" else "FAIL"}"
|
||||
statusTextView.text = status
|
||||
Log.i(TAG, status)
|
||||
|
||||
// Container for this result
|
||||
val resultContainer = LinearLayout(this)
|
||||
resultContainer.orientation = LinearLayout.VERTICAL
|
||||
resultContainer.setPadding(0, 10, 0, 20)
|
||||
|
||||
// Header
|
||||
val resultView = TextView(this)
|
||||
resultView.text = "${result.testName}: ${if(result.passed) "PASS" else "FAIL"} (Diff: ${result.diffMetric})"
|
||||
resultView.setTextColor(if(result.passed) Color.GREEN else Color.RED)
|
||||
resultView.textSize = 16f
|
||||
resultView.setTypeface(null, android.graphics.Typeface.BOLD)
|
||||
resultContainer.addView(resultView)
|
||||
|
||||
// Images Row
|
||||
val imagesRow = LinearLayout(this)
|
||||
imagesRow.orientation = LinearLayout.HORIZONTAL
|
||||
|
||||
fun addImage(label: String, bitmap: Bitmap?) {
|
||||
if (bitmap != null) {
|
||||
val container = LinearLayout(this)
|
||||
container.orientation = LinearLayout.VERTICAL
|
||||
container.setPadding(0, 0, 10, 0)
|
||||
|
||||
val labelView = TextView(this)
|
||||
labelView.text = label
|
||||
labelView.textSize = 12f
|
||||
container.addView(labelView)
|
||||
|
||||
val iv = ImageView(this)
|
||||
iv.setImageBitmap(bitmap) // Use the same bitmap (or copy if needed, but same is usually fine for UI)
|
||||
iv.layoutParams = LinearLayout.LayoutParams(250, 250) // Smaller thumbnails
|
||||
iv.scaleType = ImageView.ScaleType.FIT_CENTER
|
||||
iv.setBackgroundColor(0xFF404040.toInt())
|
||||
container.addView(iv)
|
||||
|
||||
imagesRow.addView(container)
|
||||
}
|
||||
}
|
||||
|
||||
addImage("Rendered", currentRenderedBitmap)
|
||||
addImage("Golden", currentGoldenBitmap)
|
||||
if (!result.passed) {
|
||||
addImage("Diff", currentDiffBitmap)
|
||||
}
|
||||
|
||||
resultContainer.addView(imagesRow)
|
||||
resultsContainer.addView(resultContainer)
|
||||
|
||||
// Clear current images for next test
|
||||
currentRenderedBitmap = null
|
||||
currentGoldenBitmap = null
|
||||
currentDiffBitmap = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAllTestsFinished() {
|
||||
runOnUiThread {
|
||||
statusTextView.text = "All tests finished!"
|
||||
Log.i(TAG, "All tests finished")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStatusChanged(status: String) {
|
||||
runOnUiThread {
|
||||
statusTextView.text = status
|
||||
}
|
||||
}
|
||||
|
||||
override fun onImageResult(type: String, bitmap: Bitmap) {
|
||||
runOnUiThread {
|
||||
// Update the "live" views
|
||||
when (type) {
|
||||
"Rendered" -> {
|
||||
currentRenderedBitmap = bitmap
|
||||
}
|
||||
"Golden" -> {
|
||||
currentGoldenBitmap = bitmap
|
||||
}
|
||||
"Diff" -> {
|
||||
currentDiffBitmap = bitmap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Scripts for reference:
|
||||
*
|
||||
* generate_goldens.sh:
|
||||
* --------------------
|
||||
* #!/bin/bash
|
||||
* set -e
|
||||
*
|
||||
* # Config path (on device)
|
||||
* CONFIG_PATH=$1
|
||||
* if [ -z "$CONFIG_PATH" ]; then
|
||||
* echo "Usage: $0 <device_config_path>"
|
||||
* echo "Example: $0 /sdcard/Android/data/com.google.android.filament.validation/files/default_test.json"
|
||||
* exit 1
|
||||
* fi
|
||||
*
|
||||
* echo "Starting Golden Generation for $CONFIG_PATH..."
|
||||
* adb shell am force-stop com.google.android.filament.validation
|
||||
* adb shell am start -n com.google.android.filament.validation/.MainActivity \
|
||||
* -e test_config "$CONFIG_PATH" \
|
||||
* --ez generate_goldens true
|
||||
*
|
||||
* echo "Check device or logcat for progress."
|
||||
* echo "adb logcat -s FilamentValidation:I ValidationRunner:I"
|
||||
* echo "To pull results: ./samples/sample-render-validation/pull_goldens.sh"
|
||||
*
|
||||
* pull_goldens.sh:
|
||||
* ----------------
|
||||
* #!/bin/bash
|
||||
* set -e
|
||||
*
|
||||
* # Default destination is local golden directory relative to script
|
||||
* SCRIPT_DIR=$(cd $(dirname $0); pwd)
|
||||
* DEST_DIR=${1:-"$SCRIPT_DIR/golden"}
|
||||
*
|
||||
* echo "Pulling goldens to $DEST_DIR..."
|
||||
* mkdir -p "$DEST_DIR"
|
||||
*
|
||||
* # Path on device
|
||||
* DEVICE_GOLDEN_DIR="/storage/emulated/0/Android/data/com.google.android.filament.validation/files/golden/."
|
||||
*
|
||||
* adb pull "$DEVICE_GOLDEN_DIR" "$DEST_DIR"
|
||||
*
|
||||
* echo "Done."
|
||||
* ls -l "$DEST_DIR"
|
||||
*/
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.validation
|
||||
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
data class RenderTestConfig(
|
||||
val name: String,
|
||||
val backends: List<String>,
|
||||
val models: Map<String, String>, // name -> path
|
||||
val tests: List<TestConfig>
|
||||
)
|
||||
|
||||
data class TestConfig(
|
||||
val name: String,
|
||||
val description: String?,
|
||||
val backends: List<String>,
|
||||
val models: Set<String>,
|
||||
val rendering: JSONObject,
|
||||
val tolerance: JSONObject?
|
||||
)
|
||||
|
||||
|
||||
// See test/renderdiff/FORMAT.md for the full specification matched by this parser.
|
||||
class ConfigParser {
|
||||
companion object {
|
||||
fun parseFromPath(path: String): RenderTestConfig {
|
||||
val file = File(path)
|
||||
val jsonTxt = removeComments(file.readText())
|
||||
val json = JSONObject(jsonTxt)
|
||||
return parseRenderTestConfig(json, file.parentFile)
|
||||
}
|
||||
|
||||
private fun removeComments(json: String): String {
|
||||
return json.lines().joinToString("\n") { it.substringBefore("//") }
|
||||
}
|
||||
|
||||
private fun parseRenderTestConfig(json: JSONObject, baseDir: File?): RenderTestConfig {
|
||||
val name = json.getString("name")
|
||||
val backends = json.getJSONArray("backends").toList<String>()
|
||||
|
||||
val modelSearchPaths = json.optJSONArray("model_search_paths")?.toList<String>() ?: emptyList()
|
||||
val models = mutableMapOf<String, String>()
|
||||
|
||||
baseDir?.let { dir ->
|
||||
modelSearchPaths.forEach { searchPath ->
|
||||
val searchDir = File(dir, searchPath)
|
||||
if (searchDir.exists()) {
|
||||
searchDir.walkTopDown().filter { it.isFile && (it.extension == "glb" || it.extension == "gltf") }.forEach { file ->
|
||||
models[file.nameWithoutExtension] = file.absolutePath
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Explicit models map override
|
||||
val modelsJson = json.optJSONObject("models")
|
||||
if (modelsJson != null) {
|
||||
val keys = modelsJson.keys()
|
||||
while (keys.hasNext()) {
|
||||
val name = keys.next()
|
||||
val path = modelsJson.getString(name)
|
||||
// Resolve path relative to baseDir if not absolute
|
||||
val file = File(path)
|
||||
if (file.isAbsolute) {
|
||||
models[name] = path
|
||||
} else if (baseDir != null) {
|
||||
models[name] = File(baseDir, path).absolutePath
|
||||
} else {
|
||||
models[name] = path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val presetsJson = json.optJSONArray("presets")
|
||||
val presets = mutableMapOf<String, PresetConfig>()
|
||||
if (presetsJson != null) {
|
||||
for (i in 0 until presetsJson.length()) {
|
||||
val p = parsePreset(presetsJson.getJSONObject(i), models.keys)
|
||||
presets[p.name] = p
|
||||
}
|
||||
}
|
||||
|
||||
val testsJson = json.getJSONArray("tests")
|
||||
val tests = mutableListOf<TestConfig>()
|
||||
for (i in 0 until testsJson.length()) {
|
||||
tests.add(parseTestConfig(testsJson.getJSONObject(i), models.keys, presets, backends))
|
||||
}
|
||||
|
||||
return RenderTestConfig(name, backends, models, tests)
|
||||
}
|
||||
|
||||
private fun parsePreset(json: JSONObject, existingModels: Set<String>): PresetConfig {
|
||||
val name = json.getString("name")
|
||||
val rendering = json.getJSONObject("rendering")
|
||||
val models = json.optJSONArray("models")?.toList<String>() ?: emptyList()
|
||||
|
||||
// Validate models
|
||||
models.forEach { if (!existingModels.contains(it)) throw IllegalArgumentException("Model $it not found") }
|
||||
|
||||
val tolerance = json.optJSONObject("tolerance")
|
||||
return PresetConfig(name, rendering, models, tolerance)
|
||||
}
|
||||
|
||||
private fun parseTestConfig(
|
||||
json: JSONObject,
|
||||
existingModels: Set<String>,
|
||||
presets: Map<String, PresetConfig>,
|
||||
defaultBackends: List<String>
|
||||
): TestConfig {
|
||||
val name = json.getString("name")
|
||||
val description = json.optString("description")
|
||||
val backends = json.optJSONArray("backends")?.toList<String>() ?: defaultBackends
|
||||
|
||||
val applyPresets = json.optJSONArray("apply_presets")?.toList<String>() ?: emptyList()
|
||||
|
||||
val rendering = JSONObject()
|
||||
val combinedModels = mutableSetOf<String>()
|
||||
var lastTolerance: JSONObject? = null
|
||||
|
||||
applyPresets.forEach { presetName ->
|
||||
val preset = presets[presetName] ?: throw IllegalArgumentException("Unknown preset $presetName")
|
||||
// Merge rendering (flat copy)
|
||||
val keys = preset.rendering.keys()
|
||||
while(keys.hasNext()) {
|
||||
val k = keys.next()
|
||||
rendering.put(k, preset.rendering.get(k))
|
||||
}
|
||||
combinedModels.addAll(preset.models)
|
||||
if (preset.tolerance != null) lastTolerance = preset.tolerance
|
||||
}
|
||||
|
||||
val testRendering = json.optJSONObject("rendering")
|
||||
if (testRendering != null) {
|
||||
val keys = testRendering.keys()
|
||||
while(keys.hasNext()) {
|
||||
val k = keys.next()
|
||||
rendering.put(k, testRendering.get(k))
|
||||
}
|
||||
}
|
||||
|
||||
val testModels = json.optJSONArray("models")?.toList<String>() ?: emptyList()
|
||||
combinedModels.addAll(testModels)
|
||||
|
||||
// Validate models
|
||||
combinedModels.forEach { if (!existingModels.contains(it)) throw IllegalArgumentException("Model $it not found") }
|
||||
|
||||
val tolerance = json.optJSONObject("tolerance") ?: lastTolerance
|
||||
|
||||
return TestConfig(name, description, backends, combinedModels, rendering, tolerance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class PresetConfig(
|
||||
val name: String,
|
||||
val rendering: JSONObject,
|
||||
val models: List<String>,
|
||||
val tolerance: JSONObject?
|
||||
)
|
||||
|
||||
private inline fun <reified T> JSONArray.toList(): List<T> {
|
||||
val list = mutableListOf<T>()
|
||||
for (i in 0 until length()) {
|
||||
list.add(get(i) as T)
|
||||
}
|
||||
return list
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.validation
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
|
||||
/**
|
||||
* Handles the retrieval and preparation of test configuration and assets.
|
||||
* Supports loading from:
|
||||
* 1. Intent extras (local path or URL)
|
||||
* 2. Default embedded assets (fallback)
|
||||
*/
|
||||
class ValidationInputManager(private val context: Context) {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ValidationInputManager"
|
||||
}
|
||||
|
||||
data class ValidationInput(
|
||||
val config: RenderTestConfig,
|
||||
val outputDir: File,
|
||||
val generateGoldens: Boolean
|
||||
)
|
||||
|
||||
/**
|
||||
* Resolves the test configuration based on the provided intent extras.
|
||||
* This may involve extracting assets or downloading files.
|
||||
*/
|
||||
suspend fun resolveConfig(intent: Intent): ValidationInput = withContext(Dispatchers.IO) {
|
||||
val testConfigPath = intent.getStringExtra("test_config")
|
||||
val urlConfig = intent.getStringExtra("url_config")
|
||||
val urlModelsBase = intent.getStringExtra("url_models_base")
|
||||
val generateGoldens = intent.getBooleanExtra("generate_goldens", false)
|
||||
val outputPath = intent.getStringExtra("output_path")
|
||||
|
||||
val outputDir = if (outputPath != null) {
|
||||
File(outputPath).apply { mkdirs() }
|
||||
} else {
|
||||
File(context.getExternalFilesDir(null), "validation_results").apply { mkdirs() }
|
||||
}
|
||||
|
||||
val config = when {
|
||||
urlConfig != null -> downloadConfig(urlConfig, urlModelsBase)
|
||||
testConfigPath != null -> ConfigParser.parseFromPath(testConfigPath)
|
||||
else -> extractDefaultAssets()
|
||||
}
|
||||
|
||||
return@withContext ValidationInput(config, outputDir, generateGoldens)
|
||||
}
|
||||
|
||||
private suspend fun extractDefaultAssets(): RenderTestConfig {
|
||||
Log.i(TAG, "Extracting default assets...")
|
||||
val filesDir = context.getExternalFilesDir(null) ?: context.filesDir
|
||||
val assetManager = context.assets
|
||||
|
||||
// Copy default_test.json
|
||||
val configDir = File(filesDir, "config")
|
||||
configDir.mkdirs()
|
||||
val configOut = File(configDir, "default_test.json")
|
||||
|
||||
assetManager.open("default_test.json").use { input ->
|
||||
FileOutputStream(configOut).use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
|
||||
// Copy DamagedHelmet.glb
|
||||
val modelsDir = File(filesDir, "models")
|
||||
modelsDir.mkdirs()
|
||||
val modelOut = File(modelsDir, "DamagedHelmet.glb")
|
||||
|
||||
assetManager.open("DamagedHelmet.glb").use { input ->
|
||||
FileOutputStream(modelOut).use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
|
||||
// Update config to point to relative path (standardizing on relative for portability where possible)
|
||||
// or absolute. Here we use relative as per previous logic.
|
||||
val configJson = JSONObject(configOut.readText())
|
||||
val models = configJson.getJSONObject("models")
|
||||
|
||||
// Ensure the default model points to the extracted file
|
||||
// We can use absolute path to be safe since we know where it is now.
|
||||
models.put("DamagedHelmet", modelOut.absolutePath)
|
||||
|
||||
configOut.writeText(configJson.toString(2))
|
||||
|
||||
return ConfigParser.parseFromPath(configOut.absolutePath)
|
||||
}
|
||||
|
||||
private suspend fun downloadConfig(urlConfig: String, urlModelsBase: String?): RenderTestConfig {
|
||||
Log.i(TAG, "Downloading config from $urlConfig")
|
||||
val filesDir = context.getExternalFilesDir(null) ?: context.filesDir
|
||||
val configDir = File(filesDir, "config")
|
||||
configDir.mkdirs()
|
||||
|
||||
val modelsDir = File(filesDir, "models")
|
||||
modelsDir.mkdirs()
|
||||
|
||||
val configName = "downloaded_config.json"
|
||||
val configFile = File(configDir, configName)
|
||||
|
||||
downloadFile(urlConfig, configFile)
|
||||
|
||||
if (urlModelsBase != null) {
|
||||
val configJson = JSONObject(configFile.readText())
|
||||
val models = configJson.optJSONObject("models")
|
||||
if (models != null) {
|
||||
val keys = models.keys()
|
||||
while (keys.hasNext()) {
|
||||
val key = keys.next()
|
||||
val modelPath = models.getString(key)
|
||||
val fileName = File(modelPath).name
|
||||
val modelFile = File(modelsDir, fileName)
|
||||
val modelUrl = "$urlModelsBase/$fileName"
|
||||
|
||||
Log.i(TAG, "Downloading model: $fileName from $modelUrl")
|
||||
downloadFile(modelUrl, modelFile)
|
||||
|
||||
// Update config to point to absolute path
|
||||
models.put(key, modelFile.absolutePath)
|
||||
}
|
||||
configFile.writeText(configJson.toString())
|
||||
}
|
||||
}
|
||||
|
||||
return ConfigParser.parseFromPath(configFile.absolutePath)
|
||||
}
|
||||
|
||||
private fun downloadFile(urlStr: String, destFile: File) {
|
||||
val url = URL(urlStr)
|
||||
val connection = url.openConnection() as HttpURLConnection
|
||||
connection.connect()
|
||||
|
||||
if (connection.responseCode != HttpURLConnection.HTTP_OK) {
|
||||
throw Exception("Server returned HTTP ${connection.responseCode} for $urlStr")
|
||||
}
|
||||
|
||||
destFile.parentFile?.mkdirs()
|
||||
connection.inputStream.use { input ->
|
||||
FileOutputStream(destFile).use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.validation
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.util.Log
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
data class ValidationResult(
|
||||
val testName: String,
|
||||
val passed: Boolean,
|
||||
val diffMetric: Float = 0f
|
||||
)
|
||||
|
||||
class ValidationResultManager(private val outputDir: File) {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ValidationResultManager"
|
||||
}
|
||||
|
||||
private val results = mutableListOf<ValidationResult>()
|
||||
|
||||
init {
|
||||
if (!outputDir.exists()) {
|
||||
outputDir.mkdirs()
|
||||
}
|
||||
}
|
||||
|
||||
fun addResult(result: ValidationResult) {
|
||||
results.add(result)
|
||||
}
|
||||
|
||||
fun saveImage(name: String, bitmap: Bitmap) {
|
||||
val file = File(outputDir, "$name.png")
|
||||
try {
|
||||
FileOutputStream(file).use { out ->
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to save image $name", e)
|
||||
}
|
||||
}
|
||||
|
||||
fun getOutputDir(): File {
|
||||
return outputDir
|
||||
}
|
||||
|
||||
fun finalizeResults(): File? {
|
||||
// Write results JSON
|
||||
writeResultsJson()
|
||||
|
||||
// Zip results
|
||||
val zipFile = File(outputDir, "results.zip")
|
||||
try {
|
||||
ZipOutputStream(FileOutputStream(zipFile)).use { zos ->
|
||||
outputDir.walkTopDown().filter { it.isFile && it.name != "results.zip" }.forEach { file ->
|
||||
val entryName = file.relativeTo(outputDir).path
|
||||
zos.putNextEntry(ZipEntry(entryName))
|
||||
file.inputStream().use { it.copyTo(zos) }
|
||||
zos.closeEntry()
|
||||
}
|
||||
}
|
||||
Log.i(TAG, "Zipped results to ${zipFile.absolutePath}")
|
||||
return zipFile
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to zip results", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun writeResultsJson() {
|
||||
val jsonArray = JSONArray()
|
||||
for (result in results) {
|
||||
val jsonObject = JSONObject()
|
||||
jsonObject.put("test_name", result.testName)
|
||||
jsonObject.put("passed", result.passed)
|
||||
jsonObject.put("diff_metric", result.diffMetric)
|
||||
jsonArray.put(jsonObject)
|
||||
}
|
||||
|
||||
val jsonFile = File(outputDir, "results.json")
|
||||
try {
|
||||
FileOutputStream(jsonFile).use { out ->
|
||||
out.write(jsonArray.toString(4).toByteArray())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to write results.json", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
* Copyright (C) 2026 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.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.validation
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.util.Log
|
||||
import com.google.android.filament.utils.AutomationEngine
|
||||
import com.google.android.filament.utils.ImageDiff
|
||||
import com.google.android.filament.utils.ModelViewer
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
class ValidationRunner(
|
||||
private val context: Context,
|
||||
private val modelViewer: ModelViewer,
|
||||
private val config: RenderTestConfig,
|
||||
private val resultManager: ValidationResultManager
|
||||
) {
|
||||
|
||||
private var currentState = State.IDLE
|
||||
private var currentTestIndex = 0
|
||||
private var currentModelIndex = 0
|
||||
private var currentEngine: AutomationEngine? = null
|
||||
private var currentTestConfig: TestConfig? = null
|
||||
private var currentModelName: String? = null
|
||||
|
||||
private var loadStartFence: com.google.android.filament.Fence? = null
|
||||
private var loadStartTime = 0L
|
||||
private var frameCounter = 0
|
||||
|
||||
enum class State {
|
||||
IDLE,
|
||||
LOADING_MODEL,
|
||||
WAITING_FOR_FENCE,
|
||||
WAITING_FOR_RESOURCES,
|
||||
WARMUP,
|
||||
RUNNING_TEST,
|
||||
COMPARING
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun onTestFinished(result: ValidationResult)
|
||||
fun onAllTestsFinished()
|
||||
fun onStatusChanged(status: String)
|
||||
fun onImageResult(type: String, bitmap: Bitmap)
|
||||
}
|
||||
|
||||
var callback: Callback? = null
|
||||
var generateGoldens: Boolean = false
|
||||
|
||||
fun start() {
|
||||
if (config.tests.isEmpty()) {
|
||||
callback?.onAllTestsFinished()
|
||||
return
|
||||
}
|
||||
currentTestIndex = 0
|
||||
currentModelIndex = 0
|
||||
startTest(config.tests[0])
|
||||
}
|
||||
|
||||
private fun startTest(test: TestConfig) {
|
||||
currentTestConfig = test
|
||||
if (test.models.isEmpty()) {
|
||||
nextTest()
|
||||
return
|
||||
}
|
||||
currentModelIndex = 0
|
||||
startModel(test.models.elementAt(0))
|
||||
}
|
||||
|
||||
private fun startModel(modelName: String) {
|
||||
currentModelName = modelName
|
||||
val modelPath = config.models[modelName]
|
||||
if (modelPath == null) {
|
||||
Log.e("ValidationRunner", "Model $modelName not found")
|
||||
nextModel()
|
||||
return
|
||||
}
|
||||
|
||||
currentState = State.LOADING_MODEL
|
||||
callback?.onStatusChanged("Loading $modelName for ${currentTestConfig?.name}")
|
||||
|
||||
// Load model on main thread (required by ModelViewer)
|
||||
loadModel(modelPath)
|
||||
}
|
||||
|
||||
private fun loadModel(path: String) {
|
||||
// Assume called on Main Thread
|
||||
modelViewer.destroyModel()
|
||||
try {
|
||||
Log.i("ValidationRunner", "Reading model file: $path")
|
||||
val bytes = File(path).readBytes()
|
||||
Log.i("ValidationRunner", "Loading GLB buffer... (${bytes.size} bytes)")
|
||||
val buffer = ByteBuffer.wrap(bytes)
|
||||
modelViewer.loadModelGlb(buffer)
|
||||
Log.i("ValidationRunner", "Model loaded. initializing fence.")
|
||||
modelViewer.transformToUnitCube()
|
||||
loadStartFence = modelViewer.engine.createFence()
|
||||
loadStartTime = System.nanoTime()
|
||||
currentState = State.WAITING_FOR_FENCE
|
||||
frameCounter = 0 // Reset for fence timeout tracking
|
||||
Log.i("ValidationRunner", "State set to WAITING_FOR_FENCE")
|
||||
} catch (e: Exception) {
|
||||
Log.e("ValidationRunner", "Failed to load $path", e)
|
||||
nextModel()
|
||||
}
|
||||
}
|
||||
|
||||
fun onFrame(frameTimeNanos: Long) {
|
||||
if (frameCounter % 60 == 0) {
|
||||
Log.i("ValidationRunner", "onFrame: $currentState (frame: $frameCounter)")
|
||||
}
|
||||
|
||||
when (currentState) {
|
||||
State.IDLE -> {}
|
||||
State.WAITING_FOR_FENCE -> {
|
||||
frameCounter++
|
||||
if (frameCounter > 600) {
|
||||
Log.w("ValidationRunner", "Fence timed out after 600 frames! Forcing proceed.")
|
||||
modelViewer.engine.destroyFence(loadStartFence!!)
|
||||
loadStartFence = null
|
||||
currentState = State.WAITING_FOR_RESOURCES
|
||||
return
|
||||
}
|
||||
|
||||
loadStartFence?.let { fence ->
|
||||
if (fence.wait(com.google.android.filament.Fence.Mode.FLUSH, 0) ==
|
||||
com.google.android.filament.Fence.FenceStatus.CONDITION_SATISFIED) {
|
||||
modelViewer.engine.destroyFence(fence)
|
||||
loadStartFence = null
|
||||
|
||||
// Compile materials (simplified)
|
||||
modelViewer.scene.forEach { entity ->
|
||||
// ... existing material compilation logic ...
|
||||
}
|
||||
|
||||
currentState = State.WAITING_FOR_RESOURCES
|
||||
}
|
||||
}
|
||||
}
|
||||
State.WAITING_FOR_RESOURCES -> {
|
||||
val progress = modelViewer.progress
|
||||
if (progress >= 1.0f) {
|
||||
Log.i("ValidationRunner", "Resources loaded. Starting warmup.")
|
||||
frameCounter = 0
|
||||
currentState = State.WARMUP
|
||||
}
|
||||
}
|
||||
State.WARMUP -> {
|
||||
frameCounter++
|
||||
if (frameCounter > 5) { // 5 frames warmup
|
||||
startAutomation()
|
||||
}
|
||||
}
|
||||
State.RUNNING_TEST -> {
|
||||
// Log.i("ValidationRunner", "Running test...")
|
||||
currentEngine?.let { engine ->
|
||||
val content = AutomationEngine.ViewerContent()
|
||||
content.view = modelViewer.view
|
||||
content.renderer = modelViewer.renderer
|
||||
content.scene = modelViewer.scene
|
||||
content.lightManager = modelViewer.engine.lightManager
|
||||
|
||||
// Tick
|
||||
val deltaTime = 1.0f / 60.0f
|
||||
engine.tick(modelViewer.engine, content, deltaTime)
|
||||
|
||||
frameCounter++
|
||||
if (engine.shouldClose()) {
|
||||
Log.i("ValidationRunner", "Finishing test (frames: $frameCounter)")
|
||||
// Test finished (for this spec)
|
||||
currentState = State.COMPARING
|
||||
captureAndCompare()
|
||||
}
|
||||
}
|
||||
}
|
||||
State.COMPARING -> {} // Busy
|
||||
State.LOADING_MODEL -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAutomation() {
|
||||
val test = currentTestConfig!!
|
||||
val specJson = JSONObject()
|
||||
specJson.put("name", test.name)
|
||||
specJson.put("base", test.rendering)
|
||||
val fullSpec = "[${specJson.toString()}]"
|
||||
|
||||
currentEngine = AutomationEngine(fullSpec)
|
||||
val options = AutomationEngine.Options()
|
||||
options.sleepDuration = 0.0f // Minimal sleep, let frames drive it
|
||||
options.minFrameCount = 5 // Ensure some frames pass
|
||||
currentEngine?.setOptions(options)
|
||||
|
||||
// Use batch mode to ensure shouldClose() works reliably
|
||||
currentEngine?.startBatchMode()
|
||||
currentEngine?.signalBatchMode() // Start immediately
|
||||
|
||||
frameCounter = 0
|
||||
currentState = State.RUNNING_TEST
|
||||
}
|
||||
|
||||
|
||||
private fun captureAndCompare() {
|
||||
callback?.onStatusChanged("Comparing ${currentTestConfig?.name}...")
|
||||
modelViewer.debugGetNextFrameCallback { bitmap ->
|
||||
compareCapturedImage(bitmap)
|
||||
}
|
||||
}
|
||||
|
||||
private fun compareCapturedImage(bitmap: Bitmap) {
|
||||
val testName = currentTestConfig!!.name
|
||||
val modelName = currentModelName!!
|
||||
val backend = currentTestConfig?.backends?.firstOrNull() ?: "opengl"
|
||||
val testFullName = "${testName}.${backend}.${modelName}"
|
||||
|
||||
// Golden path
|
||||
val modelFile = File(config.models.get(modelName)!!)
|
||||
val goldenFile = modelFile.parentFile!!.parentFile!!.resolve("golden/${testFullName}.png")
|
||||
|
||||
Thread {
|
||||
try {
|
||||
val flipped = bitmap
|
||||
|
||||
callback?.onImageResult("Rendered", flipped)
|
||||
|
||||
var passed = false
|
||||
var diffMetric = 0f
|
||||
|
||||
if (generateGoldens) {
|
||||
goldenFile.parentFile?.mkdirs()
|
||||
FileOutputStream(goldenFile).use { out ->
|
||||
flipped.compress(Bitmap.CompressFormat.PNG, 100, out)
|
||||
}
|
||||
passed = true // Generating goldens always passes if successful
|
||||
callback?.onStatusChanged("Golden generated")
|
||||
} else {
|
||||
if (goldenFile.exists()) {
|
||||
val golden = android.graphics.BitmapFactory.decodeFile(goldenFile.absolutePath)
|
||||
if (golden != null) {
|
||||
callback?.onImageResult("Golden", golden)
|
||||
|
||||
val tol = currentTestConfig?.tolerance ?: org.json.JSONObject()
|
||||
val tolJson = tol.toString()
|
||||
val result = ImageDiff.compare(golden, flipped, tolJson, null)
|
||||
passed = (result.status == ImageDiff.Result.Status.PASSED)
|
||||
diffMetric = result.failingPixelCount.toFloat()
|
||||
|
||||
if (!passed) {
|
||||
if (result.diffImage != null) {
|
||||
callback?.onImageResult("Diff", result.diffImage!!)
|
||||
resultManager.saveImage("${testFullName}_diff", result.diffImage!!)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
callback?.onStatusChanged("Failed to load golden")
|
||||
}
|
||||
} else {
|
||||
Log.w("ValidationRunner", "Golden not found: ${goldenFile.absolutePath}")
|
||||
callback?.onStatusChanged("Golden not found")
|
||||
}
|
||||
}
|
||||
|
||||
// Save output
|
||||
resultManager.saveImage(testFullName, flipped)
|
||||
|
||||
val result = ValidationResult(testFullName, passed, diffMetric)
|
||||
resultManager.addResult(result)
|
||||
callback?.onTestFinished(result)
|
||||
|
||||
android.os.Handler(android.os.Looper.getMainLooper()).post {
|
||||
nextModel()
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e("ValidationRunner", "Comparison failed", e)
|
||||
android.os.Handler(android.os.Looper.getMainLooper()).post { nextModel() }
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
private fun nextModel() {
|
||||
currentModelIndex++
|
||||
if (currentTestConfig != null && currentModelIndex < currentTestConfig!!.models.size) {
|
||||
startModel(currentTestConfig!!.models.elementAt(currentModelIndex))
|
||||
} else {
|
||||
nextTest()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun nextTest() {
|
||||
currentTestIndex++
|
||||
if (currentTestIndex < config.tests.size) {
|
||||
startTest(config.tests[currentTestIndex])
|
||||
} else {
|
||||
currentState = State.IDLE
|
||||
resultManager.finalizeResults()
|
||||
callback?.onAllTestsFinished()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/surface_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<SurfaceView
|
||||
android:id="@+id/surface_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</FrameLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/surface_container"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Initializing..."
|
||||
android:textSize="14sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingBottom="10dp">
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/mode_spinner"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/run_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Run" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Test Results"
|
||||
android:textSize="18sp"
|
||||
android:paddingTop="20dp"
|
||||
android:paddingBottom="10dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/results_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" />
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="5dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/image_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Label" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image_view"
|
||||
android:layout_width="300px"
|
||||
android:layout_height="300px"
|
||||
android:scaleType="fitCenter"
|
||||
android:background="#404040" />
|
||||
</LinearLayout>
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'filament-tools-plugin'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
@@ -10,7 +10,7 @@ kotlin {
|
||||
jvmToolchain(versions.jdk)
|
||||
}
|
||||
|
||||
filamentTools {
|
||||
filament {
|
||||
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
||||
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
||||
}
|
||||
|
||||
12
android/samples/sample-texture-target/.gitignore
vendored
Normal file
12
android/samples/sample-texture-target/.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
/.idea/libraries
|
||||
/.idea/caches
|
||||
/.idea/gradle.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
/src/main/assets
|
||||
.externalNativeBuild
|
||||
53
android/samples/sample-texture-target/build.gradle
Normal file
53
android/samples/sample-texture-target/build.gradle
Normal file
@@ -0,0 +1,53 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(versions.jdk)
|
||||
}
|
||||
|
||||
filament {
|
||||
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
||||
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
||||
}
|
||||
|
||||
clean.doFirst {
|
||||
delete "src/main/assets"
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.google.android.filament.texturetarget'
|
||||
|
||||
compileSdkVersion versions.compileSdk
|
||||
defaultConfig {
|
||||
applicationId "com.google.android.filament.texturetarget"
|
||||
minSdkVersion 26
|
||||
targetSdkVersion versions.targetSdk
|
||||
}
|
||||
|
||||
// NOTE: This is a workaround required because the AGP task collectReleaseDependencies
|
||||
// is not configuration-cache friendly yet; this is only useful for Play publication
|
||||
dependenciesInfo {
|
||||
includeInApk = false
|
||||
}
|
||||
|
||||
// We use the .filamat extension for materials compiled with matc
|
||||
// Telling aapt to not compress them allows to load them efficiently
|
||||
aaptOptions {
|
||||
noCompress 'filamat', 'ktx'
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility versions.jdk
|
||||
targetCompatibility versions.jdk
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation deps.kotlin
|
||||
implementation project(':filament-android')
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:exported="true"
|
||||
android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,449 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.texturetarget
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.app.Activity
|
||||
import android.hardware.HardwareBuffer
|
||||
import android.opengl.Matrix
|
||||
import android.os.Bundle
|
||||
import android.view.Choreographer
|
||||
import android.view.Surface
|
||||
import android.view.SurfaceView
|
||||
import android.view.animation.LinearInterpolator
|
||||
import com.google.android.filament.*
|
||||
import com.google.android.filament.RenderableManager.PrimitiveType
|
||||
import com.google.android.filament.VertexBuffer.AttributeType
|
||||
import com.google.android.filament.VertexBuffer.VertexAttribute
|
||||
import com.google.android.filament.android.DisplayHelper
|
||||
import com.google.android.filament.android.FilamentHelper
|
||||
import com.google.android.filament.android.UiHelper
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.nio.channels.Channels
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
class MainActivity : Activity() {
|
||||
companion object {
|
||||
init {
|
||||
Filament.init()
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var surfaceView: SurfaceView
|
||||
private lateinit var uiHelper: UiHelper
|
||||
private lateinit var displayHelper: DisplayHelper
|
||||
private lateinit var choreographer: Choreographer
|
||||
|
||||
private lateinit var engine: Engine
|
||||
private lateinit var renderer: Renderer
|
||||
private lateinit var scene: Scene
|
||||
private lateinit var view: View
|
||||
private lateinit var camera: Camera
|
||||
|
||||
private lateinit var triangleMaterial: Material
|
||||
private lateinit var texturedMaterial: Material
|
||||
private lateinit var triangleVertexBuffer: VertexBuffer
|
||||
private lateinit var triangleIndexBuffer: IndexBuffer
|
||||
private lateinit var quadVertexBuffer: VertexBuffer
|
||||
private lateinit var quadIndexBuffer: IndexBuffer
|
||||
|
||||
@Entity private var triangleRenderable = 0
|
||||
@Entity private var quadRenderable = 0
|
||||
|
||||
private var swapChain: SwapChain? = null
|
||||
|
||||
private val frameScheduler = FrameCallback()
|
||||
private val animator = ValueAnimator.ofFloat(0.0f, 360.0f)
|
||||
|
||||
private var hardwareBuffer: HardwareBuffer? = null
|
||||
private var texture: Texture? = null
|
||||
private var renderTarget: RenderTarget? = null
|
||||
|
||||
private var useExternalTexture = true
|
||||
|
||||
private lateinit var offscreenView: View
|
||||
private lateinit var offscreenCamera: Camera
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
// To use set this flag with adb, run
|
||||
// adb shell am start -n com.google.android.filament.texturetarget/.MainActivity --ez useExternalTexture false
|
||||
useExternalTexture = intent.getBooleanExtra("useExternalTexture", true)
|
||||
surfaceView = SurfaceView(this)
|
||||
setContentView(surfaceView)
|
||||
choreographer = Choreographer.getInstance()
|
||||
displayHelper = DisplayHelper(this)
|
||||
setupSurfaceView()
|
||||
setupFilament()
|
||||
setupView()
|
||||
setupScene()
|
||||
}
|
||||
|
||||
private fun setupSurfaceView() {
|
||||
uiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK)
|
||||
uiHelper.renderCallback = SurfaceCallback()
|
||||
uiHelper.attachTo(surfaceView)
|
||||
}
|
||||
|
||||
private fun setupFilament() {
|
||||
engine = Engine.create()
|
||||
renderer = engine.createRenderer()
|
||||
scene = engine.createScene()
|
||||
view = engine.createView()
|
||||
camera = engine.createCamera(engine.entityManager.create())
|
||||
offscreenView = engine.createView()
|
||||
offscreenCamera = engine.createCamera(engine.entityManager.create())
|
||||
}
|
||||
|
||||
private fun setupView() {
|
||||
scene.skybox = Skybox.Builder()
|
||||
.priority(0)
|
||||
.color(0.0f, 0.0f, 1.0f, 1.0f).build(engine)
|
||||
|
||||
// This is the view that will be drawn on screen.
|
||||
view.camera = camera
|
||||
view.scene = scene
|
||||
view.isPostProcessingEnabled = false
|
||||
|
||||
// This is the view that will be rendered off-screen.
|
||||
offscreenView.camera = offscreenCamera
|
||||
offscreenView.scene = scene
|
||||
offscreenView.isPostProcessingEnabled = false
|
||||
}
|
||||
|
||||
private fun setupScene() {
|
||||
loadMaterials()
|
||||
createTriangleMesh()
|
||||
createQuadMesh()
|
||||
|
||||
// layer 1: skybox
|
||||
// layer 2: triangle
|
||||
// layer 3: quad
|
||||
|
||||
triangleMaterial.defaultInstance.cullingMode = Material.CullingMode.NONE;
|
||||
texturedMaterial.defaultInstance.cullingMode = Material.CullingMode.NONE;
|
||||
|
||||
// The triangle is a regular renderable.
|
||||
triangleRenderable = EntityManager.get().create()
|
||||
RenderableManager.Builder(1)
|
||||
.geometry(0, PrimitiveType.TRIANGLES, triangleVertexBuffer, triangleIndexBuffer, 0, 3)
|
||||
.material(0, triangleMaterial.defaultInstance)
|
||||
.culling(false)
|
||||
.castShadows(false)
|
||||
.receiveShadows(false)
|
||||
.layerMask(7, 2)
|
||||
.build(engine, triangleRenderable)
|
||||
|
||||
// The quad is a regular renderable.
|
||||
quadRenderable = EntityManager.get().create()
|
||||
RenderableManager.Builder(1)
|
||||
.geometry(0, PrimitiveType.TRIANGLES, quadVertexBuffer, quadIndexBuffer, 0, 6)
|
||||
.material(0, texturedMaterial.defaultInstance)
|
||||
.culling(false)
|
||||
.castShadows(false)
|
||||
.receiveShadows(false)
|
||||
.layerMask(7, 4)
|
||||
.build(engine, quadRenderable)
|
||||
|
||||
// We only want to render the triangle in the offscreen view.
|
||||
offscreenView.setVisibleLayers(7, 3) // render skybox + triangle
|
||||
|
||||
// We only want to render the quad in the on-screen view.
|
||||
view.setVisibleLayers(7, 4) // render quad only
|
||||
|
||||
scene.addEntity(triangleRenderable)
|
||||
scene.addEntity(quadRenderable)
|
||||
|
||||
startAnimation()
|
||||
}
|
||||
|
||||
private fun loadMaterials() {
|
||||
readUncompressedAsset("materials/baked_color.filamat").let {
|
||||
triangleMaterial = Material.Builder().payload(it, it.remaining()).build(engine)
|
||||
}
|
||||
|
||||
if (useExternalTexture) {
|
||||
readUncompressedAsset("materials/texturedExternal.filamat").let {
|
||||
texturedMaterial = Material.Builder().payload(it, it.remaining()).build(engine)
|
||||
}
|
||||
} else {
|
||||
readUncompressedAsset("materials/textured.filamat").let {
|
||||
texturedMaterial = Material.Builder().payload(it, it.remaining()).build(engine)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTriangleMesh() {
|
||||
val intSize = 4
|
||||
val floatSize = 4
|
||||
val shortSize = 2
|
||||
val vertexSize = 3 * floatSize + intSize
|
||||
|
||||
data class Vertex(val x: Float, val y: Float, val z: Float, val color: Int)
|
||||
fun ByteBuffer.put(v: Vertex): ByteBuffer {
|
||||
putFloat(v.x)
|
||||
putFloat(v.y)
|
||||
putFloat(v.z)
|
||||
putInt(v.color)
|
||||
return this
|
||||
}
|
||||
|
||||
val vertexCount = 3
|
||||
val a1 = PI * 2.0 / 3.0
|
||||
val a2 = PI * 4.0 / 3.0
|
||||
|
||||
val vertexData = ByteBuffer.allocate(vertexCount * vertexSize)
|
||||
.order(ByteOrder.nativeOrder())
|
||||
.put(Vertex(1.0f, 0.0f, 0.0f, 0xffff0000.toInt()))
|
||||
.put(Vertex(cos(a1).toFloat(), sin(a1).toFloat(), 0.0f, 0xff00ff00.toInt()))
|
||||
.put(Vertex(cos(a2).toFloat(), sin(a2).toFloat(), 0.0f, 0xff0000ff.toInt()))
|
||||
.flip()
|
||||
|
||||
triangleVertexBuffer = VertexBuffer.Builder()
|
||||
.bufferCount(1)
|
||||
.vertexCount(vertexCount)
|
||||
.attribute(VertexAttribute.POSITION, 0, AttributeType.FLOAT3, 0, vertexSize)
|
||||
.attribute(VertexAttribute.COLOR, 0, AttributeType.UBYTE4, 3 * floatSize, vertexSize)
|
||||
.normalized(VertexAttribute.COLOR)
|
||||
.build(engine)
|
||||
triangleVertexBuffer.setBufferAt(engine, 0, vertexData)
|
||||
|
||||
val indexData = ByteBuffer.allocate(vertexCount * shortSize)
|
||||
.order(ByteOrder.nativeOrder())
|
||||
.putShort(0)
|
||||
.putShort(1)
|
||||
.putShort(2)
|
||||
.flip()
|
||||
|
||||
triangleIndexBuffer = IndexBuffer.Builder()
|
||||
.indexCount(3)
|
||||
.bufferType(IndexBuffer.Builder.IndexType.USHORT)
|
||||
.build(engine)
|
||||
triangleIndexBuffer.setBuffer(engine, indexData)
|
||||
}
|
||||
|
||||
private fun createQuadMesh() {
|
||||
val floatSize = 4
|
||||
val shortSize = 2
|
||||
val vertexSize = (2 * floatSize) + (2 * floatSize) // position + UV
|
||||
|
||||
data class Vertex(val x: Float, val y: Float, val u: Float, val v: Float)
|
||||
fun ByteBuffer.put(v: Vertex): ByteBuffer {
|
||||
putFloat(v.x)
|
||||
putFloat(v.y)
|
||||
putFloat(v.u)
|
||||
putFloat(v.v)
|
||||
return this
|
||||
}
|
||||
|
||||
val vertexCount = 4
|
||||
val vertexData = ByteBuffer.allocate(vertexCount * vertexSize)
|
||||
.order(ByteOrder.nativeOrder())
|
||||
.put(Vertex(-1.0f, -1.0f, 0.0f, 0.0f))
|
||||
.put(Vertex( 1.0f, -1.0f, 1.0f, 0.0f))
|
||||
.put(Vertex( 1.0f, 1.0f, 1.0f, 1.0f))
|
||||
.put(Vertex(-1.0f, 1.0f, 0.0f, 1.0f))
|
||||
.flip()
|
||||
|
||||
quadVertexBuffer = VertexBuffer.Builder()
|
||||
.bufferCount(1)
|
||||
.vertexCount(vertexCount)
|
||||
.attribute(VertexAttribute.POSITION, 0, AttributeType.FLOAT2, 0, vertexSize)
|
||||
.attribute(VertexAttribute.UV0, 0, AttributeType.FLOAT2, 2 * floatSize, vertexSize)
|
||||
.build(engine)
|
||||
quadVertexBuffer.setBufferAt(engine, 0, vertexData)
|
||||
|
||||
val indexData = ByteBuffer.allocate(6 * shortSize)
|
||||
.order(ByteOrder.nativeOrder())
|
||||
.putShort(0).putShort(1).putShort(2)
|
||||
.putShort(0).putShort(2).putShort(3)
|
||||
.flip()
|
||||
|
||||
quadIndexBuffer = IndexBuffer.Builder()
|
||||
.indexCount(6)
|
||||
.bufferType(IndexBuffer.Builder.IndexType.USHORT)
|
||||
.build(engine)
|
||||
quadIndexBuffer.setBuffer(engine, indexData)
|
||||
}
|
||||
|
||||
private fun startAnimation() {
|
||||
animator.interpolator = LinearInterpolator()
|
||||
animator.duration = 4000
|
||||
animator.repeatMode = ValueAnimator.RESTART
|
||||
animator.repeatCount = ValueAnimator.INFINITE
|
||||
animator.addUpdateListener { a ->
|
||||
val transformMatrix = FloatArray(16)
|
||||
Matrix.setRotateM(transformMatrix, 0, -(a.animatedValue as Float), 0.0f, 0.0f, 1.0f)
|
||||
val tcm = engine.transformManager
|
||||
tcm.setTransform(tcm.getInstance(triangleRenderable), transformMatrix)
|
||||
}
|
||||
animator.start()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
choreographer.postFrameCallback(frameScheduler)
|
||||
animator.start()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
choreographer.removeFrameCallback(frameScheduler)
|
||||
animator.cancel()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
choreographer.removeFrameCallback(frameScheduler)
|
||||
animator.cancel()
|
||||
uiHelper.detach()
|
||||
|
||||
// Destroy all renderables.
|
||||
scene.remove(triangleRenderable)
|
||||
scene.remove(quadRenderable)
|
||||
|
||||
// Destroy all resources.
|
||||
engine.destroyEntity(triangleRenderable)
|
||||
engine.destroyEntity(quadRenderable)
|
||||
engine.destroyRenderer(renderer)
|
||||
engine.destroyVertexBuffer(triangleVertexBuffer)
|
||||
engine.destroyIndexBuffer(triangleIndexBuffer)
|
||||
engine.destroyVertexBuffer(quadVertexBuffer)
|
||||
engine.destroyIndexBuffer(quadIndexBuffer)
|
||||
engine.destroyMaterial(triangleMaterial)
|
||||
engine.destroyMaterial(texturedMaterial)
|
||||
engine.destroyView(view)
|
||||
engine.destroyView(offscreenView)
|
||||
engine.destroyScene(scene)
|
||||
engine.destroyCameraComponent(camera.entity)
|
||||
engine.destroyCameraComponent(offscreenCamera.entity)
|
||||
renderTarget?.let { engine.destroyRenderTarget(it) }
|
||||
texture?.let { engine.destroyTexture(it) }
|
||||
hardwareBuffer?.close()
|
||||
|
||||
val entityManager = EntityManager.get()
|
||||
entityManager.destroy(triangleRenderable)
|
||||
entityManager.destroy(quadRenderable)
|
||||
entityManager.destroy(camera.entity)
|
||||
entityManager.destroy(offscreenCamera.entity)
|
||||
|
||||
engine.destroy()
|
||||
}
|
||||
|
||||
inner class FrameCallback : Choreographer.FrameCallback {
|
||||
override fun doFrame(frameTimeNanos: Long) {
|
||||
choreographer.postFrameCallback(this)
|
||||
if (uiHelper.isReadyToRender) {
|
||||
if (renderer.beginFrame(swapChain!!, frameTimeNanos)) {
|
||||
// Render the triangle to the texture.
|
||||
renderer.render(offscreenView)
|
||||
// Render the quad to the screen.
|
||||
renderer.render(view)
|
||||
renderer.endFrame()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner class SurfaceCallback : UiHelper.RendererCallback {
|
||||
override fun onNativeWindowChanged(surface: Surface) {
|
||||
swapChain?.let { engine.destroySwapChain(it) }
|
||||
swapChain = engine.createSwapChain(surface, uiHelper.swapChainFlags)
|
||||
displayHelper.attach(renderer, surfaceView.display)
|
||||
}
|
||||
|
||||
override fun onDetachedFromSurface() {
|
||||
displayHelper.detach()
|
||||
swapChain?.let {
|
||||
engine.destroySwapChain(it)
|
||||
engine.flushAndWait()
|
||||
swapChain = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResized(width: Int, height: Int) {
|
||||
// On-screen camera
|
||||
val zoom = 1.0
|
||||
val aspect = width.toDouble() / height.toDouble()
|
||||
camera.setProjection(Camera.Projection.ORTHO, -aspect * zoom, aspect * zoom, -zoom, zoom, 0.0, 10.0)
|
||||
view.viewport = Viewport(0, 0, width, height)
|
||||
|
||||
// Off-screen camera
|
||||
val offscreenZoom = 1.5
|
||||
offscreenCamera.setProjection(Camera.Projection.ORTHO,
|
||||
-aspect * offscreenZoom, aspect * offscreenZoom,
|
||||
-offscreenZoom, offscreenZoom, 0.0, 10.0)
|
||||
offscreenView.viewport = Viewport(0, 0, width, height)
|
||||
|
||||
// If we have a render target, destroy it.
|
||||
renderTarget?.let { engine.destroyRenderTarget(it) }
|
||||
texture?.let { engine.destroyTexture(it) }
|
||||
hardwareBuffer?.close()
|
||||
|
||||
if (useExternalTexture) {
|
||||
// Create a new render target.
|
||||
hardwareBuffer = HardwareBuffer.create(width, height,
|
||||
HardwareBuffer.RGBA_8888, 1,
|
||||
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT)
|
||||
|
||||
texture = Texture.Builder()
|
||||
.width(width)
|
||||
.height(height)
|
||||
.usage(Texture.Usage.COLOR_ATTACHMENT or Texture.Usage.SAMPLEABLE)
|
||||
.sampler(Texture.Sampler.SAMPLER_EXTERNAL)
|
||||
.format(Texture.InternalFormat.RGBA8)
|
||||
.external()
|
||||
.build(engine)
|
||||
|
||||
texture!!.setExternalImage(engine, hardwareBuffer!!)
|
||||
} else {
|
||||
texture = Texture.Builder()
|
||||
.width(width)
|
||||
.height(height)
|
||||
.levels(1)
|
||||
.usage(Texture.Usage.COLOR_ATTACHMENT or Texture.Usage.SAMPLEABLE)
|
||||
.format(Texture.InternalFormat.RGBA8)
|
||||
.build(engine)
|
||||
}
|
||||
|
||||
renderTarget = RenderTarget.Builder()
|
||||
.texture(RenderTarget.AttachmentPoint.COLOR, texture!!)
|
||||
.build(engine)
|
||||
|
||||
offscreenView.renderTarget = renderTarget
|
||||
// Set the texture on the quad material.
|
||||
texturedMaterial.defaultInstance.setParameter("texture", texture!!,
|
||||
TextureSampler(TextureSampler.MinFilter.LINEAR, TextureSampler.MagFilter.LINEAR,
|
||||
TextureSampler.WrapMode.CLAMP_TO_EDGE))
|
||||
FilamentHelper.synchronizePendingFrames(engine)
|
||||
}
|
||||
}
|
||||
|
||||
private fun readUncompressedAsset(assetName: String): ByteBuffer {
|
||||
assets.openFd(assetName).use { fd ->
|
||||
val input = fd.createInputStream()
|
||||
val dst = ByteBuffer.allocate(fd.length.toInt())
|
||||
val src = Channels.newChannel(input)
|
||||
src.read(dst)
|
||||
src.close()
|
||||
return dst.apply { rewind() }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
material {
|
||||
name : baked_color,
|
||||
shadingModel : unlit,
|
||||
requires : [
|
||||
color
|
||||
]
|
||||
}
|
||||
|
||||
fragment {
|
||||
void material(inout MaterialInputs material) {
|
||||
// You must always call the prepareMaterial() function
|
||||
prepareMaterial(material);
|
||||
|
||||
// We set the material's color to the color interpolated from
|
||||
// the model's vertices
|
||||
material.baseColor = getColor();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
material {
|
||||
name : textured,
|
||||
shadingModel : unlit,
|
||||
parameters : [
|
||||
{
|
||||
type : sampler2d,
|
||||
name : texture
|
||||
}
|
||||
],
|
||||
requires: [
|
||||
uv0
|
||||
]
|
||||
}
|
||||
|
||||
fragment {
|
||||
void material(inout MaterialInputs material) {
|
||||
prepareMaterial(material);
|
||||
material.baseColor = texture(materialParams_texture, uvToRenderTargetUV(getUV0()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
material {
|
||||
name : textured,
|
||||
shadingModel : unlit,
|
||||
parameters : [
|
||||
{
|
||||
type : samplerExternal,
|
||||
name : texture
|
||||
}
|
||||
],
|
||||
requires: [
|
||||
uv0
|
||||
]
|
||||
}
|
||||
|
||||
fragment {
|
||||
void material(inout MaterialInputs material) {
|
||||
prepareMaterial(material);
|
||||
material.baseColor = texture(materialParams_texture, uvToRenderTargetUV(getUV0()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="78.5885"
|
||||
android:endY="90.9159"
|
||||
android:startX="48.7653"
|
||||
android:startY="61.0927"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0"/>
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,171 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillColor="#26A69A"
|
||||
android:pathData="M0,0h108v108h-108z"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user