Compare commits
193 Commits
ry/wgpuFix
...
rc/1.70.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02602c6a9c | ||
|
|
9dfd3f73bc | ||
|
|
a102daed53 | ||
|
|
859d930f60 | ||
|
|
5177b5c973 | ||
|
|
6416cf1eaa | ||
|
|
ddb2de1649 | ||
|
|
20aa91edc9 | ||
|
|
d65589bf77 | ||
|
|
81961f6d49 | ||
|
|
9fbf438baa | ||
|
|
5007b4e428 | ||
|
|
0d2ecb5436 | ||
|
|
f24db4633e | ||
|
|
45d92cac14 | ||
|
|
c5e915b96c | ||
|
|
e2b195706a | ||
|
|
3ebb2086ed | ||
|
|
052092f553 | ||
|
|
7163aaa627 | ||
|
|
0c7ff3fcf4 | ||
|
|
962756e283 | ||
|
|
48ee727c8d | ||
|
|
2d1e4b8ce2 | ||
|
|
749b03ed2a | ||
|
|
497a1cc42d | ||
|
|
256a494cd0 | ||
|
|
b2531fff15 | ||
|
|
f18afe1d3e | ||
|
|
771eb4c1a4 | ||
|
|
70e7cb2a27 | ||
|
|
f95f127495 | ||
|
|
c0d63e826d | ||
|
|
36583618f1 | ||
|
|
9d322e7208 | ||
|
|
b897d22d20 | ||
|
|
e5fe3d495e | ||
|
|
35f501b3d5 | ||
|
|
5b799928c3 | ||
|
|
f7f586caff | ||
|
|
5428812b93 | ||
|
|
e57f2f5c02 | ||
|
|
fd9bcaa735 | ||
|
|
9a14e54fc2 | ||
|
|
d78bb294ed | ||
|
|
0b8dbe9b0a | ||
|
|
e595fd4b79 | ||
|
|
c5d36cff7f | ||
|
|
f392e8be54 | ||
|
|
83653fb358 | ||
|
|
8a3c48fef1 | ||
|
|
7f6b9bb144 | ||
|
|
687c42583b | ||
|
|
71e8cab08a | ||
|
|
afae31a975 | ||
|
|
5ac5dc4c95 | ||
|
|
e975572972 | ||
|
|
29e91f0d3a | ||
|
|
6f0d47f275 | ||
|
|
cf66813f41 | ||
|
|
5f89e8e711 | ||
|
|
be9e9298e1 | ||
|
|
9218b90c9c | ||
|
|
070a07679d | ||
|
|
10b7bd71f9 | ||
|
|
e4ae96a2a1 | ||
|
|
56ac08e353 | ||
|
|
52b0b553b4 | ||
|
|
00f3c7175c | ||
|
|
e4fa86fb01 | ||
|
|
7da2a08df6 | ||
|
|
82246d934d | ||
|
|
c60969ef67 | ||
|
|
ce37f216bc | ||
|
|
81c71fbbb9 | ||
|
|
07a7c6003a | ||
|
|
804a74c205 | ||
|
|
dde49a410a | ||
|
|
770ce7f8ec | ||
|
|
11714d3adc | ||
|
|
6aac9071b3 | ||
|
|
da9173e9dc | ||
|
|
cd64d50408 | ||
|
|
a3145cb96f | ||
|
|
cdfb92e14a | ||
|
|
55c16e6e7a | ||
|
|
65e3c3bfb9 | ||
|
|
902f869721 | ||
|
|
ad1bc6f360 | ||
|
|
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 |
32
.github/actions/get-commit-msg/action.yml
vendored
32
.github/actions/get-commit-msg/action.yml
vendored
@@ -27,17 +27,24 @@ runs:
|
||||
echo "$HASH" > /tmp/commit_hash.txt
|
||||
- name: Find commit message (PR)
|
||||
shell: bash
|
||||
id: checkout_code
|
||||
if: github.event_name == 'pull_request'
|
||||
run: |
|
||||
BEFORE_HASH=$(git rev-parse HEAD)
|
||||
echo "hash=$BEFORE_HASH" >> "$GITHUB_OUTPUT"
|
||||
# Next we will checkout the actual head (not the merge commits) of the PR
|
||||
AFTER_HASH="${{ github.event.pull_request.head.sha }}"
|
||||
git checkout $AFTER_HASH
|
||||
COMMIT_MESSAGE=$(git log -1 --no-merges)
|
||||
echo "$COMMIT_MESSAGE" > /tmp/commit_msg.txt
|
||||
echo "$AFTER_HASH" > /tmp/commit_hash.txt
|
||||
PR_NUMBER="${{ github.event.pull_request.number }}"
|
||||
# Fetch the head of the PR explicitly to handle forks. Depth 50 ensures we can traverse past recent merge commits.
|
||||
git fetch --depth=50 origin "pull/$PR_NUMBER/head:pr-head"
|
||||
|
||||
COMMIT_HASH=$(git log -1 --no-merges pr-head --format=%H)
|
||||
AUTHOR_NAME=$(git log -1 --no-merges pr-head --format=%an)
|
||||
AUTHOR_EMAIL=$(git log -1 --no-merges pr-head --format=%ae)
|
||||
TSTAMP=$(git log -1 --no-merges pr-head --format=%aI)
|
||||
COMMIT_MESSAGE=$(git log -1 --no-merges pr-head --format=%B)
|
||||
|
||||
echo "commit $COMMIT_HASH" > /tmp/commit_msg.txt
|
||||
echo "Author: ${AUTHOR_NAME}<${AUTHOR_EMAIL}>" >> /tmp/commit_msg.txt
|
||||
echo "Date: ${TSTAMP}" >> /tmp/commit_msg.txt
|
||||
echo "" >> /tmp/commit_msg.txt
|
||||
echo "$COMMIT_MESSAGE" >> /tmp/commit_msg.txt
|
||||
echo "$COMMIT_HASH" > /tmp/commit_hash.txt
|
||||
- shell: bash
|
||||
id: action_output
|
||||
run: |
|
||||
@@ -47,9 +54,4 @@ runs:
|
||||
cat /tmp/commit_msg.txt >> "$GITHUB_OUTPUT"
|
||||
echo "$DELIMITER" >> "$GITHUB_OUTPUT"
|
||||
# Get the commit hash
|
||||
echo "hash=$(cat /tmp/commit_hash.txt)" >> "$GITHUB_OUTPUT"
|
||||
- name: Cleanup Find commit message (PR)
|
||||
shell: bash
|
||||
if: github.event_name == 'pull_request'
|
||||
run: |
|
||||
git checkout ${{ steps.checkout_code.outputs.hash }}
|
||||
echo "hash=$(cat /tmp/commit_hash.txt)" >> "$GITHUB_OUTPUT"
|
||||
2
.github/actions/get-mesa/action.yml
vendored
2
.github/actions/get-mesa/action.yml
vendored
@@ -9,7 +9,7 @@ runs:
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: mesa
|
||||
key: ${{ runner.os }}-mesa-deps-${{ env.GITHUB_MESA_VERSION }}
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-mesa-deps-${{ env.GITHUB_MESA_VERSION }}
|
||||
- name: Get Mesa
|
||||
run: |
|
||||
bash build/common/get-mesa.sh
|
||||
|
||||
3
.github/actions/get-vulkan-sdk/action.yml
vendored
3
.github/actions/get-vulkan-sdk/action.yml
vendored
@@ -9,7 +9,7 @@ runs:
|
||||
id: cache-vulkan-sdk
|
||||
with:
|
||||
path: ~/VulkanSDK
|
||||
key: vulkansdk-${{ env.GITHUB_VULKANSDK_VERSION }}-2-${{ runner.os }}
|
||||
key: vulkansdk-${{ env.GITHUB_VULKANSDK_VERSION }}-2-${{ runner.os }}-${{ runner.arch }}
|
||||
- name: Download Vulkan SDK
|
||||
if: steps.cache-vulkan-sdk.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
@@ -23,6 +23,7 @@ runs:
|
||||
unpack_vulkan_installer
|
||||
shell: bash
|
||||
- name: Run Vulkan SDK setup
|
||||
if: runner.os != 'Linux'
|
||||
run: |
|
||||
pushd .
|
||||
cd ~/VulkanSDK/${GITHUB_VULKANSDK_VERSION}
|
||||
|
||||
13
.github/actions/linux-prereq/action.yml
vendored
13
.github/actions/linux-prereq/action.yml
vendored
@@ -11,24 +11,13 @@ runs:
|
||||
echo "set man-db/auto-update false" | sudo debconf-communicate
|
||||
sudo dpkg-reconfigure man-db
|
||||
|
||||
# Install ninja
|
||||
source ./build/common/get-ninja.sh
|
||||
|
||||
# Install CMake
|
||||
mkdir -p cmake
|
||||
cd cmake
|
||||
|
||||
sudo wget https://github.com/Kitware/CMake/releases/download/v$GITHUB_CMAKE_VERSION/cmake-$GITHUB_CMAKE_VERSION-Linux-x86_64.sh
|
||||
sudo chmod +x ./cmake-$GITHUB_CMAKE_VERSION-Linux-x86_64.sh
|
||||
sudo ./cmake-$GITHUB_CMAKE_VERSION-Linux-x86_64.sh --skip-license > /dev/null
|
||||
sudo update-alternatives --install /usr/bin/cmake cmake $(pwd)/bin/cmake 1000 --force
|
||||
|
||||
cd ..
|
||||
|
||||
sudo wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
||||
sudo apt-get update
|
||||
sudo apt-get install clang-$GITHUB_CLANG_VERSION libc++-$GITHUB_CLANG_VERSION-dev libc++abi-$GITHUB_CLANG_VERSION-dev
|
||||
sudo apt-get install mesa-common-dev libxi-dev libxxf86vm-dev
|
||||
sudo apt-get install cmake ninja-build
|
||||
|
||||
# For dawn
|
||||
sudo apt-get install libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libx11-xcb-dev
|
||||
|
||||
32
.github/actions/renderdiff-generate/action.yml
vendored
Normal file
32
.github/actions/renderdiff-generate/action.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: 'Renderdiff Generate'
|
||||
description: 'Sets up prerequisites and runs the generate script for renderdiff'
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: ./.github/actions/mac-prereq
|
||||
- uses: ./.github/actions/get-gltf-assets
|
||||
- uses: ./.github/actions/get-mesa
|
||||
- uses: ./.github/actions/get-vulkan-sdk
|
||||
- name: Prerequisites
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
# Must have at least clang-16 for a webgpu/dawn build.
|
||||
sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer
|
||||
# this enforces the gltf_viewer build to use apple clang as oppose the brew clang used for get-mesa
|
||||
echo "CC=/usr/bin/clang" >> $GITHUB_ENV
|
||||
echo "CXX=/usr/bin/clang++" >> $GITHUB_ENV
|
||||
echo "/usr/local/bin" >> $GITHUB_PATH
|
||||
echo "/usr/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
- name: Generate images
|
||||
run: |
|
||||
TEST_DIR=test/renderdiff
|
||||
source ${TEST_DIR}/src/preamble.sh
|
||||
set -eux
|
||||
bash ${TEST_DIR}/generate.sh
|
||||
set +eux
|
||||
shell: bash
|
||||
- name: Build diffimg tool
|
||||
run: |
|
||||
./build.sh release diffimg
|
||||
shell: bash
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM python:3.10.4
|
||||
FROM python:3.12.13
|
||||
|
||||
RUN pip3 install pygithub==1.55
|
||||
|
||||
|
||||
4
.github/actions/web-prereq/action.yml
vendored
4
.github/actions/web-prereq/action.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: 'Web Preqrequisites'
|
||||
name: 'Web Prerequisites'
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
@@ -8,7 +8,7 @@ runs:
|
||||
uses: actions/cache@v4 # Use a specific version
|
||||
with:
|
||||
path: emsdk
|
||||
key: ${{ runner.os }}-emsdk-${{ env.GITHUB_EMSDK_VERSION }}
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-emsdk-${{ env.GITHUB_EMSDK_VERSION }}
|
||||
- name: Install Web Prerequisites
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
79
.github/workflows/postsubmit-main.yml
vendored
79
.github/workflows/postsubmit-main.yml
vendored
@@ -6,18 +6,31 @@ on:
|
||||
- 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'
|
||||
runs-on: 'macos-14'
|
||||
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: Check if accepting new goldens
|
||||
id: check_accept
|
||||
env:
|
||||
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
|
||||
run: |
|
||||
if echo "${COMMIT_MESSAGE}" | python3 test/renderdiff/src/commit_msg.py --mode=accept_new_goldens; then
|
||||
echo "accept=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "accept=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
shell: bash
|
||||
- name: Renderdiff Generate for new goldens
|
||||
if: steps.check_accept.outputs.accept == 'true'
|
||||
uses: ./.github/actions/renderdiff-generate
|
||||
- name: Run update script
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.FILAMENTBOT_TOKEN }}
|
||||
@@ -25,16 +38,31 @@ jobs:
|
||||
run: |
|
||||
GOLDEN_BRANCH=$(echo "${COMMIT_MESSAGE}" | python3 test/renderdiff/src/commit_msg.py)
|
||||
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
|
||||
|
||||
if [[ "${{ steps.check_accept.outputs.accept }}" == "true" ]]; then
|
||||
SHORT_HASH="${COMMIT_HASH:0:8}"
|
||||
GOLDEN_BRANCH="accept-goldens-${SHORT_HASH}"
|
||||
echo "Generating new goldens for branch ${GOLDEN_BRANCH}"
|
||||
python3 test/renderdiff/src/update_golden.py \
|
||||
--branch=${GOLDEN_BRANCH} \
|
||||
--source=$(pwd)/out/renderdiff/renders \
|
||||
--commit-msg="Auto-update goldens from ${COMMIT_HASH}" \
|
||||
--push-to-remote \
|
||||
--golden-repo-token=${GH_TOKEN}
|
||||
fi
|
||||
|
||||
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'
|
||||
@@ -57,3 +85,40 @@ jobs:
|
||||
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-4core'
|
||||
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
|
||||
|
||||
24
.github/workflows/postsubmit.yml
vendored
24
.github/workflows/postsubmit.yml
vendored
@@ -12,8 +12,7 @@ jobs:
|
||||
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'
|
||||
|
||||
runs-on: 'ubuntu-24.04-4core'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -26,8 +25,7 @@ jobs:
|
||||
|
||||
build-ios:
|
||||
name: build-ios
|
||||
runs-on: macos-14-xlarge
|
||||
|
||||
runs-on: 'macos-14'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -46,8 +44,7 @@ jobs:
|
||||
|
||||
build-linux:
|
||||
name: build-linux
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
|
||||
runs-on: 'arm-ubuntu-24.04-16core'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -58,13 +55,12 @@ jobs:
|
||||
cd build/linux && printf "y" | ./build.sh continuous
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: filament-linux
|
||||
path: out/filament-release-linux.tgz
|
||||
name: filament-arm-linux
|
||||
path: out/filament-release-arm-linux.tgz
|
||||
|
||||
build-mac:
|
||||
name: build-mac
|
||||
runs-on: macos-14-xlarge
|
||||
|
||||
runs-on: 'macos-14'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -77,14 +73,10 @@ jobs:
|
||||
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'
|
||||
|
||||
runs-on: 'arm-ubuntu-24.04-16core'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -101,7 +93,7 @@ jobs:
|
||||
|
||||
build-windows:
|
||||
name: build-windows
|
||||
runs-on: windows-2022-32core
|
||||
runs-on: 'windows-2022'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
|
||||
233
.github/workflows/presubmit.yml
vendored
233
.github/workflows/presubmit.yml
vendored
@@ -8,10 +8,54 @@ on:
|
||||
branches:
|
||||
- main
|
||||
|
||||
# This will cancel in-flight runs when there is an update to a PR
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
# The conditional 'if' on each job is meant to skip presubmit jobs when a commit is pushed to main
|
||||
# and that commit is cryptographically "verified". Typically, a verified commit (like a GitHub UI
|
||||
# squash-and-merge) has already passed through presubmit during the Pull Request phase.
|
||||
# The conditional explicitly checks:
|
||||
# 1. always() && !cancelled(): Ensures the job runs even if 'check-verification' is skipped, but
|
||||
# aborts if the workflow was manually cancelled.
|
||||
# 2. github.event_name == 'pull_request': Presubmits should always run normally on PRs.
|
||||
# 3. github.event_name == 'push' && ...: If it's a push to main, it only runs if the
|
||||
# 'check-verification' job confirmed the commit is NOT verified.
|
||||
|
||||
jobs:
|
||||
check-verification:
|
||||
if: github.event_name == 'push'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
verified: ${{ steps.check.outputs.verified }}
|
||||
steps:
|
||||
- name: Check commit verification
|
||||
id: check
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const { data } = await github.rest.repos.getCommit({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: context.sha
|
||||
});
|
||||
core.setOutput('verified', data.commit.verification.verified);
|
||||
|
||||
build-desktop-mac:
|
||||
name: build-mac
|
||||
runs-on: macos-14-xlarge
|
||||
runs-on: 'macos-14-xlarge'
|
||||
needs: [check-verification]
|
||||
if: >
|
||||
always() && !cancelled() &&
|
||||
(
|
||||
github.event_name == 'pull_request' ||
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
needs.check-verification.result == 'success' &&
|
||||
needs.check-verification.outputs.verified == 'false'
|
||||
)
|
||||
)
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -20,13 +64,30 @@ jobs:
|
||||
- name: Run build script
|
||||
run: |
|
||||
cd build/mac && printf "y" | ./build.sh presubmit-with-test
|
||||
- name: Test material parser
|
||||
- name: Test - material parser
|
||||
run: |
|
||||
out/cmake-release/filament/test/test_material_parser
|
||||
- name: Test - public headers
|
||||
run: |
|
||||
# out/cmake-release should have the artifacts ready for installation. Here we install it
|
||||
# to test the public headers
|
||||
ninja -C out/cmake-release install
|
||||
test/check-headers/test.sh out/release/filament/include
|
||||
|
||||
build-desktop-linux:
|
||||
name: build-linux
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
runs-on: 'arm-ubuntu-24.04-16core'
|
||||
needs: [check-verification]
|
||||
if: >
|
||||
always() && !cancelled() &&
|
||||
(
|
||||
github.event_name == 'pull_request' ||
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
needs.check-verification.result == 'success' &&
|
||||
needs.check-verification.outputs.verified == 'false'
|
||||
)
|
||||
)
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -35,13 +96,24 @@ jobs:
|
||||
- name: Run build script
|
||||
run: |
|
||||
cd build/linux && printf "y" | ./build.sh presubmit
|
||||
- name: Test material parser
|
||||
- name: Test - material parser
|
||||
run: |
|
||||
out/cmake-release/filament/test/test_material_parser
|
||||
|
||||
build-windows:
|
||||
name: build-windows
|
||||
runs-on: windows-2022-32core
|
||||
runs-on: 'windows-2022'
|
||||
needs: [check-verification]
|
||||
if: >
|
||||
always() && !cancelled() &&
|
||||
(
|
||||
github.event_name == 'pull_request' ||
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
needs.check-verification.result == 'success' &&
|
||||
needs.check-verification.outputs.verified == 'false'
|
||||
)
|
||||
)
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -53,7 +125,18 @@ jobs:
|
||||
|
||||
build-android:
|
||||
name: build-android
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
runs-on: 'ubuntu-24.04-4core'
|
||||
needs: [check-verification]
|
||||
if: >
|
||||
always() && !cancelled() &&
|
||||
(
|
||||
github.event_name == 'pull_request' ||
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
needs.check-verification.result == 'success' &&
|
||||
needs.check-verification.outputs.verified == 'false'
|
||||
)
|
||||
)
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -67,11 +150,31 @@ 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
|
||||
runs-on: macos-14-xlarge
|
||||
runs-on: 'macos-14-xlarge'
|
||||
needs: [check-verification]
|
||||
if: >
|
||||
always() && !cancelled() &&
|
||||
(
|
||||
github.event_name == 'pull_request' ||
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
needs.check-verification.result == 'success' &&
|
||||
needs.check-verification.outputs.verified == 'false'
|
||||
)
|
||||
)
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -86,7 +189,18 @@ jobs:
|
||||
|
||||
build-web:
|
||||
name: build-web
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
runs-on: 'arm-ubuntu-24.04-16core'
|
||||
needs: [check-verification]
|
||||
if: >
|
||||
always() && !cancelled() &&
|
||||
(
|
||||
github.event_name == 'pull_request' ||
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
needs.check-verification.result == 'success' &&
|
||||
needs.check-verification.outputs.verified == 'false'
|
||||
)
|
||||
)
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -99,7 +213,18 @@ jobs:
|
||||
|
||||
validate-docs:
|
||||
name: validate-docs
|
||||
runs-on: 'ubuntu-24.04-4core'
|
||||
runs-on: 'ubuntu-24.04'
|
||||
needs: [check-verification]
|
||||
if: >
|
||||
always() && !cancelled() &&
|
||||
(
|
||||
github.event_name == 'pull_request' ||
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
needs.check-verification.result == 'success' &&
|
||||
needs.check-verification.outputs.verified == 'false'
|
||||
)
|
||||
)
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -112,24 +237,40 @@ jobs:
|
||||
|
||||
test-renderdiff:
|
||||
name: test-renderdiff
|
||||
runs-on: macos-14-xlarge
|
||||
runs-on: 'macos-14-xlarge'
|
||||
needs: [check-verification]
|
||||
if: >
|
||||
always() && !cancelled() &&
|
||||
(
|
||||
github.event_name == 'pull_request' ||
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
needs.check-verification.result == 'success' &&
|
||||
needs.check-verification.outputs.verified == 'false'
|
||||
)
|
||||
)
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/mac-prereq
|
||||
- uses: ./.github/actions/get-gltf-assets
|
||||
- uses: ./.github/actions/get-mesa
|
||||
- uses: ./.github/actions/get-vulkan-sdk
|
||||
- id: get_commit_msg
|
||||
uses: ./.github/actions/get-commit-msg
|
||||
- name: Prerequisites
|
||||
- name: Check if accepting new goldens
|
||||
id: check_accept
|
||||
env:
|
||||
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
|
||||
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
|
||||
if echo "${COMMIT_MESSAGE}" | python3 test/renderdiff/src/commit_msg.py --mode=accept_new_goldens; then
|
||||
echo "accept=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "accept=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
shell: bash
|
||||
- name: Render and compare
|
||||
- name: Renderdiff generate
|
||||
if: steps.check_accept.outputs.accept != 'true'
|
||||
uses: ./.github/actions/renderdiff-generate
|
||||
- name: Compare rendered images
|
||||
if: steps.check_accept.outputs.accept != 'true'
|
||||
id: render_compare
|
||||
env:
|
||||
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
|
||||
@@ -138,7 +279,7 @@ jobs:
|
||||
source ${TEST_DIR}/src/preamble.sh
|
||||
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 \
|
||||
--branch=${GOLDEN_BRANCH} \
|
||||
--output=${GOLDEN_OUTPUT_DIR}
|
||||
@@ -149,7 +290,9 @@ jobs:
|
||||
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,11 +301,14 @@ jobs:
|
||||
echo "$DELIMITER" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
shell: bash
|
||||
- uses: actions/upload-artifact@v4
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
if: steps.check_accept.outputs.accept != 'true'
|
||||
with:
|
||||
name: presubmit-renderdiff-result
|
||||
path: ./out/renderdiff
|
||||
- name: Compare result
|
||||
- name: Check results
|
||||
if: steps.check_accept.outputs.accept != 'true'
|
||||
run: |
|
||||
ERROR_STR="${{ steps.render_compare.outputs.err }}"
|
||||
if [ -n "${ERROR_STR}" ]; then
|
||||
@@ -170,9 +316,20 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
validate-wgsl-webgpu:
|
||||
build-wgsl-webgpu:
|
||||
name: validate-wgsl-webgpu
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
runs-on: 'arm-ubuntu-24.04-16core'
|
||||
needs: [check-verification]
|
||||
if: >
|
||||
always() && !cancelled() &&
|
||||
(
|
||||
github.event_name == 'pull_request' ||
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
needs.check-verification.result == 'success' &&
|
||||
needs.check-verification.outputs.verified == 'false'
|
||||
)
|
||||
)
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -186,6 +343,17 @@ jobs:
|
||||
test-code-correctness:
|
||||
name: test-code-correctness
|
||||
runs-on: 'macos-14-xlarge'
|
||||
needs: [check-verification]
|
||||
if: >
|
||||
always() && !cancelled() &&
|
||||
(
|
||||
github.event_name == 'pull_request' ||
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
needs.check-verification.result == 'success' &&
|
||||
needs.check-verification.outputs.verified == 'false'
|
||||
)
|
||||
)
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -206,7 +374,18 @@ jobs:
|
||||
|
||||
test-backend:
|
||||
name: test-backend
|
||||
runs-on: macos-14-xlarge
|
||||
runs-on: 'macos-14-xlarge'
|
||||
needs: [check-verification]
|
||||
if: >
|
||||
always() && !cancelled() &&
|
||||
(
|
||||
github.event_name == 'pull_request' ||
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
needs.check-verification.result == 'success' &&
|
||||
needs.check-verification.outputs.verified == 'false'
|
||||
)
|
||||
)
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
|
||||
90
.github/workflows/release.yml
vendored
90
.github/workflows/release.yml
vendored
@@ -63,6 +63,39 @@ jobs:
|
||||
const globber = await glob.create('out/*.tgz');
|
||||
await upload({ github, context }, await globber.glob(), TAG);
|
||||
|
||||
build-arm-linux:
|
||||
name: build-arm-linux
|
||||
runs-on: 'arm-ubuntu-24.04-16core'
|
||||
if: github.event_name == 'release' || github.event.inputs.platform == 'desktop'
|
||||
steps:
|
||||
- name: Decide Git ref
|
||||
id: git_ref
|
||||
run: |
|
||||
REF=${RELEASE_TAG:-${GITHUB_REF}}
|
||||
TAG=${REF##*/}
|
||||
echo "ref=${REF}" >> $GITHUB_OUTPUT
|
||||
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
ref: ${{ steps.git_ref.outputs.ref }}
|
||||
- uses: ./.github/actions/linux-prereq
|
||||
- name: Run build script
|
||||
env:
|
||||
TAG: ${{ steps.git_ref.outputs.tag }}
|
||||
run: |
|
||||
cd build/linux && printf "y" | ./build.sh release
|
||||
cd ../..
|
||||
mv out/filament-release-linux.tgz out/filament-${TAG}-arm-linux.tgz
|
||||
- uses: actions/github-script@v6
|
||||
env:
|
||||
TAG: ${{ steps.git_ref.outputs.tag }}
|
||||
with:
|
||||
script: |
|
||||
const upload = require('./build/common/upload-release-assets');
|
||||
const { TAG } = process.env;
|
||||
const globber = await glob.create('out/*.tgz');
|
||||
await upload({ github, context }, await globber.glob(), TAG);
|
||||
|
||||
build-mac:
|
||||
name: build-mac
|
||||
runs-on: macos-14-xlarge
|
||||
@@ -99,7 +132,7 @@ jobs:
|
||||
|
||||
build-web:
|
||||
name: build-web
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
runs-on: 'arm-ubuntu-24.04-16core'
|
||||
if: github.event_name == 'release' || github.event.inputs.platform == 'web'
|
||||
|
||||
steps:
|
||||
@@ -163,9 +196,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
|
||||
@@ -190,6 +221,54 @@ jobs:
|
||||
const globber = await glob.create(['out/*.aar', 'out/*.apk', 'out/*.tgz'].join('\n'));
|
||||
await upload({ github, context }, await globber.glob(), TAG);
|
||||
|
||||
sonatype-publish:
|
||||
name: sonatype-publish
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
# Depends on the the Android build for the Android binaries.
|
||||
# Depends on the Mac, Linux, and Windows builds for host tools.
|
||||
needs: [build-mac, build-linux, build-windows, build-android]
|
||||
if: github.event_name == 'release' || github.event.inputs.platform == 'android'
|
||||
|
||||
steps:
|
||||
- name: Decide Git ref
|
||||
id: git_ref
|
||||
run: |
|
||||
REF=${RELEASE_TAG:-${GITHUB_REF}}
|
||||
TAG=${REF##*/}
|
||||
echo "ref=${REF}" >> $GITHUB_OUTPUT
|
||||
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
ref: ${{ steps.git_ref.outputs.ref }}
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
- uses: ./.github/actions/linux-prereq
|
||||
- name: Download Android Release
|
||||
run: |
|
||||
gh release download ${TAG} \
|
||||
--repo ${{ github.repository }} \
|
||||
--pattern 'filament-*-android-native.tgz'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ steps.git_ref.outputs.tag }}
|
||||
- name: Unzip Android Release
|
||||
run: |
|
||||
mkdir -p out/android-release
|
||||
tar -xzvf filament-${TAG}-android-native.tgz -C out/android-release/
|
||||
env:
|
||||
TAG: ${{ steps.git_ref.outputs.tag }}
|
||||
- name: Publish To Sonatype
|
||||
run: |
|
||||
cd android
|
||||
./gradlew publishToSonatype closeSonatypeStagingRepository
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.SONATYPE_USERNAME }}
|
||||
ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.SONATYPE_PASSWORD }}
|
||||
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.MAVEN_SIGNING_KEY }}
|
||||
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.MAVEN_SIGNING_PASSWORD }}
|
||||
|
||||
build-ios:
|
||||
name: build-ios
|
||||
runs-on: macos-14-xlarge
|
||||
@@ -245,7 +324,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
|
||||
|
||||
11
.github/workflows/verify-release-notes.yml
vendored
11
.github/workflows/verify-release-notes.yml
vendored
@@ -11,14 +11,11 @@ jobs:
|
||||
name: Verify Release Notes
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# We *only* need to check out the .github/ directory.
|
||||
# The verify RELEASE_NOTES script uses the GitHub API to verify that RELEASE_NOTES was
|
||||
# modified.
|
||||
- name: Check out action
|
||||
uses: Bhacaz/checkout-files@73e17cfbe8d7e0c6b2672b20cb05a718e20d18d4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
files: .github
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
fetch-depth: 0
|
||||
sparse-checkout: |
|
||||
.github
|
||||
- name: Verify release notes
|
||||
uses: ./.github/actions/verify-release-notes
|
||||
with:
|
||||
|
||||
@@ -65,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."
|
||||
)
|
||||
@@ -324,11 +326,13 @@ endif()
|
||||
if (FILAMENT_ENABLE_LTO)
|
||||
include(CheckIPOSupported)
|
||||
|
||||
check_ipo_supported(RESULT IPO_SUPPORT)
|
||||
check_ipo_supported(RESULT IPO_SUPPORT OUTPUT IPO_ERROR)
|
||||
|
||||
if (IPO_SUPPORT)
|
||||
message(STATUS "LTO support is enabled")
|
||||
message(STATUS "IPO / LTO is enabled")
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
else()
|
||||
message(WARNING "IPO / LTO is not supported by this architecture: ${IPO_ERROR}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -865,6 +869,12 @@ endfunction()
|
||||
# Sub-projects
|
||||
# ==================================================================================================
|
||||
|
||||
include(CheckSymbolExists)
|
||||
check_symbol_exists(getopt_long "getopt.h" HAS_SYSTEM_GETOPT)
|
||||
if (NOT HAS_SYSTEM_GETOPT)
|
||||
add_subdirectory(${EXTERNAL}/getopt)
|
||||
endif()
|
||||
|
||||
# Common to all platforms
|
||||
add_subdirectory(${EXTERNAL}/libgtest/tnt)
|
||||
add_subdirectory(${LIBRARIES}/camutils)
|
||||
@@ -877,6 +887,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)
|
||||
@@ -899,10 +910,11 @@ add_subdirectory(${EXTERNAL}/cgltf/tnt)
|
||||
add_subdirectory(${EXTERNAL}/draco/tnt)
|
||||
add_subdirectory(${EXTERNAL}/jsmn/tnt)
|
||||
add_subdirectory(${EXTERNAL}/stb/tnt)
|
||||
add_subdirectory(${EXTERNAL}/getopt)
|
||||
add_subdirectory(${EXTERNAL}/perfetto/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)
|
||||
@@ -971,6 +983,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)
|
||||
|
||||
25
README.md
25
README.md
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.68.5'
|
||||
implementation 'com.google.android.filament:filament-android:1.70.2'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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.68.5'
|
||||
pod 'Filament', '~> 1.70.2'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
@@ -89,7 +88,8 @@ pod 'Filament', '~> 1.68.5'
|
||||
- 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.68.5'
|
||||
|
||||
- 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.68.5'
|
||||
- [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,35 @@ 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.70.2
|
||||
|
||||
|
||||
## v1.70.1
|
||||
|
||||
|
||||
## v1.70.0
|
||||
|
||||
- engine: fix crash when using variance shadow maps
|
||||
- materials: better shadow normal-bias calculations [⚠️ **New Material Version**]
|
||||
|
||||
## v1.69.5
|
||||
|
||||
- engine: fix crash when using variance shadow maps
|
||||
|
||||
## 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**]
|
||||
|
||||
@@ -40,15 +40,21 @@
|
||||
// - Build and upload artifacts with ./gradlew publish
|
||||
// - Close and release staging repo on Nexus with ./gradlew closeAndReleaseStagingRepository
|
||||
//
|
||||
// The following is needed in ~/gradle/gradle.properties:
|
||||
// The following properties need to be set (either in ~/gradle/gradle.properties, on the command
|
||||
// line, or as environment variables, e.g.: ORG_GRADLE_PROJECT_property=value):
|
||||
//
|
||||
// sonatypeUsername=nexus_user
|
||||
// sonatypePassword=nexus_password
|
||||
//
|
||||
// To sign with a key ring file:
|
||||
// signing.keyId=pgp_key_id
|
||||
// signing.password=pgp_key_password
|
||||
// signing.secretKeyRingFile=/Users/user/.gnupg/maven_signing.key
|
||||
//
|
||||
// To sign with in-memory keys (useful for CI):,
|
||||
// signingKey=ASCII armored key (begins with -----BEGIN PGP PRIVATE KEY BLOCK-----)
|
||||
// signingPassword=key password
|
||||
//
|
||||
|
||||
buildscript {
|
||||
def path = providers
|
||||
@@ -194,7 +200,7 @@ subprojects {
|
||||
google()
|
||||
}
|
||||
|
||||
if (!name.startsWith("sample") && name != "filament-tools") {
|
||||
if (!name.startsWith("sample") && name != "filament-tools" && name != "gradle-plugin") {
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
plugins {
|
||||
id 'groovy-gradle-plugin'
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
create("filament-tools-plugin") {
|
||||
id = "filament-tools-plugin"
|
||||
implementationClass = "FilamentToolsPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -96,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();
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -173,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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
*
|
||||
@@ -587,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.
|
||||
*
|
||||
@@ -1130,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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -795,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
|
||||
@@ -1370,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);
|
||||
|
||||
@@ -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 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,
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated has no effect.
|
||||
*/
|
||||
/** @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;
|
||||
/**
|
||||
* Upscaling factor. Disables Dynamic Resolution. [BETA]
|
||||
*/
|
||||
/** Upscaling factor. Disables Dynamic Resolution. [BETA] */
|
||||
public float upscaling = 1.0f;
|
||||
/**
|
||||
* whether to filter the history buffer
|
||||
*/
|
||||
/** 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;
|
||||
/**
|
||||
* set to true for HDR content
|
||||
*/
|
||||
/** set to true for HDR content */
|
||||
public boolean hdr = true;
|
||||
/**
|
||||
* type of color gamut box
|
||||
*/
|
||||
/** 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;
|
||||
/**
|
||||
@@ -2254,7 +2121,7 @@ public class View {
|
||||
*/
|
||||
public boolean highPrecision = false;
|
||||
/**
|
||||
* VSM minimum variance scale, must be positive.
|
||||
* @deprecated has no effect.
|
||||
*/
|
||||
public float minVarianceScale = 0.5f;
|
||||
/**
|
||||
@@ -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 {
|
||||
/**
|
||||
|
||||
@@ -7,7 +7,7 @@ apply from: rootProject.file('gradle/gradle-mvn-push.gradle')
|
||||
def tools = ['matc', 'cmgen']
|
||||
|
||||
def platforms = [
|
||||
'mac': [classifier: 'osx-aarch64', archive: "filament-v${VERSION_NAME}-mac.tgz", path: { t -> "filament/bin/${t}" }],
|
||||
'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" }]
|
||||
]
|
||||
|
||||
@@ -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)
|
||||
@@ -53,10 +61,12 @@ set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size
|
||||
add_library(filament-utils-jni SHARED
|
||||
src/main/cpp/AutomationEngine.cpp
|
||||
src/main/cpp/Bookmark.cpp
|
||||
src/main/cpp/DeviceUtils.cpp
|
||||
src/main/cpp/HDRLoader.cpp
|
||||
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 +84,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 +96,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;
|
||||
|
||||
85
android/filament-utils-android/src/main/cpp/DeviceUtils.cpp
Normal file
85
android/filament-utils-android/src/main/cpp/DeviceUtils.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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 <filament/Engine.h>
|
||||
|
||||
#include <backend/Platform.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
using namespace filament;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::array<backend::Platform::DeviceInfoType, 3> VULKAN_INFO = {
|
||||
backend::Platform::DeviceInfoType::VULKAN_DEVICE_NAME,
|
||||
backend::Platform::DeviceInfoType::VULKAN_DRIVER_NAME,
|
||||
backend::Platform::DeviceInfoType::VULKAN_DRIVER_INFO,
|
||||
};
|
||||
|
||||
constexpr std::array<backend::Platform::DeviceInfoType, 3> GL_INFO = {
|
||||
backend::Platform::DeviceInfoType::OPENGL_VENDOR,
|
||||
backend::Platform::DeviceInfoType::OPENGL_RENDERER,
|
||||
backend::Platform::DeviceInfoType::OPENGL_VERSION,
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" JNIEXPORT jstring JNICALL
|
||||
Java_com_google_android_filament_utils_DeviceUtils_nGetGpuDriverInfo(JNIEnv* env, jclass,
|
||||
jlong nativeEngine) {
|
||||
auto emptyStr = [env]() { return env->NewStringUTF(""); };
|
||||
Engine* engine = (Engine*) nativeEngine;
|
||||
if (!engine) {
|
||||
return emptyStr();
|
||||
}
|
||||
|
||||
backend::Platform* platform = engine->getPlatform();
|
||||
if (!platform) {
|
||||
return emptyStr();
|
||||
}
|
||||
|
||||
std::array<backend::Platform::DeviceInfoType, 3> infoTypes;
|
||||
switch (engine->getBackend()) {
|
||||
case backend::Backend::VULKAN:
|
||||
infoTypes = VULKAN_INFO;
|
||||
break;
|
||||
case backend::Backend::OPENGL:
|
||||
infoTypes = GL_INFO;
|
||||
break;
|
||||
default:
|
||||
return emptyStr();
|
||||
}
|
||||
|
||||
backend::Driver* driver = const_cast<backend::Driver*>(engine->getDriver());
|
||||
utils::CString fullInfo;
|
||||
std::for_each(infoTypes.begin(), infoTypes.end(),
|
||||
[&](backend::Platform::DeviceInfoType infoType) {
|
||||
utils::CString const newInfo = platform->getDeviceInfo(infoType, driver);
|
||||
if (!newInfo.empty()) {
|
||||
if (!fullInfo.empty()) {
|
||||
fullInfo += " | ";
|
||||
}
|
||||
fullInfo += newInfo.c_str();
|
||||
}
|
||||
});
|
||||
return env->NewStringUTF(fullInfo.c_str());
|
||||
}
|
||||
232
android/filament-utils-android/src/main/cpp/ImageDiff.cpp
Normal file
232
android/filament-utils-android/src/main/cpp/ImageDiff.cpp
Normal file
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
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) {
|
||||
// We need to transport the bit differences accurately to the java side, so set
|
||||
// premultiplied to false. From the java-side, if the bitmap is used to draw to a
|
||||
// canvas, then client needs to set premultiplied to true again.
|
||||
jmethodID setPremultiplied = env->GetMethodID(bitmapClass, "setPremultiplied", "(Z)V");
|
||||
if (setPremultiplied) {
|
||||
env->CallVoidMethod(diffBitmap, setPremultiplied, JNI_FALSE);
|
||||
}
|
||||
|
||||
void* diffPixels;
|
||||
if (AndroidBitmap_lockPixels(env, diffBitmap, &diffPixels) == 0) {
|
||||
AndroidBitmapInfo info;
|
||||
AndroidBitmap_getInfo(env, diffBitmap, &info);
|
||||
|
||||
float const* src = result.diffImage.getPixelRef();
|
||||
uint8_t* dst = (uint8_t*) diffPixels;
|
||||
uint32_t const channels = result.diffImage.getChannels(); // usually 4
|
||||
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
uint8_t* row = dst + y * info.stride;
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
size_t srcIdx = (y * width + x) * channels;
|
||||
for (int c = 0; c < 4; ++c) {
|
||||
float v = 0.0f;
|
||||
if (c < channels) v = src[srcIdx + c];
|
||||
if (c == 3 && channels < 4) v = 1.0f; // Alpha 1.0 if missing
|
||||
|
||||
row[x * 4 + c] = uint8_t(
|
||||
std::min(255.0f, std::max(0.0f, std::round(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,26 @@
|
||||
/*
|
||||
* 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 com.google.android.filament.Engine;
|
||||
|
||||
public class DeviceUtils {
|
||||
public static String getGpuDriverInfo(Engine engine) {
|
||||
return nGetGpuDriverInfo(engine.getNativeObject());
|
||||
}
|
||||
private static native String nGetGpuDriverInfo(long nativeEngine);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -106,8 +106,8 @@ class ModelViewer(
|
||||
var skyboxCubemap: Texture? = null
|
||||
|
||||
private lateinit var displayHelper: DisplayHelper
|
||||
private lateinit var cameraManipulator: Manipulator
|
||||
private lateinit var gestureDetector: GestureDetector
|
||||
private var cameraManipulator: Manipulator? = null
|
||||
private var gestureDetector: GestureDetector? = null
|
||||
private var surfaceView: SurfaceView? = null
|
||||
private var textureView: TextureView? = null
|
||||
|
||||
@@ -157,15 +157,13 @@ class ModelViewer(
|
||||
surfaceView: SurfaceView,
|
||||
engine: Engine = Engine.create(),
|
||||
uiHelper: UiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK),
|
||||
manipulator: Manipulator? = null
|
||||
manipulator: Manipulator? = defaultCameraManipulator(surfaceView.width, surfaceView.height)
|
||||
) : this(engine, uiHelper) {
|
||||
cameraManipulator = manipulator ?: Manipulator.Builder()
|
||||
.targetPosition(kDefaultObjectPosition.x, kDefaultObjectPosition.y, kDefaultObjectPosition.z)
|
||||
.viewport(surfaceView.width, surfaceView.height)
|
||||
.build(Manipulator.Mode.ORBIT)
|
||||
|
||||
this.surfaceView = surfaceView
|
||||
gestureDetector = GestureDetector(surfaceView, cameraManipulator)
|
||||
cameraManipulator = manipulator
|
||||
cameraManipulator?.let { c ->
|
||||
gestureDetector = GestureDetector(surfaceView, c)
|
||||
}
|
||||
displayHelper = DisplayHelper(surfaceView.context)
|
||||
uiHelper.renderCallback = SurfaceCallback()
|
||||
uiHelper.attachTo(surfaceView)
|
||||
@@ -177,15 +175,14 @@ class ModelViewer(
|
||||
textureView: TextureView,
|
||||
engine: Engine = Engine.create(),
|
||||
uiHelper: UiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK),
|
||||
manipulator: Manipulator? = null
|
||||
manipulator: Manipulator? = defaultCameraManipulator(textureView.width, textureView.height)
|
||||
) : this(engine, uiHelper) {
|
||||
cameraManipulator = manipulator ?: Manipulator.Builder()
|
||||
.targetPosition(kDefaultObjectPosition.x, kDefaultObjectPosition.y, kDefaultObjectPosition.z)
|
||||
.viewport(textureView.width, textureView.height)
|
||||
.build(Manipulator.Mode.ORBIT)
|
||||
|
||||
cameraManipulator = manipulator
|
||||
this.textureView = textureView
|
||||
gestureDetector = GestureDetector(textureView, cameraManipulator)
|
||||
cameraManipulator = manipulator
|
||||
cameraManipulator?.let { c ->
|
||||
gestureDetector = GestureDetector(textureView, c)
|
||||
}
|
||||
displayHelper = DisplayHelper(textureView.context)
|
||||
uiHelper.renderCallback = SurfaceCallback()
|
||||
uiHelper.attachTo(textureView)
|
||||
@@ -269,6 +266,35 @@ class ModelViewer(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the model's transform, animation, and camera state to defaults.
|
||||
* Call this when reusing the same model across multiple tests.
|
||||
*/
|
||||
fun resetToDefaultState() {
|
||||
// 1. Reset Camera parameters
|
||||
cameraFocalLength = 28f
|
||||
cameraNear = kNearPlane
|
||||
cameraFar = kFarPlane
|
||||
updateCameraProjection()
|
||||
|
||||
// 2. Reset the manipulator's look-at vectors to initial state
|
||||
cameraManipulator?.let { cm ->
|
||||
cm.jumpToBookmark(cm.homeBookmark)
|
||||
}
|
||||
|
||||
// 3. Reset Animations
|
||||
animator?.let {
|
||||
if (it.animationCount > 0) {
|
||||
it.applyAnimation(0, 0.0f)
|
||||
}
|
||||
it.updateBoneMatrices()
|
||||
}
|
||||
|
||||
// 4. Re-apply the unit cube transform to clear custom scaling/translation
|
||||
clearRootTransform()
|
||||
transformToUnitCube()
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees all entities associated with the most recently-loaded model.
|
||||
*/
|
||||
@@ -302,11 +328,13 @@ class ModelViewer(
|
||||
asset?.let { populateScene(it) }
|
||||
|
||||
// Extract the camera basis from the helper and push it to the Filament camera.
|
||||
cameraManipulator.getLookAt(eyePos, target, upward)
|
||||
camera.lookAt(
|
||||
cameraManipulator?.let { cm ->
|
||||
cm.getLookAt(eyePos, target, upward)
|
||||
camera.lookAt(
|
||||
eyePos[0], eyePos[1], eyePos[2],
|
||||
target[0], target[1], target[2],
|
||||
upward[0], upward[1], upward[2])
|
||||
}
|
||||
|
||||
// Render the scene, unless the renderer wants to skip the frame.
|
||||
if (renderer.beginFrame(swapChain!!, frameTimeNanos)) {
|
||||
@@ -398,7 +426,7 @@ class ModelViewer(
|
||||
* Handles a [MotionEvent] to enable one-finger orbit, two-finger pan, and pinch-to-zoom.
|
||||
*/
|
||||
fun onTouchEvent(event: MotionEvent) {
|
||||
gestureDetector.onTouchEvent(event)
|
||||
gestureDetector?.onTouchEvent(event)
|
||||
}
|
||||
|
||||
@SuppressWarnings("ClickableViewAccessibility")
|
||||
@@ -451,7 +479,7 @@ class ModelViewer(
|
||||
|
||||
override fun onResized(width: Int, height: Int) {
|
||||
view.viewport = Viewport(0, 0, width, height)
|
||||
cameraManipulator.setViewport(width, height)
|
||||
cameraManipulator?.setViewport(width, height)
|
||||
updateCameraProjection()
|
||||
synchronizePendingFrames(engine)
|
||||
}
|
||||
@@ -468,5 +496,11 @@ class ModelViewer(
|
||||
|
||||
companion object {
|
||||
private val kDefaultObjectPosition = Float3(0.0f, 0.0f, -4.0f)
|
||||
private fun defaultCameraManipulator(width: Int, height: Int) : Manipulator {
|
||||
return Manipulator.Builder()
|
||||
.targetPosition(kDefaultObjectPosition.x, kDefaultObjectPosition.y, kDefaultObjectPosition.z)
|
||||
.viewport(width, height)
|
||||
.build(Manipulator.Mode.ORBIT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
120
android/gradle-plugin/README.md
Normal file
120
android/gradle-plugin/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 'com.google.android.filament-tools'
|
||||
}
|
||||
|
||||
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`).
|
||||
31
android/gradle-plugin/build.gradle
Normal file
31
android/gradle-plugin/build.gradle
Normal file
@@ -0,0 +1,31 @@
|
||||
plugins {
|
||||
id 'groovy-gradle-plugin'
|
||||
id 'com.gradle.plugin-publish' version '2.1.0'
|
||||
}
|
||||
|
||||
group = "com.google.android.filament"
|
||||
version = "0.1.0"
|
||||
|
||||
gradlePlugin {
|
||||
website = "https://github.com/google/filament/tree/main/android/gradle-plugin"
|
||||
vcsUrl = "https://github.com/google/filament/tree/main/android/gradle-plugin"
|
||||
|
||||
plugins {
|
||||
create("filament-tools") {
|
||||
id = "com.google.android.filament-tools"
|
||||
displayName = "Filament Tools Gradle Plugin"
|
||||
description = "A plugin that helps integrate Filament into Android projects"
|
||||
tags.addAll('android', 'graphics', 'rendering', 'filament', '3d', 'gltf', 'native')
|
||||
implementationClass = "com.google.android.filament.gradle.FilamentPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "com.google.gradle:osdetector-gradle-plugin:1.7.3"
|
||||
}
|
||||
@@ -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,114 @@
|
||||
/*
|
||||
* 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 to a non-empty string, we'll use it as
|
||||
// the tool's base path.
|
||||
def toolsDirProp = project.providers.gradleProperty("com.google.android.filament.tools-dir")
|
||||
if (toolsDirProp.isPresent() && !toolsDirProp.get().trim().isEmpty()) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.68.5
|
||||
VERSION_NAME=1.70.2
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -94,6 +94,13 @@ afterEvaluate { project ->
|
||||
}
|
||||
|
||||
signing {
|
||||
def signingKey = findProperty("signingKey")
|
||||
def signingPassword = findProperty("signingPassword")
|
||||
if (signingKey && signingPassword) {
|
||||
println("Signing with in-memory keys")
|
||||
useInMemoryPgpKeys(signingKey, signingPassword)
|
||||
}
|
||||
|
||||
publishing.publications.all { publication ->
|
||||
sign publication
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'filament-tools-plugin'
|
||||
id 'com.google.android.filament-tools'
|
||||
}
|
||||
|
||||
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 'com.google.android.filament-tools'
|
||||
}
|
||||
|
||||
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 'com.google.android.filament-tools'
|
||||
}
|
||||
|
||||
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 'com.google.android.filament-tools'
|
||||
}
|
||||
|
||||
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 'com.google.android.filament-tools'
|
||||
}
|
||||
|
||||
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 'com.google.android.filament-tools'
|
||||
}
|
||||
|
||||
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 'com.google.android.filament-tools'
|
||||
}
|
||||
|
||||
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 'com.google.android.filament-tools'
|
||||
}
|
||||
|
||||
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 'com.google.android.filament-tools'
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
57
android/samples/sample-render-validation/build.gradle
Normal file
57
android/samples/sample-render-validation/build.gradle
Normal file
@@ -0,0 +1,57 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
}
|
||||
|
||||
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/envs"
|
||||
delete "src/main/assets/models"
|
||||
}
|
||||
|
||||
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 'com.google.android.material:material:1.10.0'
|
||||
implementation deps.coroutines.core
|
||||
implementation project(':filament-android')
|
||||
implementation project(':gltfio-android')
|
||||
implementation project(':filament-utils-android')
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?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="@style/Theme.Material3.DayNight.NoActionBar">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
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,899 @@
|
||||
{
|
||||
"name": "Default Test",
|
||||
"backends": [
|
||||
"opengl"
|
||||
],
|
||||
"models": {
|
||||
"DamagedHelmet": "helmet.glb"
|
||||
},
|
||||
"presets": [
|
||||
{
|
||||
"name": "base",
|
||||
"models": [
|
||||
"DamagedHelmet"
|
||||
],
|
||||
"rendering": {
|
||||
"camera": {
|
||||
"enabled": true,
|
||||
"horizontalFov": 45.0,
|
||||
"center": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"lookAt": [
|
||||
0,
|
||||
0,
|
||||
-1
|
||||
]
|
||||
},
|
||||
"view": {
|
||||
"postProcessingEnabled": true,
|
||||
"dithering": "NONE"
|
||||
}
|
||||
},
|
||||
"tolerance": {
|
||||
"maxAbsDiff": 0.1,
|
||||
"maxFailingPixelsFraction": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tilted",
|
||||
"rendering": {
|
||||
"camera": {
|
||||
"enabled": true,
|
||||
"horizontalFov": 45.0,
|
||||
"center": [
|
||||
-4,
|
||||
-2,
|
||||
-3
|
||||
],
|
||||
"lookAt": [
|
||||
0,
|
||||
0,
|
||||
-4
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"name": "basic",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "rotated",
|
||||
"apply_presets": [
|
||||
"base",
|
||||
"tilted"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ssao",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "msaa",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.msaa.enabled": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom",
|
||||
"apply_presets": [
|
||||
"base",
|
||||
"tilted"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "aa_none",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.antiAliasing": "NONE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "aa_fxaa",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.antiAliasing": "FXAA"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dithering_none",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.dithering": "NONE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "msaa_8",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.msaa.enabled": true,
|
||||
"view.msaa.sampleCount": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "taa_custom",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.taa.enabled": true,
|
||||
"view.taa.feedback": 0.2,
|
||||
"view.taa.jitterPattern": "HALTON_23_X16"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_gtao",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.aoType": "GTAO"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_sao",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.aoType": "SAO"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_radius_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.radius": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_radius_low",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.radius": 0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_power_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.power": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_power_low",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.power": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_bias_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.bias": 0.05
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_resolution_half",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.resolution": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_resolution_full",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.resolution": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_intensity_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.intensity": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_bent_normals",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.bentNormals": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_quality_ultra",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.quality": "ULTRA"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_quality_low",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.quality": "LOW"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_lowPassFilter_ultra",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.lowPassFilter": "ULTRA"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_upsampling_ultra",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.upsampling": "ULTRA"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssr_basic",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.screenSpaceReflections.enabled": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssr_thickness_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.screenSpaceReflections.enabled": true,
|
||||
"view.screenSpaceReflections.thickness": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssr_bias_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.screenSpaceReflections.enabled": true,
|
||||
"view.screenSpaceReflections.bias": 0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssr_maxDistance_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.screenSpaceReflections.enabled": true,
|
||||
"view.screenSpaceReflections.maxDistance": 5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssr_stride_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.screenSpaceReflections.enabled": true,
|
||||
"view.screenSpaceReflections.stride": 4.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssr_stride_low",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.screenSpaceReflections.enabled": true,
|
||||
"view.screenSpaceReflections.stride": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssr_thickness_low",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.screenSpaceReflections.enabled": true,
|
||||
"view.screenSpaceReflections.thickness": 0.01
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssr_maxDistance_low",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.screenSpaceReflections.enabled": true,
|
||||
"view.screenSpaceReflections.maxDistance": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssr_bias_low",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.screenSpaceReflections.enabled": true,
|
||||
"view.screenSpaceReflections.bias": 0.001
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssr_quality_combo",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.screenSpaceReflections.enabled": true,
|
||||
"view.screenSpaceReflections.thickness": 0.2,
|
||||
"view.screenSpaceReflections.stride": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_levels_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.levels": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_levels_low",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.levels": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_resolution_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.resolution": 1024
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_strength_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.strength": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_strength_low",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.strength": 0.01
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_blendMode_interpolate",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.blendMode": "INTERPOLATE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_no_threshold",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.threshold": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_quality_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.quality": "HIGH"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_lensflare",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.lensFlare": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_lensflare_no_starburst",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.lensFlare": true,
|
||||
"view.bloom.starburst": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_lensflare_chromatic",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.lensFlare": true,
|
||||
"view.bloom.chromaticAberration": 0.05
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_lensflare_ghosts",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.lensFlare": true,
|
||||
"view.bloom.ghostCount": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_lensflare_ghostSpacing",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.lensFlare": true,
|
||||
"view.bloom.ghostSpacing": 0.8
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_halo_thick",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.lensFlare": true,
|
||||
"view.bloom.haloThickness": 0.2
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_halo_radius",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.lensFlare": true,
|
||||
"view.bloom.haloRadius": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dof_basic",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.dof.enabled": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dof_cocScale_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.dof.enabled": true,
|
||||
"view.dof.cocScale": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dof_cocScale_low",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.dof.enabled": true,
|
||||
"view.dof.cocScale": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dof_cocAspectRatio",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.dof.enabled": true,
|
||||
"view.dof.cocAspectRatio": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dof_maxApertureDiameter",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.dof.enabled": true,
|
||||
"view.dof.maxApertureDiameter": 0.05
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dof_filter_none",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.dof.enabled": true,
|
||||
"view.dof.filter": "NONE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dof_nativeResolution",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.dof.enabled": true,
|
||||
"view.dof.nativeResolution": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dof_rings_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.dof.enabled": true,
|
||||
"view.dof.foregroundRingCount": 5,
|
||||
"view.dof.backgroundRingCount": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dof_rings_low",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.dof.enabled": true,
|
||||
"view.dof.foregroundRingCount": 3,
|
||||
"view.dof.backgroundRingCount": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dof_max_coc",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.dof.enabled": true,
|
||||
"view.dof.maxForegroundCOC": 16,
|
||||
"view.dof.maxBackgroundCOC": 16
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fog_basic",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.fog.enabled": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fog_distance",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.fog.enabled": true,
|
||||
"view.fog.distance": 10.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fog_cutOffDistance",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.fog.enabled": true,
|
||||
"view.fog.cutOffDistance": 100.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fog_maximumOpacity",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.fog.enabled": true,
|
||||
"view.fog.maximumOpacity": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fog_height",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.fog.enabled": true,
|
||||
"view.fog.height": 5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fog_heightFalloff",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.fog.enabled": true,
|
||||
"view.fog.heightFalloff": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fog_density_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.fog.enabled": true,
|
||||
"view.fog.density": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fog_inScatteringStart",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.fog.enabled": true,
|
||||
"view.fog.inScatteringStart": 5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fog_inScatteringSize",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.fog.enabled": true,
|
||||
"view.fog.inScatteringSize": 10.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fog_fogColorFromIbl",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.fog.enabled": true,
|
||||
"view.fog.fogColorFromIbl": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "vignette_basic",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.vignette.enabled": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "vignette_midPoint",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.vignette.enabled": true,
|
||||
"view.vignette.midPoint": 0.8
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "vignette_roundness_circle",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.vignette.enabled": true,
|
||||
"view.vignette.roundness": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "vignette_roundness_rect",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.vignette.enabled": true,
|
||||
"view.vignette.roundness": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "vignette_feather_sharp",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.vignette.enabled": true,
|
||||
"view.vignette.feather": 0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cg_filmic",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.colorGrading.toneMapping": "FILMIC"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cg_aces",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.colorGrading.toneMapping": "ACES"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cg_agx",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.colorGrading.toneMapping": "AGX"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cg_pbr_neutral",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.colorGrading.toneMapping": "PBR_NEUTRAL"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cg_contrast_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.colorGrading.contrast": 1.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cg_saturation_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.colorGrading.saturation": 1.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cg_exposure_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.colorGrading.exposure": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "shadow_vsm",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.shadowType": "VSM"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "shadow_vsm_anisotropy",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.shadowType": "VSM",
|
||||
"view.vsmShadowOptions.anisotropy": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "shadow_vsm_highPrecision",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.shadowType": "VSM",
|
||||
"view.vsmShadowOptions.highPrecision": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,758 @@
|
||||
/*
|
||||
* 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.app.AlertDialog
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.text.Html
|
||||
import android.util.Log
|
||||
import android.view.Choreographer
|
||||
import android.view.SurfaceView
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Spinner
|
||||
import android.widget.TextView
|
||||
import com.google.android.filament.utils.KTX1Loader
|
||||
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 java.io.File
|
||||
import java.nio.ByteBuffer
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
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 testResultsHeader: TextView
|
||||
private lateinit var resultsContainer: LinearLayout
|
||||
private lateinit var inputManager: ValidationInputManager
|
||||
private var currentInput: ValidationInputManager.ValidationInput? = null
|
||||
|
||||
private var currentAlphaDiffBitmap: Bitmap? = null
|
||||
private var globalEnhancementFactor: Float = 1.0f
|
||||
|
||||
private data class TestImages(
|
||||
val testName: String,
|
||||
val golden: Bitmap?,
|
||||
val rendered: Bitmap?,
|
||||
val diff: Bitmap?,
|
||||
val alphaDiff: Bitmap?
|
||||
)
|
||||
|
||||
private val diffImageViews = mutableListOf<ImageView>()
|
||||
|
||||
// UI Elements
|
||||
private lateinit var runButton: Button
|
||||
private lateinit var loadButton: Button
|
||||
private lateinit var optionsButton: Button
|
||||
private lateinit var enhancementContainer: LinearLayout
|
||||
private lateinit var enhancementLabel: TextView
|
||||
private lateinit var enhancementSlider: android.widget.SeekBar
|
||||
|
||||
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)
|
||||
testResultsHeader = findViewById(R.id.test_results_header)
|
||||
resultsContainer = findViewById(R.id.results_container)
|
||||
|
||||
runButton = findViewById(R.id.run_button)
|
||||
loadButton = findViewById(R.id.load_button)
|
||||
optionsButton = findViewById(R.id.options_button)
|
||||
enhancementContainer = findViewById(R.id.enhancement_container)
|
||||
enhancementLabel = findViewById(R.id.enhancement_label)
|
||||
enhancementSlider = findViewById(R.id.enhancement_slider)
|
||||
|
||||
enhancementSlider.setOnSeekBarChangeListener(object : android.widget.SeekBar.OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(seekBar: android.widget.SeekBar?, progress: Int, fromUser: Boolean) {
|
||||
globalEnhancementFactor = 1.0f + (progress / 100f) * 49.0f
|
||||
enhancementLabel.text = String.format(Locale.US, "Enhancement: %.1fx", globalEnhancementFactor)
|
||||
applyGlobalEnhancement()
|
||||
}
|
||||
override fun onStartTrackingTouch(seekBar: android.widget.SeekBar?) {}
|
||||
override fun onStopTrackingTouch(seekBar: android.widget.SeekBar?) {}
|
||||
})
|
||||
|
||||
// Setup Run Button
|
||||
runButton.setOnClickListener {
|
||||
currentInput?.let { input ->
|
||||
// Always use the generateGoldens flag from the intent/input
|
||||
startValidation(input)
|
||||
}
|
||||
}
|
||||
|
||||
// Setup Load Button
|
||||
loadButton.setOnClickListener {
|
||||
showLoadDialog()
|
||||
}
|
||||
|
||||
// Setup Options Menu Button
|
||||
optionsButton.setOnClickListener { view ->
|
||||
val popup = android.widget.PopupMenu(this, view)
|
||||
popup.menu.add(0, 1, 0, "Generate Golden")
|
||||
popup.menu.add(0, 2, 0, "Export Test")
|
||||
popup.menu.add(0, 3, 0, "Export Result")
|
||||
popup.menu.add(0, 4, 0, "Test ADB Info")
|
||||
popup.menu.add(0, 5, 0, "Result ADB Info")
|
||||
popup.menu.add(0, 6, 0, "Toggle Enhancement Slider")
|
||||
|
||||
popup.setOnMenuItemClickListener { item ->
|
||||
when (item.itemId) {
|
||||
1 -> {
|
||||
currentInput?.let { input ->
|
||||
val goldenInput = input.copy(generateGoldens = true)
|
||||
startValidation(goldenInput)
|
||||
}
|
||||
}
|
||||
2 -> exportTestBundleAction()
|
||||
3 -> exportTestResultsAction()
|
||||
4 -> showTestAdbInfo()
|
||||
5 -> showResultAdbInfo()
|
||||
6 -> {
|
||||
enhancementContainer.visibility = if (enhancementContainer.visibility == View.VISIBLE) View.GONE else View.VISIBLE
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
popup.show()
|
||||
}
|
||||
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
|
||||
choreographer = Choreographer.getInstance()
|
||||
modelViewer = ModelViewer(surfaceView=surfaceView, manipulator=null)
|
||||
inputManager = ValidationInputManager(this)
|
||||
|
||||
// Initialize IBL
|
||||
createIndirectLight()
|
||||
|
||||
handleIntent()
|
||||
}
|
||||
|
||||
private fun showLoadDialog() {
|
||||
val exportDir = getExternalFilesDir(null) ?: filesDir
|
||||
// Filter out result zips (starting with "results_") to only show test bundles
|
||||
val zips = exportDir.listFiles { _, name ->
|
||||
name.endsWith(".zip") && !name.startsWith("results_")
|
||||
}?.sortedByDescending { it.lastModified() } ?: emptyList()
|
||||
|
||||
if (zips.isEmpty()) {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle("Load Test")
|
||||
.setMessage("No test bundles found.")
|
||||
.setPositiveButton("OK", null)
|
||||
.show()
|
||||
return
|
||||
}
|
||||
|
||||
val builder = AlertDialog.Builder(this)
|
||||
builder.setTitle("Select Test Bundle")
|
||||
|
||||
val items = zips.map { it.name }.toTypedArray()
|
||||
|
||||
builder.setItems(items) { dialog, which ->
|
||||
val selectedFile = zips[which]
|
||||
loadZipBundle(selectedFile)
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
builder.setNegativeButton("Cancel", null)
|
||||
builder.show()
|
||||
}
|
||||
|
||||
private fun showTestAdbInfo() {
|
||||
val exportDir = getExternalFilesDir(null) ?: filesDir
|
||||
val path = exportDir.absolutePath
|
||||
val isInternal = path.startsWith(filesDir.absolutePath)
|
||||
val message = StringBuilder()
|
||||
|
||||
message.append("Storage Path: $path<br><br>")
|
||||
|
||||
message.append("<b>--- PULL FROM DEVICE ---</b><br>")
|
||||
if (isInternal) {
|
||||
message.append("<tt>adb shell \"run-as $packageName cat files/<filename>\" > <filename></tt><br><br>")
|
||||
} else {
|
||||
message.append("<tt>adb pull $path/<filename> .</tt><br><br>")
|
||||
}
|
||||
|
||||
message.append("<b>--- PUSH TO DEVICE ---</b><br>")
|
||||
if (isInternal) {
|
||||
message.append("1. <tt>adb push <filename> /sdcard/Download/</tt><br>")
|
||||
message.append("2. <tt>adb shell \"run-as $packageName cp /sdcard/Download/<filename> files/\"</tt><br>")
|
||||
} else {
|
||||
message.append("<tt>adb push <filename> $path/</tt><br>")
|
||||
}
|
||||
message.append("<br>Note: Use underscores instead of spaces in <filename>.")
|
||||
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle("Test Bundle ADB Info")
|
||||
.setMessage(Html.fromHtml(message.toString(), Html.FROM_HTML_MODE_LEGACY))
|
||||
.setPositiveButton("OK", null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun showResultAdbInfo() {
|
||||
val exportDir = getExternalFilesDir(null) ?: filesDir
|
||||
val path = exportDir.absolutePath
|
||||
val isInternal = path.startsWith(filesDir.absolutePath)
|
||||
val message = StringBuilder()
|
||||
|
||||
message.append("<b>--- PULL RESULTS ---</b><br>")
|
||||
if (isInternal) {
|
||||
message.append("<tt>adb shell \"run-as $packageName cat files/<filename>\" > <filename></tt><br><br>")
|
||||
} else {
|
||||
message.append("<tt>adb pull $path/<filename> .</tt><br><br>")
|
||||
}
|
||||
|
||||
message.append("<b>--- AVAILABLE RESULTS ---</b><br>")
|
||||
val zips = exportDir.listFiles { _, name ->
|
||||
name.endsWith(".zip") && name.startsWith("results_")
|
||||
}?.sortedByDescending { it.lastModified() } ?: emptyList()
|
||||
|
||||
if (zips.isEmpty()) {
|
||||
message.append("No result zips found.<br>")
|
||||
} else {
|
||||
zips.forEach { file ->
|
||||
message.append("${file.name}<br>")
|
||||
}
|
||||
}
|
||||
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle("Result ADB Info")
|
||||
.setMessage(Html.fromHtml(message.toString(), Html.FROM_HTML_MODE_LEGACY))
|
||||
.setPositiveButton("OK", null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun loadZipBundle(file: File) {
|
||||
statusTextView.text = "Loading ${file.name}..."
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
try {
|
||||
val config = inputManager.loadFromZip(file)
|
||||
val baseDir = getExternalFilesDir(null) ?: filesDir
|
||||
val outputDir = File(baseDir, "validation_results").apply { mkdirs() }
|
||||
|
||||
// Clear existing results UI and state
|
||||
resultsContainer.removeAllViews()
|
||||
diffImageViews.clear()
|
||||
resultManager = null
|
||||
|
||||
val newInput = ValidationInputManager.ValidationInput(
|
||||
config = config,
|
||||
outputDir = outputDir,
|
||||
generateGoldens = false,
|
||||
autoRun = false,
|
||||
autoExport = false,
|
||||
autoExportResults = false,
|
||||
sourceZip = file
|
||||
)
|
||||
|
||||
currentInput = newInput
|
||||
statusTextView.text = "Loaded ${config.name}"
|
||||
Log.i(TAG, "Setting header to: Test Results: ${config.name}")
|
||||
testResultsHeader.text = "${config.name}"
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to load zip", e)
|
||||
statusTextView.text = "Error: ${e.message}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// Update header
|
||||
Log.i(TAG, "handleIntent: Setting header to: Test Results: ${input.config.name}")
|
||||
testResultsHeader.text = "${input.config.name}"
|
||||
currentInput = input
|
||||
|
||||
if (input.autoRun) {
|
||||
startValidation(input)
|
||||
} else {
|
||||
// Just show status
|
||||
statusTextView.text = "Ready: ${input.config.name}"
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to resolve config", e)
|
||||
statusTextView.text = "Error: ${e.message}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createResultManager(outputDir: File): ValidationResultManager {
|
||||
val gpuDriverInfo = com.google.android.filament.utils.DeviceUtils.getGpuDriverInfo(modelViewer.engine)
|
||||
return ValidationResultManager(
|
||||
outputDir = outputDir,
|
||||
gpuDriverInfo = gpuDriverInfo,
|
||||
deviceName = android.os.Build.MODEL,
|
||||
deviceCodeName = android.os.Build.DEVICE,
|
||||
androidVersion = android.os.Build.VERSION.RELEASE,
|
||||
androidBuildNumber = android.os.Build.DISPLAY
|
||||
)
|
||||
}
|
||||
|
||||
private fun startValidation(input: ValidationInputManager.ValidationInput) {
|
||||
try {
|
||||
resultsContainer.removeAllViews()
|
||||
diffImageViews.clear()
|
||||
enhancementSlider.isEnabled = false
|
||||
Log.i(TAG, "Starting validation with config: ${input.config.name}")
|
||||
Log.i(TAG, "Output dir: ${input.outputDir.absolutePath}")
|
||||
|
||||
testResultsHeader.text = "${input.config.name}"
|
||||
|
||||
resultManager = createResultManager(input.outputDir)
|
||||
|
||||
validationRunner = ValidationRunner(this, modelViewer, input.config, resultManager!!)
|
||||
validationRunner?.callback = this
|
||||
validationRunner?.generateGoldens = input.generateGoldens
|
||||
validationRunner?.start()
|
||||
|
||||
} 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 onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
setIntent(intent)
|
||||
handleIntent()
|
||||
}
|
||||
|
||||
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 Layout
|
||||
val headerRow = LinearLayout(this)
|
||||
headerRow.orientation = LinearLayout.HORIZONTAL
|
||||
headerRow.gravity = android.view.Gravity.CENTER_VERTICAL
|
||||
|
||||
// Status Icon + Name
|
||||
val statusView = TextView(this)
|
||||
val icon = if (result.passed) "✔" else "✖"
|
||||
|
||||
statusView.text = "$icon ${result.testName}"
|
||||
statusView.setTextColor(
|
||||
if (result.passed) Color.parseColor("#4CAF50") else Color.parseColor("#F44336")
|
||||
)
|
||||
statusView.textSize = 12f
|
||||
statusView.setTypeface(null, android.graphics.Typeface.BOLD)
|
||||
headerRow.addView(statusView)
|
||||
|
||||
// Diff Metric (only show if it's relevant/there's a diff or we just always show it like before)
|
||||
val diffView = TextView(this)
|
||||
diffView.text = " (Diff: ${result.diffMetric})"
|
||||
diffView.textSize = 12f
|
||||
diffView.setTextColor(Color.GRAY)
|
||||
headerRow.addView(diffView)
|
||||
resultContainer.addView(headerRow)
|
||||
|
||||
// Images Row
|
||||
val imagesRow = LinearLayout(this)
|
||||
imagesRow.orientation = LinearLayout.HORIZONTAL
|
||||
|
||||
val testImages = TestImages(
|
||||
testName = result.testName,
|
||||
golden = currentGoldenBitmap,
|
||||
rendered = currentRenderedBitmap,
|
||||
diff = currentDiffBitmap,
|
||||
alphaDiff = currentAlphaDiffBitmap
|
||||
)
|
||||
|
||||
fun addImage(label: String, bitmap: Bitmap?, isDiff: Boolean) {
|
||||
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 = 9f
|
||||
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())
|
||||
|
||||
if (isDiff) {
|
||||
diffImageViews.add(iv)
|
||||
applyEnhancementToView(iv, globalEnhancementFactor)
|
||||
}
|
||||
|
||||
iv.setOnClickListener {
|
||||
showImageDialog(testImages, label)
|
||||
}
|
||||
|
||||
container.addView(iv)
|
||||
|
||||
imagesRow.addView(container)
|
||||
}
|
||||
}
|
||||
|
||||
addImage("Rendered", currentRenderedBitmap, false)
|
||||
addImage("Golden", currentGoldenBitmap, false)
|
||||
if (!result.passed) {
|
||||
addImage("Diff", currentDiffBitmap, true)
|
||||
}
|
||||
if (currentAlphaDiffBitmap != null) {
|
||||
addImage("Alpha Diff", currentAlphaDiffBitmap, true)
|
||||
}
|
||||
|
||||
resultContainer.addView(imagesRow)
|
||||
resultsContainer.addView(resultContainer)
|
||||
|
||||
// Clear current images for next test
|
||||
currentRenderedBitmap = null
|
||||
currentGoldenBitmap = null
|
||||
currentDiffBitmap = null
|
||||
currentAlphaDiffBitmap = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAllTestsFinished() {
|
||||
runOnUiThread {
|
||||
statusTextView.text = "All tests finished!"
|
||||
enhancementSlider.isEnabled = true
|
||||
Log.i(TAG, "All tests finished " + if (currentInput?.autoExport == true) "Exporting bundle" else "x")
|
||||
|
||||
if (currentInput?.autoExport == true) {
|
||||
exportTestBundleAction()
|
||||
}
|
||||
if (currentInput?.autoExportResults == true) {
|
||||
exportTestResultsAction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun exportTestBundleAction() {
|
||||
currentInput?.let { input ->
|
||||
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
|
||||
val rm = resultManager ?: createResultManager(input.outputDir)
|
||||
val zip = rm.exportTestBundle(input.config, timestamp)
|
||||
if (zip != null) {
|
||||
val msg = "Exported Bundle: ${zip.name}"
|
||||
statusTextView.text = msg
|
||||
Log.i(TAG, "Exported test bundle to ${zip.absolutePath}")
|
||||
} else {
|
||||
statusTextView.text = "Export Bundle failed"
|
||||
Log.e(TAG, "Export Bundle failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun exportTestResultsAction() {
|
||||
currentInput?.let { input ->
|
||||
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
|
||||
val rm = resultManager ?: createResultManager(input.outputDir)
|
||||
val zip = rm.exportTestResults(input.sourceZip, timestamp)
|
||||
if (zip != null) {
|
||||
val msg = "Exported Results: ${zip.name}"
|
||||
statusTextView.text = msg
|
||||
Log.i(TAG, "Exported results to ${zip.absolutePath}")
|
||||
} else {
|
||||
statusTextView.text = "Export Results failed"
|
||||
Log.e(TAG, "Export Results failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
"Alpha Diff" -> {
|
||||
currentAlphaDiffBitmap = bitmap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyEnhancementToView(iv: ImageView, factor: Float) {
|
||||
val cm = android.graphics.ColorMatrix()
|
||||
cm.setScale(factor, factor, factor, 1.0f)
|
||||
iv.colorFilter = android.graphics.ColorMatrixColorFilter(cm)
|
||||
}
|
||||
|
||||
private fun applyGlobalEnhancement() {
|
||||
for (iv in diffImageViews) {
|
||||
applyEnhancementToView(iv, globalEnhancementFactor)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showImageDialog(images: TestImages, initialLabel: String) {
|
||||
val dialogView = layoutInflater.inflate(R.layout.dialog_image_viewer, null)
|
||||
val dialog = AlertDialog.Builder(this)
|
||||
.setView(dialogView)
|
||||
.create()
|
||||
|
||||
val titleView = dialogView.findViewById<TextView>(R.id.dialog_title)
|
||||
val typeView = dialogView.findViewById<TextView>(R.id.dialog_image_type)
|
||||
val imageView = dialogView.findViewById<ImageView>(R.id.dialog_image)
|
||||
val btnClose = dialogView.findViewById<View>(R.id.btn_close)
|
||||
val btnReset = dialogView.findViewById<View>(R.id.btn_reset)
|
||||
val btnPrev = dialogView.findViewById<View>(R.id.btn_prev)
|
||||
val btnNext = dialogView.findViewById<View>(R.id.btn_next)
|
||||
|
||||
val enhancementContainer = dialogView.findViewById<View>(R.id.dialog_enhancement_container)
|
||||
val enhancementLabel = dialogView.findViewById<TextView>(R.id.dialog_enhancement_label)
|
||||
val enhancementSlider = dialogView.findViewById<android.widget.SeekBar>(R.id.dialog_enhancement_slider)
|
||||
|
||||
titleView.text = images.testName
|
||||
|
||||
val availableImages = mutableListOf<Pair<String, Bitmap>>()
|
||||
images.rendered?.let { availableImages.add(Pair("Rendered", it)) }
|
||||
images.golden?.let { availableImages.add(Pair("Golden", it)) }
|
||||
images.diff?.let { availableImages.add(Pair("Diff", it)) }
|
||||
images.alphaDiff?.let { availableImages.add(Pair("Alpha Diff", it)) }
|
||||
|
||||
if (availableImages.isEmpty()) return
|
||||
|
||||
var currentIndex = availableImages.indexOfFirst { it.first == initialLabel }
|
||||
if (currentIndex == -1) currentIndex = 0
|
||||
|
||||
var currentDialogEnhancement = globalEnhancementFactor
|
||||
|
||||
val matrix = android.graphics.Matrix()
|
||||
// Save initial values for translation tracking
|
||||
var lastTouchX = 0f
|
||||
var lastTouchY = 0f
|
||||
var isDragging = false
|
||||
|
||||
val scaleDetector = android.view.ScaleGestureDetector(this, object : android.view.ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
||||
override fun onScale(detector: android.view.ScaleGestureDetector): Boolean {
|
||||
matrix.postScale(detector.scaleFactor, detector.scaleFactor, detector.focusX, detector.focusY)
|
||||
imageView.imageMatrix = matrix
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
imageView.setOnTouchListener { _, event ->
|
||||
scaleDetector.onTouchEvent(event)
|
||||
when (event.actionMasked) {
|
||||
android.view.MotionEvent.ACTION_DOWN -> {
|
||||
lastTouchX = event.x
|
||||
lastTouchY = event.y
|
||||
isDragging = true
|
||||
}
|
||||
android.view.MotionEvent.ACTION_MOVE -> {
|
||||
if (isDragging && !scaleDetector.isInProgress) {
|
||||
val dx = event.x - lastTouchX
|
||||
val dy = event.y - lastTouchY
|
||||
matrix.postTranslate(dx, dy)
|
||||
imageView.imageMatrix = matrix
|
||||
}
|
||||
lastTouchX = event.x
|
||||
lastTouchY = event.y
|
||||
}
|
||||
android.view.MotionEvent.ACTION_UP, android.view.MotionEvent.ACTION_CANCEL -> {
|
||||
isDragging = false
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fun updateView() {
|
||||
val (label, bitmap) = availableImages[currentIndex]
|
||||
typeView.text = label
|
||||
imageView.setImageBitmap(bitmap)
|
||||
(imageView.drawable as? android.graphics.drawable.BitmapDrawable)?.setAntiAlias(false)
|
||||
(imageView.drawable as? android.graphics.drawable.BitmapDrawable)?.setFilterBitmap(false)
|
||||
imageView.imageMatrix = matrix
|
||||
|
||||
if (label == "Diff" || label == "Alpha Diff") {
|
||||
enhancementContainer.visibility = View.VISIBLE
|
||||
applyEnhancementToView(imageView, currentDialogEnhancement)
|
||||
} else {
|
||||
enhancementContainer.visibility = View.GONE
|
||||
imageView.colorFilter = null
|
||||
}
|
||||
}
|
||||
|
||||
fun resetMatrix() {
|
||||
val drawable = imageView.drawable ?: return
|
||||
val width = imageView.width.toFloat()
|
||||
val height = imageView.height.toFloat()
|
||||
val dw = drawable.intrinsicWidth.toFloat()
|
||||
val dh = drawable.intrinsicHeight.toFloat()
|
||||
|
||||
val scaleX = width / dw
|
||||
val scaleY = height / dh
|
||||
val scale = Math.min(scaleX, scaleY)
|
||||
|
||||
val dx = (width - dw * scale) / 2f
|
||||
val dy = (height - dh * scale) / 2f
|
||||
|
||||
matrix.reset()
|
||||
matrix.postScale(scale, scale)
|
||||
matrix.postTranslate(dx, dy)
|
||||
imageView.imageMatrix = matrix
|
||||
}
|
||||
|
||||
btnClose.setOnClickListener { dialog.dismiss() }
|
||||
btnReset.setOnClickListener { resetMatrix() }
|
||||
btnPrev.setOnClickListener {
|
||||
currentIndex = (currentIndex - 1 + availableImages.size) % availableImages.size
|
||||
updateView()
|
||||
}
|
||||
btnNext.setOnClickListener {
|
||||
currentIndex = (currentIndex + 1) % availableImages.size
|
||||
updateView()
|
||||
}
|
||||
|
||||
val defaultProgress = ((currentDialogEnhancement - 1.0f) / 49.0f * 100).toInt()
|
||||
val safeProgress = Math.max(0, Math.min(100, defaultProgress))
|
||||
enhancementSlider.progress = safeProgress
|
||||
enhancementLabel.text = String.format(Locale.US, "Enhance: %.1fx", currentDialogEnhancement)
|
||||
|
||||
enhancementSlider.setOnSeekBarChangeListener(object : android.widget.SeekBar.OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(seekBar: android.widget.SeekBar?, progress: Int, fromUser: Boolean) {
|
||||
currentDialogEnhancement = 1.0f + (progress / 100f) * 49.0f
|
||||
enhancementLabel.text = String.format(Locale.US, "Enhance: %.1fx", currentDialogEnhancement)
|
||||
updateView()
|
||||
}
|
||||
override fun onStartTrackingTouch(seekBar: android.widget.SeekBar?) {}
|
||||
override fun onStopTrackingTouch(seekBar: android.widget.SeekBar?) {}
|
||||
})
|
||||
|
||||
imageView.post {
|
||||
resetMatrix()
|
||||
}
|
||||
|
||||
updateView()
|
||||
dialog.show()
|
||||
}
|
||||
}
|
||||
@@ -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,264 @@
|
||||
/*
|
||||
* 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
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
/**
|
||||
* 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,
|
||||
val autoRun: Boolean = false,
|
||||
val autoExport: Boolean = false,
|
||||
val autoExportResults: Boolean = false,
|
||||
val sourceZip: File? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* 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 zipPath = intent.getStringExtra("zip_path")
|
||||
val generateGoldens = intent.getBooleanExtra("generate_goldens", false)
|
||||
val autoRun = intent.getBooleanExtra("auto_run", false)
|
||||
val autoExport = intent.getBooleanExtra("auto_export", false)
|
||||
val autoExportResults = intent.getBooleanExtra("auto_export_results", false)
|
||||
val outputPath = intent.getStringExtra("output_path")
|
||||
|
||||
Log.i(TAG, "Resolving config with outputPath: $outputPath")
|
||||
|
||||
val baseDir = context.getExternalFilesDir(null) ?: context.filesDir
|
||||
Log.i(TAG, "Base directory for resolution: ${baseDir.absolutePath}")
|
||||
|
||||
val outputDir = if (!outputPath.isNullOrBlank()) {
|
||||
val file = File(outputPath)
|
||||
val resolved = if (file.isAbsolute) {
|
||||
file
|
||||
} else {
|
||||
File(baseDir, outputPath)
|
||||
}
|
||||
|
||||
// Critical check: if resolved is root or very short, it's likely wrong
|
||||
if (resolved.absolutePath == "/" || resolved.parent == null) {
|
||||
Log.w(TAG, "Resolved outputDir is root ($resolved), defaulting to app-specific dir")
|
||||
File(baseDir, "validation_results")
|
||||
} else {
|
||||
resolved
|
||||
}
|
||||
} else {
|
||||
File(baseDir, "validation_results")
|
||||
}
|
||||
|
||||
if (!outputDir.exists() && !outputDir.mkdirs()) {
|
||||
Log.e(TAG, "Failed to create outputDir: ${outputDir.absolutePath}")
|
||||
}
|
||||
Log.i(TAG, "Final outputDir: ${outputDir.absolutePath}")
|
||||
|
||||
val sourceZipFile = if (zipPath != null) {
|
||||
val file = File(zipPath)
|
||||
if (file.isAbsolute) {
|
||||
file
|
||||
} else {
|
||||
File(baseDir, zipPath)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val config = when {
|
||||
sourceZipFile != null && sourceZipFile.exists() -> loadFromZip(sourceZipFile)
|
||||
urlConfig != null -> downloadConfig(urlConfig, urlModelsBase)
|
||||
testConfigPath != null -> ConfigParser.parseFromPath(testConfigPath)
|
||||
else -> extractDefaultAssets()
|
||||
}
|
||||
|
||||
return@withContext ValidationInput(config, outputDir, generateGoldens, autoRun, autoExport, autoExportResults, sourceZipFile)
|
||||
}
|
||||
|
||||
suspend fun loadFromZip(zipFile: File): RenderTestConfig {
|
||||
Log.i(TAG, "Unzipping validation bundle: ${zipFile.absolutePath}")
|
||||
// Use a unique cache dir based on timestamp or just overwrite a common one
|
||||
// Overwriting is safer to avoid filling up disk, but we must ensure we don't conflict with current run
|
||||
val baseCacheDir = context.externalCacheDir ?: context.cacheDir
|
||||
val cacheDir = File(baseCacheDir, "unzipped_validation")
|
||||
if (cacheDir.exists()) cacheDir.deleteRecursively()
|
||||
cacheDir.mkdirs()
|
||||
|
||||
Log.i(TAG, "Using cacheDir: ${cacheDir.absolutePath}")
|
||||
|
||||
ZipFile(zipFile).use { zip ->
|
||||
val entries = zip.entries()
|
||||
while (entries.hasMoreElements()) {
|
||||
val entry = entries.nextElement()
|
||||
val entryFile = File(cacheDir, entry.name)
|
||||
// specific check to avoid zip slip vulnerability (though low risk here)
|
||||
if (!entryFile.canonicalPath.startsWith(cacheDir.canonicalPath)) {
|
||||
throw SecurityException("Zip entry is outside of the target dir: ${entry.name}")
|
||||
}
|
||||
|
||||
if (entry.isDirectory) {
|
||||
entryFile.mkdirs()
|
||||
} else {
|
||||
entryFile.parentFile?.mkdirs()
|
||||
zip.getInputStream(entry).use { input ->
|
||||
FileOutputStream(entryFile).use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find config.json
|
||||
// We look for a file ending in .json within the unzipped structure
|
||||
// Exclude results.json if it happened to be there
|
||||
val jsonFiles = cacheDir.walkTopDown()
|
||||
.filter { it.isFile && it.extension == "json" && it.name != "results.json" }
|
||||
.toList()
|
||||
|
||||
if (jsonFiles.isEmpty()) throw IllegalStateException("No config.json found in zip")
|
||||
|
||||
// Prefer one named config.json or take the first one
|
||||
val configFile = jsonFiles.find { it.name == "config.json" } ?: jsonFiles.first()
|
||||
|
||||
Log.i(TAG, "Parsed config from ${configFile.absolutePath}")
|
||||
return ConfigParser.parseFromPath(configFile.absolutePath)
|
||||
}
|
||||
|
||||
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, "helmet.glb")
|
||||
|
||||
assetManager.open("models/helmet.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,307 @@
|
||||
/*
|
||||
* 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,
|
||||
private val gpuDriverInfo: String,
|
||||
private val deviceName: String,
|
||||
private val deviceCodeName: String,
|
||||
private val androidVersion: String,
|
||||
private val androidBuildNumber: String
|
||||
) {
|
||||
|
||||
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(totalTimeMs: Long): File? {
|
||||
// Write results JSON
|
||||
writeResultsJson(totalTimeMs)
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports a zip containing:
|
||||
* - results.json
|
||||
* - input test bundle (as nested zip), if provided
|
||||
* - diff images (if any failure)
|
||||
*/
|
||||
fun exportTestResults(sourceZip: File?, timestamp: String): File? {
|
||||
// Safe parent dir resolution
|
||||
val parentDir = outputDir.canonicalFile.parentFile ?: outputDir.parentFile
|
||||
if (parentDir == null) return null
|
||||
|
||||
val resultZipName = "results_$timestamp"
|
||||
val zipFile = File(parentDir, "$resultZipName.zip")
|
||||
|
||||
Log.i(TAG, "Exporting results to ${zipFile.absolutePath}")
|
||||
|
||||
try {
|
||||
ZipOutputStream(FileOutputStream(zipFile)).use { zos ->
|
||||
// 1. Add results.json
|
||||
val resultsJson = File(outputDir, "results.json")
|
||||
if (resultsJson.exists()) {
|
||||
zos.putNextEntry(ZipEntry("results.json"))
|
||||
resultsJson.inputStream().use { it.copyTo(zos) }
|
||||
zos.closeEntry()
|
||||
}
|
||||
|
||||
// 2. Add source zip if exists
|
||||
if (sourceZip != null && sourceZip.exists()) {
|
||||
zos.putNextEntry(ZipEntry(sourceZip.name))
|
||||
sourceZip.inputStream().use { it.copyTo(zos) }
|
||||
zos.closeEntry()
|
||||
}
|
||||
|
||||
// 3. Add images (only rendered images, exclude diffs)
|
||||
outputDir.listFiles { _, name -> name.endsWith(".png") && !name.endsWith("_diff.png") }?.forEach { imgFile ->
|
||||
zos.putNextEntry(ZipEntry(imgFile.name))
|
||||
imgFile.inputStream().use { it.copyTo(zos) }
|
||||
zos.closeEntry()
|
||||
}
|
||||
}
|
||||
Log.i(TAG, "Exported results to ${zipFile.absolutePath}")
|
||||
return zipFile
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to export results", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports a zip bundle containing:
|
||||
* - Modified config.json (with updated name and relative paths)
|
||||
* - Models (in models/ subdirectory)
|
||||
* - Golden images (in goldens/ subdirectory)
|
||||
*
|
||||
* Structure:
|
||||
* test_name_TIMESTAMP/
|
||||
* config.json
|
||||
* models/
|
||||
* model.glb
|
||||
* goldens/
|
||||
* test_result.png
|
||||
*/
|
||||
fun exportTestBundle(config: RenderTestConfig, timestamp: String): File? {
|
||||
Log.i(TAG, "Starting exportTestBundle for ${config.name} at $timestamp")
|
||||
Log.i(TAG, "OutputDir: ${outputDir.absolutePath}")
|
||||
|
||||
val parentDir = outputDir.canonicalFile.parentFile ?: outputDir.parentFile
|
||||
if (parentDir == null) {
|
||||
Log.e(TAG, "OutputDir parent is null: ${outputDir.absolutePath}")
|
||||
return null
|
||||
}
|
||||
Log.i(TAG, "Using parentDir for export: ${parentDir.absolutePath}")
|
||||
|
||||
val testNameWithTimestamp = "${config.name}_$timestamp"
|
||||
val exportNameNoSpaces = testNameWithTimestamp.replace(" ", "_")
|
||||
|
||||
val exportDir = File(parentDir, "export_temp_$timestamp")
|
||||
|
||||
Log.i(TAG, "Creating export temp dir: ${exportDir.absolutePath}")
|
||||
if (exportDir.exists()) exportDir.deleteRecursively()
|
||||
if (!exportDir.mkdirs()) {
|
||||
Log.e(TAG, "Failed to create export dir: ${exportDir.absolutePath}")
|
||||
return null
|
||||
}
|
||||
|
||||
val rootDir = File(exportDir, exportNameNoSpaces)
|
||||
rootDir.mkdirs()
|
||||
|
||||
val modelsDir = File(rootDir, "models")
|
||||
modelsDir.mkdirs()
|
||||
|
||||
val goldensDir = File(rootDir, "goldens")
|
||||
goldensDir.mkdirs()
|
||||
|
||||
try {
|
||||
// 1. Copy Models and update config map
|
||||
val newModelsMap = mutableMapOf<String, String>()
|
||||
Log.i(TAG, "Copying models...")
|
||||
for ((modelName, modelPath) in config.models) {
|
||||
val sourceFile = File(modelPath)
|
||||
if (sourceFile.exists()) {
|
||||
val destFile = File(modelsDir, sourceFile.name)
|
||||
Log.d(TAG, "Copying model $modelName: $modelPath -> ${destFile.name}")
|
||||
sourceFile.copyTo(destFile, overwrite = true)
|
||||
// Use relative path for the new config
|
||||
newModelsMap[modelName] = "models/${sourceFile.name}"
|
||||
} else {
|
||||
Log.w(TAG, "Model file not found: $modelPath")
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Copy Golden Images
|
||||
// We assume goldens are in outputDir with .png extension
|
||||
Log.i(TAG, "Copying goldens from ${outputDir.absolutePath}...")
|
||||
outputDir.listFiles { _, name -> name.endsWith(".png") }?.forEach { file ->
|
||||
Log.d(TAG, "Copying golden: ${file.name}")
|
||||
file.copyTo(File(goldensDir, file.name), overwrite = true)
|
||||
}
|
||||
|
||||
// 3. Create modified config JSON
|
||||
Log.i(TAG, "Creating config.json...")
|
||||
val newConfigJson = JSONObject()
|
||||
newConfigJson.put("name", testNameWithTimestamp) // Keep spaces in JSON name
|
||||
|
||||
// Reconstruct backends
|
||||
val backendsArray = JSONArray()
|
||||
config.backends.forEach { backendsArray.put(it) }
|
||||
newConfigJson.put("backends", backendsArray)
|
||||
|
||||
// Reconstruct models
|
||||
val modelsJson = JSONObject()
|
||||
for ((k, v) in newModelsMap) {
|
||||
modelsJson.put(k, v)
|
||||
}
|
||||
newConfigJson.put("models", modelsJson)
|
||||
|
||||
// Reconstruct tests
|
||||
val testsArray = JSONArray()
|
||||
for (test in config.tests) {
|
||||
val testJson = JSONObject()
|
||||
testJson.put("name", test.name)
|
||||
if (test.description != null) testJson.put("description", test.description)
|
||||
|
||||
// Models for this test (set of strings)
|
||||
val testModelsArray = JSONArray()
|
||||
test.models.forEach { testModelsArray.put(it) }
|
||||
testJson.put("models", testModelsArray)
|
||||
|
||||
// Rendering settings
|
||||
testJson.put("rendering", test.rendering)
|
||||
|
||||
// Tolerance
|
||||
if (test.tolerance != null) {
|
||||
testJson.put("tolerance", test.tolerance)
|
||||
}
|
||||
|
||||
// Backends (optional override)
|
||||
val testBackends = JSONArray()
|
||||
test.backends.forEach { testBackends.put(it) }
|
||||
testJson.put("backends", testBackends)
|
||||
|
||||
testsArray.put(testJson)
|
||||
}
|
||||
newConfigJson.put("tests", testsArray)
|
||||
|
||||
// Write config.json
|
||||
File(rootDir, "config.json").writeText(newConfigJson.toString(4))
|
||||
|
||||
// 4. Zip it
|
||||
val zipFile = File(parentDir, "$exportNameNoSpaces.zip")
|
||||
Log.i(TAG, "Zipping to ${zipFile.absolutePath}...")
|
||||
|
||||
ZipOutputStream(FileOutputStream(zipFile)).use { zos ->
|
||||
rootDir.walkTopDown().forEach { file ->
|
||||
if (file.isFile) {
|
||||
val entryName = file.relativeTo(exportDir).path
|
||||
zos.putNextEntry(ZipEntry(entryName))
|
||||
file.inputStream().use { it.copyTo(zos) }
|
||||
zos.closeEntry()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup temp dir
|
||||
exportDir.deleteRecursively()
|
||||
|
||||
Log.i(TAG, "Exported test bundle to ${zipFile.absolutePath}")
|
||||
return zipFile
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to export test bundle", e)
|
||||
exportDir.deleteRecursively()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun writeResultsJson(totalTimeMs: Long) {
|
||||
val rootObject = JSONObject()
|
||||
|
||||
val metadataObject = JSONObject()
|
||||
metadataObject.put("gpu_driver_info", gpuDriverInfo ?: "")
|
||||
metadataObject.put("total_time_ms", totalTimeMs)
|
||||
metadataObject.put("device_name", deviceName ?: "")
|
||||
metadataObject.put("device_code_name", deviceCodeName ?: "")
|
||||
metadataObject.put("android_version", androidVersion ?: "")
|
||||
metadataObject.put("android_build_number", androidBuildNumber ?: "")
|
||||
rootObject.put("metadata", metadataObject)
|
||||
|
||||
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)
|
||||
}
|
||||
rootObject.put("results", jsonArray)
|
||||
|
||||
val jsonFile = File(outputDir, "results.json")
|
||||
try {
|
||||
FileOutputStream(jsonFile).use { out ->
|
||||
out.write(rootObject.toString(4).toByteArray())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to write results.json", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
/*
|
||||
* 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 frameCounter = 0
|
||||
private var suiteStartTime: Long = 0
|
||||
|
||||
enum class State {
|
||||
IDLE,
|
||||
WAITING_FOR_RESOURCES,
|
||||
WARMUP,
|
||||
RUNNING_TEST
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
suiteStartTime = System.currentTimeMillis()
|
||||
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) {
|
||||
if (currentModelName == modelName) {
|
||||
Log.i("ValidationRunner", "Reusing model $modelName")
|
||||
callback?.onStatusChanged("Reusing $modelName for ${currentTestConfig?.name}")
|
||||
|
||||
modelViewer.resetToDefaultState()
|
||||
|
||||
frameCounter = 0
|
||||
currentState = State.WARMUP
|
||||
return
|
||||
}
|
||||
|
||||
currentModelName = modelName
|
||||
val modelPath = config.models[modelName]
|
||||
if (modelPath == null) {
|
||||
Log.e("ValidationRunner", "Model $modelName not found")
|
||||
nextModel()
|
||||
return
|
||||
}
|
||||
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.")
|
||||
modelViewer.transformToUnitCube()
|
||||
currentState = State.WAITING_FOR_RESOURCES
|
||||
frameCounter = 0
|
||||
Log.i("ValidationRunner", "State set to WAITING_FOR_RESOURCES")
|
||||
} catch (e: Exception) {
|
||||
Log.e("ValidationRunner", "Failed to load $path", e)
|
||||
nextModel()
|
||||
}
|
||||
}
|
||||
|
||||
fun onFrame(frameTimeNanos: Long) {
|
||||
when (currentState) {
|
||||
State.IDLE -> {}
|
||||
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 -> {
|
||||
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.IDLE
|
||||
captureAndCompare()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 modelParent = modelFile.parentFile!!
|
||||
|
||||
// Search for golden in:
|
||||
// 1. ../golden/ (standard structure)
|
||||
// 2. ../goldens/ (exported structure, sibling of models)
|
||||
// 3. ./goldens/ (backup)
|
||||
|
||||
val searchPaths = mutableListOf<File>()
|
||||
modelParent.parentFile?.let {
|
||||
searchPaths.add(it.resolve("golden"))
|
||||
searchPaths.add(it.resolve("goldens"))
|
||||
}
|
||||
searchPaths.add(modelParent.resolve("goldens"))
|
||||
|
||||
var goldenFile: File? = null
|
||||
for (path in searchPaths) {
|
||||
val f = path.resolve("${testFullName}.png")
|
||||
if (f.exists()) {
|
||||
goldenFile = f
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (goldenFile != null) {
|
||||
Log.i("ValidationRunner", "Found golden at ${goldenFile.absolutePath}")
|
||||
} else {
|
||||
Log.w("ValidationRunner", "Golden not found for $testFullName. Searched in: ${searchPaths.joinToString { it.absolutePath }}")
|
||||
// Fallback to old behavior for reference if everything else fails
|
||||
goldenFile = modelParent.parentFile?.resolve("golden/${testFullName}.png") ?: File("nonexistent")
|
||||
}
|
||||
|
||||
Thread {
|
||||
try {
|
||||
val flipped = bitmap
|
||||
|
||||
callback?.onImageResult("Rendered", flipped)
|
||||
|
||||
var passed = false
|
||||
var diffMetric = 0f
|
||||
|
||||
if (generateGoldens) {
|
||||
val targetGolden = goldenFile ?: modelParent.parentFile?.resolve("golden/${testFullName}.png") ?: File(modelParent, "golden/${testFullName}.png")
|
||||
targetGolden.parentFile?.mkdirs()
|
||||
FileOutputStream(targetGolden).use { out ->
|
||||
flipped.compress(Bitmap.CompressFormat.PNG, 100, out)
|
||||
}
|
||||
passed = true // Generating goldens always passes if successful
|
||||
callback?.onStatusChanged("Golden generated")
|
||||
} else {
|
||||
if (goldenFile != null && 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) {
|
||||
val diffImg = result.diffImage!!
|
||||
val width = diffImg.width
|
||||
val height = diffImg.height
|
||||
val pixels = IntArray(width * height)
|
||||
diffImg.getPixels(pixels, 0, width, 0, 0, width, height)
|
||||
|
||||
var hasAlphaDiff = false
|
||||
val alphaPixels = IntArray(width * height)
|
||||
|
||||
for (i in pixels.indices) {
|
||||
val color = pixels[i]
|
||||
|
||||
val a = android.graphics.Color.alpha(color)
|
||||
val r = android.graphics.Color.red(color)
|
||||
val g = android.graphics.Color.green(color)
|
||||
val b = android.graphics.Color.blue(color)
|
||||
|
||||
if (a > 0) {
|
||||
hasAlphaDiff = true
|
||||
}
|
||||
|
||||
// Map alpha diff to grayscale RGB
|
||||
alphaPixels[i] = android.graphics.Color.argb(255, a, a, a)
|
||||
|
||||
// Force main diff image alpha to 255
|
||||
pixels[i] = android.graphics.Color.argb(255, r, g, b)
|
||||
}
|
||||
|
||||
// Apply updated pixels to diff image
|
||||
diffImg.setPixels(pixels, 0, width, 0, 0, width, height)
|
||||
|
||||
// The C++ ImageDiff code sets isPremultiplied to false so Android
|
||||
// doesn't erase RGB diff values when Alpha diff is 0. However, Android's
|
||||
// Canvas will crash if we try to draw a non-premultiplied bitmap.
|
||||
// Since we just forced all alpha values to 255 (fully opaque) in the
|
||||
// loop above, we can safely mark it as premultiplied again here.
|
||||
diffImg.isPremultiplied = true
|
||||
callback?.onImageResult("Diff", diffImg)
|
||||
resultManager.saveImage("${testFullName}_diff", diffImg)
|
||||
|
||||
if (hasAlphaDiff) {
|
||||
val alphaDiffImg = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
alphaDiffImg.setPixels(alphaPixels, 0, width, 0, 0, width, height)
|
||||
callback?.onImageResult("Alpha Diff", alphaDiffImg)
|
||||
resultManager.saveImage("${testFullName}_alpha_diff", alphaDiffImg)
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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
|
||||
|
||||
val totalTimeMs = System.currentTimeMillis() - suiteStartTime
|
||||
|
||||
resultManager.finalizeResults(totalTimeMs)
|
||||
callback?.onAllTestsFinished()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?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_constraintWidth_percent="0.6"
|
||||
android:layout_marginTop="32dp"
|
||||
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>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/controls_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:paddingTop="20dp"
|
||||
android:paddingBottom="0dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/surface_container"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Initializing..."
|
||||
android:textSize="12sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingBottom="10dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/run_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
style="@style/Widget.Material3.Button"
|
||||
android:text="Run Test" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/load_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:text="Load Test"
|
||||
android:layout_marginStart="8dp"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/options_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.Material3.Button.IconButton.Outlined"
|
||||
app:icon="@android:drawable/ic_menu_more"
|
||||
android:contentDescription="More Options"
|
||||
android:layout_marginStart="8dp"/>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Global Enhancement Controls (Hidden by default) -->
|
||||
<LinearLayout
|
||||
android:id="@+id/enhancement_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:visibility="gone"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/enhancement_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Enhancement: 1.0x"
|
||||
android:textSize="12sp"
|
||||
android:minWidth="120dp" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/enhancement_slider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:max="100"
|
||||
android:progress="0" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/test_results_header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Test Results"
|
||||
android:gravity="center"
|
||||
android:textSize="14sp"
|
||||
android:paddingTop="0dp"
|
||||
android:paddingBottom="8dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/controls_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:paddingStart="20dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:paddingBottom="20dp">
|
||||
|
||||
<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,130 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout 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"
|
||||
android:orientation="vertical"
|
||||
android:background="#FF202020"
|
||||
android:padding="8dp">
|
||||
|
||||
<!-- Header with title and close button -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dialog_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Test Result"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
<ImageButton
|
||||
android:id="@+id/btn_close"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:src="@android:drawable/ic_menu_close_clear_cancel" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Subtitle for Image Type -->
|
||||
<TextView
|
||||
android:id="@+id/dialog_image_type"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start"
|
||||
android:text="Rendered"
|
||||
android:textColor="#BBBBBB"
|
||||
android:textSize="14sp"
|
||||
android:paddingBottom="8dp" />
|
||||
|
||||
<!-- Image Area with Arrows -->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<FrameLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintDimensionRatio="1:1">
|
||||
<ImageView
|
||||
android:id="@+id/dialog_image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="matrix" />
|
||||
</FrameLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<!-- Navigation Arrows -->
|
||||
<LinearLayout
|
||||
android:id="@+id/dialog_arrow_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_prev"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:src="@android:drawable/ic_media_previous"
|
||||
app:tint="#FFFFFF" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_reset"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:src="@android:drawable/ic_menu_revert"
|
||||
app:tint="#FFFFFF" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_next"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:src="@android:drawable/ic_media_next"
|
||||
app:tint="#FFFFFF" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Enhancement Controls (only for diff images) -->
|
||||
<LinearLayout
|
||||
android:id="@+id/dialog_enhancement_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dialog_enhancement_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Enhance: 1.0x"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="12sp"
|
||||
android:minWidth="100dp" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/dialog_enhancement_slider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:max="100"
|
||||
android:progress="0" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -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 'com.google.android.filament-tools'
|
||||
}
|
||||
|
||||
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 'com.google.android.filament-tools'
|
||||
}
|
||||
|
||||
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 'com.google.android.filament-tools'
|
||||
}
|
||||
|
||||
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 'com.google.android.filament-tools'
|
||||
}
|
||||
|
||||
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 'com.google.android.filament-tools'
|
||||
}
|
||||
|
||||
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,3 +1,8 @@
|
||||
// Filament tools plugin
|
||||
pluginManagement {
|
||||
includeBuild 'gradle-plugin'
|
||||
}
|
||||
|
||||
// Libraries
|
||||
include ':filament-android'
|
||||
include ':filamat-android'
|
||||
@@ -21,5 +26,6 @@ include ':samples:sample-texture-view'
|
||||
include ':samples:sample-texture-target'
|
||||
include ':samples:sample-textured-object'
|
||||
include ':samples:sample-transparent-view'
|
||||
include ':samples:sample-render-validation'
|
||||
|
||||
rootProject.name = 'filament'
|
||||
|
||||
@@ -39,6 +39,11 @@ if [[ "$TARGET" == "presubmit-with-test" ]]; then
|
||||
RUN_TESTS=-u
|
||||
fi
|
||||
|
||||
if [[ "$TARGET" == "presubmit-with-archive" ]]; then
|
||||
BUILD_RELEASE=release
|
||||
GENERATE_ARCHIVES=-a
|
||||
fi
|
||||
|
||||
if [[ "$TARGET" == "debug" ]]; then
|
||||
BUILD_DEBUG=debug
|
||||
GENERATE_ARCHIVES=-a
|
||||
|
||||
@@ -54,8 +54,13 @@ if [[ "$OS_NAME" == "Linux" ]]; then
|
||||
# is constantly being updated and sometimes not compatible with the current
|
||||
# linux platform.
|
||||
# Note that we assume this platform is compatible with ubuntu-22.04 x86_64
|
||||
DPKG_ARCH=$(dpkg --print-architecture)
|
||||
EXTRA_PACKAGES=""
|
||||
if [[ "$DPKG_ARCH" == "amd64" ]]; then
|
||||
EXTRA_PACKAGES="lib32gcc-s1 lib32stdc++6 libc6-i386"
|
||||
fi
|
||||
sudo apt-get -y install \
|
||||
autoconf automake autopoint autotools-dev bindgen bison build-essential bzip2 cpp cpp-11 debhelper debugedit dh-autoreconf dh-strip-nondeterminism diffstat directx-headers-dev dpkg-dev dwz flex g++ g++-11 gcc gcc-11 gcc-11-base:amd64 gettext glslang-tools icu-devtools intltool-debian lib32gcc-s1 lib32stdc++6 libarchive-zip-perl libasan6:amd64 libatomic1:amd64 libc-dev-bin libc6-dbg:amd64 libc6-dev:amd64 libc6-i386 libcc1-0:amd64 libclang-${GITHUB_CLANG_VERSION}-dev libclang-common-${GITHUB_CLANG_VERSION}-dev libclang-cpp${GITHUB_CLANG_VERSION} libclang-cpp${GITHUB_CLANG_VERSION}-dev libclang1-14 libclang1-${GITHUB_CLANG_VERSION} libclc-${GITHUB_CLANG_VERSION} libclc-${GITHUB_CLANG_VERSION}-dev libcrypt-dev:amd64 libdebhelper-perl libdpkg-perl libdrm-amdgpu1:amd64 libdrm-dev:amd64 libdrm-intel1:amd64 libdrm-nouveau2:amd64 libdrm-radeon1:amd64 libelf-dev:amd64 libexpat1-dev:amd64 libffi-dev:amd64 libfile-stripnondeterminism-perl libgc1:amd64 libgcc-11-dev:amd64 libgl1:amd64 libgl1-mesa-dri:amd64 libglapi-mesa:amd64 libglvnd-core-dev:amd64 libglvnd0:amd64 libglx-mesa0:amd64 libglx0:amd64 libgomp1:amd64 libicu-dev:amd64 libisl23:amd64 libitm1:amd64 libllvm14:amd64 libllvm${GITHUB_CLANG_VERSION}:amd64 libllvmspirvlib-${GITHUB_CLANG_VERSION}-dev:amd64 libllvmspirvlib${GITHUB_CLANG_VERSION}:amd64 liblsan0:amd64 libmpc3:amd64 libncurses-dev:amd64 libnsl-dev:amd64 libobjc-11-dev:amd64 libobjc4:amd64 libpciaccess-dev:amd64 libpciaccess0f:amd64 libpfm4:amd64 libpthread-stubs0-dev:amd64 libquadmath0:amd64 libsensors-config libsensors-dev:amd64 libsensors5:amd64 libset-scalar-perl libstd-rust-1.75:amd64 libstd-rust-dev:amd64 libstdc++-11-dev:amd64 libsub-override-perl libtinfo-dev:amd64 libtirpc-dev:amd64 libtool libtsan0:amd64 libubsan1:amd64 libva-dev:amd64 libva-drm2:amd64 libva-glx2:amd64 libva-wayland2:amd64 libva-x11-2:amd64 libva2:amd64 libvdpau-dev:amd64 libvdpau1:amd64 libvulkan-dev:amd64 libvulkan1:amd64 libwayland-bin libwayland-client0:amd64 libwayland-cursor0:amd64 libwayland-dev:amd64 libwayland-egl-backend-dev:amd64 libwayland-egl1:amd64 libwayland-server0:amd64 libx11-dev:amd64 libx11-xcb-dev:amd64 libx11-xcb1:amd64 libxau-dev:amd64 libxcb-dri2-0:amd64 libxcb-dri2-0-dev:amd64 libxcb-dri3-0:amd64 libxcb-dri3-dev:amd64 libxcb-glx0:amd64 libxcb-glx0-dev:amd64 libxcb-present-dev:amd64 libxcb-present0:amd64 libxcb-randr0:amd64 libxcb-randr0-dev:amd64 libxcb-render0:amd64 libxcb-render0-dev:amd64 libxcb-shape0:amd64 libxcb-shape0-dev:amd64 libxcb-shm0:amd64 libxcb-shm0-dev:amd64 libxcb-sync-dev:amd64 libxcb-sync1:amd64 libxcb-xfixes0:amd64 libxcb-xfixes0-dev:amd64 libxcb1-dev:amd64 libxdmcp-dev:amd64 libxext-dev:amd64 libxfixes-dev:amd64 libxfixes3:amd64 libxml2-dev:amd64 libxrandr-dev:amd64 libxrandr2:amd64 libxrender-dev:amd64 libxrender1:amd64 libxshmfence-dev:amd64 libxshmfence1:amd64 libxxf86vm-dev:amd64 libxxf86vm1:amd64 libz3-4:amd64 libz3-dev:amd64 libzstd-dev:amd64 linux-libc-dev:amd64 llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev llvm-${LLVM_VERSION}-linker-tools llvm-${LLVM_VERSION}-runtime llvm-${LLVM_VERSION}-tools llvm-spirv-${LLVM_VERSION} lto-disabled-list m4 make meson ninja-build pkg-config po-debconf python3-mako python3-ply python3-pygments quilt rpcsvc-proto rustc spirv-tools valgrind wayland-protocols x11proto-dev xorg-sgml-doctools xtrans-dev zlib1g-dev:amd64 \
|
||||
autoconf automake autopoint autotools-dev bindgen bison build-essential bzip2 cpp cpp-11 debhelper debugedit dh-autoreconf dh-strip-nondeterminism diffstat directx-headers-dev dpkg-dev dwz flex g++ g++-11 gcc gcc-11 gcc-11-base:${DPKG_ARCH} gettext glslang-tools icu-devtools intltool-debian ${EXTRA_PACKAGES} libarchive-zip-perl libasan6:${DPKG_ARCH} libatomic1:${DPKG_ARCH} libc-dev-bin libc6-dbg:${DPKG_ARCH} libc6-dev:${DPKG_ARCH} libcc1-0:${DPKG_ARCH} libclang-${GITHUB_CLANG_VERSION}-dev libclang-common-${GITHUB_CLANG_VERSION}-dev libclang-cpp${GITHUB_CLANG_VERSION} libclang-cpp${GITHUB_CLANG_VERSION}-dev libclang1-14 libclang1-${GITHUB_CLANG_VERSION} libclc-${GITHUB_CLANG_VERSION} libclc-${GITHUB_CLANG_VERSION}-dev libcrypt-dev:${DPKG_ARCH} libdebhelper-perl libdpkg-perl libdrm-amdgpu1:${DPKG_ARCH} libdrm-dev:${DPKG_ARCH} libdrm-intel1:${DPKG_ARCH} libdrm-nouveau2:${DPKG_ARCH} libdrm-radeon1:${DPKG_ARCH} libelf-dev:${DPKG_ARCH} libexpat1-dev:${DPKG_ARCH} libffi-dev:${DPKG_ARCH} libfile-stripnondeterminism-perl libgc1:${DPKG_ARCH} libgcc-11-dev:${DPKG_ARCH} libgl1:${DPKG_ARCH} libgl1-mesa-dri:${DPKG_ARCH} libglapi-mesa:${DPKG_ARCH} libglvnd-core-dev:${DPKG_ARCH} libglvnd0:${DPKG_ARCH} libglx-mesa0:${DPKG_ARCH} libglx0:${DPKG_ARCH} libgomp1:${DPKG_ARCH} libicu-dev:${DPKG_ARCH} libisl23:${DPKG_ARCH} libitm1:${DPKG_ARCH} libllvm14:${DPKG_ARCH} libllvm${GITHUB_CLANG_VERSION}:${DPKG_ARCH} libllvmspirvlib-${GITHUB_CLANG_VERSION}-dev:${DPKG_ARCH} libllvmspirvlib${GITHUB_CLANG_VERSION}:${DPKG_ARCH} liblsan0:${DPKG_ARCH} libmpc3:${DPKG_ARCH} libncurses-dev:${DPKG_ARCH} libnsl-dev:${DPKG_ARCH} libobjc-11-dev:${DPKG_ARCH} libobjc4:${DPKG_ARCH} libpciaccess-dev:${DPKG_ARCH} libpciaccess0f:${DPKG_ARCH} libpfm4:${DPKG_ARCH} libpthread-stubs0-dev:${DPKG_ARCH} libquadmath0:${DPKG_ARCH} libsensors-config libsensors-dev:${DPKG_ARCH} libsensors5:${DPKG_ARCH} libset-scalar-perl libstd-rust-1.75:${DPKG_ARCH} libstd-rust-dev:${DPKG_ARCH} libstdc++-11-dev:${DPKG_ARCH} libsub-override-perl libtinfo-dev:${DPKG_ARCH} libtirpc-dev:${DPKG_ARCH} libtool libtsan0:${DPKG_ARCH} libubsan1:${DPKG_ARCH} libva-dev:${DPKG_ARCH} libva-drm2:${DPKG_ARCH} libva-glx2:${DPKG_ARCH} libva-wayland2:${DPKG_ARCH} libva-x11-2:${DPKG_ARCH} libva2:${DPKG_ARCH} libvdpau-dev:${DPKG_ARCH} libvdpau1:${DPKG_ARCH} libvulkan-dev:${DPKG_ARCH} libvulkan1:${DPKG_ARCH} libwayland-bin libwayland-client0:${DPKG_ARCH} libwayland-cursor0:${DPKG_ARCH} libwayland-dev:${DPKG_ARCH} libwayland-egl-backend-dev:${DPKG_ARCH} libwayland-egl1:${DPKG_ARCH} libwayland-server0:${DPKG_ARCH} libx11-dev:${DPKG_ARCH} libx11-xcb-dev:${DPKG_ARCH} libx11-xcb1:${DPKG_ARCH} libxau-dev:${DPKG_ARCH} libxcb-dri2-0:${DPKG_ARCH} libxcb-dri2-0-dev:${DPKG_ARCH} libxcb-dri3-0:${DPKG_ARCH} libxcb-dri3-dev:${DPKG_ARCH} libxcb-glx0:${DPKG_ARCH} libxcb-glx0-dev:${DPKG_ARCH} libxcb-present-dev:${DPKG_ARCH} libxcb-present0:${DPKG_ARCH} libxcb-randr0:${DPKG_ARCH} libxcb-randr0-dev:${DPKG_ARCH} libxcb-render0:${DPKG_ARCH} libxcb-render0-dev:${DPKG_ARCH} libxcb-shape0:${DPKG_ARCH} libxcb-shape0-dev:${DPKG_ARCH} libxcb-shm0:${DPKG_ARCH} libxcb-shm0-dev:${DPKG_ARCH} libxcb-sync-dev:${DPKG_ARCH} libxcb-sync1:${DPKG_ARCH} libxcb-xfixes0:${DPKG_ARCH} libxcb-xfixes0-dev:${DPKG_ARCH} libxcb1-dev:${DPKG_ARCH} libxdmcp-dev:${DPKG_ARCH} libxext-dev:${DPKG_ARCH} libxfixes-dev:${DPKG_ARCH} libxfixes3:${DPKG_ARCH} libxml2-dev:${DPKG_ARCH} libxrandr-dev:${DPKG_ARCH} libxrandr2:${DPKG_ARCH} libxrender-dev:${DPKG_ARCH} libxrender1:${DPKG_ARCH} libxshmfence-dev:${DPKG_ARCH} libxshmfence1:${DPKG_ARCH} libxxf86vm-dev:${DPKG_ARCH} libxxf86vm1:${DPKG_ARCH} libz3-4:${DPKG_ARCH} libz3-dev:${DPKG_ARCH} libzstd-dev:${DPKG_ARCH} linux-libc-dev:${DPKG_ARCH} llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev llvm-${LLVM_VERSION}-linker-tools llvm-${LLVM_VERSION}-runtime llvm-${LLVM_VERSION}-tools llvm-spirv-${LLVM_VERSION} lto-disabled-list m4 make meson ninja-build pkg-config po-debconf python3-mako python3-ply python3-pygments quilt rpcsvc-proto rustc spirv-tools valgrind wayland-protocols x11proto-dev xorg-sgml-doctools xtrans-dev zlib1g-dev:${DPKG_ARCH} \
|
||||
clang-$GITHUB_CLANG_VERSION libc++-$GITHUB_CLANG_VERSION-dev libc++abi-$GITHUB_CLANG_VERSION-dev
|
||||
|
||||
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang-${GITHUB_CLANG_VERSION} 100
|
||||
@@ -80,6 +85,7 @@ elif [[ "$OS_NAME" == "Darwin" ]]; then
|
||||
if command -v brew > /dev/null 2>&1; then
|
||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=true brew install autoconf automake libx11 libxext libxrandr \
|
||||
llvm@${LLVM_VERSION} ninja meson pkg-config libxshmfence
|
||||
brew link --overwrite llvm@${LLVM_VERSION}
|
||||
# For reasons unknown, this is necessary for pkg-config to find homebrew's packages
|
||||
LOCAL_PKG_CONFIG_PATH="/opt/homebrew/lib/pkgconfig:$PKG_CONFIG_PATH"
|
||||
elif command -v port > /dev/null 2>&1; then
|
||||
|
||||
@@ -63,6 +63,10 @@ function _preferred_os_filename() {
|
||||
|
||||
function download_vulkan_installer() {
|
||||
local os=$(_get_os)
|
||||
if [[ "$os" == "linux" ]]; then
|
||||
echo "Linux uses apt to install vulkan dependencies, skipping tarball download." >&2
|
||||
return 0
|
||||
fi
|
||||
local dl_filename=$(_os_filename ${VULKAN_SDK_VERSION})
|
||||
local filename=$(_preferred_os_filename)
|
||||
local url=https://sdk.lunarg.com/sdk/download/$VULKAN_SDK_VERSION/$os/$dl_filename?Human=true
|
||||
@@ -79,15 +83,22 @@ function download_vulkan_installer() {
|
||||
|
||||
function unpack_vulkan_installer() {
|
||||
local os=$(_get_os)
|
||||
if [[ "$os" == "linux" ]]; then
|
||||
install_${os}
|
||||
return 0
|
||||
fi
|
||||
local filename=$(_preferred_os_filename $os)
|
||||
test -f $filename
|
||||
install_${os}
|
||||
}
|
||||
|
||||
function install_linux() {
|
||||
test -d $VULKAN_SDK_DIR && test -f vulkan_sdk.tar.gz
|
||||
echo "extract just the SDK's prebuilt binaries ($VULKAN_SDK_VERSION/x86_64) from vulkan_sdk.tar.gz into $VULKAN_SDK" >&2
|
||||
tar -C "$VULKAN_SDK_DIR" --strip-components 2 -xf vulkan_sdk.tar.gz $VULKAN_SDK_VERSION/x86_64
|
||||
echo "Installing Vulkan dependencies via apt for Linux..." >&2
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y libvulkan-dev vulkan-validationlayers glslang-tools spirv-tools
|
||||
|
||||
# Create a dummy SDK dir to satisfy scripts expecting a VulkanSDK folder structure
|
||||
mkdir -p ~/VulkanSDK/${VULKAN_SDK_VERSION}
|
||||
}
|
||||
|
||||
function install_mac() {
|
||||
|
||||
12
build/common/upload-release-assets/package-lock.json
generated
12
build/common/upload-release-assets/package-lock.json
generated
@@ -235,9 +235,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz",
|
||||
"integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
@@ -429,9 +429,9 @@
|
||||
"integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q=="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz",
|
||||
"integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==",
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ list(SORT NDK_VERSIONS)
|
||||
list(GET NDK_VERSIONS -1 NDK_VERSION)
|
||||
get_filename_component(NDK_VERSION ${NDK_VERSION} NAME)
|
||||
set(TOOLCHAIN ${ANDROID_HOME_UNIX}/ndk/${NDK_VERSION}/toolchains/llvm/prebuilt/${HOST_NAME_L}-x86_64)
|
||||
set(CMAKE_ANDROID_NDK_VERSION ${NDK_VERSION})
|
||||
|
||||
# specify the cross compiler
|
||||
set(COMPILER_SUFFIX)
|
||||
|
||||
@@ -47,6 +47,7 @@ list(SORT NDK_VERSIONS)
|
||||
list(GET NDK_VERSIONS -1 NDK_VERSION)
|
||||
get_filename_component(NDK_VERSION ${NDK_VERSION} NAME)
|
||||
set(TOOLCHAIN ${ANDROID_HOME_UNIX}/ndk/${NDK_VERSION}/toolchains/llvm/prebuilt/${HOST_NAME_L}-x86_64)
|
||||
set(CMAKE_ANDROID_NDK_VERSION ${NDK_VERSION})
|
||||
|
||||
# specify the cross compiler
|
||||
set(COMPILER_SUFFIX)
|
||||
|
||||
@@ -46,6 +46,7 @@ list(SORT NDK_VERSIONS)
|
||||
list(GET NDK_VERSIONS -1 NDK_VERSION)
|
||||
get_filename_component(NDK_VERSION ${NDK_VERSION} NAME)
|
||||
set(TOOLCHAIN ${ANDROID_HOME_UNIX}/ndk/${NDK_VERSION}/toolchains/llvm/prebuilt/${HOST_NAME_L}-x86_64)
|
||||
set(CMAKE_ANDROID_NDK_VERSION ${NDK_VERSION})
|
||||
|
||||
# specify the cross compiler
|
||||
set(COMPILER_SUFFIX)
|
||||
|
||||
@@ -46,6 +46,7 @@ list(SORT NDK_VERSIONS)
|
||||
list(GET NDK_VERSIONS -1 NDK_VERSION)
|
||||
get_filename_component(NDK_VERSION ${NDK_VERSION} NAME)
|
||||
set(TOOLCHAIN ${ANDROID_HOME_UNIX}/ndk/${NDK_VERSION}/toolchains/llvm/prebuilt/${HOST_NAME_L}-x86_64)
|
||||
set(CMAKE_ANDROID_NDK_VERSION ${NDK_VERSION})
|
||||
|
||||
# specify the cross compiler
|
||||
set(COMPILER_SUFFIX)
|
||||
|
||||
@@ -181,22 +181,21 @@ important for <code>matc</code> (material compiler).</p>
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.68.5'
|
||||
implementation 'com.google.android.filament:filament-android:1.70.0'
|
||||
}
|
||||
</code></pre>
|
||||
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
|
||||
<div class="table-wrapper"><table><thead><tr><th>Artifact</th><th>Description</th></tr></thead><tbody>
|
||||
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android/badge.svg?subject=filament-android" alt="filament-android" /></a></td><td>The Filament rendering engine itself.</td></tr>
|
||||
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android-debug"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android-debug/badge.svg?subject=filament-android-debug" alt="filament-android-debug" /></a></td><td>Debug version of <code>filament-android</code>.</td></tr>
|
||||
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/gltfio-android"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/gltfio-android/badge.svg?subject=gltfio-android" alt="gltfio-android" /></a></td><td>A glTF 2.0 loader for Filament, depends on <code>filament-android</code>.</td></tr>
|
||||
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-utils-android"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-utils-android/badge.svg?subject=filament-utils-android" alt="filament-utils-android" /></a></td><td>KTX loading, Kotlin math, and camera utilities, depends on <code>gltfio-android</code>.</td></tr>
|
||||
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android/badge.svg?subject=filamat-android" alt="filamat-android" /></a></td><td>A runtime material builder/compiler. This library is large but contains a full shader compiler/validator/optimizer and supports both OpenGL and Vulkan.</td></tr>
|
||||
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android-lite"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android-lite/badge.svg?subject=filamat-android-lite" alt="filamat-android-lite" /></a></td><td>A much smaller alternative to <code>filamat-android</code> that can only generate OpenGL shaders. It does not provide validation or optimizations.</td></tr>
|
||||
<tr><td><a href="https://mvnrepository.com/artifact/com.google.android.filament/filament-android"><img src="https://img.shields.io/maven-central/v/com.google.android.filament/filament-android?label=filament-android&color=green" alt="filament-android" /></a></td><td>The Filament rendering engine itself.</td></tr>
|
||||
<tr><td><a href="https://mvnrepository.com/artifact/com.google.android.filament/filament-android-debug"><img src="https://img.shields.io/maven-central/v/com.google.android.filament/filament-android-debug?label=filament-android-debug&color=green" alt="filament-android-debug" /></a></td><td>Debug version of <code>filament-android</code>.</td></tr>
|
||||
<tr><td><a href="https://mvnrepository.com/artifact/com.google.android.filament/gltfio-android"><img src="https://img.shields.io/maven-central/v/com.google.android.filament/gltfio-android?label=gltfio-android&color=green" alt="gltfio-android" /></a></td><td>A glTF 2.0 loader for Filament, depends on <code>filament-android</code>.</td></tr>
|
||||
<tr><td><a href="https://mvnrepository.com/artifact/com.google.android.filament/filament-utils-android"><img src="https://img.shields.io/maven-central/v/com.google.android.filament/filament-utils-android?label=filament-utils-android&color=green" alt="filament-utils-android" /></a></td><td>KTX loading, Kotlin math, and camera utilities, depends on <code>gltfio-android</code>.</td></tr>
|
||||
<tr><td><a href="https://mvnrepository.com/artifact/com.google.android.filament/filamat-android"><img src="https://img.shields.io/maven-central/v/com.google.android.filament/filamat-android?label=filamat-android&color=green" alt="filamat-android" /></a></td><td>A runtime material builder/compiler. This library is large but contains a full shader compiler/validator/optimizer and supports both OpenGL and Vulkan.</td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
|
||||
<p>iOS projects can use CocoaPods to install the latest release:</p>
|
||||
<pre><code class="language-shell">pod 'Filament', '~> 1.68.5'
|
||||
<pre><code class="language-shell">pod 'Filament', '~> 1.70.0'
|
||||
</code></pre>
|
||||
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
|
||||
<ul>
|
||||
@@ -230,7 +229,8 @@ sheet for the standard material model.</li>
|
||||
<li>OpenGL ES 3.0+ for Android and iOS</li>
|
||||
<li>Metal for macOS and iOS</li>
|
||||
<li>Vulkan 1.0 for Android, Linux, macOS, and Windows</li>
|
||||
<li>WebGL 2.0 for all platforms</li>
|
||||
<li>WebGPU for Android, Linux, macOS, and Windows</li>
|
||||
<li>WebGL 2.0 for all browsers supporting it</li>
|
||||
</ul>
|
||||
<h3 id="rendering"><a class="header" href="#rendering">Rendering</a></h3>
|
||||
<ul>
|
||||
@@ -265,7 +265,7 @@ sheet for the standard material model.</li>
|
||||
<ul>
|
||||
<li>HDR bloom</li>
|
||||
<li>Depth of field bokeh</li>
|
||||
<li>Multiple tone mappers: generic (customizable), ACES, filmic, etc.</li>
|
||||
<li>Multiple tone mappers: PBR Neutral, AgX, generic (customizable), ACES, filmic, etc.</li>
|
||||
<li>Color and tone management: luminance scaling, gamut mapping</li>
|
||||
<li>Color grading: exposure, night adaptation, white balance, channel mixer,
|
||||
shadows/mid-tones/highlights, ASC CDL, contrast, saturation, etc.</li>
|
||||
@@ -332,6 +332,8 @@ KHR_lights_punctual</li>
|
||||
<li><input disabled="" type="checkbox" checked=""/>
|
||||
KHR_materials_clearcoat</li>
|
||||
<li><input disabled="" type="checkbox" checked=""/>
|
||||
KHR_materials_dispersion</li>
|
||||
<li><input disabled="" type="checkbox" checked=""/>
|
||||
KHR_materials_emissive_strength</li>
|
||||
<li><input disabled="" type="checkbox" checked=""/>
|
||||
KHR_materials_ior</li>
|
||||
@@ -340,6 +342,8 @@ KHR_materials_pbrSpecularGlossiness</li>
|
||||
<li><input disabled="" type="checkbox" checked=""/>
|
||||
KHR_materials_sheen</li>
|
||||
<li><input disabled="" type="checkbox" checked=""/>
|
||||
KHR_materials_specular</li>
|
||||
<li><input disabled="" type="checkbox" checked=""/>
|
||||
KHR_materials_transmission</li>
|
||||
<li><input disabled="" type="checkbox" checked=""/>
|
||||
KHR_materials_unlit</li>
|
||||
@@ -348,8 +352,6 @@ KHR_materials_variants</li>
|
||||
<li><input disabled="" type="checkbox" checked=""/>
|
||||
KHR_materials_volume</li>
|
||||
<li><input disabled="" type="checkbox" checked=""/>
|
||||
KHR_materials_specular</li>
|
||||
<li><input disabled="" type="checkbox" checked=""/>
|
||||
KHR_mesh_quantization</li>
|
||||
<li><input disabled="" type="checkbox" checked=""/>
|
||||
KHR_texture_basisu</li>
|
||||
@@ -501,7 +503,7 @@ and tools.</p>
|
||||
<li><code>filamesh</code>: Mesh converter</li>
|
||||
<li><code>glslminifier</code>: Minifies GLSL source code</li>
|
||||
<li><code>matc</code>: Material compiler</li>
|
||||
<li><code>filament-matp</code>: Material parser</li>
|
||||
<li><code>matedit</code>: Material editor for compiled materials</li>
|
||||
<li><code>matinfo</code> Displays information about materials compiled with <code>matc</code></li>
|
||||
<li><code>mipgen</code> Generates a series of miplevels from a source image</li>
|
||||
<li><code>normal-blending</code>: Tool to blend normal maps</li>
|
||||
|
||||
@@ -352,7 +352,7 @@ used to create the SPIR-V is not available.</p>
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../dup/uberz.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<a rel="next prefetch" href="../dup/viewer.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
@@ -366,7 +366,7 @@ used to create the SPIR-V is not available.</p>
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../dup/uberz.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<a rel="next prefetch" href="../dup/viewer.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
@@ -225,9 +225,10 @@ into <strong>branch</strong> of <code>filament-assets</code>. This branch is pai
|
||||
<code>filament</code> repo.</p>
|
||||
<p>As an example, imagine I am working on a PR, and I've uploaded my change, which is in a
|
||||
branch called <code>my-pr-branch</code>, to <code>filament</code>. This PR requires updating the golden. We would do
|
||||
it in the following fashion</p>
|
||||
it in the following fashion on a macOS machine:</p>
|
||||
<h3 id="using-a-script-to-update-the-golden-repo"><a class="header" href="#using-a-script-to-update-the-golden-repo">Using a script to update the golden repo</a></h3>
|
||||
<ul>
|
||||
<li>Make sure you've completed the steps in 'Setting up python'</li>
|
||||
<li>Run interactive mode in the <code>update_golden.py</code> script.
|
||||
<pre><code>python3 test/renderdiff/src/update_golden.py
|
||||
</code></pre>
|
||||
@@ -263,6 +264,27 @@ branch of the golden repo (i.e. <code>my-pr-branch-golden</code>).</li>
|
||||
<li>If the PR is merged, then there is another workflow that will merge <code>my-pr-branch-golden</code>
|
||||
to the <code>main</code> branch of the golden repo.</li>
|
||||
</ul>
|
||||
<h3 id="automated-update-via-commit-message"><a class="header" href="#automated-update-via-commit-message">Automated update via commit message</a></h3>
|
||||
<p>Alternatively, if you are confident in your changes and want the CI to handle the update
|
||||
for you, you can use the following tag in your commit message:</p>
|
||||
<ul>
|
||||
<li>In the commit message of your working branch on <code>filament</code>, add the following line:
|
||||
<pre><code>RDIFF_ACCEPT_NEW_GOLDENS
|
||||
</code></pre>
|
||||
</li>
|
||||
</ul>
|
||||
<p>This has the following effects:</p>
|
||||
<ul>
|
||||
<li>The presubmit test <code>test-renderdiff</code> will be bypassed (it will not perform rendering or
|
||||
comparison).</li>
|
||||
<li>When the PR is merged, the postsubmit CI will automatically:
|
||||
<ol>
|
||||
<li>Build Filament and generate the new images.</li>
|
||||
<li>Upload them to a temporary branch in <code>filament-assets</code>.</li>
|
||||
<li>Merge that branch into <code>main</code>.</li>
|
||||
</ol>
|
||||
</li>
|
||||
</ul>
|
||||
<h2 id="viewing-test-results"><a class="header" href="#viewing-test-results">Viewing test results</a></h2>
|
||||
<p>We provide a viewer for looking at the result of a test run. The viewer is a webapp that can
|
||||
be used by pointing your browser to a localhost port. If you input the viewer with a PR or a
|
||||
|
||||
@@ -236,7 +236,7 @@ mapping should be specified as an <code>optional</code> feature of the ubershade
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
<a rel="prev" href="../dup/matdbg.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<a rel="prev" href="../dup/viewer.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
@@ -250,7 +250,7 @@ mapping should be specified as an <code>optional</code> feature of the ubershade
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
<a rel="prev" href="../dup/matdbg.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<a rel="prev" href="../dup/viewer.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
|
||||
333
docs/dup/viewer.html
Normal file
333
docs/dup/viewer.html
Normal file
@@ -0,0 +1,333 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="light sidebar-visible" dir="ltr">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>viewer - Filament</title>
|
||||
|
||||
|
||||
<!-- Custom HTML head -->
|
||||
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
<link rel="shortcut icon" href="../favicon.png">
|
||||
<link rel="stylesheet" href="../css/variables.css">
|
||||
<link rel="stylesheet" href="../css/general.css">
|
||||
<link rel="stylesheet" href="../css/chrome.css">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
|
||||
<link rel="stylesheet" href="../fonts/fonts.css">
|
||||
|
||||
<!-- Highlight.js Stylesheets -->
|
||||
<link rel="stylesheet" href="../highlight.css">
|
||||
<link rel="stylesheet" href="../tomorrow-night.css">
|
||||
<link rel="stylesheet" href="../ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme stylesheets -->
|
||||
|
||||
<!-- MathJax -->
|
||||
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
||||
|
||||
<!-- Provide site root to javascript -->
|
||||
<script>
|
||||
var path_to_root = "../";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
|
||||
</script>
|
||||
<!-- Start loading toc.js asap -->
|
||||
<script src="../toc.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="body-container">
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script>
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
|
||||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||||
}
|
||||
|
||||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||||
}
|
||||
} catch (e) { }
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script>
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
const html = document.documentElement;
|
||||
html.classList.remove('light')
|
||||
html.classList.add(theme);
|
||||
html.classList.add("js");
|
||||
</script>
|
||||
|
||||
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script>
|
||||
var sidebar = null;
|
||||
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||||
sidebar = sidebar || 'visible';
|
||||
} else {
|
||||
sidebar = 'hidden';
|
||||
}
|
||||
sidebar_toggle.checked = sidebar === 'visible';
|
||||
html.classList.remove('sidebar-visible');
|
||||
html.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<div style="display:flex;align-items:center;justify-content:center">
|
||||
<img class="flogo" src="../images/filament_logo_small.png"></img>
|
||||
</div>
|
||||
<!-- populated by js -->
|
||||
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
|
||||
<noscript>
|
||||
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
|
||||
</noscript>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
|
||||
<div class="sidebar-resize-indicator"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
<div id="menu-bar-hover-placeholder"></div>
|
||||
<div id="menu-bar" class="menu-bar sticky">
|
||||
<div class="left-buttons">
|
||||
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</label>
|
||||
<!-- Filament: disable themes because the markdeep part does not look good for dark themes -->
|
||||
<!--
|
||||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
</ul>
|
||||
-->
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h1 class="menu-title">Filament</h1>
|
||||
|
||||
<div class="right-buttons">
|
||||
<a href="https://github.com/google/filament" title="Git repository" aria-label="Git repository">
|
||||
<i id="git-repository-button" class="fa fa-github"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||||
<div id="searchresults-header" class="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script>
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1 id="viewer-library"><a class="header" href="#viewer-library">Viewer Library</a></h1>
|
||||
<p>The <strong>Viewer Library</strong> (<code>libs/viewer</code>) provides a high-level abstraction for configuring and rendering Filament scenes. It is used by tools like <code>gltf_viewer</code> to load assets, manage settings, and drive the rendering loop.</p>
|
||||
<h2 id="features"><a class="header" href="#features">Features</a></h2>
|
||||
<ul>
|
||||
<li><strong>Settings Management</strong>: Centralized configuration for View, Camera, Lights, and Materials via the <code>Settings</code> struct.</li>
|
||||
<li><strong>JSON Serialization</strong>: Full support for loading and saving settings via JSON.</li>
|
||||
<li><strong>Automation</strong>: <code>AutomationEngine</code> allows scripting the viewer with a sequence of JSON-based test cases (batch mode).</li>
|
||||
<li><strong>GUI Integration</strong>: Built-in support for <code>imgui</code> via <code>ViewerGui</code> and <code>Settings</code> binding.</li>
|
||||
</ul>
|
||||
<h2 id="json-settings-schema"><a class="header" href="#json-settings-schema">JSON Settings Schema</a></h2>
|
||||
<p>The viewer settings can be configured using a JSON object. This is used for <code>gltf_viewer --settings</code> or in automation specs.</p>
|
||||
<h3 id="root-object"><a class="header" href="#root-object">Root Object</a></h3>
|
||||
<p>The root object contains the following categories:</p>
|
||||
<div class="table-wrapper"><table><thead><tr><th style="text-align: left">Key</th><th style="text-align: left">Type</th><th style="text-align: left">Description</th></tr></thead><tbody>
|
||||
<tr><td style="text-align: left"><code>view</code></td><td style="text-align: left">Object</td><td style="text-align: left">Post-processing and rendering quality settings.</td></tr>
|
||||
<tr><td style="text-align: left"><code>camera</code></td><td style="text-align: left">Object</td><td style="text-align: left"><strong>[NEW]</strong> Explicit camera control (pose, projection, exposure).</td></tr>
|
||||
<tr><td style="text-align: left"><code>lighting</code></td><td style="text-align: left">Object</td><td style="text-align: left"><strong>[NEW]</strong> Environment and dynamic light settings.</td></tr>
|
||||
<tr><td style="text-align: left"><code>viewer</code></td><td style="text-align: left">Object</td><td style="text-align: left">Global viewer options (skybox, background, scaling).</td></tr>
|
||||
<tr><td style="text-align: left"><code>animation</code></td><td style="text-align: left">Object</td><td style="text-align: left"><strong>[NEW]</strong> Animation playback control.</td></tr>
|
||||
<tr><td style="text-align: left"><code>material</code></td><td style="text-align: left">Object</td><td style="text-align: left">Material overrides.</td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
<hr />
|
||||
<h3 id="camera-settings-camera"><a class="header" href="#camera-settings-camera">Camera Settings (<code>camera</code>)</a></h3>
|
||||
<p>Allows explicit control over the camera. If <code>enabled</code> is false, the viewer uses its default orbit camera logic (auto-scaling/centering).</p>
|
||||
<pre><code class="language-json">"camera": {
|
||||
"enabled": true, // Must be true to use these explicit settings
|
||||
"projection": "PERSPECTIVE", // "PERSPECTIVE" or "ORTHO"
|
||||
"center": [0, 0, 0], // World-space look-at point
|
||||
"lookAt": [0, 0, -1], // World-space eye position (confusingly named 'lookAt' in internal legacy, often 'eye')
|
||||
"up": [0, 1, 0], // Up vector
|
||||
"near": 0.1, // Near plane
|
||||
"far": 100.0, // Far plane
|
||||
"focalLength": 28.0, // Focal length in mm (Perspective only)
|
||||
"fov": 0.0, // Field of view in degrees (overrides focalLength if > 0)
|
||||
"aperture": 16.0, // f-stop
|
||||
"shutterSpeed": 125.0, // 1/seconds
|
||||
"sensitivity": 100.0, // ISO
|
||||
"focusDistance": 10.0, // Focus distance in world units
|
||||
"scaling": [1.0, 1.0], // Custom projection matrix scaling (mostly for Ortho)
|
||||
"shift": [0.0, 0.0] // Custom projection matrix shift
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="lighting-settings-lighting"><a class="header" href="#lighting-settings-lighting">Lighting Settings (<code>lighting</code>)</a></h3>
|
||||
<p>Controls the Image Based Lighting (IBL), the Sun, and additional dynamic lights.</p>
|
||||
<pre><code class="language-json">"lighting": {
|
||||
"iblIntensity": 30000.0,
|
||||
"iblRotation": 0.0, // Rotation in degrees
|
||||
"enableSunlight": true,
|
||||
"enableShadows": true,
|
||||
"sunlight": { // **[NEW]** Nested sunlight properties
|
||||
"intensity": 100000.0,
|
||||
"color": [0.98, 0.92, 0.89],
|
||||
"direction": [0.6, -1.0, -0.8],
|
||||
"sunHaloSize": 10.0,
|
||||
"sunHaloFalloff": 80.0,
|
||||
"sunAngularRadius": 1.9,
|
||||
"castShadows": true,
|
||||
"shadowOptions": { // Per-light shadow options
|
||||
"mapSize": 1024,
|
||||
"shadowCascades": 1,
|
||||
"stable": false
|
||||
}
|
||||
},
|
||||
"lights": [ // **[NEW]** Array of custom lights
|
||||
{
|
||||
"type": "POINT", // "POINT", "SPOT", "FOCUSED_SPOT", "DIRECTIONAL", "SUN"
|
||||
"position": [0, 2, 0],
|
||||
"color": [1, 0, 0],
|
||||
"intensity": 5000.0,
|
||||
"falloff": 10.0,
|
||||
"castShadows": true,
|
||||
"shadowOptions": { "mapSize": 512 }
|
||||
},
|
||||
{
|
||||
"type": "SPOT",
|
||||
"position": [2, 5, 2],
|
||||
"direction": [0, -1, 0],
|
||||
"spotInner": 0.5, // Inner cone angle (radians)
|
||||
"spotOuter": 0.8 // Outer cone angle (radians)
|
||||
}
|
||||
]
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="view-settings-view"><a class="header" href="#view-settings-view">View Settings (<code>view</code>)</a></h3>
|
||||
<p>Standard Filament view settings.</p>
|
||||
<pre><code class="language-json">"view": {
|
||||
"postProcessingEnabled": true,
|
||||
"antiAliasing": "FXAA", // "NONE", "FXAA"
|
||||
"msaa": {
|
||||
"enabled": true,
|
||||
"sampleCount": 4
|
||||
},
|
||||
"ssao": { "enabled": true, ... },
|
||||
"bloom": { "enabled": true, ... },
|
||||
"dof": { "enabled": false, ... },
|
||||
"vignette": { "enabled": false, ... },
|
||||
"colorGrading": {
|
||||
"toneMapping": "ACES_LEGACY", // "LINEAR", "ACES", "FILMIC", "PBR_NEUTRAL", etc.
|
||||
"exposure": 0.0,
|
||||
"gamma": [1.0, 1.0, 1.0]
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="viewer-options-viewer"><a class="header" href="#viewer-options-viewer">Viewer Options (<code>viewer</code>)</a></h3>
|
||||
<p>General app-level settings.</p>
|
||||
<pre><code class="language-json">"viewer": {
|
||||
"skyboxEnabled": true,
|
||||
"backgroundColor": [0, 0, 0], // Used if skybox is disabled
|
||||
"autoScaleEnabled": true, // Fit model to unit cube
|
||||
"groundPlaneEnabled": false
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="animation-settings-animation"><a class="header" href="#animation-settings-animation">Animation Settings (<code>animation</code>)</a></h3>
|
||||
<p>Control glTF animation playback.</p>
|
||||
<pre><code class="language-json">"animation": {
|
||||
"enabled": true,
|
||||
"speed": 1.0,
|
||||
"time": -1.0 // If >= 0, forces animation to this specific time (seconds)
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
</main>
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
<a rel="prev" href="../dup/matdbg.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../dup/uberz.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
<a rel="prev" href="../dup/matdbg.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../dup/uberz.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
window.playground_copyable = true;
|
||||
</script>
|
||||
|
||||
|
||||
<script src="../elasticlunr.min.js"></script>
|
||||
<script src="../mark.min.js"></script>
|
||||
<script src="../searcher.js"></script>
|
||||
|
||||
<script src="../clipboard.min.js"></script>
|
||||
<script src="../highlight.js"></script>
|
||||
<script src="../book.js"></script>
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -195,6 +195,30 @@ for inspection.</li>
|
||||
<p><strong>Tip:</strong> To view all defined subspecs, grep the podspec file:
|
||||
<code>grep "spec.subspec" ios/CocoaPods/Filament.podspec</code></p>
|
||||
</blockquote>
|
||||
<h2 id="testing-a-podspec-locally"><a class="header" href="#testing-a-podspec-locally">Testing a Podspec Locally</a></h2>
|
||||
<p>Before pushing a new version to the CocoaPods trunk, you should verify the podspec in a sample project.</p>
|
||||
<h3 id="using-the-sample-project"><a class="header" href="#using-the-sample-project">Using the Sample Project</a></h3>
|
||||
<p>Filament includes a dedicated sample project located in <code>ios/samples/HelloCocoaPods</code>. To test your
|
||||
local changes:</p>
|
||||
<ol>
|
||||
<li><strong>Modify the Podfile</strong>: Add a <code>:podspec</code> directive pointing to your local <code>.podspec</code> file.</li>
|
||||
</ol>
|
||||
<pre><code class="language-ruby">platform :ios, '13.0'
|
||||
|
||||
target 'HelloCocoaPods' do
|
||||
# Use the local development podspec
|
||||
pod 'Filament', :podspec => '../../CocoaPods/Filament.podspec'
|
||||
end
|
||||
</code></pre>
|
||||
<ol start="2">
|
||||
<li><strong>Sync Dependencies</strong>: Run the installation command from the sample directory:</li>
|
||||
</ol>
|
||||
<pre><code class="language-bash">pod install
|
||||
</code></pre>
|
||||
<ol start="3">
|
||||
<li><strong>Verify the Build</strong>: Open the generated <code>HelloCocoaPods.xcworkspace</code> in Xcode. Build and run the
|
||||
target to ensure the headers are found and the binaries link correctly.</li>
|
||||
</ol>
|
||||
|
||||
</main>
|
||||
|
||||
|
||||
@@ -199,17 +199,21 @@ Sonatype's <a href="https://central.sonatype.org/">Central Publisher Portal</a>.
|
||||
<p>The new Central Publisher Portal does not officially support Gradle. However, Sonatype provides a
|
||||
staging API compatibility service, which works with Filament's Gradle setup.</p>
|
||||
<hr />
|
||||
<h3 id="1-upload-to-the-staging-api-compatibility-service"><a class="header" href="#1-upload-to-the-staging-api-compatibility-service">1. Upload to the Staging API Compatibility Service</a></h3>
|
||||
<h3 id="1-upload-to-the-central-publisher-portal"><a class="header" href="#1-upload-to-the-central-publisher-portal">1. Upload to the Central Publisher Portal</a></h3>
|
||||
<p>To upload the artifacts, it is important to run both of these Gradle tasks together in a single
|
||||
command. This ensures the staging repository is created and closed automatically.</p>
|
||||
<pre><code class="language-bash">cd android
|
||||
./gradlew publishToSonatype
|
||||
./gradlew publishToSonatype closeSonatypeStagingRepository
|
||||
</code></pre>
|
||||
<h3 id="2-move-the-repository-to-the-central-publisher-portal"><a class="header" href="#2-move-the-repository-to-the-central-publisher-portal">2. Move the Repository to the Central Publisher Portal</a></h3>
|
||||
<p>We have a script to automate this. It reads the <code>sonatypeUsername</code> and <code>sonatypePassword</code> from your
|
||||
<code>~/.gradle/gradle.properties</code> file.</p>
|
||||
<h4 id="troubleshooting-manual-staging"><a class="header" href="#troubleshooting-manual-staging">Troubleshooting: Manual Staging</a></h4>
|
||||
<p>If you ran <code>publishToSonatype</code> by itself, the repository will remain open and won't appear in the
|
||||
portal correctly. You can fix this by running our automation script, which uses the
|
||||
<code>sonatypeUsername</code> and <code>sonatypePassword</code> from your ~/.gradle/gradle.properties file:</p>
|
||||
<pre><code class="language-bash">python3 build/common/close-sonatype-staging-repository.py
|
||||
</code></pre>
|
||||
<h3 id="3-publish-the-release-on-sonatype"><a class="header" href="#3-publish-the-release-on-sonatype">3. Publish the Release on Sonatype</a></h3>
|
||||
<p>Navigate to <a href="https://central.sonatype.com/publishing/deployments">Maven Central Repository Deployments</a>.</p>
|
||||
<h3 id="2-publish-the-release-on-sonatype"><a class="header" href="#2-publish-the-release-on-sonatype">2. Publish the Release on Sonatype</a></h3>
|
||||
<p>Once the upload is successful, you must manually trigger the final release. Navigate to <a href="https://central.sonatype.com/publishing/deployments">Maven
|
||||
Central Repository Deployments</a>.</p>
|
||||
<p>Here, you should see a new deployment with a <strong>Validated</strong> status and all your artifacts listed. Click
|
||||
the <strong>Publish</strong> button to publish the artifacts. It typically takes around 5 minutes after clicking
|
||||
<strong>Publish</strong> for the artifacts to go live.</p>
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user