Compare commits
157 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c772b06572 | ||
|
|
02602c6a9c | ||
|
|
ebda7353af | ||
|
|
9dfd3f73bc | ||
|
|
84c9752493 | ||
|
|
a102daed53 | ||
|
|
cab8a89346 | ||
|
|
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 | ||
|
|
90254338d6 | ||
|
|
06b7c1ffad | ||
|
|
e5fe3d495e | ||
|
|
6eff9a9b00 | ||
|
|
2f885cb66d | ||
|
|
35f501b3d5 | ||
|
|
5b799928c3 | ||
|
|
f7f586caff | ||
|
|
5428812b93 | ||
|
|
e57f2f5c02 | ||
|
|
fd9bcaa735 | ||
|
|
9a14e54fc2 | ||
|
|
d78bb294ed | ||
|
|
0b8dbe9b0a | ||
|
|
e595fd4b79 | ||
|
|
c5d36cff7f | ||
|
|
f392e8be54 | ||
|
|
83653fb358 | ||
|
|
8a3c48fef1 | ||
|
|
7f6b9bb144 | ||
|
|
687c42583b | ||
|
|
71e8cab08a | ||
|
|
afae31a975 | ||
|
|
5ac5dc4c95 | ||
|
|
e975572972 | ||
|
|
13dcae6d5f | ||
|
|
c14b428acc | ||
|
|
29e91f0d3a | ||
|
|
6f0d47f275 | ||
|
|
cf66813f41 | ||
|
|
5f89e8e711 | ||
|
|
be9e9298e1 | ||
|
|
9218b90c9c | ||
|
|
070a07679d | ||
|
|
10b7bd71f9 | ||
|
|
e4ae96a2a1 | ||
|
|
56ac08e353 | ||
|
|
52b0b553b4 | ||
|
|
00f3c7175c | ||
|
|
e4fa86fb01 | ||
|
|
7da2a08df6 | ||
|
|
82246d934d | ||
|
|
c60969ef67 | ||
|
|
ce37f216bc | ||
|
|
81c71fbbb9 | ||
|
|
07a7c6003a | ||
|
|
804a74c205 | ||
|
|
dde49a410a | ||
|
|
770ce7f8ec | ||
|
|
847d657ad5 | ||
|
|
48592d7d22 | ||
|
|
11714d3adc | ||
|
|
6aac9071b3 | ||
|
|
da9173e9dc | ||
|
|
cd64d50408 | ||
|
|
a3145cb96f | ||
|
|
cdfb92e14a | ||
|
|
551add7519 | ||
|
|
55c16e6e7a | ||
|
|
65e3c3bfb9 | ||
|
|
902f869721 | ||
|
|
b118ded3fa | ||
|
|
b3d0416a65 | ||
|
|
ad1bc6f360 | ||
|
|
1d5f6cd6a9 | ||
|
|
73c343635e | ||
|
|
432e672022 | ||
|
|
b56b04c5f8 | ||
|
|
99816d67c2 | ||
|
|
d6d4f92922 | ||
|
|
6a59a68622 | ||
|
|
4580f57987 | ||
|
|
5e4c24a7f6 | ||
|
|
3c27a83315 | ||
|
|
38f7e579f1 | ||
|
|
9b1c8a2bf5 | ||
|
|
4504471021 | ||
|
|
37c316fa03 | ||
|
|
14960f7118 | ||
|
|
1deb657442 | ||
|
|
45c0d1b34f | ||
|
|
1ddd10f326 | ||
|
|
308668a705 | ||
|
|
1cd48619e3 | ||
|
|
89c3b3f40b | ||
|
|
487f4d077d | ||
|
|
e830ec28e4 | ||
|
|
e00be09af6 | ||
|
|
b58ffb87e0 | ||
|
|
385d8969cf | ||
|
|
53bc372876 | ||
|
|
58f6d77e78 | ||
|
|
3769d0a9d3 | ||
|
|
2bc71240cf | ||
|
|
e1fb3f7442 | ||
|
|
e832805faf | ||
|
|
2ce71d6d98 | ||
|
|
26c51e0d9a | ||
|
|
510ae15867 | ||
|
|
d6caa9dc0b | ||
|
|
19209a00e6 | ||
|
|
188113bad6 | ||
|
|
5916837318 | ||
|
|
27aa517c48 | ||
|
|
4622e88a6b | ||
|
|
9bdb6acd63 | ||
|
|
751d213145 | ||
|
|
7ea1f97b57 | ||
|
|
0c3ae457a6 | ||
|
|
92d4be6923 | ||
|
|
ad8c188f58 | ||
|
|
9716b3924b | ||
|
|
ae9b951b08 | ||
|
|
78a0d8f4f6 | ||
|
|
675d8bc5be | ||
|
|
a90019baa2 | ||
|
|
72997ee71e |
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:
|
||||
|
||||
83
.github/workflows/release.yml
vendored
83
.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:
|
||||
@@ -188,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
|
||||
|
||||
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:
|
||||
|
||||
@@ -49,6 +49,8 @@ option(FILAMENT_ENABLE_COVERAGE "Enable LLVM code coverage" OFF)
|
||||
|
||||
option(FILAMENT_ENABLE_FEATURE_LEVEL_0 "Enable Feature Level 0" ON)
|
||||
|
||||
option(FILAMENT_ENABLE_MULTIVIEW "Enable multiview for Filament" OFF)
|
||||
|
||||
option(FILAMENT_SUPPORTS_OSMESA "Enable OSMesa (headless GL context) for Filament" OFF)
|
||||
|
||||
option(FILAMENT_ENABLE_FGVIEWER "Enable the frame graph viewer" OFF)
|
||||
@@ -63,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."
|
||||
)
|
||||
@@ -322,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()
|
||||
|
||||
@@ -605,6 +611,23 @@ else()
|
||||
option(FILAMENT_DISABLE_MATOPT "Disable material optimizations" ON)
|
||||
endif()
|
||||
|
||||
# This only affects the prebuilt shader files in gltfio and samples, not filament library.
|
||||
# The value can be either "instanced", "multiview", or "none"
|
||||
set(FILAMENT_SAMPLES_STEREO_TYPE "none" CACHE STRING
|
||||
"Stereoscopic type that shader files in gltfio and samples are built for."
|
||||
)
|
||||
string(TOLOWER "${FILAMENT_SAMPLES_STEREO_TYPE}" FILAMENT_SAMPLES_STEREO_TYPE)
|
||||
if (NOT FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "instanced"
|
||||
AND NOT FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "multiview"
|
||||
AND NOT FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "none")
|
||||
message(FATAL_ERROR "Invalid stereo type: \"${FILAMENT_SAMPLES_STEREO_TYPE}\" choose either \"instanced\", \"multiview\", or \"none\" ")
|
||||
endif ()
|
||||
|
||||
# Compiling samples for multiview implies enabling multiview feature as well.
|
||||
if (FILAMENT_SAMPLES_STEREO_TYPE STREQUAL "multiview")
|
||||
set(FILAMENT_ENABLE_MULTIVIEW ON)
|
||||
endif ()
|
||||
|
||||
# Define backend flag for debug only
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT FILAMENT_BACKEND_DEBUG_FLAG STREQUAL "")
|
||||
add_definitions(-DFILAMENT_BACKEND_DEBUG_FLAG=${FILAMENT_BACKEND_DEBUG_FLAG})
|
||||
@@ -846,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)
|
||||
@@ -881,7 +910,6 @@ 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)
|
||||
|
||||
@@ -955,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.69.2'
|
||||
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.69.2'
|
||||
pod 'Filament', '~> 1.70.2'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
@@ -89,7 +88,8 @@ pod 'Filament', '~> 1.69.2'
|
||||
- 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.69.2'
|
||||
|
||||
- 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.69.2'
|
||||
- [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,27 @@ 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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -2121,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;
|
||||
/**
|
||||
|
||||
@@ -61,6 +61,7 @@ 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
|
||||
|
||||
@@ -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
|
||||
|
||||
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());
|
||||
}
|
||||
@@ -20,8 +20,6 @@
|
||||
#include <imagediff/ImageDiff.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace imagediff;
|
||||
using namespace utils;
|
||||
|
||||
@@ -102,30 +100,48 @@ jobject createResult(JNIEnv* env, ImageDiffResult const& result, bool generateDi
|
||||
|
||||
if (generateDiff && result.diffImage.getWidth() > 0) {
|
||||
jclass bitmapClass = env->FindClass("android/graphics/Bitmap");
|
||||
jmethodID createBitmap = env->GetStaticMethodID(bitmapClass, "createBitmap",
|
||||
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);
|
||||
|
||||
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 channels = result.diffImage.getChannels(); // usually 4
|
||||
|
||||
for (size_t i = 0; i < width * height; ++i) {
|
||||
for (int c = 0; c < 4; ++c) {
|
||||
float v = 0.0f;
|
||||
if (c < channels) v = src[i * channels + c];
|
||||
if (c == 3 && channels < 4) v = 1.0f; // Alpha 1.0 if missing
|
||||
dst[i * 4 + c] = (uint8_t) std::min(255.0f, std::max(0.0f, v * 255.0f));
|
||||
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);
|
||||
@@ -133,7 +149,7 @@ jobject createResult(JNIEnv* env, ImageDiffResult const& result, bool generateDi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return resultObj;
|
||||
}
|
||||
|
||||
@@ -147,7 +163,7 @@ Java_com_google_android_filament_utils_ImageDiff_nCompareBasic(JNIEnv* env, jcla
|
||||
BitmapLock maskArg(env, maskBitmap);
|
||||
|
||||
if (!refArg.isValid() || !candArg.isValid()) {
|
||||
ImageDiffResult emptyResult;
|
||||
ImageDiffResult emptyResult;
|
||||
emptyResult.status = ImageDiffResult::Status::SIZE_MISMATCH; // or ERROR
|
||||
return createResult(env, emptyResult, false);
|
||||
}
|
||||
@@ -175,13 +191,13 @@ Java_com_google_android_filament_utils_ImageDiff_nCompareBasic(JNIEnv* env, jcla
|
||||
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;
|
||||
ImageDiffResult emptyResult;
|
||||
emptyResult.status = ImageDiffResult::Status::SIZE_MISMATCH; // or ERROR
|
||||
return createResult(env, emptyResult, false);
|
||||
}
|
||||
@@ -189,7 +205,7 @@ Java_com_google_android_filament_utils_ImageDiff_nCompareJson(JNIEnv* env, jclas
|
||||
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);
|
||||
|
||||
@@ -214,4 +230,3 @@ Java_com_google_android_filament_utils_ImageDiff_nCompareJson(JNIEnv* env, jclas
|
||||
|
||||
return createResult(env, result, generateDiff);
|
||||
}
|
||||
|
||||
|
||||
@@ -178,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -287,7 +292,8 @@ 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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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.69.2
|
||||
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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
@@ -9,6 +10,26 @@ 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'
|
||||
|
||||
@@ -27,9 +48,10 @@ android {
|
||||
|
||||
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')
|
||||
implementation project(':filament-utils-android')
|
||||
}
|
||||
|
||||
@@ -12,10 +12,11 @@
|
||||
android:supportsRtl="true"
|
||||
android:largeHeap="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:theme="@android:style/Theme.NoTitleBar">
|
||||
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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -17,16 +17,35 @@
|
||||
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 android.widget.Toast
|
||||
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 {
|
||||
|
||||
@@ -42,8 +61,35 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
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) {
|
||||
@@ -55,58 +101,302 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// Simple layout
|
||||
val layout = android.widget.FrameLayout(this)
|
||||
surfaceView = SurfaceView(this)
|
||||
layout.addView(surfaceView)
|
||||
|
||||
statusTextView = TextView(this)
|
||||
statusTextView.setTextColor(0xFFFFFFFF.toInt())
|
||||
statusTextView.textSize = 16f
|
||||
statusTextView.setPadding(20, 20, 20, 20)
|
||||
statusTextView.text = "Initializing..."
|
||||
layout.addView(statusTextView)
|
||||
|
||||
setContentView(layout)
|
||||
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)
|
||||
|
||||
// Check permissions?
|
||||
// We assume 'adb install -g' or permissions granted.
|
||||
// But for scoped storage we might not need PERMISSION if reading from app-specific dirs,
|
||||
// but user mentioned /sdcard/ so we need MANAGE_EXTERNAL_STORAGE or READ_EXTERNAL_STORAGE.
|
||||
// For waiting/simplicity, we just try.
|
||||
|
||||
modelViewer = ModelViewer(surfaceView=surfaceView, manipulator=null)
|
||||
inputManager = ValidationInputManager(this)
|
||||
|
||||
// Initialize IBL
|
||||
createIndirectLight()
|
||||
|
||||
handleIntent()
|
||||
}
|
||||
|
||||
private fun handleIntent() {
|
||||
val intent = intent
|
||||
val testConfigPath = intent.getStringExtra("test_config")
|
||||
|
||||
if (testConfigPath != null) {
|
||||
startValidation(testConfigPath)
|
||||
|
||||
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 {
|
||||
statusTextView.text = "No test_config provided via Intent.\nUse -e test_config <path>"
|
||||
Log.w(TAG, "No test config provided")
|
||||
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 startValidation(configPath: String) {
|
||||
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 {
|
||||
Log.i(TAG, "Parsing config from $configPath")
|
||||
val config = ConfigParser.parseFromPath(configPath)
|
||||
|
||||
val outputDir = File(getExternalFilesDir(null), "validation_results")
|
||||
Log.i(TAG, "Output dir: ${outputDir.absolutePath}")
|
||||
|
||||
validationRunner = ValidationRunner(this, modelViewer, config, outputDir)
|
||||
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}"
|
||||
@@ -118,6 +408,12 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
choreographer.postFrameCallback(frameScheduler)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
setIntent(intent)
|
||||
handleIntent()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
choreographer.removeFrameCallback(frameScheduler)
|
||||
@@ -128,20 +424,154 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
choreographer.removeFrameCallback(frameScheduler)
|
||||
}
|
||||
|
||||
override fun onTestFinished(result: ValidationRunner.TestResult) {
|
||||
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.name} finished: ${if(result.passed) "PASS" else "FAIL"}"
|
||||
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!"
|
||||
Log.i(TAG, "All tests finished")
|
||||
// Optional: Auto-close activity?
|
||||
// finish()
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,5 +580,179 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,26 @@ class ConfigParser {
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,25 +19,19 @@ package com.google.android.filament.validation
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.util.Log
|
||||
import com.google.android.filament.Engine
|
||||
import com.google.android.filament.Renderer
|
||||
import com.google.android.filament.View
|
||||
import com.google.android.filament.utils.AutomationEngine
|
||||
import com.google.android.filament.utils.ImageDiff
|
||||
import com.google.android.filament.utils.ModelViewer
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
|
||||
class ValidationRunner(
|
||||
private val context: Context,
|
||||
private val modelViewer: ModelViewer,
|
||||
private val config: RenderTestConfig,
|
||||
private val outputDir: File
|
||||
private val resultManager: ValidationResultManager
|
||||
) {
|
||||
|
||||
private var currentState = State.IDLE
|
||||
@@ -46,31 +40,33 @@ class ValidationRunner(
|
||||
private var currentEngine: AutomationEngine? = null
|
||||
private var currentTestConfig: TestConfig? = null
|
||||
private var currentModelName: String? = null
|
||||
|
||||
private var loadStartFence: com.google.android.filament.Fence? = null
|
||||
private var loadStartTime = 0L
|
||||
|
||||
private var frameCounter = 0
|
||||
private var suiteStartTime: Long = 0
|
||||
|
||||
enum class State {
|
||||
IDLE,
|
||||
LOADING_MODEL,
|
||||
WAITING_FOR_FENCE,
|
||||
RUNNING_TEST,
|
||||
COMPARING
|
||||
WAITING_FOR_RESOURCES,
|
||||
WARMUP,
|
||||
RUNNING_TEST
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun onTestFinished(result: TestResult)
|
||||
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])
|
||||
@@ -87,6 +83,17 @@ class ValidationRunner(
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -94,12 +101,9 @@ class ValidationRunner(
|
||||
nextModel()
|
||||
return
|
||||
}
|
||||
|
||||
currentState = State.LOADING_MODEL
|
||||
callback?.onStatusChanged("Loading $modelName for ${currentTestConfig?.name}")
|
||||
|
||||
|
||||
// Load model on main thread (required by ModelViewer)
|
||||
// We assume this is called from main thread or we dispatch
|
||||
loadModel(modelPath)
|
||||
}
|
||||
|
||||
@@ -107,35 +111,37 @@ class ValidationRunner(
|
||||
// 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()
|
||||
loadStartFence = modelViewer.engine.createFence()
|
||||
loadStartTime = System.nanoTime()
|
||||
currentState = State.WAITING_FOR_FENCE
|
||||
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()
|
||||
Log.e("ValidationRunner", "Failed to load $path", e)
|
||||
nextModel()
|
||||
}
|
||||
}
|
||||
|
||||
fun onFrame(frameTimeNanos: Long) {
|
||||
when (currentState) {
|
||||
State.IDLE -> {}
|
||||
State.WAITING_FOR_FENCE -> {
|
||||
loadStartFence?.let { fence ->
|
||||
if (fence.wait(com.google.android.filament.Fence.Mode.FLUSH, 0) == com.google.android.filament.Fence.FenceStatus.CONDITION_SATISFIED) {
|
||||
modelViewer.engine.destroyFence(fence)
|
||||
loadStartFence = null
|
||||
|
||||
// Compile materials (simplified)
|
||||
modelViewer.scene.forEach { entity ->
|
||||
// ... existing material compilation logic ...
|
||||
}
|
||||
|
||||
startAutomation()
|
||||
}
|
||||
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 -> {
|
||||
@@ -145,21 +151,20 @@ class ValidationRunner(
|
||||
content.renderer = modelViewer.renderer
|
||||
content.scene = modelViewer.scene
|
||||
content.lightManager = modelViewer.engine.lightManager
|
||||
|
||||
|
||||
// Tick
|
||||
// Delta time?
|
||||
val deltaTime = 1.0f / 60.0f // Fixed step for consistency?
|
||||
val deltaTime = 1.0f / 60.0f
|
||||
engine.tick(modelViewer.engine, content, deltaTime)
|
||||
|
||||
if (!engine.isRunning) {
|
||||
|
||||
frameCounter++
|
||||
if (engine.shouldClose()) {
|
||||
Log.i("ValidationRunner", "Finishing test (frames: $frameCounter)")
|
||||
// Test finished (for this spec)
|
||||
currentState = State.COMPARING
|
||||
currentState = State.IDLE
|
||||
captureAndCompare()
|
||||
}
|
||||
}
|
||||
}
|
||||
State.COMPARING -> {} // Busy
|
||||
State.LOADING_MODEL -> {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,104 +180,157 @@ class ValidationRunner(
|
||||
options.sleepDuration = 0.0f // Minimal sleep, let frames drive it
|
||||
options.minFrameCount = 5 // Ensure some frames pass
|
||||
currentEngine?.setOptions(options)
|
||||
currentEngine?.startRunning()
|
||||
|
||||
// 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}...")
|
||||
val view = modelViewer.view
|
||||
val renderer = modelViewer.renderer
|
||||
val width = view.viewport.width
|
||||
val height = view.viewport.height
|
||||
|
||||
val buffer = ByteBuffer.allocateDirect(width * height * 4)
|
||||
|
||||
val pbd = com.google.android.filament.Texture.PixelBufferDescriptor(
|
||||
buffer,
|
||||
com.google.android.filament.Texture.Format.RGBA,
|
||||
com.google.android.filament.Texture.Type.UBYTE,
|
||||
1, 0, 0, 0, 0, // alignment, left, top, stride (0=default)
|
||||
null // handler (null = current thread? no, handler is for callback)
|
||||
) {
|
||||
// Callback when readPixels is done
|
||||
// Dispatch to background thread for comparison to avoid blocking UI?
|
||||
// "it" is undefined here? The callback interface is Runnable?
|
||||
// Kotlin lambda for Runnable.
|
||||
compareCapturedImage(buffer, width, height)
|
||||
modelViewer.debugGetNextFrameCallback { bitmap ->
|
||||
compareCapturedImage(bitmap)
|
||||
}
|
||||
renderer.readPixels(0, 0, width, height, pbd)
|
||||
}
|
||||
|
||||
private fun compareCapturedImage(buffer: java.nio.Buffer, width: Int, height: Int) {
|
||||
// This runs on... which thread? Filament driver thread possibly.
|
||||
// We should use a helper to process.
|
||||
|
||||
private fun compareCapturedImage(bitmap: Bitmap) {
|
||||
val testName = currentTestConfig!!.name
|
||||
val modelName = currentModelName!!
|
||||
val backend = "opengl" // Hardcoded for now, or get from View/Engine?
|
||||
val backend = currentTestConfig?.backends?.firstOrNull() ?: "opengl"
|
||||
val testFullName = "${testName}.${backend}.${modelName}"
|
||||
|
||||
|
||||
// Golden path
|
||||
// We expect a golden directory.
|
||||
val goldenFile = File(config.models.get(modelName)!!).parentFile.parentFile.resolve("golden/${testFullName}.png")
|
||||
// Strategy: models are in .../models/model.glb
|
||||
// Goldens are in .../golden/
|
||||
|
||||
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 {
|
||||
// Convert buffer to Bitmap
|
||||
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
bitmap.copyPixelsFromBuffer(buffer)
|
||||
|
||||
// Flip Y? ReadPixels is typically bottom-up?
|
||||
// Filament readPixels is bottom-left? YES.
|
||||
// Bitmap is top-left.
|
||||
// We need to flip.
|
||||
val matrix = android.graphics.Matrix()
|
||||
matrix.postScale(1f, -1f)
|
||||
val flipped = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true)
|
||||
|
||||
val flipped = bitmap
|
||||
|
||||
callback?.onImageResult("Rendered", flipped)
|
||||
|
||||
var passed = false
|
||||
if (goldenFile.exists()) {
|
||||
val golden = android.graphics.BitmapFactory.decodeFile(goldenFile.absolutePath)
|
||||
if (golden != null) {
|
||||
// Populate tolerance from config
|
||||
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)
|
||||
|
||||
// Save diff if failed?
|
||||
if (!passed) {
|
||||
val diffFile = File(outputDir, "${testFullName}_diff.png")
|
||||
if (result.diffImage != null) {
|
||||
FileOutputStream(diffFile).use { out ->
|
||||
result.diffImage.compress(Bitmap.CompressFormat.PNG, 100, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.e("ValidationRunner", "Failed to load golden: ${goldenFile.absolutePath}")
|
||||
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 {
|
||||
Log.w("ValidationRunner", "Golden not found: ${goldenFile.absolutePath}")
|
||||
}
|
||||
|
||||
// Save output
|
||||
val outFile = File(outputDir, "${testFullName}.png")
|
||||
FileOutputStream(outFile).use { out ->
|
||||
flipped.compress(Bitmap.CompressFormat.PNG, 100, out)
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
callback?.onTestFinished(TestResult(testFullName, passed))
|
||||
|
||||
// Schedule next model on main thread
|
||||
// Use Handler or View.post
|
||||
modelViewer.view.viewport
|
||||
// dispatch nextModel()
|
||||
// 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()
|
||||
}
|
||||
@@ -300,28 +358,11 @@ class ValidationRunner(
|
||||
startTest(config.tests[currentTestIndex])
|
||||
} else {
|
||||
currentState = State.IDLE
|
||||
zipResults()
|
||||
|
||||
val totalTimeMs = System.currentTimeMillis() - suiteStartTime
|
||||
|
||||
resultManager.finalizeResults(totalTimeMs)
|
||||
callback?.onAllTestsFinished()
|
||||
}
|
||||
}
|
||||
|
||||
private fun zipResults() {
|
||||
callback?.onStatusChanged("Zipping results...")
|
||||
val zipFile = File(outputDir, "results.zip")
|
||||
try {
|
||||
java.util.zip.ZipOutputStream(java.io.FileOutputStream(zipFile)).use { zos ->
|
||||
outputDir.walkTopDown().filter { it.isFile && it.name != "results.zip" }.forEach { file ->
|
||||
val entryName = file.relativeTo(outputDir).path
|
||||
zos.putNextEntry(java.util.zip.ZipEntry(entryName))
|
||||
file.inputStream().use { it.copyTo(zos) }
|
||||
zos.closeEntry()
|
||||
}
|
||||
}
|
||||
Log.i("ValidationRunner", "Zipped results to ${zipFile.absolutePath}")
|
||||
} catch (e: Exception) {
|
||||
Log.e("ValidationRunner", "Failed to zip results", e)
|
||||
}
|
||||
}
|
||||
|
||||
data class TestResult(val name: String, val passed: Boolean)
|
||||
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
19
build.sh
19
build.sh
@@ -214,6 +214,8 @@ ENABLE_PERFETTO=""
|
||||
|
||||
BACKEND_DEBUG_FLAG_OPTION=""
|
||||
|
||||
STEREOSCOPIC_OPTION=""
|
||||
|
||||
OSMESA_OPTION=""
|
||||
|
||||
IOS_BUILD_SIMULATOR=false
|
||||
@@ -314,6 +316,7 @@ function build_desktop_target {
|
||||
${ASAN_UBSAN_OPTION} \
|
||||
${COVERAGE_OPTION} \
|
||||
${BACKEND_DEBUG_FLAG_OPTION} \
|
||||
${STEREOSCOPIC_OPTION} \
|
||||
${OSMESA_OPTION} \
|
||||
${architectures} \
|
||||
../..
|
||||
@@ -452,6 +455,7 @@ function build_android_target {
|
||||
${VULKAN_ANDROID_OPTION} \
|
||||
${WEBGPU_OPTION} \
|
||||
${BACKEND_DEBUG_FLAG_OPTION} \
|
||||
${STEREOSCOPIC_OPTION} \
|
||||
${ENABLE_PERFETTO} \
|
||||
../..
|
||||
ln -sf "out/cmake-android-${lc_target}-${arch}/compile_commands.json" \
|
||||
@@ -693,6 +697,7 @@ function build_ios_target {
|
||||
${WEBGPU_OPTION} \
|
||||
${MATDBG_OPTION} \
|
||||
${MATOPT_OPTION} \
|
||||
${STEREOSCOPIC_OPTION} \
|
||||
../..
|
||||
ln -sf "out/cmake-ios-${lc_target}-${arch}/compile_commands.json" \
|
||||
../../compile_commands.json
|
||||
@@ -1006,6 +1011,20 @@ while getopts ":hacCfgimp:q:uvWslwedtk:bVx:S:X:Py:" opt; do
|
||||
;;
|
||||
x) BACKEND_DEBUG_FLAG_OPTION="-DFILAMENT_BACKEND_DEBUG_FLAG=${OPTARG}"
|
||||
;;
|
||||
S) case $(echo "${OPTARG}" | tr '[:upper:]' '[:lower:]') in
|
||||
instanced)
|
||||
STEREOSCOPIC_OPTION="-DFILAMENT_SAMPLES_STEREO_TYPE=instanced"
|
||||
;;
|
||||
multiview)
|
||||
STEREOSCOPIC_OPTION="-DFILAMENT_SAMPLES_STEREO_TYPE=multiview"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown stereoscopic type ${OPTARG}"
|
||||
echo "Type must be one of [instanced|multiview]"
|
||||
echo ""
|
||||
exit 1
|
||||
esac
|
||||
;;
|
||||
X) OSMESA_OPTION="-DFILAMENT_OSMESA_PATH=${OPTARG}"
|
||||
;;
|
||||
y)
|
||||
|
||||
@@ -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.69.0'
|
||||
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.69.0'
|
||||
<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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
File diff suppressed because one or more lines are too long
@@ -16,12 +16,13 @@ class SimulatedSkybox {
|
||||
this.ozone = 0.0;
|
||||
this.msFactors = [0.1, 0.5, 0.0];
|
||||
this.contrast = 1.0;
|
||||
this.nightColor = [0.0, 0.0003, 0.00075];
|
||||
this.nightColor = [0.0, 3.0e-9, 7.5e-9];
|
||||
this.shimmerControl = [0.0, 20.0, 0.1];
|
||||
this.cloudControl = [0.0, 0.1, 8000.0, 0.0];
|
||||
this.cloudControl2 = [0.0, 0.0, 0.0, 0.0];
|
||||
this.waterControl = [50.0, 1.0, 1.0, 4.0]; // x=Strength, y=Speed, z=DerivativeTrick, w=Octaves
|
||||
this.starControl = [0.001, 1.0, 350.0, 0.01]; // x=Density, y=Enabled, z=Frequency, w=PixelScale
|
||||
this.starIntensity = 1.0;
|
||||
this.focalLength = 24.0;
|
||||
this.height = 1000.0;
|
||||
this.planetRadius = 6360.0;
|
||||
@@ -43,7 +44,7 @@ class SimulatedSkybox {
|
||||
|
||||
// Milky Way Parameters
|
||||
// x=Intensity, y=Saturation, z=Unused
|
||||
this.milkyWayControl = [1.0, 1.0, 0.07];
|
||||
this.milkyWayControl = [1.0, 1.2, 0.05];
|
||||
this.milkyWayEnabled = true;
|
||||
this.milkyWayRotation = [1, 0, 0, 0, 1, 0, 0, 0, 1]; // Identity by default
|
||||
this.initEntity();
|
||||
@@ -453,6 +454,11 @@ class SimulatedSkybox {
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setStarIntensity(intensity) {
|
||||
this.starIntensity = Math.max(0.0, intensity);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setMoonPosition(direction) {
|
||||
// normalize
|
||||
const len = Math.hypot(direction[0], direction[1], direction[2]);
|
||||
@@ -500,6 +506,13 @@ class SimulatedSkybox {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
setExposure(exposure) {
|
||||
this.exposure = exposure;
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
updateCoefficients() {
|
||||
if (!this.materialInstance) {
|
||||
console.warn("updateCoefficients called before material loaded");
|
||||
@@ -585,6 +598,7 @@ class SimulatedSkybox {
|
||||
this.materialInstance.setFloat4Parameter('cloudControl2', new Float32Array(this.cloudControl2));
|
||||
this.materialInstance.setFloat4Parameter('waterControl', new Float32Array(this.waterControl));
|
||||
this.materialInstance.setFloat4Parameter('starControl', new Float32Array(this.starControl));
|
||||
this.materialInstance.setFloatParameter('starIntensity', this.starIntensity);
|
||||
|
||||
this.materialInstance.setFloatParameter('sunIntensity', physicalSunIntensity);
|
||||
|
||||
@@ -615,12 +629,13 @@ class SimulatedSkybox {
|
||||
|
||||
this.materialInstance.setFloatParameter('sunIntensity2', finalMoonIntensity);
|
||||
|
||||
// Scale Milky Way by Sun Intensity (Pre-exposed) to match dynamic range
|
||||
// Calibration: 1.0 User Intensity ~ 0.025 Lux Relative (approx 2.5% of Sun Pixel Value at Day)
|
||||
// At Sunny 16 (Sun ~ 2.6), this gives ~0.065 pixel brightness, which is visible but dim.
|
||||
// Scale Milky Way by Sun Intensity
|
||||
// Calibration: 1.0 User Intensity = 1.5e-3 cd/m^2 (Nits) approx.
|
||||
// Target: 1.5e-3 Nits. SunIntensity = 100,000.
|
||||
// Scale = 1.5e-3 / 1.0e5 = 1.5e-8.
|
||||
const mwIntensity = this.milkyWayEnabled ? this.milkyWayControl[0] : 0.0;
|
||||
const mwUniform = [
|
||||
mwIntensity * this.sunIntensity * 0.025,
|
||||
mwIntensity * this.sunIntensity * 1.5e-8,
|
||||
this.milkyWayControl[1],
|
||||
this.milkyWayControl[2]
|
||||
];
|
||||
@@ -634,6 +649,7 @@ class SimulatedSkybox {
|
||||
const moonHaloUpload = [...this.moonHalo];
|
||||
moonHaloUpload[2] *= moonRadConv;
|
||||
this.materialInstance.setFloat4Parameter('sunHalo2', new Float32Array(moonHaloUpload));
|
||||
this.materialInstance.setFloatParameter('exposure', this.exposure !== undefined ? this.exposure : 1.0);
|
||||
|
||||
// Solar Eclipse (CPU Calculation)
|
||||
const sunRadius = Math.acos(this.sunHalo[0]);
|
||||
|
||||
Binary file not shown.
@@ -218,6 +218,7 @@ class App {
|
||||
const exposure = this.getExposure();
|
||||
const preExposedIntensity = this.params.sunIntensity * exposure;
|
||||
this.skybox.setSunIntensity(preExposedIntensity);
|
||||
this.skybox.setExposure(exposure); // Update Skybox Exposure Uniform
|
||||
|
||||
// Moon Exposure
|
||||
if (this.mParams) {
|
||||
@@ -389,7 +390,7 @@ class App {
|
||||
};
|
||||
|
||||
mwFolder.add(this.mwParams, 'enabled').name('Enabled').onChange(updateMW);
|
||||
mwFolder.add(this.mwParams, 'intensity', 0.0, 5.0).onChange(updateMW);
|
||||
mwFolder.add(this.mwParams, 'intensity', 0.0, 100.0).onChange(updateMW);
|
||||
mwFolder.add(this.mwParams, 'saturation', 0.0, 2.0).onChange(updateMW);
|
||||
mwFolder.add(this.mwParams, 'blackPoint', 0.0, 0.5).name('Black Point').onChange(updateMW);
|
||||
mwFolder.add(this.mwParams, 'siderealTime', 0.0, 24.0).name('Sidereal Time').listen().onChange(updateMW);
|
||||
@@ -464,18 +465,23 @@ class App {
|
||||
const starFolder = gui.addFolder('Stars');
|
||||
this.sParams = {
|
||||
enabled: true,
|
||||
density: 0.001
|
||||
density: 0.001,
|
||||
intensity: 0.0 // 2^0 = 1.0
|
||||
};
|
||||
// Initialize defaults (Density 0.001, Enabled True)
|
||||
sky.setStarControl(0.001, true);
|
||||
sky.setStarIntensity(Math.pow(2.0, 0.0));
|
||||
|
||||
const updateStars = () => {
|
||||
sky.setStarControl(this.sParams.density, this.sParams.enabled);
|
||||
sky.setStarIntensity(Math.pow(2.0, this.sParams.intensity));
|
||||
};
|
||||
|
||||
starFolder.add(this.sParams, 'enabled').name('Enabled').onChange(updateStars);
|
||||
starFolder.add(this.sParams, 'density', 0.0, 0.01, 0.0001).name('Density').onChange(updateStars);
|
||||
starFolder.add(this.sParams, 'intensity', 0.0, 24.0).name('Intensity (Exp)').onChange(updateStars);
|
||||
starFolder.close();
|
||||
this.updateStars = updateStars;
|
||||
|
||||
const artFolder = gui.addFolder('Artistic');
|
||||
// Set Horizon Glow default to 0.0
|
||||
@@ -489,7 +495,7 @@ class App {
|
||||
artFolder.add(sky.msFactors, 2, 0.0, 1.0).name('Horizon Glow').onChange(v => sky.setHorizonGlow(v));
|
||||
artFolder.add(sky, 'contrast', 0.1, 2.0).onChange(v => sky.setContrast(v));
|
||||
|
||||
artFolder.addColor(sky, 'nightColor').onChange(v => sky.setNightColor(v));
|
||||
|
||||
|
||||
const shmFolder = artFolder.addFolder('Shimmer');
|
||||
// Set Shimmer Strength default to 0.0
|
||||
@@ -502,7 +508,7 @@ class App {
|
||||
const camFolder = gui.addFolder('Camera');
|
||||
camFolder.add(this.params, 'focalLength', 8.0, 300.0).name('Focal Length').onChange(() => this.updateCameraProjection());
|
||||
camFolder.add(this.params, 'aperture', 1.4, 32.0).onChange(() => this.updateCameraExposure());
|
||||
camFolder.add(this.params, 'shutterSpeed', 1.0, 1000.0).onChange(() => this.updateCameraExposure());
|
||||
camFolder.add(this.params, 'shutterSpeed', 0.05, 1000.0).onChange(() => this.updateCameraExposure());
|
||||
camFolder.add(this.params, 'iso', 50.0, 3200.0).onChange(() => this.updateCameraExposure());
|
||||
|
||||
const bloomFolder = camFolder.addFolder('Bloom');
|
||||
@@ -692,13 +698,15 @@ class App {
|
||||
const s = this.sParams;
|
||||
const b = this.bParams;
|
||||
const m = this.mParams;
|
||||
const mw = this.mwParams;
|
||||
const sk = this.skybox;
|
||||
|
||||
return {
|
||||
p: { a: p.aperture, ss: p.shutterSpeed, i: p.iso, st: p.sunTheta, sp: p.sunPhi, fl: p.focalLength, si: p.sunIntensity },
|
||||
c: { v: c.volumetrics, co: c.coverage, d: c.density, h: c.height, s: c.speed, e: c.evolution },
|
||||
w: { dt: w.derivativeTrick, st: w.strength, s: w.speed, o: w.octaves },
|
||||
s: { e: s.enabled, d: s.density },
|
||||
s: { e: s.enabled, d: s.density, i: s.intensity },
|
||||
mw: { e: mw.enabled, i: mw.intensity, s: mw.saturation, bp: mw.blackPoint, st: mw.siderealTime, l: mw.latitude },
|
||||
b: { e: b.enabled, lf: b.lensFlare },
|
||||
m: { e: m.enabled, az: m.azimuth, h: m.height, r: m.radius, i: m.intensity },
|
||||
cm: { t: this.camState.theta, p: this.camState.phi },
|
||||
@@ -723,6 +731,7 @@ class App {
|
||||
const c = state.c;
|
||||
const w = state.w;
|
||||
const s = state.s;
|
||||
const mw = state.mw;
|
||||
const b = state.b;
|
||||
const m = state.m;
|
||||
const k = state.k;
|
||||
@@ -757,6 +766,18 @@ class App {
|
||||
if (s) {
|
||||
if (s.e !== undefined) this.sParams.enabled = s.e;
|
||||
if (s.d !== undefined) this.sParams.density = s.d;
|
||||
if (s.i !== undefined) this.sParams.intensity = s.i;
|
||||
if (this.updateStars) this.updateStars();
|
||||
}
|
||||
|
||||
if (mw) {
|
||||
if (mw.e !== undefined) this.mwParams.enabled = mw.e;
|
||||
if (mw.i !== undefined) this.mwParams.intensity = mw.i;
|
||||
if (mw.s !== undefined) this.mwParams.saturation = mw.s;
|
||||
if (mw.bp !== undefined) this.mwParams.blackPoint = mw.bp;
|
||||
if (mw.st !== undefined) this.mwParams.siderealTime = mw.st;
|
||||
if (mw.l !== undefined) this.mwParams.latitude = mw.l;
|
||||
if (this.updateMW) this.updateMW();
|
||||
}
|
||||
|
||||
if (b) {
|
||||
|
||||
@@ -96,10 +96,20 @@ material {
|
||||
name : starControl, // x=Density, y=Enabled, z=Frequency, w=PixelScale
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : starIntensity,
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : sampler2d,
|
||||
name : moonTexture
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : exposure,
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : sampler2d,
|
||||
name : moonNormal
|
||||
@@ -162,9 +172,9 @@ fragment {
|
||||
// --- CONFIGURATION ---
|
||||
|
||||
// Stars
|
||||
#define STAR_GLOBAL_INTENSITY 150.0 // Master brightness multiplier [0.0 - 500.0]
|
||||
#define STAR_BRIGHTNESS_BASE 0.5 // Minimum random brightness [0.0 - 1.0]
|
||||
#define STAR_BRIGHTNESS_VAR 4.0 // Random brightness variance range [0.0 - 10.0]
|
||||
#define STAR_GLOBAL_INTENSITY 100.0 // Master brightness multiplier [0.0 - 500.0]
|
||||
#define STAR_BRIGHTNESS_BASE 0.01 // Minimum random brightness [0.0 - 1.0]
|
||||
#define STAR_BRIGHTNESS_VAR 15.0 // Random brightness variance range [0.0 - 10.0]
|
||||
#define STAR_FADE_SUN_ELV_HIGH 0.10 // Sun elevation (sin) where stars are fully hidden [0.0 - 0.5]
|
||||
#define STAR_FADE_SUN_ELV_LOW -0.20 // Sun elevation (sin) where stars are fully visible [-0.5 - 0.0]
|
||||
#define STAR_CLOUD_OCCLUSION 0.1 // Visibility when covered by clouds [0.0 - 1.0]
|
||||
@@ -961,7 +971,11 @@ fragment {
|
||||
highp vec4 sunHalo, highp vec4 cloudControl, highp vec4 cloudControl2,
|
||||
highp vec4 shimmerControl, highp vec4 waterControl,
|
||||
highp vec3 L2, highp float sunIntensity2, highp vec4 sunHalo2,
|
||||
sampler2D moonTex, sampler2D moonNormal) {
|
||||
sampler2D moonTex, sampler2D moonNormal,
|
||||
highp vec3 nightColor,
|
||||
highp mat3 milkyWayRotation,
|
||||
highp vec3 milkyWayControl,
|
||||
sampler2D milkyWayTexture) {
|
||||
|
||||
// Project to plane y=0
|
||||
highp float t = WATER_PLANE_HEIGHT / min(V.y, -0.0002); // Reduced clamp to minimize "wall" artifact
|
||||
@@ -1057,7 +1071,7 @@ fragment {
|
||||
highp float rHorizonMask = 1.0 - smoothstep(0.0, REFLECTION_HORIZON_FADE, R.y);
|
||||
|
||||
if (rHorizonMask > 0.0) {
|
||||
reflection += getStarLayer(R, L, reflCloudDensity, outTransmittance, materialParams.starControl) * rHorizonMask;
|
||||
reflection += getStarLayer(R, L, reflCloudDensity, outTransmittance, materialParams.starControl) * rHorizonMask * materialParams.exposure;
|
||||
}
|
||||
|
||||
// Sun Disk Reflection
|
||||
@@ -1088,6 +1102,29 @@ fragment {
|
||||
// Apply clouds to reflection
|
||||
reflection = mix(reflection, reflCloudLayer, reflCloudDensity);
|
||||
|
||||
// Add Milky Way to Reflection
|
||||
// Calculate Fade based on Sun Elevation
|
||||
highp float sunElvSin = L.y;
|
||||
highp float mwFade = smoothstep(STAR_FADE_SUN_ELV_HIGH, STAR_FADE_SUN_ELV_LOW, sunElvSin);
|
||||
|
||||
if (mwFade > 0.0) {
|
||||
highp vec3 mwColor = getMilkyWay(R, milkyWayRotation, milkyWayTexture, milkyWayControl);
|
||||
|
||||
// Apply Atmosphere Transmittance (approximate)
|
||||
mwColor *= outTransmittance;
|
||||
|
||||
// Apply Fades
|
||||
mwColor *= mwFade;
|
||||
mwColor *= (1.0 - reflMoonOcclusion); // Occlude by Moon
|
||||
mwColor *= (1.0 - reflCloudDensity); // Occlude by Clouds
|
||||
|
||||
reflection += mwColor;
|
||||
}
|
||||
|
||||
|
||||
// Add Night Color to Reflection
|
||||
reflection += nightColor;
|
||||
|
||||
// Fresnel
|
||||
highp float F0 = WATER_FRESNEL_F0; // Water
|
||||
highp float cosTheta = clamp(dot(-V, N_water), 0.0, 1.0);
|
||||
@@ -1187,7 +1224,7 @@ fragment {
|
||||
|
||||
// 7. Stars
|
||||
// Add stars before clouds (clouds cover stars)
|
||||
highp vec3 starColor = getStarLayer(V, L, cloudDensityVal, transmittance, materialParams.starControl);
|
||||
highp vec3 starColor = getStarLayer(V, L, cloudDensityVal, transmittance, materialParams.starControl) * materialParams.exposure * materialParams.starIntensity;
|
||||
|
||||
// 7b. Milky Way
|
||||
// Add Milky Way behind stars (conceptually) but handled similarly
|
||||
@@ -1232,7 +1269,11 @@ fragment {
|
||||
materialParams.shimmerControl,
|
||||
materialParams.waterControl,
|
||||
L2, materialParams.sunIntensity2, materialParams.sunHalo2,
|
||||
materialParams_moonTexture, materialParams_moonNormal);
|
||||
materialParams_moonTexture, materialParams_moonNormal,
|
||||
materialParams.nightColor,
|
||||
materialParams.milkyWayRotation,
|
||||
materialParams.milkyWayControl,
|
||||
materialParams_milkyWayTexture);
|
||||
color = applyDynamicToneMapping(color, L, materialParams.contrast);
|
||||
}
|
||||
|
||||
|
||||
@@ -64,25 +64,30 @@ staging API compatibility service, which works with Filament's Gradle setup.
|
||||
|
||||
-----
|
||||
|
||||
### 1\. Upload to the Staging API Compatibility Service
|
||||
### 1\. Upload to the Central Publisher Portal
|
||||
|
||||
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.
|
||||
|
||||
```bash
|
||||
cd android
|
||||
./gradlew publishToSonatype
|
||||
./gradlew publishToSonatype closeSonatypeStagingRepository
|
||||
```
|
||||
|
||||
### 2\. Move the Repository to the Central Publisher Portal
|
||||
#### Troubleshooting: Manual Staging
|
||||
|
||||
We have a script to automate this. It reads the `sonatypeUsername` and `sonatypePassword` from your
|
||||
`~/.gradle/gradle.properties` file.
|
||||
If you ran `publishToSonatype` 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
|
||||
`sonatypeUsername` and `sonatypePassword` from your ~/.gradle/gradle.properties file:
|
||||
|
||||
```bash
|
||||
python3 build/common/close-sonatype-staging-repository.py
|
||||
```
|
||||
|
||||
### 3\. Publish the Release on Sonatype
|
||||
### 2\. Publish the Release on Sonatype
|
||||
|
||||
Navigate to [Maven Central Repository Deployments](https://central.sonatype.com/publishing/deployments).
|
||||
Once the upload is successful, you must manually trigger the final release. Navigate to [Maven
|
||||
Central Repository Deployments](https://central.sonatype.com/publishing/deployments).
|
||||
|
||||
Here, you should see a new deployment with a **Validated** status and all your artifacts listed. Click
|
||||
the **Publish** button to publish the artifacts. It typically takes around 5 minutes after clicking
|
||||
|
||||
@@ -16,12 +16,13 @@ class SimulatedSkybox {
|
||||
this.ozone = 0.0;
|
||||
this.msFactors = [0.1, 0.5, 0.0];
|
||||
this.contrast = 1.0;
|
||||
this.nightColor = [0.0, 0.0003, 0.00075];
|
||||
this.nightColor = [0.0, 3.0e-9, 7.5e-9];
|
||||
this.shimmerControl = [0.0, 20.0, 0.1];
|
||||
this.cloudControl = [0.0, 0.1, 8000.0, 0.0];
|
||||
this.cloudControl2 = [0.0, 0.0, 0.0, 0.0];
|
||||
this.waterControl = [50.0, 1.0, 1.0, 4.0]; // x=Strength, y=Speed, z=DerivativeTrick, w=Octaves
|
||||
this.starControl = [0.001, 1.0, 350.0, 0.01]; // x=Density, y=Enabled, z=Frequency, w=PixelScale
|
||||
this.starIntensity = 1.0;
|
||||
this.focalLength = 24.0;
|
||||
this.height = 1000.0;
|
||||
this.planetRadius = 6360.0;
|
||||
@@ -43,7 +44,7 @@ class SimulatedSkybox {
|
||||
|
||||
// Milky Way Parameters
|
||||
// x=Intensity, y=Saturation, z=Unused
|
||||
this.milkyWayControl = [1.0, 1.0, 0.07];
|
||||
this.milkyWayControl = [1.0, 1.2, 0.05];
|
||||
this.milkyWayEnabled = true;
|
||||
this.milkyWayRotation = [1, 0, 0, 0, 1, 0, 0, 0, 1]; // Identity by default
|
||||
this.initEntity();
|
||||
@@ -453,6 +454,11 @@ class SimulatedSkybox {
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setStarIntensity(intensity) {
|
||||
this.starIntensity = Math.max(0.0, intensity);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setMoonPosition(direction) {
|
||||
// normalize
|
||||
const len = Math.hypot(direction[0], direction[1], direction[2]);
|
||||
@@ -500,6 +506,13 @@ class SimulatedSkybox {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
setExposure(exposure) {
|
||||
this.exposure = exposure;
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
updateCoefficients() {
|
||||
if (!this.materialInstance) {
|
||||
console.warn("updateCoefficients called before material loaded");
|
||||
@@ -585,6 +598,7 @@ class SimulatedSkybox {
|
||||
this.materialInstance.setFloat4Parameter('cloudControl2', new Float32Array(this.cloudControl2));
|
||||
this.materialInstance.setFloat4Parameter('waterControl', new Float32Array(this.waterControl));
|
||||
this.materialInstance.setFloat4Parameter('starControl', new Float32Array(this.starControl));
|
||||
this.materialInstance.setFloatParameter('starIntensity', this.starIntensity);
|
||||
|
||||
this.materialInstance.setFloatParameter('sunIntensity', physicalSunIntensity);
|
||||
|
||||
@@ -615,12 +629,13 @@ class SimulatedSkybox {
|
||||
|
||||
this.materialInstance.setFloatParameter('sunIntensity2', finalMoonIntensity);
|
||||
|
||||
// Scale Milky Way by Sun Intensity (Pre-exposed) to match dynamic range
|
||||
// Calibration: 1.0 User Intensity ~ 0.025 Lux Relative (approx 2.5% of Sun Pixel Value at Day)
|
||||
// At Sunny 16 (Sun ~ 2.6), this gives ~0.065 pixel brightness, which is visible but dim.
|
||||
// Scale Milky Way by Sun Intensity
|
||||
// Calibration: 1.0 User Intensity = 1.5e-3 cd/m^2 (Nits) approx.
|
||||
// Target: 1.5e-3 Nits. SunIntensity = 100,000.
|
||||
// Scale = 1.5e-3 / 1.0e5 = 1.5e-8.
|
||||
const mwIntensity = this.milkyWayEnabled ? this.milkyWayControl[0] : 0.0;
|
||||
const mwUniform = [
|
||||
mwIntensity * this.sunIntensity * 0.025,
|
||||
mwIntensity * this.sunIntensity * 1.5e-8,
|
||||
this.milkyWayControl[1],
|
||||
this.milkyWayControl[2]
|
||||
];
|
||||
@@ -634,6 +649,7 @@ class SimulatedSkybox {
|
||||
const moonHaloUpload = [...this.moonHalo];
|
||||
moonHaloUpload[2] *= moonRadConv;
|
||||
this.materialInstance.setFloat4Parameter('sunHalo2', new Float32Array(moonHaloUpload));
|
||||
this.materialInstance.setFloatParameter('exposure', this.exposure !== undefined ? this.exposure : 1.0);
|
||||
|
||||
// Solar Eclipse (CPU Calculation)
|
||||
const sunRadius = Math.acos(this.sunHalo[0]);
|
||||
|
||||
Binary file not shown.
@@ -218,6 +218,7 @@ class App {
|
||||
const exposure = this.getExposure();
|
||||
const preExposedIntensity = this.params.sunIntensity * exposure;
|
||||
this.skybox.setSunIntensity(preExposedIntensity);
|
||||
this.skybox.setExposure(exposure); // Update Skybox Exposure Uniform
|
||||
|
||||
// Moon Exposure
|
||||
if (this.mParams) {
|
||||
@@ -389,7 +390,7 @@ class App {
|
||||
};
|
||||
|
||||
mwFolder.add(this.mwParams, 'enabled').name('Enabled').onChange(updateMW);
|
||||
mwFolder.add(this.mwParams, 'intensity', 0.0, 5.0).onChange(updateMW);
|
||||
mwFolder.add(this.mwParams, 'intensity', 0.0, 100.0).onChange(updateMW);
|
||||
mwFolder.add(this.mwParams, 'saturation', 0.0, 2.0).onChange(updateMW);
|
||||
mwFolder.add(this.mwParams, 'blackPoint', 0.0, 0.5).name('Black Point').onChange(updateMW);
|
||||
mwFolder.add(this.mwParams, 'siderealTime', 0.0, 24.0).name('Sidereal Time').listen().onChange(updateMW);
|
||||
@@ -464,18 +465,23 @@ class App {
|
||||
const starFolder = gui.addFolder('Stars');
|
||||
this.sParams = {
|
||||
enabled: true,
|
||||
density: 0.001
|
||||
density: 0.001,
|
||||
intensity: 0.0 // 2^0 = 1.0
|
||||
};
|
||||
// Initialize defaults (Density 0.001, Enabled True)
|
||||
sky.setStarControl(0.001, true);
|
||||
sky.setStarIntensity(Math.pow(2.0, 0.0));
|
||||
|
||||
const updateStars = () => {
|
||||
sky.setStarControl(this.sParams.density, this.sParams.enabled);
|
||||
sky.setStarIntensity(Math.pow(2.0, this.sParams.intensity));
|
||||
};
|
||||
|
||||
starFolder.add(this.sParams, 'enabled').name('Enabled').onChange(updateStars);
|
||||
starFolder.add(this.sParams, 'density', 0.0, 0.01, 0.0001).name('Density').onChange(updateStars);
|
||||
starFolder.add(this.sParams, 'intensity', 0.0, 24.0).name('Intensity (Exp)').onChange(updateStars);
|
||||
starFolder.close();
|
||||
this.updateStars = updateStars;
|
||||
|
||||
const artFolder = gui.addFolder('Artistic');
|
||||
// Set Horizon Glow default to 0.0
|
||||
@@ -489,7 +495,7 @@ class App {
|
||||
artFolder.add(sky.msFactors, 2, 0.0, 1.0).name('Horizon Glow').onChange(v => sky.setHorizonGlow(v));
|
||||
artFolder.add(sky, 'contrast', 0.1, 2.0).onChange(v => sky.setContrast(v));
|
||||
|
||||
artFolder.addColor(sky, 'nightColor').onChange(v => sky.setNightColor(v));
|
||||
|
||||
|
||||
const shmFolder = artFolder.addFolder('Shimmer');
|
||||
// Set Shimmer Strength default to 0.0
|
||||
@@ -502,7 +508,7 @@ class App {
|
||||
const camFolder = gui.addFolder('Camera');
|
||||
camFolder.add(this.params, 'focalLength', 8.0, 300.0).name('Focal Length').onChange(() => this.updateCameraProjection());
|
||||
camFolder.add(this.params, 'aperture', 1.4, 32.0).onChange(() => this.updateCameraExposure());
|
||||
camFolder.add(this.params, 'shutterSpeed', 1.0, 1000.0).onChange(() => this.updateCameraExposure());
|
||||
camFolder.add(this.params, 'shutterSpeed', 0.05, 1000.0).onChange(() => this.updateCameraExposure());
|
||||
camFolder.add(this.params, 'iso', 50.0, 3200.0).onChange(() => this.updateCameraExposure());
|
||||
|
||||
const bloomFolder = camFolder.addFolder('Bloom');
|
||||
@@ -692,13 +698,15 @@ class App {
|
||||
const s = this.sParams;
|
||||
const b = this.bParams;
|
||||
const m = this.mParams;
|
||||
const mw = this.mwParams;
|
||||
const sk = this.skybox;
|
||||
|
||||
return {
|
||||
p: { a: p.aperture, ss: p.shutterSpeed, i: p.iso, st: p.sunTheta, sp: p.sunPhi, fl: p.focalLength, si: p.sunIntensity },
|
||||
c: { v: c.volumetrics, co: c.coverage, d: c.density, h: c.height, s: c.speed, e: c.evolution },
|
||||
w: { dt: w.derivativeTrick, st: w.strength, s: w.speed, o: w.octaves },
|
||||
s: { e: s.enabled, d: s.density },
|
||||
s: { e: s.enabled, d: s.density, i: s.intensity },
|
||||
mw: { e: mw.enabled, i: mw.intensity, s: mw.saturation, bp: mw.blackPoint, st: mw.siderealTime, l: mw.latitude },
|
||||
b: { e: b.enabled, lf: b.lensFlare },
|
||||
m: { e: m.enabled, az: m.azimuth, h: m.height, r: m.radius, i: m.intensity },
|
||||
cm: { t: this.camState.theta, p: this.camState.phi },
|
||||
@@ -723,6 +731,7 @@ class App {
|
||||
const c = state.c;
|
||||
const w = state.w;
|
||||
const s = state.s;
|
||||
const mw = state.mw;
|
||||
const b = state.b;
|
||||
const m = state.m;
|
||||
const k = state.k;
|
||||
@@ -757,6 +766,18 @@ class App {
|
||||
if (s) {
|
||||
if (s.e !== undefined) this.sParams.enabled = s.e;
|
||||
if (s.d !== undefined) this.sParams.density = s.d;
|
||||
if (s.i !== undefined) this.sParams.intensity = s.i;
|
||||
if (this.updateStars) this.updateStars();
|
||||
}
|
||||
|
||||
if (mw) {
|
||||
if (mw.e !== undefined) this.mwParams.enabled = mw.e;
|
||||
if (mw.i !== undefined) this.mwParams.intensity = mw.i;
|
||||
if (mw.s !== undefined) this.mwParams.saturation = mw.s;
|
||||
if (mw.bp !== undefined) this.mwParams.blackPoint = mw.bp;
|
||||
if (mw.st !== undefined) this.mwParams.siderealTime = mw.st;
|
||||
if (mw.l !== undefined) this.mwParams.latitude = mw.l;
|
||||
if (this.updateMW) this.updateMW();
|
||||
}
|
||||
|
||||
if (b) {
|
||||
|
||||
@@ -96,10 +96,20 @@ material {
|
||||
name : starControl, // x=Density, y=Enabled, z=Frequency, w=PixelScale
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : starIntensity,
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : sampler2d,
|
||||
name : moonTexture
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : exposure,
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : sampler2d,
|
||||
name : moonNormal
|
||||
@@ -162,9 +172,9 @@ fragment {
|
||||
// --- CONFIGURATION ---
|
||||
|
||||
// Stars
|
||||
#define STAR_GLOBAL_INTENSITY 150.0 // Master brightness multiplier [0.0 - 500.0]
|
||||
#define STAR_BRIGHTNESS_BASE 0.5 // Minimum random brightness [0.0 - 1.0]
|
||||
#define STAR_BRIGHTNESS_VAR 4.0 // Random brightness variance range [0.0 - 10.0]
|
||||
#define STAR_GLOBAL_INTENSITY 100.0 // Master brightness multiplier [0.0 - 500.0]
|
||||
#define STAR_BRIGHTNESS_BASE 0.01 // Minimum random brightness [0.0 - 1.0]
|
||||
#define STAR_BRIGHTNESS_VAR 15.0 // Random brightness variance range [0.0 - 10.0]
|
||||
#define STAR_FADE_SUN_ELV_HIGH 0.10 // Sun elevation (sin) where stars are fully hidden [0.0 - 0.5]
|
||||
#define STAR_FADE_SUN_ELV_LOW -0.20 // Sun elevation (sin) where stars are fully visible [-0.5 - 0.0]
|
||||
#define STAR_CLOUD_OCCLUSION 0.1 // Visibility when covered by clouds [0.0 - 1.0]
|
||||
@@ -961,7 +971,11 @@ fragment {
|
||||
highp vec4 sunHalo, highp vec4 cloudControl, highp vec4 cloudControl2,
|
||||
highp vec4 shimmerControl, highp vec4 waterControl,
|
||||
highp vec3 L2, highp float sunIntensity2, highp vec4 sunHalo2,
|
||||
sampler2D moonTex, sampler2D moonNormal) {
|
||||
sampler2D moonTex, sampler2D moonNormal,
|
||||
highp vec3 nightColor,
|
||||
highp mat3 milkyWayRotation,
|
||||
highp vec3 milkyWayControl,
|
||||
sampler2D milkyWayTexture) {
|
||||
|
||||
// Project to plane y=0
|
||||
highp float t = WATER_PLANE_HEIGHT / min(V.y, -0.0002); // Reduced clamp to minimize "wall" artifact
|
||||
@@ -1057,7 +1071,7 @@ fragment {
|
||||
highp float rHorizonMask = 1.0 - smoothstep(0.0, REFLECTION_HORIZON_FADE, R.y);
|
||||
|
||||
if (rHorizonMask > 0.0) {
|
||||
reflection += getStarLayer(R, L, reflCloudDensity, outTransmittance, materialParams.starControl) * rHorizonMask;
|
||||
reflection += getStarLayer(R, L, reflCloudDensity, outTransmittance, materialParams.starControl) * rHorizonMask * materialParams.exposure;
|
||||
}
|
||||
|
||||
// Sun Disk Reflection
|
||||
@@ -1088,6 +1102,29 @@ fragment {
|
||||
// Apply clouds to reflection
|
||||
reflection = mix(reflection, reflCloudLayer, reflCloudDensity);
|
||||
|
||||
// Add Milky Way to Reflection
|
||||
// Calculate Fade based on Sun Elevation
|
||||
highp float sunElvSin = L.y;
|
||||
highp float mwFade = smoothstep(STAR_FADE_SUN_ELV_HIGH, STAR_FADE_SUN_ELV_LOW, sunElvSin);
|
||||
|
||||
if (mwFade > 0.0) {
|
||||
highp vec3 mwColor = getMilkyWay(R, milkyWayRotation, milkyWayTexture, milkyWayControl);
|
||||
|
||||
// Apply Atmosphere Transmittance (approximate)
|
||||
mwColor *= outTransmittance;
|
||||
|
||||
// Apply Fades
|
||||
mwColor *= mwFade;
|
||||
mwColor *= (1.0 - reflMoonOcclusion); // Occlude by Moon
|
||||
mwColor *= (1.0 - reflCloudDensity); // Occlude by Clouds
|
||||
|
||||
reflection += mwColor;
|
||||
}
|
||||
|
||||
|
||||
// Add Night Color to Reflection
|
||||
reflection += nightColor;
|
||||
|
||||
// Fresnel
|
||||
highp float F0 = WATER_FRESNEL_F0; // Water
|
||||
highp float cosTheta = clamp(dot(-V, N_water), 0.0, 1.0);
|
||||
@@ -1187,7 +1224,7 @@ fragment {
|
||||
|
||||
// 7. Stars
|
||||
// Add stars before clouds (clouds cover stars)
|
||||
highp vec3 starColor = getStarLayer(V, L, cloudDensityVal, transmittance, materialParams.starControl);
|
||||
highp vec3 starColor = getStarLayer(V, L, cloudDensityVal, transmittance, materialParams.starControl) * materialParams.exposure * materialParams.starIntensity;
|
||||
|
||||
// 7b. Milky Way
|
||||
// Add Milky Way behind stars (conceptually) but handled similarly
|
||||
@@ -1232,7 +1269,11 @@ fragment {
|
||||
materialParams.shimmerControl,
|
||||
materialParams.waterControl,
|
||||
L2, materialParams.sunIntensity2, materialParams.sunHalo2,
|
||||
materialParams_moonTexture, materialParams_moonNormal);
|
||||
materialParams_moonTexture, materialParams_moonNormal,
|
||||
materialParams.nightColor,
|
||||
materialParams.milkyWayRotation,
|
||||
materialParams.milkyWayControl,
|
||||
materialParams_milkyWayTexture);
|
||||
color = applyDynamicToneMapping(color, L, materialParams.contrast);
|
||||
}
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@ set(SRCS
|
||||
src/MorphTargetBuffer.cpp
|
||||
src/PostProcessManager.cpp
|
||||
src/ProgramSpecialization.cpp
|
||||
src/LocalProgramCache.cpp
|
||||
src/RenderPass.cpp
|
||||
src/RenderPrimitive.cpp
|
||||
src/RenderTarget.cpp
|
||||
@@ -186,6 +187,7 @@ set(PRIVATE_HDRS
|
||||
src/MaterialCache.h
|
||||
src/MaterialDefinition.h
|
||||
src/MaterialParser.h
|
||||
src/LocalProgramCache.h
|
||||
src/MaterialInstanceManager.h
|
||||
src/PIDController.h
|
||||
src/PostProcessManager.h
|
||||
@@ -314,6 +316,12 @@ set(MATERIAL_FL0_SRCS
|
||||
src/materials/skybox.mat
|
||||
)
|
||||
|
||||
set(MATERIAL_MULTIVIEW_SRCS
|
||||
src/materials/clearDepth.mat
|
||||
src/materials/defaultMaterial.mat
|
||||
src/materials/skybox.mat
|
||||
)
|
||||
|
||||
# ==================================================================================================
|
||||
# Configuration
|
||||
# ==================================================================================================
|
||||
@@ -338,6 +346,11 @@ if (FILAMENT_ENABLE_FEATURE_LEVEL_0)
|
||||
add_definitions(-DFILAMENT_ENABLE_FEATURE_LEVEL_0)
|
||||
endif()
|
||||
|
||||
# Whether to include MULTIVIEW materials.
|
||||
if (FILAMENT_ENABLE_MULTIVIEW)
|
||||
add_definitions(-DFILAMENT_ENABLE_MULTIVIEW)
|
||||
endif()
|
||||
|
||||
# Whether to force the profiling mode.
|
||||
if (FILAMENT_FORCE_PROFILING_MODE)
|
||||
add_definitions(-DFILAMENT_FORCE_PROFILING_MODE)
|
||||
@@ -428,6 +441,21 @@ foreach(mat_dir ${MATERIAL_DIRS})
|
||||
list(APPEND FILAMAT_FILES_FOR_GROUP ${output_path_fl0})
|
||||
list(APPEND FILAMAT_TARGETS_FOR_GROUP ${output_path_fl0})
|
||||
endif()
|
||||
|
||||
# --- Multiview variant ---
|
||||
list(FIND MATERIAL_MULTIVIEW_SRCS ${mat_src} index)
|
||||
if (${index} GREATER -1 AND FILAMENT_ENABLE_MULTIVIEW)
|
||||
string(REGEX REPLACE "[.]filamat$" "_multiview.filamat" output_path_multiview ${output_path})
|
||||
add_custom_command(
|
||||
OUTPUT ${output_path_multiview}
|
||||
COMMAND matc ${MATC_BASE_FLAGS} -PstereoscopicType=multiview -o ${output_path_multiview} ${fullname}
|
||||
MAIN_DEPENDENCY ${fullname}
|
||||
DEPENDS matc
|
||||
COMMENT "Compiling material ${fullname} (Multiview)"
|
||||
)
|
||||
list(APPEND FILAMAT_FILES_FOR_GROUP ${output_path_multiview})
|
||||
list(APPEND FILAMAT_TARGETS_FOR_GROUP ${output_path_multiview})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Generate a single resource file for the whole group
|
||||
|
||||
@@ -564,169 +564,169 @@ endif()
|
||||
# ==================================================================================================
|
||||
# Test
|
||||
# ==================================================================================================
|
||||
option(INSTALL_BACKEND_TEST "Install the backend test library so it can be consumed on iOS" OFF)
|
||||
if (FILAMENT_BUILD_TESTING)
|
||||
option(INSTALL_BACKEND_TEST "Install the backend test library so it can be consumed on iOS" OFF)
|
||||
|
||||
if (APPLE OR LINUX)
|
||||
set(BACKEND_TEST_SRC
|
||||
test/BackendTest.cpp
|
||||
test/ShaderGenerator.cpp
|
||||
test/TrianglePrimitive.cpp
|
||||
test/Arguments.cpp
|
||||
test/ImageExpectations.cpp
|
||||
test/Lifetimes.cpp
|
||||
test/PlatformRunner.cpp
|
||||
test/Shader.cpp
|
||||
test/SharedShaders.cpp
|
||||
test/Skip.cpp
|
||||
test/test_FeedbackLoops.cpp
|
||||
test/test_Blit.cpp
|
||||
test/test_MissingRequiredAttributes.cpp
|
||||
test/test_ReadPixels.cpp
|
||||
test/test_ReadTexture.cpp
|
||||
test/test_BufferUpdates.cpp
|
||||
test/test_Callbacks.cpp
|
||||
test/test_JobQueue.cpp
|
||||
test/test_MemoryMappedBuffer.cpp
|
||||
test/test_MsaaSwapChain.cpp
|
||||
test/test_MRT.cpp
|
||||
test/test_PushConstants.cpp
|
||||
test/test_LoadImage.cpp
|
||||
test/test_StencilBuffer.cpp
|
||||
test/test_Scissor.cpp
|
||||
test/test_MipLevels.cpp
|
||||
test/test_Handles.cpp
|
||||
test/test_CircularBuffer.cpp
|
||||
test/test_CommandBufferQueue.cpp
|
||||
test/test_Template.cpp
|
||||
)
|
||||
if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
list(APPEND BACKEND_TEST_SRC
|
||||
test/test_WebGPUAsyncTaskCounter.cpp
|
||||
test/test_WebGPUComposeSwizzle.cpp)
|
||||
endif()
|
||||
if (APPLE)
|
||||
# Metal-specific tests
|
||||
list(APPEND BACKEND_TEST_SRC
|
||||
test/test_MetalBlitter.mm
|
||||
if (APPLE OR LINUX)
|
||||
set(BACKEND_TEST_SRC
|
||||
test/BackendTest.cpp
|
||||
test/ShaderGenerator.cpp
|
||||
test/TrianglePrimitive.cpp
|
||||
test/Arguments.cpp
|
||||
test/ImageExpectations.cpp
|
||||
test/Lifetimes.cpp
|
||||
test/PlatformRunner.cpp
|
||||
test/Shader.cpp
|
||||
test/SharedShaders.cpp
|
||||
test/Skip.cpp
|
||||
test/test_Autoresolve.cpp
|
||||
test/test_FeedbackLoops.cpp
|
||||
test/test_Blit.cpp
|
||||
test/test_MissingRequiredAttributes.cpp
|
||||
test/test_ReadPixels.cpp
|
||||
test/test_ReadTexture.cpp
|
||||
test/test_BufferUpdates.cpp
|
||||
test/test_Callbacks.cpp
|
||||
test/test_JobQueue.cpp
|
||||
test/test_MemoryMappedBuffer.cpp
|
||||
test/test_MsaaSwapChain.cpp
|
||||
test/test_MRT.cpp
|
||||
test/test_PushConstants.cpp
|
||||
test/test_LoadImage.cpp
|
||||
test/test_StencilBuffer.cpp
|
||||
test/test_Scissor.cpp
|
||||
test/test_MipLevels.cpp
|
||||
test/test_Handles.cpp
|
||||
test/test_CircularBuffer.cpp
|
||||
test/test_CommandBufferQueue.cpp
|
||||
test/test_Template.cpp
|
||||
test/test_Platform.cpp
|
||||
)
|
||||
endif()
|
||||
set(BACKEND_TEST_LIBS
|
||||
absl::str_format
|
||||
backend
|
||||
getopt
|
||||
gtest
|
||||
imageio
|
||||
filamat
|
||||
SPIRV
|
||||
spirv-cross-glsl)
|
||||
# Create input/output directories for test result images.
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/actual_images)
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/diff_images)
|
||||
file(COPY test/expected_images DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/images)
|
||||
endif()
|
||||
|
||||
# TODO: Disabling IOS test due to breakage wrt glslang update
|
||||
if (APPLE AND NOT IOS)
|
||||
# TODO: we should expand this test to Linux and other platforms.
|
||||
list(APPEND BACKEND_TEST_SRC
|
||||
test/test_RenderExternalImage.cpp)
|
||||
add_library(backend_test STATIC ${BACKEND_TEST_SRC})
|
||||
target_link_libraries(backend_test PUBLIC ${BACKEND_TEST_LIBS})
|
||||
target_compile_options(backend_test PRIVATE "-fobjc-arc")
|
||||
|
||||
set(BACKEND_TEST_DEPS
|
||||
OSDependent
|
||||
SPIRV
|
||||
SPIRV-Tools
|
||||
SPIRV-Tools-opt
|
||||
backend_test
|
||||
getopt
|
||||
if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
list(APPEND BACKEND_TEST_SRC
|
||||
test/test_WebGPUAsyncTaskCounter.cpp
|
||||
test/test_WebGPUComposeSwizzle.cpp)
|
||||
endif()
|
||||
if (APPLE)
|
||||
# Metal-specific tests
|
||||
list(APPEND BACKEND_TEST_SRC
|
||||
test/test_MetalBlitter.mm
|
||||
)
|
||||
endif()
|
||||
set(BACKEND_TEST_LIBS
|
||||
absl::str_format
|
||||
backend
|
||||
gtest
|
||||
glslang
|
||||
spirv-cross-core
|
||||
spirv-cross-glsl
|
||||
spirv-cross-msl)
|
||||
|
||||
if (NOT IOS)
|
||||
target_link_libraries(backend_test PRIVATE image imageio)
|
||||
list(APPEND BACKEND_TEST_DEPS image)
|
||||
endif()
|
||||
if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
target_link_libraries(backend_test PRIVATE webgpu_dawn dawncpp_headers)
|
||||
list(APPEND BACKEND_TEST_DEPS webgpu_dawn dawncpp_headers)
|
||||
imageio
|
||||
filamat
|
||||
SPIRV
|
||||
spirv-cross-glsl)
|
||||
# Create input/output directories for test result images.
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/actual_images)
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/diff_images)
|
||||
file(COPY test/expected_images DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/images)
|
||||
endif()
|
||||
|
||||
set(BACKEND_TEST_COMBINED_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/libbackendtest_combined.a")
|
||||
combine_static_libs(backend_test "${BACKEND_TEST_COMBINED_OUTPUT}" "${BACKEND_TEST_DEPS}")
|
||||
# TODO: Disabling IOS test due to breakage wrt glslang update
|
||||
if (APPLE AND NOT IOS)
|
||||
# TODO: we should expand this test to Linux and other platforms.
|
||||
list(APPEND BACKEND_TEST_SRC
|
||||
test/test_RenderExternalImage.cpp)
|
||||
add_library(backend_test STATIC ${BACKEND_TEST_SRC})
|
||||
target_link_libraries(backend_test PUBLIC ${BACKEND_TEST_LIBS})
|
||||
target_compile_options(backend_test PRIVATE "-fobjc-arc")
|
||||
|
||||
set(BACKEND_TEST_LIB_NAME ${CMAKE_STATIC_LIBRARY_PREFIX}backend_test${CMAKE_STATIC_LIBRARY_SUFFIX})
|
||||
set(BACKEND_TEST_DEPS
|
||||
OSDependent
|
||||
SPIRV
|
||||
SPIRV-Tools
|
||||
SPIRV-Tools-opt
|
||||
backend_test
|
||||
getopt
|
||||
gtest
|
||||
glslang
|
||||
spirv-cross-core
|
||||
spirv-cross-glsl
|
||||
spirv-cross-msl)
|
||||
|
||||
if (INSTALL_BACKEND_TEST)
|
||||
install(FILES "${BACKEND_TEST_COMBINED_OUTPUT}" DESTINATION lib/${DIST_DIR} RENAME ${BACKEND_TEST_LIB_NAME})
|
||||
install(FILES test/PlatformRunner.h DESTINATION include/backend_test)
|
||||
if (NOT IOS)
|
||||
target_link_libraries(backend_test PRIVATE image imageio)
|
||||
list(APPEND BACKEND_TEST_DEPS image)
|
||||
endif()
|
||||
if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
target_link_libraries(backend_test PRIVATE webgpu_dawn dawncpp_headers)
|
||||
list(APPEND BACKEND_TEST_DEPS webgpu_dawn dawncpp_headers)
|
||||
endif()
|
||||
|
||||
set(BACKEND_TEST_COMBINED_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/libbackendtest_combined.a")
|
||||
combine_static_libs(backend_test "${BACKEND_TEST_COMBINED_OUTPUT}" "${BACKEND_TEST_DEPS}")
|
||||
|
||||
set(BACKEND_TEST_LIB_NAME ${CMAKE_STATIC_LIBRARY_PREFIX}backend_test${CMAKE_STATIC_LIBRARY_SUFFIX})
|
||||
|
||||
if (INSTALL_BACKEND_TEST)
|
||||
install(FILES "${BACKEND_TEST_COMBINED_OUTPUT}" DESTINATION lib/${DIST_DIR} RENAME ${BACKEND_TEST_LIB_NAME})
|
||||
install(FILES test/PlatformRunner.h DESTINATION include/backend_test)
|
||||
endif()
|
||||
|
||||
set_target_properties(backend_test PROPERTIES FOLDER Tests)
|
||||
|
||||
if (APPLE AND NOT IOS)
|
||||
add_executable(backend_test_mac test/mac_runner.mm)
|
||||
target_link_libraries(backend_test_mac PRIVATE "-framework Metal -framework AppKit -framework QuartzCore")
|
||||
# Because each test case is a separate file, the -force_load flag is necessary to prevent the
|
||||
# linker from removing "unused" symbols.
|
||||
target_link_libraries(backend_test_mac PRIVATE -force_load backend_test)
|
||||
set_target_properties(backend_test_mac PROPERTIES FOLDER Tests)
|
||||
|
||||
# This is needed after XCode 15.3
|
||||
set_target_properties(backend_test_mac PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE)
|
||||
set_target_properties(backend_test_mac PROPERTIES INSTALL_RPATH /usr/local/lib)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set_target_properties(backend_test PROPERTIES FOLDER Tests)
|
||||
if (LINUX)
|
||||
add_executable(backend_test_linux test/linux_runner.cpp ${BACKEND_TEST_SRC})
|
||||
target_link_libraries(backend_test_linux PRIVATE ${BACKEND_TEST_LIBS})
|
||||
set_target_properties(backend_test_linux PROPERTIES FOLDER Tests)
|
||||
if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
target_link_libraries(backend_test_linux PRIVATE webgpu_dawn dawncpp_headers)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# ==================================================================================================
|
||||
# Compute tests
|
||||
#
|
||||
#if (NOT IOS AND NOT WEBGL)
|
||||
#
|
||||
#add_executable(compute_test
|
||||
# test/ComputeTest.cpp
|
||||
# test/Arguments.cpp
|
||||
# test/test_ComputeBasic.cpp
|
||||
# )
|
||||
#
|
||||
#target_link_libraries(compute_test PRIVATE
|
||||
# backend
|
||||
# getopt
|
||||
# gtest
|
||||
# )
|
||||
#
|
||||
#set_target_properties(compute_test PROPERTIES FOLDER Tests)
|
||||
#
|
||||
#endif()
|
||||
|
||||
# ==================================================================================================
|
||||
# Metal utils tests
|
||||
|
||||
if (APPLE AND NOT IOS)
|
||||
add_executable(backend_test_mac test/mac_runner.mm)
|
||||
target_link_libraries(backend_test_mac PRIVATE "-framework Metal -framework AppKit -framework QuartzCore")
|
||||
# Because each test case is a separate file, the -force_load flag is necessary to prevent the
|
||||
# linker from removing "unused" symbols.
|
||||
target_link_libraries(backend_test_mac PRIVATE -force_load backend_test)
|
||||
set_target_properties(backend_test_mac PROPERTIES FOLDER Tests)
|
||||
add_executable(metal_utils_test test/MetalTest.mm)
|
||||
|
||||
# This is needed after XCode 15.3
|
||||
set_target_properties(backend_test_mac PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE)
|
||||
set_target_properties(backend_test_mac PROPERTIES INSTALL_RPATH /usr/local/lib)
|
||||
target_compile_options(metal_utils_test PRIVATE "-fobjc-arc")
|
||||
|
||||
target_link_libraries(metal_utils_test PRIVATE
|
||||
backend
|
||||
gtest
|
||||
)
|
||||
|
||||
set_target_properties(metal_utils_test PROPERTIES FOLDER Tests)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (LINUX)
|
||||
add_executable(backend_test_linux test/linux_runner.cpp ${BACKEND_TEST_SRC})
|
||||
target_link_libraries(backend_test_linux PRIVATE ${BACKEND_TEST_LIBS})
|
||||
set_target_properties(backend_test_linux PROPERTIES FOLDER Tests)
|
||||
if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
target_link_libraries(backend_test_linux PRIVATE webgpu_dawn dawncpp_headers)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# ==================================================================================================
|
||||
# Compute tests
|
||||
#
|
||||
#if (NOT IOS AND NOT WEBGL)
|
||||
#
|
||||
#add_executable(compute_test
|
||||
# test/ComputeTest.cpp
|
||||
# test/Arguments.cpp
|
||||
# test/test_ComputeBasic.cpp
|
||||
# )
|
||||
#
|
||||
#target_link_libraries(compute_test PRIVATE
|
||||
# backend
|
||||
# getopt
|
||||
# gtest
|
||||
# )
|
||||
#
|
||||
#set_target_properties(compute_test PROPERTIES FOLDER Tests)
|
||||
#
|
||||
#endif()
|
||||
|
||||
# ==================================================================================================
|
||||
# Metal utils tests
|
||||
|
||||
if (APPLE AND NOT IOS)
|
||||
|
||||
add_executable(metal_utils_test test/MetalTest.mm)
|
||||
|
||||
target_compile_options(metal_utils_test PRIVATE "-fobjc-arc")
|
||||
|
||||
target_link_libraries(metal_utils_test PRIVATE
|
||||
backend
|
||||
getopt
|
||||
gtest
|
||||
)
|
||||
|
||||
set_target_properties(metal_utils_test PROPERTIES FOLDER Tests)
|
||||
|
||||
endif()
|
||||
|
||||
@@ -216,6 +216,18 @@ public:
|
||||
MULTIVIEW,
|
||||
};
|
||||
|
||||
/**
|
||||
* Types of device/driver information that can be queried from the platform.
|
||||
*/
|
||||
enum class DeviceInfoType {
|
||||
OPENGL_RENDERER, //!< glGetString(GL_RENDERER)
|
||||
OPENGL_VENDOR, //!< glGetString(GL_VENDOR)
|
||||
OPENGL_VERSION, //!< glGetString(GL_VERSION)
|
||||
VULKAN_DEVICE_NAME, //!< VkPhysicalDeviceProperties::deviceName
|
||||
VULKAN_DRIVER_NAME, //!< VkPhysicalDeviceDriverProperties::driverName
|
||||
VULKAN_DRIVER_INFO, //!< VkPhysicalDeviceDriverProperties::driverInfo
|
||||
};
|
||||
|
||||
/**
|
||||
* This controls the priority level for GPU work scheduling, which helps prioritize the
|
||||
* submitted GPU work and enables preemption.
|
||||
@@ -316,7 +328,7 @@ public:
|
||||
*/
|
||||
StereoscopicType stereoscopicType = StereoscopicType::NONE;
|
||||
|
||||
/*
|
||||
/**
|
||||
* The number of eyes to render when stereoscopic rendering is enabled. Supported values are
|
||||
* between 1 and Engine::getMaxStereoscopicEyes() (inclusive).
|
||||
*/
|
||||
@@ -377,6 +389,16 @@ public:
|
||||
*/
|
||||
virtual int getOSVersion() const noexcept = 0;
|
||||
|
||||
/**
|
||||
* Queries device/driver information of the graphics API.
|
||||
* @param infoType the type of information to query.
|
||||
* @param driver a pointer to the current driver.
|
||||
* @return a CString containing the requested information.
|
||||
*/
|
||||
virtual utils::CString getDeviceInfo(DeviceInfoType infoType,
|
||||
Driver* UTILS_NULLABLE driver) const noexcept = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Creates and initializes the low-level API (e.g. an OpenGL context or Vulkan instance),
|
||||
* then creates the concrete Driver.
|
||||
@@ -561,16 +583,18 @@ public:
|
||||
|
||||
/**
|
||||
* Sets the callback function that the backend can use to update backend-specific statistics
|
||||
* to aid with debugging. This callback is guaranteed to be called on the Filament driver
|
||||
* thread.
|
||||
* to aid with debugging. This callback can be called on either the Filament main thread or
|
||||
* the Filament driver thread.
|
||||
*
|
||||
* The callback signature is (key, intValue, stringValue). Note that for any given call,
|
||||
* only one of the value parameters (intValue or stringValue) will be meaningful, depending on
|
||||
* the specific key.
|
||||
*
|
||||
* IMPORTANT_NOTE: because the callback is called on the driver thread, only quick, non-blocking
|
||||
* work should be done inside it. Furthermore, no graphics API calls (such as GL calls) should
|
||||
* be made, which could interfere with Filament's driver state.
|
||||
* IMPORTANT_NOTE: because the callback can be called on the driver thread, only quick,
|
||||
* non-blocking work should be done inside it. Furthermore, no graphics API calls (such as GL
|
||||
* calls) should be made, which could interfere with Filament's driver state. Lastly, the
|
||||
* callback implementation must be synchronized (thread-safe) since it can be called from
|
||||
* either thread.
|
||||
*
|
||||
* @param debugUpdateStat an Invocable that updates debug statistics
|
||||
*/
|
||||
@@ -587,8 +611,7 @@ public:
|
||||
* with a given key. It is possible for this function to be called multiple times with the
|
||||
* same key, in which case newer values should overwrite older values.
|
||||
*
|
||||
* This function is guaranteed to be called only on a single thread, the Filament driver
|
||||
* thread.
|
||||
* This function can be called on either the Filament main thread or the Filament driver thread.
|
||||
*
|
||||
* @param key a null-terminated C-string with the key of the debug statistic
|
||||
* @param intValue the updated integer value of key (the string value passed to the
|
||||
@@ -602,8 +625,7 @@ public:
|
||||
* with a given key. It is possible for this function to be called multiple times with the
|
||||
* same key, in which case newer values should overwrite older values.
|
||||
*
|
||||
* This function is guaranteed to be called only on a single thread, the Filament driver
|
||||
* thread.
|
||||
* This function can be called on either the Filament main thread or the Filament driver thread.
|
||||
*
|
||||
* @param key a null-terminated C-string with the key of the debug statistic
|
||||
* @param stringValue the updated string value of key (the integer value passed to the
|
||||
|
||||
@@ -52,6 +52,9 @@ protected:
|
||||
|
||||
~OpenGLPlatform() noexcept override;
|
||||
|
||||
utils::CString getDeviceInfo(DeviceInfoType infoType,
|
||||
Driver* UTILS_NULLABLE driver) const noexcept override;
|
||||
|
||||
public:
|
||||
struct ExternalTexture {
|
||||
unsigned int target; // GLenum target
|
||||
@@ -70,6 +73,12 @@ public:
|
||||
*/
|
||||
static utils::CString getRendererString(Driver const* UTILS_NONNULL driver);
|
||||
|
||||
/**
|
||||
* Return the OpenGL version string of the specified Driver instance.
|
||||
* @return The GL_VERSION string
|
||||
*/
|
||||
static utils::CString getVersionString(Driver const* UTILS_NONNULL driver);
|
||||
|
||||
/**
|
||||
* Called by the driver to destroy the OpenGL context. This should clean up any windows
|
||||
* or buffers from initialization. This is for instance where `eglDestroyContext` would be
|
||||
|
||||
@@ -159,6 +159,12 @@ protected:
|
||||
EGLConfig getEglConfig() const noexcept { return mEGLConfig; }
|
||||
EGLConfig getSuitableConfigForSwapChain(uint64_t flags, bool window, bool pbuffer) const;
|
||||
|
||||
// Sets the EGLDisplay to be used by this platform. This should only be called by derived
|
||||
// classes before invoking createDriver. Calling it after that point will result in
|
||||
// undefined behaviour. This class will take ownership of the display and call eglTerminate
|
||||
// on it during shutdown.
|
||||
void setEglDisplay(EGLDisplay display) noexcept;
|
||||
|
||||
// supported extensions detected at runtime
|
||||
struct {
|
||||
struct {
|
||||
|
||||
@@ -185,7 +185,7 @@ private:
|
||||
};
|
||||
|
||||
int mOSVersion;
|
||||
ExternalStreamManagerAndroid& mExternalStreamManager;
|
||||
ExternalStreamManagerAndroid* mExternalStreamManager = nullptr;
|
||||
AndroidDetails& mAndroidDetails;
|
||||
utils::PerformanceHintManager mPerformanceHintManager;
|
||||
utils::PerformanceHintManager::Session mPerformanceHintSession;
|
||||
|
||||
@@ -38,6 +38,7 @@ public:
|
||||
|
||||
Driver* createDriver(void* sharedContext, const Platform::DriverConfig& driverConfig) override;
|
||||
int getOSVersion() const noexcept override { return 0; }
|
||||
utils::CString getDeviceInfo(DeviceInfoType, Driver*) const noexcept override { return {}; }
|
||||
|
||||
/**
|
||||
* Optionally initializes the Metal platform by acquiring resources necessary for rendering.
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
#include <cstring>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <unordered_set>
|
||||
|
||||
@@ -69,7 +68,7 @@ public:
|
||||
|
||||
struct ExtensionHashFn {
|
||||
std::size_t operator()(utils::CString const& s) const noexcept {
|
||||
return std::hash<std::string>{}(s.data());
|
||||
return std::hash<utils::CString>{}(s.data());
|
||||
}
|
||||
};
|
||||
// Note: utils::CString::operator== has an edge case that breaks for the extension set.
|
||||
@@ -143,6 +142,8 @@ public:
|
||||
return 0;
|
||||
}
|
||||
|
||||
utils::CString getDeviceInfo(DeviceInfoType infoType, Driver* driver) const noexcept override;
|
||||
|
||||
// ----------------------------------------------------
|
||||
// ---------- Platform Customization options ----------
|
||||
struct Customization {
|
||||
@@ -175,6 +176,12 @@ public:
|
||||
* presentation. Default is true.
|
||||
*/
|
||||
bool transitionSwapChainImageLayoutForPresent = true;
|
||||
|
||||
/**
|
||||
* The number of frames before an unused framebuffer is evicted from the cache.
|
||||
* Default is 3.
|
||||
*/
|
||||
uint32_t timeBeforeEvictionFbo = 3;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,6 +46,9 @@ public:
|
||||
~WebGPUPlatform() override = default;
|
||||
|
||||
[[nodiscard]] int getOSVersion() const noexcept final { return 0; }
|
||||
[[nodiscard]] utils::CString getDeviceInfo(DeviceInfoType, Driver*) const noexcept override {
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] wgpu::Instance& getInstance() noexcept { return mInstance; }
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#ifdef __ANDROID__
|
||||
|
||||
@@ -19,21 +19,169 @@
|
||||
|
||||
#include <backend/PixelBufferDescriptor.h>
|
||||
|
||||
#include <math/scalar.h>
|
||||
#include <math/half.h>
|
||||
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Logger.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <math/scalar.h>
|
||||
|
||||
#include <utils/debug.h>
|
||||
|
||||
namespace filament {
|
||||
namespace backend {
|
||||
|
||||
namespace {
|
||||
|
||||
// Provides an alpha value when expanding 3-channel images to 4-channel.
|
||||
// Also used as a normalization scale when converting between numeric types.
|
||||
template<typename componentType> inline componentType getMaxValue();
|
||||
|
||||
template<> inline constexpr float getMaxValue() { return 1.0f; }
|
||||
template<> inline constexpr int32_t getMaxValue() { return 0x7fffffff; }
|
||||
template<> inline constexpr uint32_t getMaxValue() { return 0xffffffff; }
|
||||
template<> inline constexpr uint16_t getMaxValue() { return 0x3c00; } // 0x3c00 is 1.0 in half-float.
|
||||
template<> inline constexpr uint8_t getMaxValue() { return 0xff; }
|
||||
template<> inline math::half getMaxValue() { return math::half(1.0f); }
|
||||
|
||||
// We use template below to reduce code duplication across the different input/output
|
||||
// type/channle-count permutations. Morever, templates help us reduce the number of conditionals
|
||||
// in the inner-loop of the reshape operation. However, this needs to be a carefully considered
|
||||
// because too many templated params will cause a large binary size increase.
|
||||
|
||||
// Note that we intentionally do not want to expand the template params to include the channel count
|
||||
// because of the size increase.
|
||||
template<typename dstComponentType, bool hasAlpha>
|
||||
void grayscaleFill(dstComponentType* dst, uint8_t, uint8_t) {
|
||||
for (size_t channel = 1; channel < 3; ++channel) {
|
||||
dst[channel] = dst[0];
|
||||
}
|
||||
if constexpr (hasAlpha) {
|
||||
dst[3] = getMaxValue<dstComponentType>();
|
||||
}
|
||||
}
|
||||
|
||||
// Note that we intentionally do not want to expand the template params to include the channel count
|
||||
// because of the size increase.
|
||||
template<typename dstComponentType>
|
||||
inline void maxValFill(dstComponentType* dst, uint8_t srcChannelCount, uint8_t dstChannelCount) {
|
||||
dstComponentType dstMaxValue = getMaxValue<dstComponentType>();
|
||||
for (size_t channel = srcChannelCount; channel < dstChannelCount; ++channel) {
|
||||
dst[channel] = dstMaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Converts a n-channel image of UBYTE, INT, UINT, HALF, or FLOAT to a different type.
|
||||
template<typename dstComponentType, typename srcComponentType>
|
||||
void reshapeImageImpl(uint8_t* UTILS_RESTRICT dest, const uint8_t* UTILS_RESTRICT src,
|
||||
size_t srcBytesPerRow, size_t srcChannelCount, size_t dstRowOffset, size_t dstColumnOffset,
|
||||
size_t dstBytesPerRow, size_t dstChannelCount, size_t width, size_t height, bool swizzle) {
|
||||
|
||||
static_assert(!std::is_same_v<dstComponentType, math::half>);
|
||||
const size_t minChannelCount = math::min(srcChannelCount, dstChannelCount);
|
||||
const dstComponentType dstMaxValue = getMaxValue<dstComponentType>();
|
||||
const srcComponentType srcMaxValue = getMaxValue<srcComponentType>();
|
||||
double const mFactor = dstMaxValue / ((double) srcMaxValue);
|
||||
assert_invariant(minChannelCount <= 4);
|
||||
UTILS_ASSUME(minChannelCount <= 4);
|
||||
dest += (dstRowOffset * dstBytesPerRow);
|
||||
|
||||
void (*fill)(dstComponentType*, uint8_t, uint8_t);
|
||||
if (srcChannelCount == 1 && dstChannelCount == 3) {
|
||||
fill = grayscaleFill<dstComponentType, false>;
|
||||
} else if (srcChannelCount == 1 && dstChannelCount == 4) {
|
||||
fill = grayscaleFill<dstComponentType, true>;
|
||||
} else {
|
||||
fill = maxValFill<dstComponentType>;
|
||||
}
|
||||
|
||||
const int inds[4] = { swizzle ? 2 : 0, 1, swizzle ? 0 : 2, 3 };
|
||||
for (size_t row = 0; row < height; ++row) {
|
||||
const srcComponentType* in = (const srcComponentType*) src;
|
||||
dstComponentType* out = (dstComponentType*) dest + (dstColumnOffset * dstChannelCount);
|
||||
for (size_t column = 0; column < width; ++column) {
|
||||
for (uint8_t channel = 0; channel < minChannelCount; ++channel) {
|
||||
if constexpr (std::is_same_v<dstComponentType, srcComponentType>) {
|
||||
out[channel] = in[inds[channel]];
|
||||
} else {
|
||||
// convert to double then clamp and cast to dst type.
|
||||
out[channel] = static_cast<dstComponentType>(std::clamp(
|
||||
in[inds[channel]] * mFactor, 0.0,
|
||||
static_cast<double>(std::numeric_limits<dstComponentType>::max())));
|
||||
}
|
||||
}
|
||||
|
||||
// This will fill in all the channels that are not copied.
|
||||
fill(out, srcChannelCount, dstChannelCount);
|
||||
in += srcChannelCount;
|
||||
out += dstChannelCount;
|
||||
}
|
||||
src += srcBytesPerRow;
|
||||
dest += dstBytesPerRow;
|
||||
}
|
||||
}
|
||||
|
||||
struct UnpackerR11G11B10 {
|
||||
static void unpack(const uint8_t* src, float* out) {
|
||||
uint32_t p;
|
||||
std::memcpy(&p, src, 4);
|
||||
|
||||
using R11 = math::fp<0, 5, 6>;
|
||||
using G11 = math::fp<0, 5, 6>;
|
||||
using B10 = math::fp<0, 5, 5>;
|
||||
|
||||
out[0] = R11::tof(R11(uint16_t((p >> 21) & 0x7FF)));
|
||||
out[1] = G11::tof(G11(uint16_t((p >> 10) & 0x7FF)));
|
||||
out[2] = B10::tof(B10(uint16_t(p & 0x3FF)));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename dstComponentType, typename Unpacker, bool Swizzle>
|
||||
static void reshapeImagePacked(uint8_t* UTILS_RESTRICT dest, const uint8_t* UTILS_RESTRICT src,
|
||||
size_t srcBytesPerRow, size_t srcChannelCount, size_t dstRowOffset, size_t dstColumnOffset,
|
||||
size_t dstBytesPerRow, size_t dstChannelCount, size_t width, size_t height, bool /*swizzle*/) {
|
||||
|
||||
dest += (dstRowOffset * dstBytesPerRow);
|
||||
const dstComponentType dstMaxValue = getMaxValue<dstComponentType>();
|
||||
|
||||
for (size_t row = 0; row < height; ++row) {
|
||||
const uint8_t* inPtr = src;
|
||||
dstComponentType* out = (dstComponentType*) dest + (dstColumnOffset * dstChannelCount);
|
||||
|
||||
for (size_t column = 0; column < width; ++column) {
|
||||
float rgba[4] = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
Unpacker::unpack(inPtr, rgba);
|
||||
|
||||
if constexpr (Swizzle) {
|
||||
std::swap(rgba[0], rgba[2]);
|
||||
}
|
||||
|
||||
for (size_t c = 0; c < dstChannelCount; ++c) {
|
||||
if constexpr (std::is_same_v<dstComponentType, float>) {
|
||||
out[c] = rgba[c];
|
||||
} else if constexpr (std::is_same_v<dstComponentType, math::half>) {
|
||||
out[c] = math::half(rgba[c]);
|
||||
} else {
|
||||
out[c] = static_cast<dstComponentType>(std::clamp(
|
||||
static_cast<double>(rgba[c]) * static_cast<double>(dstMaxValue),
|
||||
0.0,
|
||||
static_cast<double>(std::numeric_limits<dstComponentType>::max())
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
inPtr += 4;
|
||||
out += dstChannelCount;
|
||||
}
|
||||
src += srcBytesPerRow;
|
||||
dest += dstBytesPerRow;
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
class DataReshaper {
|
||||
public:
|
||||
|
||||
@@ -76,51 +224,9 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// Converts a n-channel image of UBYTE, INT, UINT, or FLOAT to a different type.
|
||||
template<typename dstComponentType, typename srcComponentType>
|
||||
static void reshapeImage(uint8_t* UTILS_RESTRICT dest, const uint8_t* UTILS_RESTRICT src,
|
||||
size_t srcBytesPerRow,
|
||||
size_t srcChannelCount,
|
||||
size_t dstRowOffset, size_t dstColumnOffset,
|
||||
size_t dstBytesPerRow, size_t dstChannelCount,
|
||||
size_t width, size_t height, bool swizzle) {
|
||||
// TODO: there's a fast-path where memcpy will work but currently not being taken advantage
|
||||
// of.
|
||||
|
||||
const dstComponentType dstMaxValue = getMaxValue<dstComponentType>();
|
||||
const srcComponentType srcMaxValue = getMaxValue<srcComponentType>();
|
||||
const size_t minChannelCount = math::min(srcChannelCount, dstChannelCount);
|
||||
assert_invariant(minChannelCount <= 4);
|
||||
UTILS_ASSUME(minChannelCount <= 4);
|
||||
dest += (dstRowOffset * dstBytesPerRow);
|
||||
const int inds[4] = { swizzle ? 2 : 0, 1, swizzle ? 0 : 2, 3 };
|
||||
for (size_t row = 0; row < height; ++row) {
|
||||
const srcComponentType* in = (const srcComponentType*) src;
|
||||
dstComponentType* out = (dstComponentType*)dest + (dstColumnOffset * dstChannelCount);
|
||||
for (size_t column = 0; column < width; ++column) {
|
||||
for (size_t channel = 0; channel < minChannelCount; ++channel) {
|
||||
if constexpr (std::is_same_v<dstComponentType, srcComponentType>) {
|
||||
out[channel] = in[inds[channel]];
|
||||
} else {
|
||||
// FIXME: beware of overflows in the multiply
|
||||
// FIXME: probably not correct for _INTEGER src/dst
|
||||
out[channel] = in[inds[channel]] * dstMaxValue / srcMaxValue;
|
||||
}
|
||||
}
|
||||
for (size_t channel = srcChannelCount; channel < dstChannelCount; ++channel) {
|
||||
out[channel] = dstMaxValue;
|
||||
}
|
||||
in += srcChannelCount;
|
||||
out += dstChannelCount;
|
||||
}
|
||||
src += srcBytesPerRow;
|
||||
dest += dstBytesPerRow;
|
||||
}
|
||||
}
|
||||
|
||||
// Converts a n-channel image of UBYTE, INT, UINT, or FLOAT to a different type.
|
||||
static bool reshapeImage(PixelBufferDescriptor* UTILS_RESTRICT dst, PixelDataType srcType,
|
||||
uint32_t srcChannelCount, const uint8_t* UTILS_RESTRICT srcBytes, int srcBytesPerRow,
|
||||
uint32_t srcChannelCount, const uint8_t* UTILS_RESTRICT srcBytes, int srcBytesPerRow,
|
||||
int width, int height, bool swizzle) {
|
||||
size_t dstChannelCount;
|
||||
switch (dst->format) {
|
||||
@@ -132,87 +238,123 @@ public:
|
||||
case PixelDataFormat::RG: dstChannelCount = 2; break;
|
||||
case PixelDataFormat::RGB: dstChannelCount = 3; break;
|
||||
case PixelDataFormat::RGBA: dstChannelCount = 4; break;
|
||||
default: return false;
|
||||
default:
|
||||
LOG(ERROR) << "DataReshaper: unsupported dst->format: " << (int) dst->format;
|
||||
return false;
|
||||
}
|
||||
void (*reshaper)(uint8_t* dest, const uint8_t* src, size_t srcBytesPerRow,
|
||||
size_t srcChannelCount,
|
||||
size_t srcRowOffset, size_t srcColumnOffset,
|
||||
size_t dstBytesPerRow, size_t dstChannelCount,
|
||||
size_t width, size_t height, bool swizzle) = nullptr;
|
||||
size_t srcChannelCount, size_t srcRowOffset, size_t srcColumnOffset,
|
||||
size_t dstBytesPerRow, size_t dstChannelCount, size_t width, size_t height,
|
||||
bool swizzle) = nullptr;
|
||||
constexpr auto UBYTE = PixelDataType::UBYTE;
|
||||
constexpr auto FLOAT = PixelDataType::FLOAT;
|
||||
constexpr auto UINT = PixelDataType::UINT;
|
||||
constexpr auto INT = PixelDataType::INT;
|
||||
constexpr auto HALF = PixelDataType::HALF;
|
||||
constexpr auto UINT_10F_11F_11F_REV = PixelDataType::UINT_10F_11F_11F_REV;
|
||||
|
||||
switch (dst->type) {
|
||||
case UBYTE:
|
||||
switch (srcType) {
|
||||
case UBYTE:
|
||||
reshaper = reshapeImage<uint8_t, uint8_t>;
|
||||
reshaper = reshapeImageImpl<uint8_t, uint8_t>;
|
||||
if (dst->format == PixelDataFormat::RGBA &&
|
||||
dstChannelCount == srcChannelCount && !swizzle && dst->top == 0 &&
|
||||
dst->left == 0) {
|
||||
reshaper = copyImage;
|
||||
}
|
||||
break;
|
||||
case FLOAT: reshaper = reshapeImage<uint8_t, float>; break;
|
||||
case INT: reshaper = reshapeImage<uint8_t, int32_t>; break;
|
||||
case UINT: reshaper = reshapeImage<uint8_t, uint32_t>; break;
|
||||
default: return false;
|
||||
case FLOAT: reshaper = reshapeImageImpl<uint8_t, float>; break;
|
||||
case INT: reshaper = reshapeImageImpl<uint8_t, int32_t>; break;
|
||||
case UINT: reshaper = reshapeImageImpl<uint8_t, uint32_t>; break;
|
||||
case HALF: reshaper = reshapeImageImpl<uint8_t, math::half>; break;
|
||||
case UINT_10F_11F_11F_REV:
|
||||
if (swizzle) reshaper = reshapeImagePacked<uint8_t, UnpackerR11G11B10, true>;
|
||||
else reshaper = reshapeImagePacked<uint8_t, UnpackerR11G11B10, false>;
|
||||
break;
|
||||
default:
|
||||
LOG(ERROR) << "DataReshaper: UBYTE dst, unsupported srcType: "
|
||||
<< (int) srcType;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case FLOAT:
|
||||
switch (srcType) {
|
||||
case UBYTE: reshaper = reshapeImage<float, uint8_t>; break;
|
||||
case FLOAT: reshaper = reshapeImage<float, float>; break;
|
||||
case INT: reshaper = reshapeImage<float, int32_t>; break;
|
||||
case UINT: reshaper = reshapeImage<float, uint32_t>; break;
|
||||
default: return false;
|
||||
case UBYTE: reshaper = reshapeImageImpl<float, uint8_t>; break;
|
||||
case FLOAT: reshaper = reshapeImageImpl<float, float>; break;
|
||||
case INT: reshaper = reshapeImageImpl<float, int32_t>; break;
|
||||
case UINT: reshaper = reshapeImageImpl<float, uint32_t>; break;
|
||||
case UINT_10F_11F_11F_REV:
|
||||
if (swizzle) reshaper = reshapeImagePacked<float, UnpackerR11G11B10, true>;
|
||||
else reshaper = reshapeImagePacked<float, UnpackerR11G11B10, false>;
|
||||
break;
|
||||
default:
|
||||
LOG(ERROR) << "DataReshaper: FLOAT dst, unsupported srcType: "
|
||||
<< (int) srcType;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case INT:
|
||||
switch (srcType) {
|
||||
case UBYTE: reshaper = reshapeImage<int32_t, uint8_t>; break;
|
||||
case FLOAT: reshaper = reshapeImage<int32_t, float>; break;
|
||||
case INT: reshaper = reshapeImage<int32_t, int32_t>; break;
|
||||
case UINT: reshaper = reshapeImage<int32_t, uint32_t>; break;
|
||||
default: return false;
|
||||
case UBYTE: reshaper = reshapeImageImpl<int32_t, uint8_t>; break;
|
||||
case FLOAT: reshaper = reshapeImageImpl<int32_t, float>; break;
|
||||
case INT: reshaper = reshapeImageImpl<int32_t, int32_t>; break;
|
||||
case UINT: reshaper = reshapeImageImpl<int32_t, uint32_t>; break;
|
||||
case UINT_10F_11F_11F_REV:
|
||||
if (swizzle) reshaper = reshapeImagePacked<int32_t, UnpackerR11G11B10, true>;
|
||||
else reshaper = reshapeImagePacked<int32_t, UnpackerR11G11B10, false>;
|
||||
break;
|
||||
default:
|
||||
LOG(ERROR)
|
||||
<< "DataReshaper: INT dst, unsupported srcType: " << (int) srcType;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case UINT:
|
||||
switch (srcType) {
|
||||
case UBYTE: reshaper = reshapeImage<uint32_t, uint8_t>; break;
|
||||
case FLOAT: reshaper = reshapeImage<uint32_t, float>; break;
|
||||
case INT: reshaper = reshapeImage<uint32_t, int32_t>; break;
|
||||
case UINT: reshaper = reshapeImage<uint32_t, uint32_t>; break;
|
||||
default: return false;
|
||||
case UBYTE: reshaper = reshapeImageImpl<uint32_t, uint8_t>; break;
|
||||
case FLOAT: reshaper = reshapeImageImpl<uint32_t, float>; break;
|
||||
case INT: reshaper = reshapeImageImpl<uint32_t, int32_t>; break;
|
||||
case UINT: reshaper = reshapeImageImpl<uint32_t, uint32_t>; break;
|
||||
case UINT_10F_11F_11F_REV:
|
||||
if (swizzle) reshaper = reshapeImagePacked<uint32_t, UnpackerR11G11B10, true>;
|
||||
else reshaper = reshapeImagePacked<uint32_t, UnpackerR11G11B10, false>;
|
||||
break;
|
||||
default:
|
||||
LOG(ERROR)
|
||||
<< "DataReshaper: UINT dst, unsupported srcType: " << (int) srcType;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case HALF:
|
||||
switch (srcType) {
|
||||
case HALF: reshaper = copyImage; break;
|
||||
default: return false;
|
||||
case HALF:
|
||||
reshaper = copyImage;
|
||||
break;
|
||||
case UINT_10F_11F_11F_REV:
|
||||
if (swizzle) reshaper = reshapeImagePacked<math::half, UnpackerR11G11B10, true>;
|
||||
else reshaper = reshapeImagePacked<math::half, UnpackerR11G11B10, false>;
|
||||
break;
|
||||
default:
|
||||
LOG(ERROR)
|
||||
<< "DataReshaper: HALF dst, unsupported srcType: " << (int) srcType;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG(ERROR) << "DataReshaper: unsupported dst->type: " << (int) dst->type;
|
||||
return false;
|
||||
}
|
||||
uint8_t* dstBytes = (uint8_t*) dst->buffer;
|
||||
const int dstBytesPerRow = PixelBufferDescriptor::computeDataSize(dst->format, dst->type,
|
||||
dst->stride ? dst->stride : width, 1, dst->alignment);
|
||||
reshaper(dstBytes, srcBytes, srcBytesPerRow, srcChannelCount,
|
||||
dst->top, dst->left, dstBytesPerRow,
|
||||
dstChannelCount, width, height, swizzle);
|
||||
reshaper(dstBytes, srcBytes, srcBytesPerRow, srcChannelCount, dst->top, dst->left,
|
||||
dstBytesPerRow, dstChannelCount, width, height, swizzle);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template<> inline float getMaxValue() { return 1.0f; }
|
||||
template<> inline int32_t getMaxValue() { return 0x7fffffff; }
|
||||
template<> inline uint32_t getMaxValue() { return 0xffffffff; }
|
||||
template<> inline uint16_t getMaxValue() { return 0x3c00; } // 0x3c00 is 1.0 in half-float.
|
||||
template<> inline uint8_t getMaxValue() { return 0xff; }
|
||||
|
||||
} // namespace backend
|
||||
} // namespace filament
|
||||
|
||||
|
||||
@@ -25,6 +25,11 @@
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
|
||||
namespace filament {
|
||||
|
||||
using namespace utils;
|
||||
@@ -105,8 +110,21 @@ JNIEnv* VirtualMachineEnv::getEnvironmentSlow() {
|
||||
FILAMENT_CHECK_PRECONDITION(mVirtualMachine)
|
||||
<< "JNI_OnLoad() has not been called";
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
jint const result = mVirtualMachine->AttachCurrentThread(&mJniEnv, nullptr);
|
||||
#ifdef __ANDROID__
|
||||
JavaVMAttachArgs args;
|
||||
args.version = JNI_VERSION_1_6;
|
||||
args.group = nullptr;
|
||||
char threadName[16]; // pthread_getname_np returns at most 16 bytes
|
||||
if (__builtin_available(android 26, *)) {
|
||||
if (pthread_getname_np(pthread_self(), threadName, sizeof(threadName)) == 0) {
|
||||
args.name = threadName;
|
||||
} else {
|
||||
args.name = nullptr;
|
||||
}
|
||||
} else {
|
||||
args.name = nullptr;
|
||||
}
|
||||
jint const result = mVirtualMachine->AttachCurrentThread(&mJniEnv, &args);
|
||||
#else
|
||||
jint const result = mVirtualMachine->AttachCurrentThread(reinterpret_cast<void**>(&mJniEnv), nullptr);
|
||||
#endif
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user