Compare commits
72
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5462621eb | ||
|
|
e2b195706a | ||
|
|
3ebb2086ed | ||
|
|
052092f553 | ||
|
|
7163aaa627 | ||
|
|
0c7ff3fcf4 | ||
|
|
962756e283 | ||
|
|
48ee727c8d | ||
|
|
2d1e4b8ce2 | ||
|
|
749b03ed2a | ||
|
|
497a1cc42d | ||
|
|
256a494cd0 | ||
|
|
b2531fff15 | ||
|
|
f18afe1d3e | ||
|
|
771eb4c1a4 | ||
|
|
70e7cb2a27 | ||
|
|
f95f127495 | ||
|
|
c0d63e826d | ||
|
|
36583618f1 | ||
|
|
9d322e7208 | ||
|
|
b897d22d20 | ||
|
|
e5fe3d495e | ||
|
|
35f501b3d5 | ||
|
|
5b799928c3 | ||
|
|
f7f586caff | ||
|
|
5428812b93 | ||
|
|
e57f2f5c02 | ||
|
|
fd9bcaa735 | ||
|
|
9a14e54fc2 | ||
|
|
d78bb294ed | ||
|
|
0b8dbe9b0a | ||
|
|
e595fd4b79 | ||
|
|
c5d36cff7f | ||
|
|
f392e8be54 | ||
|
|
83653fb358 | ||
|
|
8a3c48fef1 | ||
|
|
7f6b9bb144 | ||
|
|
687c42583b | ||
|
|
71e8cab08a | ||
|
|
afae31a975 | ||
|
|
5ac5dc4c95 | ||
|
|
e975572972 | ||
|
|
29e91f0d3a | ||
|
|
6f0d47f275 | ||
|
|
cf66813f41 | ||
|
|
5f89e8e711 | ||
|
|
be9e9298e1 | ||
|
|
9218b90c9c | ||
|
|
070a07679d | ||
|
|
10b7bd71f9 | ||
|
|
e4ae96a2a1 | ||
|
|
56ac08e353 | ||
|
|
52b0b553b4 | ||
|
|
00f3c7175c | ||
|
|
e4fa86fb01 | ||
|
|
7da2a08df6 | ||
|
|
82246d934d | ||
|
|
c60969ef67 | ||
|
|
ce37f216bc | ||
|
|
81c71fbbb9 | ||
|
|
07a7c6003a | ||
|
|
804a74c205 | ||
|
|
dde49a410a | ||
|
|
770ce7f8ec | ||
|
|
11714d3adc | ||
|
|
6aac9071b3 | ||
|
|
da9173e9dc | ||
|
|
cd64d50408 | ||
|
|
a3145cb96f | ||
|
|
cdfb92e14a | ||
|
|
55c16e6e7a | ||
|
|
65e3c3bfb9 |
@@ -27,17 +27,24 @@ runs:
|
|||||||
echo "$HASH" > /tmp/commit_hash.txt
|
echo "$HASH" > /tmp/commit_hash.txt
|
||||||
- name: Find commit message (PR)
|
- name: Find commit message (PR)
|
||||||
shell: bash
|
shell: bash
|
||||||
id: checkout_code
|
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
run: |
|
run: |
|
||||||
BEFORE_HASH=$(git rev-parse HEAD)
|
PR_NUMBER="${{ github.event.pull_request.number }}"
|
||||||
echo "hash=$BEFORE_HASH" >> "$GITHUB_OUTPUT"
|
# Fetch the head of the PR explicitly to handle forks. Depth 50 ensures we can traverse past recent merge commits.
|
||||||
# Next we will checkout the actual head (not the merge commits) of the PR
|
git fetch --depth=50 origin "pull/$PR_NUMBER/head:pr-head"
|
||||||
AFTER_HASH="${{ github.event.pull_request.head.sha }}"
|
|
||||||
git checkout $AFTER_HASH
|
COMMIT_HASH=$(git log -1 --no-merges pr-head --format=%H)
|
||||||
COMMIT_MESSAGE=$(git log -1 --no-merges)
|
AUTHOR_NAME=$(git log -1 --no-merges pr-head --format=%an)
|
||||||
echo "$COMMIT_MESSAGE" > /tmp/commit_msg.txt
|
AUTHOR_EMAIL=$(git log -1 --no-merges pr-head --format=%ae)
|
||||||
echo "$AFTER_HASH" > /tmp/commit_hash.txt
|
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
|
- shell: bash
|
||||||
id: action_output
|
id: action_output
|
||||||
run: |
|
run: |
|
||||||
@@ -47,9 +54,4 @@ runs:
|
|||||||
cat /tmp/commit_msg.txt >> "$GITHUB_OUTPUT"
|
cat /tmp/commit_msg.txt >> "$GITHUB_OUTPUT"
|
||||||
echo "$DELIMITER" >> "$GITHUB_OUTPUT"
|
echo "$DELIMITER" >> "$GITHUB_OUTPUT"
|
||||||
# Get the commit hash
|
# Get the commit hash
|
||||||
echo "hash=$(cat /tmp/commit_hash.txt)" >> "$GITHUB_OUTPUT"
|
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 }}
|
|
||||||
@@ -11,24 +11,13 @@ runs:
|
|||||||
echo "set man-db/auto-update false" | sudo debconf-communicate
|
echo "set man-db/auto-update false" | sudo debconf-communicate
|
||||||
sudo dpkg-reconfigure man-db
|
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 ..
|
cd ..
|
||||||
|
|
||||||
sudo wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
sudo wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
||||||
sudo apt-get update
|
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 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 mesa-common-dev libxi-dev libxxf86vm-dev
|
||||||
|
sudo apt-get install cmake ninja-build
|
||||||
|
|
||||||
# For dawn
|
# For dawn
|
||||||
sudo apt-get install libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libx11-xcb-dev
|
sudo apt-get install libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libx11-xcb-dev
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
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
|
||||||
|
run: |
|
||||||
|
# Must have at least clang-16 for a webgpu/dawn build.
|
||||||
|
sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer
|
||||||
|
shell: bash
|
||||||
|
- name: 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
|
RUN pip3 install pygithub==1.55
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ runs:
|
|||||||
uses: actions/cache@v4 # Use a specific version
|
uses: actions/cache@v4 # Use a specific version
|
||||||
with:
|
with:
|
||||||
path: emsdk
|
path: emsdk
|
||||||
key: ${{ runner.os }}-emsdk-${{ env.GITHUB_EMSDK_VERSION }}
|
key: ${{ runner.os }}-${{ runner.arch }}-emsdk-${{ env.GITHUB_EMSDK_VERSION }}
|
||||||
- name: Install Web Prerequisites
|
- name: Install Web Prerequisites
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -10,16 +10,27 @@ jobs:
|
|||||||
# a branch on filament-assets.
|
# a branch on filament-assets.
|
||||||
update-renderdiff-goldens:
|
update-renderdiff-goldens:
|
||||||
name: update-renderdiff-goldens
|
name: update-renderdiff-goldens
|
||||||
runs-on: 'ubuntu-24.04-4core'
|
runs-on: 'macos-14'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: ./.github/actions/linux-prereq
|
|
||||||
- id: get_commit_msg
|
- id: get_commit_msg
|
||||||
uses: ./.github/actions/get-commit-msg
|
uses: ./.github/actions/get-commit-msg
|
||||||
- name: Build diffimg
|
- name: Check if accepting new goldens
|
||||||
run: ./build.sh release diffimg
|
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
|
- name: Run update script
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.FILAMENTBOT_TOKEN }}
|
GH_TOKEN: ${{ secrets.FILAMENTBOT_TOKEN }}
|
||||||
@@ -27,10 +38,24 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
GOLDEN_BRANCH=$(echo "${COMMIT_MESSAGE}" | python3 test/renderdiff/src/commit_msg.py)
|
GOLDEN_BRANCH=$(echo "${COMMIT_MESSAGE}" | python3 test/renderdiff/src/commit_msg.py)
|
||||||
COMMIT_HASH="${{ steps.get_commit_msg.outputs.hash }}"
|
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
|
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 "branch==${GOLDEN_BRANCH}"
|
||||||
echo "hash==${COMMIT_HASH}"
|
echo "hash==${COMMIT_HASH}"
|
||||||
python3 test/renderdiff/src/update_golden.py --branch=${GOLDEN_BRANCH} \
|
python3 test/renderdiff/src/update_golden.py --branch=${GOLDEN_BRANCH} \
|
||||||
@@ -65,7 +90,7 @@ jobs:
|
|||||||
# filament-assets
|
# filament-assets
|
||||||
update-sizeguard:
|
update-sizeguard:
|
||||||
name: update-sizeguard
|
name: update-sizeguard
|
||||||
runs-on: 'ubuntu-24.04-16core'
|
runs-on: 'ubuntu-24.04-4core'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ jobs:
|
|||||||
name: build-android
|
name: build-android
|
||||||
# We intentially use a larger runner here to enable larger disk space
|
# We intentially use a larger runner here to enable larger disk space
|
||||||
# (standard linux runner will fail on disk space and faster build time).
|
# (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:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
@@ -26,8 +25,7 @@ jobs:
|
|||||||
|
|
||||||
build-ios:
|
build-ios:
|
||||||
name: build-ios
|
name: build-ios
|
||||||
runs-on: macos-14-xlarge
|
runs-on: 'macos-14'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
@@ -46,8 +44,7 @@ jobs:
|
|||||||
|
|
||||||
build-linux:
|
build-linux:
|
||||||
name: build-linux
|
name: build-linux
|
||||||
runs-on: 'ubuntu-24.04-16core'
|
runs-on: 'arm-ubuntu-24.04-16core'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
@@ -58,13 +55,12 @@ jobs:
|
|||||||
cd build/linux && printf "y" | ./build.sh continuous
|
cd build/linux && printf "y" | ./build.sh continuous
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: filament-linux
|
name: filament-arm-linux
|
||||||
path: out/filament-release-linux.tgz
|
path: out/filament-release-arm-linux.tgz
|
||||||
|
|
||||||
build-mac:
|
build-mac:
|
||||||
name: build-mac
|
name: build-mac
|
||||||
runs-on: macos-14-xlarge
|
runs-on: 'macos-14'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
@@ -77,14 +73,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: filament-mac
|
name: filament-mac
|
||||||
path: out/filament-release-darwin.tgz
|
path: out/filament-release-darwin.tgz
|
||||||
- name: Check public headers
|
|
||||||
run: |
|
|
||||||
test/check-headers/test.sh out/release/filament/include
|
|
||||||
|
|
||||||
build-web:
|
build-web:
|
||||||
name: build-web
|
name: build-web
|
||||||
runs-on: 'ubuntu-24.04-16core'
|
runs-on: 'arm-ubuntu-24.04-16core'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
@@ -101,7 +93,7 @@ jobs:
|
|||||||
|
|
||||||
build-windows:
|
build-windows:
|
||||||
name: build-windows
|
name: build-windows
|
||||||
runs-on: windows-2022-32core
|
runs-on: 'windows-2022'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
|
|||||||
+186
-21
@@ -8,10 +8,54 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- 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:
|
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:
|
build-desktop-mac:
|
||||||
name: build-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:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
@@ -20,13 +64,30 @@ jobs:
|
|||||||
- name: Run build script
|
- name: Run build script
|
||||||
run: |
|
run: |
|
||||||
cd build/mac && printf "y" | ./build.sh presubmit-with-test
|
cd build/mac && printf "y" | ./build.sh presubmit-with-test
|
||||||
- name: Test material parser
|
- name: Test - material parser
|
||||||
run: |
|
run: |
|
||||||
out/cmake-release/filament/test/test_material_parser
|
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:
|
build-desktop-linux:
|
||||||
name: build-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:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
@@ -41,7 +102,18 @@ jobs:
|
|||||||
|
|
||||||
build-windows:
|
build-windows:
|
||||||
name: 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:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
@@ -53,7 +125,18 @@ jobs:
|
|||||||
|
|
||||||
build-android:
|
build-android:
|
||||||
name: 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:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
@@ -80,7 +163,18 @@ jobs:
|
|||||||
|
|
||||||
build-ios:
|
build-ios:
|
||||||
name: 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:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
@@ -95,7 +189,18 @@ jobs:
|
|||||||
|
|
||||||
build-web:
|
build-web:
|
||||||
name: 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:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
@@ -108,7 +213,18 @@ jobs:
|
|||||||
|
|
||||||
validate-docs:
|
validate-docs:
|
||||||
name: 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:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
@@ -121,23 +237,40 @@ jobs:
|
|||||||
|
|
||||||
test-renderdiff:
|
test-renderdiff:
|
||||||
name: 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:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
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
|
- id: get_commit_msg
|
||||||
uses: ./.github/actions/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: |
|
run: |
|
||||||
# Must have at least clang-16 for a webgpu/dawn build.
|
if echo "${COMMIT_MESSAGE}" | python3 test/renderdiff/src/commit_msg.py --mode=accept_new_goldens; then
|
||||||
sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer
|
echo "accept=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "accept=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
shell: bash
|
shell: bash
|
||||||
|
- name: Renderdiff Generate
|
||||||
|
if: steps.check_accept.outputs.accept != 'true'
|
||||||
|
uses: ./.github/actions/renderdiff-generate
|
||||||
- name: Render and compare
|
- name: Render and compare
|
||||||
|
if: steps.check_accept.outputs.accept != 'true'
|
||||||
id: render_compare
|
id: render_compare
|
||||||
env:
|
env:
|
||||||
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
|
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
|
||||||
@@ -146,9 +279,6 @@ jobs:
|
|||||||
source ${TEST_DIR}/src/preamble.sh
|
source ${TEST_DIR}/src/preamble.sh
|
||||||
set -eux
|
set -eux
|
||||||
GOLDEN_BRANCH=$(echo "${COMMIT_MESSAGE}" | python3 ${TEST_DIR}/src/commit_msg.py)
|
GOLDEN_BRANCH=$(echo "${COMMIT_MESSAGE}" | python3 ${TEST_DIR}/src/commit_msg.py)
|
||||||
bash ${TEST_DIR}/generate.sh
|
|
||||||
# Build diffimg tool
|
|
||||||
./build.sh release diffimg
|
|
||||||
|
|
||||||
python3 ${TEST_DIR}/src/golden_manager.py \
|
python3 ${TEST_DIR}/src/golden_manager.py \
|
||||||
--branch=${GOLDEN_BRANCH} \
|
--branch=${GOLDEN_BRANCH} \
|
||||||
@@ -172,10 +302,12 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
shell: bash
|
shell: bash
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
|
if: steps.check_accept.outputs.accept != 'true'
|
||||||
with:
|
with:
|
||||||
name: presubmit-renderdiff-result
|
name: presubmit-renderdiff-result
|
||||||
path: ./out/renderdiff
|
path: ./out/renderdiff
|
||||||
- name: Compare result
|
- name: Compare result
|
||||||
|
if: steps.check_accept.outputs.accept != 'true'
|
||||||
run: |
|
run: |
|
||||||
ERROR_STR="${{ steps.render_compare.outputs.err }}"
|
ERROR_STR="${{ steps.render_compare.outputs.err }}"
|
||||||
if [ -n "${ERROR_STR}" ]; then
|
if [ -n "${ERROR_STR}" ]; then
|
||||||
@@ -185,7 +317,18 @@ jobs:
|
|||||||
|
|
||||||
validate-wgsl-webgpu:
|
validate-wgsl-webgpu:
|
||||||
name: validate-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:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
@@ -199,6 +342,17 @@ jobs:
|
|||||||
test-code-correctness:
|
test-code-correctness:
|
||||||
name: test-code-correctness
|
name: test-code-correctness
|
||||||
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:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
@@ -219,7 +373,18 @@ jobs:
|
|||||||
|
|
||||||
test-backend:
|
test-backend:
|
||||||
name: 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:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -63,6 +63,39 @@ jobs:
|
|||||||
const globber = await glob.create('out/*.tgz');
|
const globber = await glob.create('out/*.tgz');
|
||||||
await upload({ github, context }, await globber.glob(), TAG);
|
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:
|
build-mac:
|
||||||
name: build-mac
|
name: build-mac
|
||||||
runs-on: macos-14-xlarge
|
runs-on: macos-14-xlarge
|
||||||
@@ -99,7 +132,7 @@ jobs:
|
|||||||
|
|
||||||
build-web:
|
build-web:
|
||||||
name: 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'
|
if: github.event_name == 'release' || github.event.inputs.platform == 'web'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -188,6 +221,54 @@ jobs:
|
|||||||
const globber = await glob.create(['out/*.aar', 'out/*.apk', 'out/*.tgz'].join('\n'));
|
const globber = await glob.create(['out/*.aar', 'out/*.apk', 'out/*.tgz'].join('\n'));
|
||||||
await upload({ github, context }, await globber.glob(), TAG);
|
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:
|
build-ios:
|
||||||
name: build-ios
|
name: build-ios
|
||||||
runs-on: macos-14-xlarge
|
runs-on: macos-14-xlarge
|
||||||
|
|||||||
@@ -11,14 +11,11 @@ jobs:
|
|||||||
name: Verify Release Notes
|
name: Verify Release Notes
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
# We *only* need to check out the .github/ directory.
|
- uses: actions/checkout@v6
|
||||||
# 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
|
|
||||||
with:
|
with:
|
||||||
files: .github
|
fetch-depth: 0
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
sparse-checkout: |
|
||||||
|
.github
|
||||||
- name: Verify release notes
|
- name: Verify release notes
|
||||||
uses: ./.github/actions/verify-release-notes
|
uses: ./.github/actions/verify-release-notes
|
||||||
with:
|
with:
|
||||||
|
|||||||
+10
-3
@@ -326,11 +326,13 @@ endif()
|
|||||||
if (FILAMENT_ENABLE_LTO)
|
if (FILAMENT_ENABLE_LTO)
|
||||||
include(CheckIPOSupported)
|
include(CheckIPOSupported)
|
||||||
|
|
||||||
check_ipo_supported(RESULT IPO_SUPPORT)
|
check_ipo_supported(RESULT IPO_SUPPORT OUTPUT IPO_ERROR)
|
||||||
|
|
||||||
if (IPO_SUPPORT)
|
if (IPO_SUPPORT)
|
||||||
message(STATUS "LTO support is enabled")
|
message(STATUS "IPO / LTO is enabled")
|
||||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
|
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||||
|
else()
|
||||||
|
message(WARNING "IPO / LTO is not supported by this architecture: ${IPO_ERROR}")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -867,6 +869,12 @@ endfunction()
|
|||||||
# Sub-projects
|
# 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
|
# Common to all platforms
|
||||||
add_subdirectory(${EXTERNAL}/libgtest/tnt)
|
add_subdirectory(${EXTERNAL}/libgtest/tnt)
|
||||||
add_subdirectory(${LIBRARIES}/camutils)
|
add_subdirectory(${LIBRARIES}/camutils)
|
||||||
@@ -902,7 +910,6 @@ add_subdirectory(${EXTERNAL}/cgltf/tnt)
|
|||||||
add_subdirectory(${EXTERNAL}/draco/tnt)
|
add_subdirectory(${EXTERNAL}/draco/tnt)
|
||||||
add_subdirectory(${EXTERNAL}/jsmn/tnt)
|
add_subdirectory(${EXTERNAL}/jsmn/tnt)
|
||||||
add_subdirectory(${EXTERNAL}/stb/tnt)
|
add_subdirectory(${EXTERNAL}/stb/tnt)
|
||||||
add_subdirectory(${EXTERNAL}/getopt)
|
|
||||||
add_subdirectory(${EXTERNAL}/perfetto/tnt)
|
add_subdirectory(${EXTERNAL}/perfetto/tnt)
|
||||||
add_subdirectory(${EXTERNAL}/basisu/tnt)
|
add_subdirectory(${EXTERNAL}/basisu/tnt)
|
||||||
|
|
||||||
|
|||||||
@@ -6,5 +6,3 @@
|
|||||||
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||||
|
|
||||||
## Release notes for next branch cut
|
## Release notes for next branch cut
|
||||||
|
|
||||||
- engine: fix crash when using variance shadow maps
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.google.android.filament:filament-android:1.69.3'
|
implementation 'com.google.android.filament:filament-android:1.70.0'
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
|||||||
iOS projects can use CocoaPods to install the latest release:
|
iOS projects can use CocoaPods to install the latest release:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pod 'Filament', '~> 1.69.3'
|
pod 'Filament', '~> 1.70.0'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|||||||
@@ -7,6 +7,18 @@ 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
|
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||||
|
|
||||||
|
## 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.4
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -40,15 +40,21 @@
|
|||||||
// - Build and upload artifacts with ./gradlew publish
|
// - Build and upload artifacts with ./gradlew publish
|
||||||
// - Close and release staging repo on Nexus with ./gradlew closeAndReleaseStagingRepository
|
// - 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
|
// sonatypeUsername=nexus_user
|
||||||
// sonatypePassword=nexus_password
|
// sonatypePassword=nexus_password
|
||||||
//
|
//
|
||||||
|
// To sign with a key ring file:
|
||||||
// signing.keyId=pgp_key_id
|
// signing.keyId=pgp_key_id
|
||||||
// signing.password=pgp_key_password
|
// signing.password=pgp_key_password
|
||||||
// signing.secretKeyRingFile=/Users/user/.gnupg/maven_signing.key
|
// 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 {
|
buildscript {
|
||||||
def path = providers
|
def path = providers
|
||||||
@@ -194,7 +200,7 @@ subprojects {
|
|||||||
google()
|
google()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!name.startsWith("sample") && name != "filament-tools") {
|
if (!name.startsWith("sample") && name != "filament-tools" && name != "gradle-plugin") {
|
||||||
apply plugin: 'com.android.library'
|
apply plugin: 'com.android.library'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id 'groovy-gradle-plugin'
|
|
||||||
}
|
|
||||||
|
|
||||||
gradlePlugin {
|
|
||||||
plugins {
|
|
||||||
create("filament-plugin") {
|
|
||||||
id = "filament-plugin"
|
|
||||||
implementationClass = "com.google.android.filament.gradle.FilamentPlugin"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
gradlePluginPortal()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation "com.google.gradle:osdetector-gradle-plugin:1.7.3"
|
|
||||||
}
|
|
||||||
@@ -2121,7 +2121,7 @@ public class View {
|
|||||||
*/
|
*/
|
||||||
public boolean highPrecision = false;
|
public boolean highPrecision = false;
|
||||||
/**
|
/**
|
||||||
* VSM minimum variance scale, must be positive.
|
* @deprecated has no effect.
|
||||||
*/
|
*/
|
||||||
public float minVarianceScale = 0.5f;
|
public float minVarianceScale = 0.5f;
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -20,8 +20,6 @@
|
|||||||
#include <imagediff/ImageDiff.h>
|
#include <imagediff/ImageDiff.h>
|
||||||
#include <utils/Log.h>
|
#include <utils/Log.h>
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
using namespace imagediff;
|
using namespace imagediff;
|
||||||
using namespace utils;
|
using namespace utils;
|
||||||
|
|
||||||
@@ -102,30 +100,48 @@ jobject createResult(JNIEnv* env, ImageDiffResult const& result, bool generateDi
|
|||||||
|
|
||||||
if (generateDiff && result.diffImage.getWidth() > 0) {
|
if (generateDiff && result.diffImage.getWidth() > 0) {
|
||||||
jclass bitmapClass = env->FindClass("android/graphics/Bitmap");
|
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;");
|
"(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
|
||||||
|
|
||||||
jclass configClass = env->FindClass("android/graphics/Bitmap$Config");
|
jclass configClass = env->FindClass("android/graphics/Bitmap$Config");
|
||||||
jfieldID argb8888 = env->GetStaticFieldID(configClass, "ARGB_8888", "Landroid/graphics/Bitmap$Config;");
|
jfieldID argb8888 = env->GetStaticFieldID(configClass, "ARGB_8888", "Landroid/graphics/Bitmap$Config;");
|
||||||
jobject configObj = env->GetStaticObjectField(configClass, argb8888);
|
jobject configObj = env->GetStaticObjectField(configClass, argb8888);
|
||||||
|
|
||||||
uint32_t width = result.diffImage.getWidth();
|
uint32_t width = result.diffImage.getWidth();
|
||||||
uint32_t height = result.diffImage.getHeight();
|
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) {
|
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;
|
void* diffPixels;
|
||||||
if (AndroidBitmap_lockPixels(env, diffBitmap, &diffPixels) == 0) {
|
if (AndroidBitmap_lockPixels(env, diffBitmap, &diffPixels) == 0) {
|
||||||
|
AndroidBitmapInfo info;
|
||||||
|
AndroidBitmap_getInfo(env, diffBitmap, &info);
|
||||||
|
|
||||||
float const* src = result.diffImage.getPixelRef();
|
float const* src = result.diffImage.getPixelRef();
|
||||||
uint8_t* dst = (uint8_t*) diffPixels;
|
uint8_t* dst = (uint8_t*) diffPixels;
|
||||||
uint32_t channels = result.diffImage.getChannels(); // usually 4
|
uint32_t const channels = result.diffImage.getChannels(); // usually 4
|
||||||
|
|
||||||
for (size_t i = 0; i < width * height; ++i) {
|
for (size_t y = 0; y < height; ++y) {
|
||||||
for (int c = 0; c < 4; ++c) {
|
uint8_t* row = dst + y * info.stride;
|
||||||
float v = 0.0f;
|
for (size_t x = 0; x < width; ++x) {
|
||||||
if (c < channels) v = src[i * channels + c];
|
size_t srcIdx = (y * width + x) * channels;
|
||||||
if (c == 3 && channels < 4) v = 1.0f; // Alpha 1.0 if missing
|
for (int c = 0; c < 4; ++c) {
|
||||||
dst[i * 4 + c] = (uint8_t) std::min(255.0f, std::max(0.0f, v * 255.0f));
|
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);
|
AndroidBitmap_unlockPixels(env, diffBitmap);
|
||||||
@@ -133,7 +149,7 @@ jobject createResult(JNIEnv* env, ImageDiffResult const& result, bool generateDi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultObj;
|
return resultObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +163,7 @@ Java_com_google_android_filament_utils_ImageDiff_nCompareBasic(JNIEnv* env, jcla
|
|||||||
BitmapLock maskArg(env, maskBitmap);
|
BitmapLock maskArg(env, maskBitmap);
|
||||||
|
|
||||||
if (!refArg.isValid() || !candArg.isValid()) {
|
if (!refArg.isValid() || !candArg.isValid()) {
|
||||||
ImageDiffResult emptyResult;
|
ImageDiffResult emptyResult;
|
||||||
emptyResult.status = ImageDiffResult::Status::SIZE_MISMATCH; // or ERROR
|
emptyResult.status = ImageDiffResult::Status::SIZE_MISMATCH; // or ERROR
|
||||||
return createResult(env, emptyResult, false);
|
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
|
extern "C" JNIEXPORT jobject JNICALL
|
||||||
Java_com_google_android_filament_utils_ImageDiff_nCompareJson(JNIEnv* env, jclass,
|
Java_com_google_android_filament_utils_ImageDiff_nCompareJson(JNIEnv* env, jclass,
|
||||||
jobject refBitmap, jobject candBitmap, jstring jsonConfig, jobject maskBitmap) {
|
jobject refBitmap, jobject candBitmap, jstring jsonConfig, jobject maskBitmap) {
|
||||||
|
|
||||||
BitmapLock refArg(env, refBitmap);
|
BitmapLock refArg(env, refBitmap);
|
||||||
BitmapLock candArg(env, candBitmap);
|
BitmapLock candArg(env, candBitmap);
|
||||||
BitmapLock maskArg(env, maskBitmap);
|
BitmapLock maskArg(env, maskBitmap);
|
||||||
|
|
||||||
if (!refArg.isValid() || !candArg.isValid()) {
|
if (!refArg.isValid() || !candArg.isValid()) {
|
||||||
ImageDiffResult emptyResult;
|
ImageDiffResult emptyResult;
|
||||||
emptyResult.status = ImageDiffResult::Status::SIZE_MISMATCH; // or ERROR
|
emptyResult.status = ImageDiffResult::Status::SIZE_MISMATCH; // or ERROR
|
||||||
return createResult(env, emptyResult, false);
|
return createResult(env, emptyResult, false);
|
||||||
}
|
}
|
||||||
@@ -189,7 +205,7 @@ Java_com_google_android_filament_utils_ImageDiff_nCompareJson(JNIEnv* env, jclas
|
|||||||
ImageDiffConfig config;
|
ImageDiffConfig config;
|
||||||
const char* nativeJson = env->GetStringUTFChars(jsonConfig, 0);
|
const char* nativeJson = env->GetStringUTFChars(jsonConfig, 0);
|
||||||
size_t length = env->GetStringUTFLength(jsonConfig);
|
size_t length = env->GetStringUTFLength(jsonConfig);
|
||||||
|
|
||||||
bool parsed = parseConfig(nativeJson, length, &config);
|
bool parsed = parseConfig(nativeJson, length, &config);
|
||||||
env->ReleaseStringUTFChars(jsonConfig, nativeJson);
|
env->ReleaseStringUTFChars(jsonConfig, nativeJson);
|
||||||
|
|
||||||
@@ -214,4 +230,3 @@ Java_com_google_android_filament_utils_ImageDiff_nCompareJson(JNIEnv* env, jclas
|
|||||||
|
|
||||||
return createResult(env, result, generateDiff);
|
return createResult(env, result, generateDiff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+25
-20
@@ -106,8 +106,8 @@ class ModelViewer(
|
|||||||
var skyboxCubemap: Texture? = null
|
var skyboxCubemap: Texture? = null
|
||||||
|
|
||||||
private lateinit var displayHelper: DisplayHelper
|
private lateinit var displayHelper: DisplayHelper
|
||||||
private lateinit var cameraManipulator: Manipulator
|
private var cameraManipulator: Manipulator? = null
|
||||||
private lateinit var gestureDetector: GestureDetector
|
private var gestureDetector: GestureDetector? = null
|
||||||
private var surfaceView: SurfaceView? = null
|
private var surfaceView: SurfaceView? = null
|
||||||
private var textureView: TextureView? = null
|
private var textureView: TextureView? = null
|
||||||
|
|
||||||
@@ -157,15 +157,13 @@ class ModelViewer(
|
|||||||
surfaceView: SurfaceView,
|
surfaceView: SurfaceView,
|
||||||
engine: Engine = Engine.create(),
|
engine: Engine = Engine.create(),
|
||||||
uiHelper: UiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK),
|
uiHelper: UiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK),
|
||||||
manipulator: Manipulator? = null
|
manipulator: Manipulator? = defaultCameraManipulator(surfaceView.width, surfaceView.height)
|
||||||
) : this(engine, uiHelper) {
|
) : 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
|
this.surfaceView = surfaceView
|
||||||
gestureDetector = GestureDetector(surfaceView, cameraManipulator)
|
cameraManipulator = manipulator
|
||||||
|
cameraManipulator?.let { c ->
|
||||||
|
gestureDetector = GestureDetector(surfaceView, c)
|
||||||
|
}
|
||||||
displayHelper = DisplayHelper(surfaceView.context)
|
displayHelper = DisplayHelper(surfaceView.context)
|
||||||
uiHelper.renderCallback = SurfaceCallback()
|
uiHelper.renderCallback = SurfaceCallback()
|
||||||
uiHelper.attachTo(surfaceView)
|
uiHelper.attachTo(surfaceView)
|
||||||
@@ -177,15 +175,14 @@ class ModelViewer(
|
|||||||
textureView: TextureView,
|
textureView: TextureView,
|
||||||
engine: Engine = Engine.create(),
|
engine: Engine = Engine.create(),
|
||||||
uiHelper: UiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK),
|
uiHelper: UiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK),
|
||||||
manipulator: Manipulator? = null
|
manipulator: Manipulator? = defaultCameraManipulator(textureView.width, textureView.height)
|
||||||
) : this(engine, uiHelper) {
|
) : this(engine, uiHelper) {
|
||||||
cameraManipulator = manipulator ?: Manipulator.Builder()
|
cameraManipulator = manipulator
|
||||||
.targetPosition(kDefaultObjectPosition.x, kDefaultObjectPosition.y, kDefaultObjectPosition.z)
|
|
||||||
.viewport(textureView.width, textureView.height)
|
|
||||||
.build(Manipulator.Mode.ORBIT)
|
|
||||||
|
|
||||||
this.textureView = textureView
|
this.textureView = textureView
|
||||||
gestureDetector = GestureDetector(textureView, cameraManipulator)
|
cameraManipulator = manipulator
|
||||||
|
cameraManipulator?.let { c ->
|
||||||
|
gestureDetector = GestureDetector(textureView, c)
|
||||||
|
}
|
||||||
displayHelper = DisplayHelper(textureView.context)
|
displayHelper = DisplayHelper(textureView.context)
|
||||||
uiHelper.renderCallback = SurfaceCallback()
|
uiHelper.renderCallback = SurfaceCallback()
|
||||||
uiHelper.attachTo(textureView)
|
uiHelper.attachTo(textureView)
|
||||||
@@ -302,11 +299,13 @@ class ModelViewer(
|
|||||||
asset?.let { populateScene(it) }
|
asset?.let { populateScene(it) }
|
||||||
|
|
||||||
// Extract the camera basis from the helper and push it to the Filament camera.
|
// Extract the camera basis from the helper and push it to the Filament camera.
|
||||||
cameraManipulator.getLookAt(eyePos, target, upward)
|
cameraManipulator?.let { cm ->
|
||||||
camera.lookAt(
|
cm.getLookAt(eyePos, target, upward)
|
||||||
|
camera.lookAt(
|
||||||
eyePos[0], eyePos[1], eyePos[2],
|
eyePos[0], eyePos[1], eyePos[2],
|
||||||
target[0], target[1], target[2],
|
target[0], target[1], target[2],
|
||||||
upward[0], upward[1], upward[2])
|
upward[0], upward[1], upward[2])
|
||||||
|
}
|
||||||
|
|
||||||
// Render the scene, unless the renderer wants to skip the frame.
|
// Render the scene, unless the renderer wants to skip the frame.
|
||||||
if (renderer.beginFrame(swapChain!!, frameTimeNanos)) {
|
if (renderer.beginFrame(swapChain!!, frameTimeNanos)) {
|
||||||
@@ -398,7 +397,7 @@ class ModelViewer(
|
|||||||
* Handles a [MotionEvent] to enable one-finger orbit, two-finger pan, and pinch-to-zoom.
|
* Handles a [MotionEvent] to enable one-finger orbit, two-finger pan, and pinch-to-zoom.
|
||||||
*/
|
*/
|
||||||
fun onTouchEvent(event: MotionEvent) {
|
fun onTouchEvent(event: MotionEvent) {
|
||||||
gestureDetector.onTouchEvent(event)
|
gestureDetector?.onTouchEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("ClickableViewAccessibility")
|
@SuppressWarnings("ClickableViewAccessibility")
|
||||||
@@ -451,7 +450,7 @@ class ModelViewer(
|
|||||||
|
|
||||||
override fun onResized(width: Int, height: Int) {
|
override fun onResized(width: Int, height: Int) {
|
||||||
view.viewport = Viewport(0, 0, width, height)
|
view.viewport = Viewport(0, 0, width, height)
|
||||||
cameraManipulator.setViewport(width, height)
|
cameraManipulator?.setViewport(width, height)
|
||||||
updateCameraProjection()
|
updateCameraProjection()
|
||||||
synchronizePendingFrames(engine)
|
synchronizePendingFrames(engine)
|
||||||
}
|
}
|
||||||
@@ -468,5 +467,11 @@ class ModelViewer(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val kDefaultObjectPosition = Float3(0.0f, 0.0f, -4.0f)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ configured, the corresponding task will be disabled.
|
|||||||
|
|
||||||
```groovy
|
```groovy
|
||||||
plugins {
|
plugins {
|
||||||
id 'filament-plugin'
|
id 'com.google.android.filament-tools'
|
||||||
}
|
}
|
||||||
|
|
||||||
filament {
|
filament {
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
+3
-2
@@ -60,9 +60,10 @@ class ToolsLocator {
|
|||||||
def classifier =
|
def classifier =
|
||||||
project.extensions.getByType(com.google.gradle.osdetector.OsDetector).classifier
|
project.extensions.getByType(com.google.gradle.osdetector.OsDetector).classifier
|
||||||
|
|
||||||
// If com.google.android.filament.tools-dir is set, we'll use it as the tool's base path.
|
// 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")
|
def toolsDirProp = project.providers.gradleProperty("com.google.android.filament.tools-dir")
|
||||||
if (toolsDirProp.isPresent()) {
|
if (toolsDirProp.isPresent() && !toolsDirProp.get().trim().isEmpty()) {
|
||||||
def toolsDir = toolsDirProp.get()
|
def toolsDir = toolsDirProp.get()
|
||||||
def path = OperatingSystem.current().isWindows() ?
|
def path = OperatingSystem.current().isWindows() ?
|
||||||
"${toolsDir}/bin/${name}.exe" :
|
"${toolsDir}/bin/${name}.exe" :
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
GROUP=com.google.android.filament
|
GROUP=com.google.android.filament
|
||||||
VERSION_NAME=1.69.3
|
VERSION_NAME=1.70.0
|
||||||
|
|
||||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,13 @@ afterEvaluate { project ->
|
|||||||
}
|
}
|
||||||
|
|
||||||
signing {
|
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 ->
|
publishing.publications.all { publication ->
|
||||||
sign publication
|
sign publication
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-plugin'
|
id 'com.google.android.filament-tools'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-plugin'
|
id 'com.google.android.filament-tools'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-plugin'
|
id 'com.google.android.filament-tools'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-plugin'
|
id 'com.google.android.filament-tools'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-plugin'
|
id 'com.google.android.filament-tools'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-plugin'
|
id 'com.google.android.filament-tools'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-plugin'
|
id 'com.google.android.filament-tools'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-plugin'
|
id 'com.google.android.filament-tools'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-plugin'
|
id 'com.google.android.filament-tools'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-plugin'
|
id 'com.google.android.filament-tools'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
@@ -26,7 +26,8 @@ tasks.register('copyDamagedHelmetGltf', Copy) {
|
|||||||
preBuild.dependsOn copyDamagedHelmetGltf
|
preBuild.dependsOn copyDamagedHelmetGltf
|
||||||
|
|
||||||
clean.doFirst {
|
clean.doFirst {
|
||||||
delete "src/main/assets"
|
delete "src/main/assets/envs"
|
||||||
|
delete "src/main/assets/models"
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -48,6 +49,7 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation deps.kotlin
|
implementation deps.kotlin
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
|
implementation 'com.google.android.material:material:1.10.0'
|
||||||
implementation deps.coroutines.core
|
implementation deps.coroutines.core
|
||||||
implementation project(':filament-android')
|
implementation project(':filament-android')
|
||||||
implementation project(':gltfio-android')
|
implementation project(':gltfio-android')
|
||||||
|
|||||||
@@ -12,10 +12,11 @@
|
|||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
android:theme="@android:style/Theme.NoTitleBar">
|
android:theme="@style/Theme.Material3.DayNight.NoActionBar">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
|
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
|
||||||
android:screenOrientation="fullSensor">
|
android:screenOrientation="fullSensor">
|
||||||
<intent-filter>
|
<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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+488
-92
@@ -17,36 +17,35 @@
|
|||||||
package com.google.android.filament.validation
|
package com.google.android.filament.validation
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.app.AlertDialog
|
||||||
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.text.Html
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Choreographer
|
import android.view.Choreographer
|
||||||
import android.view.SurfaceView
|
import android.view.SurfaceView
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.Button
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.ScrollView
|
import android.widget.Spinner
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import com.google.android.filament.utils.KTX1Loader
|
||||||
import com.google.android.filament.utils.ModelViewer
|
import com.google.android.filament.utils.ModelViewer
|
||||||
import com.google.android.filament.utils.Utils
|
import com.google.android.filament.utils.Utils
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import com.google.android.filament.utils.KTX1Loader
|
|
||||||
import com.google.android.filament.IndirectLight
|
|
||||||
import com.google.android.filament.Skybox
|
|
||||||
import android.graphics.Color
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.net.HttpURLConnection
|
|
||||||
import java.net.URL
|
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import android.widget.ArrayAdapter
|
import java.text.SimpleDateFormat
|
||||||
import android.widget.Button
|
import java.util.Date
|
||||||
import android.widget.Spinner
|
import java.util.Locale
|
||||||
import android.widget.AdapterView
|
|
||||||
|
|
||||||
class MainActivity : Activity(), ValidationRunner.Callback {
|
class MainActivity : Activity(), ValidationRunner.Callback {
|
||||||
|
|
||||||
@@ -62,13 +61,33 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
|||||||
private lateinit var choreographer: Choreographer
|
private lateinit var choreographer: Choreographer
|
||||||
private lateinit var modelViewer: ModelViewer
|
private lateinit var modelViewer: ModelViewer
|
||||||
private lateinit var statusTextView: TextView
|
private lateinit var statusTextView: TextView
|
||||||
|
private lateinit var testResultsHeader: TextView
|
||||||
private lateinit var resultsContainer: LinearLayout
|
private lateinit var resultsContainer: LinearLayout
|
||||||
private lateinit var inputManager: ValidationInputManager
|
private lateinit var inputManager: ValidationInputManager
|
||||||
private var currentInput: ValidationInputManager.ValidationInput? = null
|
private var currentInput: ValidationInputManager.ValidationInput? = null
|
||||||
private lateinit var modeSpinner: Spinner
|
|
||||||
private lateinit var runButton: Button
|
|
||||||
private var resultManager: ValidationResultManager? = 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
|
private var validationRunner: ValidationRunner? = null
|
||||||
|
|
||||||
// Frame callback
|
// Frame callback
|
||||||
@@ -89,25 +108,70 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
|||||||
surfaceView.holder.setFixedSize(512, 512)
|
surfaceView.holder.setFixedSize(512, 512)
|
||||||
|
|
||||||
statusTextView = findViewById(R.id.status_text)
|
statusTextView = findViewById(R.id.status_text)
|
||||||
modeSpinner = findViewById(R.id.mode_spinner)
|
testResultsHeader = findViewById(R.id.test_results_header)
|
||||||
runButton = findViewById(R.id.run_button)
|
|
||||||
resultsContainer = findViewById(R.id.results_container)
|
resultsContainer = findViewById(R.id.results_container)
|
||||||
|
|
||||||
// Setup Spinner
|
runButton = findViewById(R.id.run_button)
|
||||||
val modes = arrayOf("Run Validation", "Generate Goldens")
|
loadButton = findViewById(R.id.load_button)
|
||||||
val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, modes)
|
optionsButton = findViewById(R.id.options_button)
|
||||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
enhancementContainer = findViewById(R.id.enhancement_container)
|
||||||
modeSpinner.adapter = adapter
|
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
|
// Setup Run Button
|
||||||
runButton.setOnClickListener {
|
runButton.setOnClickListener {
|
||||||
currentInput?.let { input ->
|
currentInput?.let { input ->
|
||||||
val generateGoldens = modeSpinner.selectedItemPosition == 1
|
// Always use the generateGoldens flag from the intent/input
|
||||||
val newInput = input.copy(generateGoldens = generateGoldens)
|
startValidation(input)
|
||||||
startValidation(newInput)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
|
||||||
choreographer = Choreographer.getInstance()
|
choreographer = Choreographer.getInstance()
|
||||||
@@ -120,6 +184,135 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
|||||||
handleIntent()
|
handleIntent()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showLoadDialog() {
|
||||||
|
val exportDir = getExternalFilesDir(null) ?: filesDir
|
||||||
|
// Filter out result zips (starting with "results_") to only show test bundles
|
||||||
|
val zips = exportDir.listFiles { _, name ->
|
||||||
|
name.endsWith(".zip") && !name.startsWith("results_")
|
||||||
|
}?.sortedByDescending { it.lastModified() } ?: emptyList()
|
||||||
|
|
||||||
|
if (zips.isEmpty()) {
|
||||||
|
AlertDialog.Builder(this)
|
||||||
|
.setTitle("Load Test")
|
||||||
|
.setMessage("No test bundles found.")
|
||||||
|
.setPositiveButton("OK", null)
|
||||||
|
.show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val builder = AlertDialog.Builder(this)
|
||||||
|
builder.setTitle("Select Test Bundle")
|
||||||
|
|
||||||
|
val items = zips.map { it.name }.toTypedArray()
|
||||||
|
|
||||||
|
builder.setItems(items) { dialog, which ->
|
||||||
|
val selectedFile = zips[which]
|
||||||
|
loadZipBundle(selectedFile)
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.setNegativeButton("Cancel", null)
|
||||||
|
builder.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showTestAdbInfo() {
|
||||||
|
val exportDir = getExternalFilesDir(null) ?: filesDir
|
||||||
|
val path = exportDir.absolutePath
|
||||||
|
val isInternal = path.startsWith(filesDir.absolutePath)
|
||||||
|
val message = StringBuilder()
|
||||||
|
|
||||||
|
message.append("Storage Path: $path<br><br>")
|
||||||
|
|
||||||
|
message.append("<b>--- PULL FROM DEVICE ---</b><br>")
|
||||||
|
if (isInternal) {
|
||||||
|
message.append("<tt>adb shell \"run-as $packageName cat files/<filename>\" > <filename></tt><br><br>")
|
||||||
|
} else {
|
||||||
|
message.append("<tt>adb pull $path/<filename> .</tt><br><br>")
|
||||||
|
}
|
||||||
|
|
||||||
|
message.append("<b>--- PUSH TO DEVICE ---</b><br>")
|
||||||
|
if (isInternal) {
|
||||||
|
message.append("1. <tt>adb push <filename> /sdcard/Download/</tt><br>")
|
||||||
|
message.append("2. <tt>adb shell \"run-as $packageName cp /sdcard/Download/<filename> files/\"</tt><br>")
|
||||||
|
} else {
|
||||||
|
message.append("<tt>adb push <filename> $path/</tt><br>")
|
||||||
|
}
|
||||||
|
message.append("<br>Note: Use underscores instead of spaces in <filename>.")
|
||||||
|
|
||||||
|
AlertDialog.Builder(this)
|
||||||
|
.setTitle("Test Bundle ADB Info")
|
||||||
|
.setMessage(Html.fromHtml(message.toString(), Html.FROM_HTML_MODE_LEGACY))
|
||||||
|
.setPositiveButton("OK", null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showResultAdbInfo() {
|
||||||
|
val exportDir = getExternalFilesDir(null) ?: filesDir
|
||||||
|
val path = exportDir.absolutePath
|
||||||
|
val isInternal = path.startsWith(filesDir.absolutePath)
|
||||||
|
val message = StringBuilder()
|
||||||
|
|
||||||
|
message.append("<b>--- PULL RESULTS ---</b><br>")
|
||||||
|
if (isInternal) {
|
||||||
|
message.append("<tt>adb shell \"run-as $packageName cat files/<filename>\" > <filename></tt><br><br>")
|
||||||
|
} else {
|
||||||
|
message.append("<tt>adb pull $path/<filename> .</tt><br><br>")
|
||||||
|
}
|
||||||
|
|
||||||
|
message.append("<b>--- AVAILABLE RESULTS ---</b><br>")
|
||||||
|
val zips = exportDir.listFiles { _, name ->
|
||||||
|
name.endsWith(".zip") && name.startsWith("results_")
|
||||||
|
}?.sortedByDescending { it.lastModified() } ?: emptyList()
|
||||||
|
|
||||||
|
if (zips.isEmpty()) {
|
||||||
|
message.append("No result zips found.<br>")
|
||||||
|
} else {
|
||||||
|
zips.forEach { file ->
|
||||||
|
message.append("${file.name}<br>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertDialog.Builder(this)
|
||||||
|
.setTitle("Result ADB Info")
|
||||||
|
.setMessage(Html.fromHtml(message.toString(), Html.FROM_HTML_MODE_LEGACY))
|
||||||
|
.setPositiveButton("OK", null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadZipBundle(file: File) {
|
||||||
|
statusTextView.text = "Loading ${file.name}..."
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
try {
|
||||||
|
val config = inputManager.loadFromZip(file)
|
||||||
|
val baseDir = getExternalFilesDir(null) ?: filesDir
|
||||||
|
val outputDir = File(baseDir, "validation_results").apply { mkdirs() }
|
||||||
|
|
||||||
|
// Clear existing results UI and state
|
||||||
|
resultsContainer.removeAllViews()
|
||||||
|
diffImageViews.clear()
|
||||||
|
resultManager = null
|
||||||
|
|
||||||
|
val newInput = ValidationInputManager.ValidationInput(
|
||||||
|
config = config,
|
||||||
|
outputDir = outputDir,
|
||||||
|
generateGoldens = false,
|
||||||
|
autoRun = false,
|
||||||
|
autoExport = false,
|
||||||
|
autoExportResults = false,
|
||||||
|
sourceZip = file
|
||||||
|
)
|
||||||
|
|
||||||
|
currentInput = newInput
|
||||||
|
statusTextView.text = "Loaded ${config.name}"
|
||||||
|
Log.i(TAG, "Setting header to: Test Results: ${config.name}")
|
||||||
|
testResultsHeader.text = "${config.name}"
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Failed to load zip", e)
|
||||||
|
statusTextView.text = "Error: ${e.message}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun createIndirectLight() {
|
private fun createIndirectLight() {
|
||||||
try {
|
try {
|
||||||
val engine = modelViewer.engine
|
val engine = modelViewer.engine
|
||||||
@@ -156,12 +349,18 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
|||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
try {
|
try {
|
||||||
val input = inputManager.resolveConfig(intent)
|
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
|
currentInput = input
|
||||||
|
|
||||||
// Sync spinner with intent
|
if (input.autoRun) {
|
||||||
modeSpinner.setSelection(if (input.generateGoldens) 1 else 0)
|
startValidation(input)
|
||||||
|
} else {
|
||||||
startValidation(input)
|
// Just show status
|
||||||
|
statusTextView.text = "Ready: ${input.config.name}"
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to resolve config", e)
|
Log.e(TAG, "Failed to resolve config", e)
|
||||||
statusTextView.text = "Error: ${e.message}"
|
statusTextView.text = "Error: ${e.message}"
|
||||||
@@ -172,9 +371,13 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
|||||||
private fun startValidation(input: ValidationInputManager.ValidationInput) {
|
private fun startValidation(input: ValidationInputManager.ValidationInput) {
|
||||||
try {
|
try {
|
||||||
resultsContainer.removeAllViews()
|
resultsContainer.removeAllViews()
|
||||||
|
diffImageViews.clear()
|
||||||
|
enhancementSlider.isEnabled = false
|
||||||
Log.i(TAG, "Starting validation with config: ${input.config.name}")
|
Log.i(TAG, "Starting validation with config: ${input.config.name}")
|
||||||
Log.i(TAG, "Output dir: ${input.outputDir.absolutePath}")
|
Log.i(TAG, "Output dir: ${input.outputDir.absolutePath}")
|
||||||
|
|
||||||
|
testResultsHeader.text = "${input.config.name}"
|
||||||
|
|
||||||
resultManager = ValidationResultManager(input.outputDir)
|
resultManager = ValidationResultManager(input.outputDir)
|
||||||
|
|
||||||
validationRunner = ValidationRunner(this, modelViewer, input.config, resultManager!!)
|
validationRunner = ValidationRunner(this, modelViewer, input.config, resultManager!!)
|
||||||
@@ -182,9 +385,6 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
|||||||
validationRunner?.generateGoldens = input.generateGoldens
|
validationRunner?.generateGoldens = input.generateGoldens
|
||||||
validationRunner?.start()
|
validationRunner?.start()
|
||||||
|
|
||||||
// Sync spinner in case it was called programmatically or changed implicitly
|
|
||||||
modeSpinner.setSelection(if (input.generateGoldens) 1 else 0)
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to start validation", e)
|
Log.e(TAG, "Failed to start validation", e)
|
||||||
statusTextView.text = "Error: ${e.message}"
|
statusTextView.text = "Error: ${e.message}"
|
||||||
@@ -196,6 +396,12 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
|||||||
choreographer.postFrameCallback(frameScheduler)
|
choreographer.postFrameCallback(frameScheduler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onNewIntent(intent: Intent) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
setIntent(intent)
|
||||||
|
handleIntent()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
choreographer.removeFrameCallback(frameScheduler)
|
choreographer.removeFrameCallback(frameScheduler)
|
||||||
@@ -221,19 +427,44 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
|||||||
resultContainer.orientation = LinearLayout.VERTICAL
|
resultContainer.orientation = LinearLayout.VERTICAL
|
||||||
resultContainer.setPadding(0, 10, 0, 20)
|
resultContainer.setPadding(0, 10, 0, 20)
|
||||||
|
|
||||||
// Header
|
// Header Layout
|
||||||
val resultView = TextView(this)
|
val headerRow = LinearLayout(this)
|
||||||
resultView.text = "${result.testName}: ${if(result.passed) "PASS" else "FAIL"} (Diff: ${result.diffMetric})"
|
headerRow.orientation = LinearLayout.HORIZONTAL
|
||||||
resultView.setTextColor(if(result.passed) Color.GREEN else Color.RED)
|
headerRow.gravity = android.view.Gravity.CENTER_VERTICAL
|
||||||
resultView.textSize = 16f
|
|
||||||
resultView.setTypeface(null, android.graphics.Typeface.BOLD)
|
// Status Icon + Name
|
||||||
resultContainer.addView(resultView)
|
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
|
// Images Row
|
||||||
val imagesRow = LinearLayout(this)
|
val imagesRow = LinearLayout(this)
|
||||||
imagesRow.orientation = LinearLayout.HORIZONTAL
|
imagesRow.orientation = LinearLayout.HORIZONTAL
|
||||||
|
|
||||||
fun addImage(label: String, bitmap: Bitmap?) {
|
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) {
|
if (bitmap != null) {
|
||||||
val container = LinearLayout(this)
|
val container = LinearLayout(this)
|
||||||
container.orientation = LinearLayout.VERTICAL
|
container.orientation = LinearLayout.VERTICAL
|
||||||
@@ -241,7 +472,7 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
|||||||
|
|
||||||
val labelView = TextView(this)
|
val labelView = TextView(this)
|
||||||
labelView.text = label
|
labelView.text = label
|
||||||
labelView.textSize = 12f
|
labelView.textSize = 9f
|
||||||
container.addView(labelView)
|
container.addView(labelView)
|
||||||
|
|
||||||
val iv = ImageView(this)
|
val iv = ImageView(this)
|
||||||
@@ -249,16 +480,29 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
|||||||
iv.layoutParams = LinearLayout.LayoutParams(250, 250) // Smaller thumbnails
|
iv.layoutParams = LinearLayout.LayoutParams(250, 250) // Smaller thumbnails
|
||||||
iv.scaleType = ImageView.ScaleType.FIT_CENTER
|
iv.scaleType = ImageView.ScaleType.FIT_CENTER
|
||||||
iv.setBackgroundColor(0xFF404040.toInt())
|
iv.setBackgroundColor(0xFF404040.toInt())
|
||||||
|
|
||||||
|
if (isDiff) {
|
||||||
|
diffImageViews.add(iv)
|
||||||
|
applyEnhancementToView(iv, globalEnhancementFactor)
|
||||||
|
}
|
||||||
|
|
||||||
|
iv.setOnClickListener {
|
||||||
|
showImageDialog(testImages, label)
|
||||||
|
}
|
||||||
|
|
||||||
container.addView(iv)
|
container.addView(iv)
|
||||||
|
|
||||||
imagesRow.addView(container)
|
imagesRow.addView(container)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addImage("Rendered", currentRenderedBitmap)
|
addImage("Rendered", currentRenderedBitmap, false)
|
||||||
addImage("Golden", currentGoldenBitmap)
|
addImage("Golden", currentGoldenBitmap, false)
|
||||||
if (!result.passed) {
|
if (!result.passed) {
|
||||||
addImage("Diff", currentDiffBitmap)
|
addImage("Diff", currentDiffBitmap, true)
|
||||||
|
}
|
||||||
|
if (currentAlphaDiffBitmap != null) {
|
||||||
|
addImage("Alpha Diff", currentAlphaDiffBitmap, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
resultContainer.addView(imagesRow)
|
resultContainer.addView(imagesRow)
|
||||||
@@ -268,13 +512,54 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
|||||||
currentRenderedBitmap = null
|
currentRenderedBitmap = null
|
||||||
currentGoldenBitmap = null
|
currentGoldenBitmap = null
|
||||||
currentDiffBitmap = null
|
currentDiffBitmap = null
|
||||||
|
currentAlphaDiffBitmap = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAllTestsFinished() {
|
override fun onAllTestsFinished() {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
statusTextView.text = "All tests finished!"
|
statusTextView.text = "All tests finished!"
|
||||||
Log.i(TAG, "All tests finished")
|
enhancementSlider.isEnabled = true
|
||||||
|
Log.i(TAG, "All tests finished " + if (currentInput?.autoExport == true) "Exporting bundle" else "x")
|
||||||
|
|
||||||
|
if (currentInput?.autoExport == true) {
|
||||||
|
exportTestBundleAction()
|
||||||
|
}
|
||||||
|
if (currentInput?.autoExportResults == true) {
|
||||||
|
exportTestResultsAction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun exportTestBundleAction() {
|
||||||
|
currentInput?.let { input ->
|
||||||
|
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
|
||||||
|
val rm = resultManager ?: ValidationResultManager(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 ?: ValidationResultManager(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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,54 +582,165 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
|||||||
"Diff" -> {
|
"Diff" -> {
|
||||||
currentDiffBitmap = bitmap
|
currentDiffBitmap = bitmap
|
||||||
}
|
}
|
||||||
|
"Alpha Diff" -> {
|
||||||
|
currentAlphaDiffBitmap = bitmap
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
private fun applyEnhancementToView(iv: ImageView, factor: Float) {
|
||||||
* Scripts for reference:
|
val cm = android.graphics.ColorMatrix()
|
||||||
*
|
cm.setScale(factor, factor, factor, 1.0f)
|
||||||
* generate_goldens.sh:
|
iv.colorFilter = android.graphics.ColorMatrixColorFilter(cm)
|
||||||
* --------------------
|
}
|
||||||
* #!/bin/bash
|
|
||||||
* set -e
|
private fun applyGlobalEnhancement() {
|
||||||
*
|
for (iv in diffImageViews) {
|
||||||
* # Config path (on device)
|
applyEnhancementToView(iv, globalEnhancementFactor)
|
||||||
* CONFIG_PATH=$1
|
}
|
||||||
* if [ -z "$CONFIG_PATH" ]; then
|
}
|
||||||
* echo "Usage: $0 <device_config_path>"
|
|
||||||
* echo "Example: $0 /sdcard/Android/data/com.google.android.filament.validation/files/default_test.json"
|
private fun showImageDialog(images: TestImages, initialLabel: String) {
|
||||||
* exit 1
|
val dialogView = layoutInflater.inflate(R.layout.dialog_image_viewer, null)
|
||||||
* fi
|
val dialog = AlertDialog.Builder(this)
|
||||||
*
|
.setView(dialogView)
|
||||||
* echo "Starting Golden Generation for $CONFIG_PATH..."
|
.create()
|
||||||
* adb shell am force-stop com.google.android.filament.validation
|
|
||||||
* adb shell am start -n com.google.android.filament.validation/.MainActivity \
|
val titleView = dialogView.findViewById<TextView>(R.id.dialog_title)
|
||||||
* -e test_config "$CONFIG_PATH" \
|
val typeView = dialogView.findViewById<TextView>(R.id.dialog_image_type)
|
||||||
* --ez generate_goldens true
|
val imageView = dialogView.findViewById<ImageView>(R.id.dialog_image)
|
||||||
*
|
val btnClose = dialogView.findViewById<View>(R.id.btn_close)
|
||||||
* echo "Check device or logcat for progress."
|
val btnReset = dialogView.findViewById<View>(R.id.btn_reset)
|
||||||
* echo "adb logcat -s FilamentValidation:I ValidationRunner:I"
|
val btnPrev = dialogView.findViewById<View>(R.id.btn_prev)
|
||||||
* echo "To pull results: ./samples/sample-render-validation/pull_goldens.sh"
|
val btnNext = dialogView.findViewById<View>(R.id.btn_next)
|
||||||
*
|
|
||||||
* pull_goldens.sh:
|
val enhancementContainer = dialogView.findViewById<View>(R.id.dialog_enhancement_container)
|
||||||
* ----------------
|
val enhancementLabel = dialogView.findViewById<TextView>(R.id.dialog_enhancement_label)
|
||||||
* #!/bin/bash
|
val enhancementSlider = dialogView.findViewById<android.widget.SeekBar>(R.id.dialog_enhancement_slider)
|
||||||
* set -e
|
|
||||||
*
|
titleView.text = images.testName
|
||||||
* # Default destination is local golden directory relative to script
|
|
||||||
* SCRIPT_DIR=$(cd $(dirname $0); pwd)
|
val availableImages = mutableListOf<Pair<String, Bitmap>>()
|
||||||
* DEST_DIR=${1:-"$SCRIPT_DIR/golden"}
|
images.rendered?.let { availableImages.add(Pair("Rendered", it)) }
|
||||||
*
|
images.golden?.let { availableImages.add(Pair("Golden", it)) }
|
||||||
* echo "Pulling goldens to $DEST_DIR..."
|
images.diff?.let { availableImages.add(Pair("Diff", it)) }
|
||||||
* mkdir -p "$DEST_DIR"
|
images.alphaDiff?.let { availableImages.add(Pair("Alpha Diff", it)) }
|
||||||
*
|
|
||||||
* # Path on device
|
if (availableImages.isEmpty()) return
|
||||||
* DEVICE_GOLDEN_DIR="/storage/emulated/0/Android/data/com.google.android.filament.validation/files/golden/."
|
|
||||||
*
|
var currentIndex = availableImages.indexOfFirst { it.first == initialLabel }
|
||||||
* adb pull "$DEVICE_GOLDEN_DIR" "$DEST_DIR"
|
if (currentIndex == -1) currentIndex = 0
|
||||||
*
|
|
||||||
* echo "Done."
|
var currentDialogEnhancement = globalEnhancementFactor
|
||||||
* ls -l "$DEST_DIR"
|
|
||||||
*/
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+101
-7
@@ -26,6 +26,7 @@ import java.io.File
|
|||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
import java.util.zip.ZipFile
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the retrieval and preparation of test configuration and assets.
|
* Handles the retrieval and preparation of test configuration and assets.
|
||||||
@@ -42,7 +43,11 @@ class ValidationInputManager(private val context: Context) {
|
|||||||
data class ValidationInput(
|
data class ValidationInput(
|
||||||
val config: RenderTestConfig,
|
val config: RenderTestConfig,
|
||||||
val outputDir: File,
|
val outputDir: File,
|
||||||
val generateGoldens: Boolean
|
val generateGoldens: Boolean,
|
||||||
|
val autoRun: Boolean = false,
|
||||||
|
val autoExport: Boolean = false,
|
||||||
|
val autoExportResults: Boolean = false,
|
||||||
|
val sourceZip: File? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,22 +58,111 @@ class ValidationInputManager(private val context: Context) {
|
|||||||
val testConfigPath = intent.getStringExtra("test_config")
|
val testConfigPath = intent.getStringExtra("test_config")
|
||||||
val urlConfig = intent.getStringExtra("url_config")
|
val urlConfig = intent.getStringExtra("url_config")
|
||||||
val urlModelsBase = intent.getStringExtra("url_models_base")
|
val urlModelsBase = intent.getStringExtra("url_models_base")
|
||||||
|
val zipPath = intent.getStringExtra("zip_path")
|
||||||
val generateGoldens = intent.getBooleanExtra("generate_goldens", false)
|
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")
|
val outputPath = intent.getStringExtra("output_path")
|
||||||
|
|
||||||
val outputDir = if (outputPath != null) {
|
Log.i(TAG, "Resolving config with outputPath: $outputPath")
|
||||||
File(outputPath).apply { mkdirs() }
|
|
||||||
|
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 {
|
} else {
|
||||||
File(context.getExternalFilesDir(null), "validation_results").apply { mkdirs() }
|
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 {
|
val config = when {
|
||||||
|
sourceZipFile != null && sourceZipFile.exists() -> loadFromZip(sourceZipFile)
|
||||||
urlConfig != null -> downloadConfig(urlConfig, urlModelsBase)
|
urlConfig != null -> downloadConfig(urlConfig, urlModelsBase)
|
||||||
testConfigPath != null -> ConfigParser.parseFromPath(testConfigPath)
|
testConfigPath != null -> ConfigParser.parseFromPath(testConfigPath)
|
||||||
else -> extractDefaultAssets()
|
else -> extractDefaultAssets()
|
||||||
}
|
}
|
||||||
|
|
||||||
return@withContext ValidationInput(config, outputDir, generateGoldens)
|
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 {
|
private suspend fun extractDefaultAssets(): RenderTestConfig {
|
||||||
@@ -90,9 +184,9 @@ class ValidationInputManager(private val context: Context) {
|
|||||||
// Copy DamagedHelmet.glb
|
// Copy DamagedHelmet.glb
|
||||||
val modelsDir = File(filesDir, "models")
|
val modelsDir = File(filesDir, "models")
|
||||||
modelsDir.mkdirs()
|
modelsDir.mkdirs()
|
||||||
val modelOut = File(modelsDir, "DamagedHelmet.glb")
|
val modelOut = File(modelsDir, "helmet.glb")
|
||||||
|
|
||||||
assetManager.open("DamagedHelmet.glb").use { input ->
|
assetManager.open("models/helmet.glb").use { input ->
|
||||||
FileOutputStream(modelOut).use { output ->
|
FileOutputStream(modelOut).use { output ->
|
||||||
input.copyTo(output)
|
input.copyTo(output)
|
||||||
}
|
}
|
||||||
|
|||||||
+187
-8
@@ -67,22 +67,201 @@ class ValidationResultManager(private val outputDir: File) {
|
|||||||
fun finalizeResults(): File? {
|
fun finalizeResults(): File? {
|
||||||
// Write results JSON
|
// Write results JSON
|
||||||
writeResultsJson()
|
writeResultsJson()
|
||||||
|
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}")
|
||||||
|
|
||||||
// Zip results
|
|
||||||
val zipFile = File(outputDir, "results.zip")
|
|
||||||
try {
|
try {
|
||||||
ZipOutputStream(FileOutputStream(zipFile)).use { zos ->
|
ZipOutputStream(FileOutputStream(zipFile)).use { zos ->
|
||||||
outputDir.walkTopDown().filter { it.isFile && it.name != "results.zip" }.forEach { file ->
|
// 1. Add results.json
|
||||||
val entryName = file.relativeTo(outputDir).path
|
val resultsJson = File(outputDir, "results.json")
|
||||||
zos.putNextEntry(ZipEntry(entryName))
|
if (resultsJson.exists()) {
|
||||||
file.inputStream().use { it.copyTo(zos) }
|
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 diff images (any file ending in _diff.png in outputDir)
|
||||||
|
outputDir.listFiles { _, name -> name.endsWith("_diff.png") }?.forEach { diffFile ->
|
||||||
|
zos.putNextEntry(ZipEntry(diffFile.name))
|
||||||
|
diffFile.inputStream().use { it.copyTo(zos) }
|
||||||
zos.closeEntry()
|
zos.closeEntry()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Log.i(TAG, "Zipped results to ${zipFile.absolutePath}")
|
Log.i(TAG, "Exported results to ${zipFile.absolutePath}")
|
||||||
return zipFile
|
return zipFile
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to zip results", e)
|
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
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+92
-59
@@ -41,18 +41,13 @@ class ValidationRunner(
|
|||||||
private var currentTestConfig: TestConfig? = null
|
private var currentTestConfig: TestConfig? = null
|
||||||
private var currentModelName: String? = 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 frameCounter = 0
|
||||||
|
|
||||||
enum class State {
|
enum class State {
|
||||||
IDLE,
|
IDLE,
|
||||||
LOADING_MODEL,
|
|
||||||
WAITING_FOR_FENCE,
|
|
||||||
WAITING_FOR_RESOURCES,
|
WAITING_FOR_RESOURCES,
|
||||||
WARMUP,
|
WARMUP,
|
||||||
RUNNING_TEST,
|
RUNNING_TEST
|
||||||
COMPARING
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Callback {
|
interface Callback {
|
||||||
@@ -93,8 +88,6 @@ class ValidationRunner(
|
|||||||
nextModel()
|
nextModel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
currentState = State.LOADING_MODEL
|
|
||||||
callback?.onStatusChanged("Loading $modelName for ${currentTestConfig?.name}")
|
callback?.onStatusChanged("Loading $modelName for ${currentTestConfig?.name}")
|
||||||
|
|
||||||
// Load model on main thread (required by ModelViewer)
|
// Load model on main thread (required by ModelViewer)
|
||||||
@@ -110,51 +103,20 @@ class ValidationRunner(
|
|||||||
Log.i("ValidationRunner", "Loading GLB buffer... (${bytes.size} bytes)")
|
Log.i("ValidationRunner", "Loading GLB buffer... (${bytes.size} bytes)")
|
||||||
val buffer = ByteBuffer.wrap(bytes)
|
val buffer = ByteBuffer.wrap(bytes)
|
||||||
modelViewer.loadModelGlb(buffer)
|
modelViewer.loadModelGlb(buffer)
|
||||||
Log.i("ValidationRunner", "Model loaded. initializing fence.")
|
Log.i("ValidationRunner", "Model loaded.")
|
||||||
modelViewer.transformToUnitCube()
|
modelViewer.transformToUnitCube()
|
||||||
loadStartFence = modelViewer.engine.createFence()
|
currentState = State.WAITING_FOR_RESOURCES
|
||||||
loadStartTime = System.nanoTime()
|
frameCounter = 0
|
||||||
currentState = State.WAITING_FOR_FENCE
|
Log.i("ValidationRunner", "State set to WAITING_FOR_RESOURCES")
|
||||||
frameCounter = 0 // Reset for fence timeout tracking
|
|
||||||
Log.i("ValidationRunner", "State set to WAITING_FOR_FENCE")
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("ValidationRunner", "Failed to load $path", e)
|
Log.e("ValidationRunner", "Failed to load $path", e)
|
||||||
nextModel()
|
nextModel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onFrame(frameTimeNanos: Long) {
|
fun onFrame(frameTimeNanos: Long) {
|
||||||
if (frameCounter % 60 == 0) {
|
|
||||||
Log.i("ValidationRunner", "onFrame: $currentState (frame: $frameCounter)")
|
|
||||||
}
|
|
||||||
|
|
||||||
when (currentState) {
|
when (currentState) {
|
||||||
State.IDLE -> {}
|
State.IDLE -> {}
|
||||||
State.WAITING_FOR_FENCE -> {
|
|
||||||
frameCounter++
|
|
||||||
if (frameCounter > 600) {
|
|
||||||
Log.w("ValidationRunner", "Fence timed out after 600 frames! Forcing proceed.")
|
|
||||||
modelViewer.engine.destroyFence(loadStartFence!!)
|
|
||||||
loadStartFence = null
|
|
||||||
currentState = State.WAITING_FOR_RESOURCES
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
loadStartFence?.let { fence ->
|
|
||||||
if (fence.wait(com.google.android.filament.Fence.Mode.FLUSH, 0) ==
|
|
||||||
com.google.android.filament.Fence.FenceStatus.CONDITION_SATISFIED) {
|
|
||||||
modelViewer.engine.destroyFence(fence)
|
|
||||||
loadStartFence = null
|
|
||||||
|
|
||||||
// Compile materials (simplified)
|
|
||||||
modelViewer.scene.forEach { entity ->
|
|
||||||
// ... existing material compilation logic ...
|
|
||||||
}
|
|
||||||
|
|
||||||
currentState = State.WAITING_FOR_RESOURCES
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
State.WAITING_FOR_RESOURCES -> {
|
State.WAITING_FOR_RESOURCES -> {
|
||||||
val progress = modelViewer.progress
|
val progress = modelViewer.progress
|
||||||
if (progress >= 1.0f) {
|
if (progress >= 1.0f) {
|
||||||
@@ -166,12 +128,11 @@ class ValidationRunner(
|
|||||||
State.WARMUP -> {
|
State.WARMUP -> {
|
||||||
frameCounter++
|
frameCounter++
|
||||||
if (frameCounter > 5) { // 5 frames warmup
|
if (frameCounter > 5) { // 5 frames warmup
|
||||||
startAutomation()
|
startAutomation()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
State.RUNNING_TEST -> {
|
State.RUNNING_TEST -> {
|
||||||
// Log.i("ValidationRunner", "Running test...")
|
currentEngine?.let { engine ->
|
||||||
currentEngine?.let { engine ->
|
|
||||||
val content = AutomationEngine.ViewerContent()
|
val content = AutomationEngine.ViewerContent()
|
||||||
content.view = modelViewer.view
|
content.view = modelViewer.view
|
||||||
content.renderer = modelViewer.renderer
|
content.renderer = modelViewer.renderer
|
||||||
@@ -186,13 +147,11 @@ class ValidationRunner(
|
|||||||
if (engine.shouldClose()) {
|
if (engine.shouldClose()) {
|
||||||
Log.i("ValidationRunner", "Finishing test (frames: $frameCounter)")
|
Log.i("ValidationRunner", "Finishing test (frames: $frameCounter)")
|
||||||
// Test finished (for this spec)
|
// Test finished (for this spec)
|
||||||
currentState = State.COMPARING
|
currentState = State.IDLE
|
||||||
captureAndCompare()
|
captureAndCompare()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
State.COMPARING -> {} // Busy
|
|
||||||
State.LOADING_MODEL -> {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,7 +192,36 @@ class ValidationRunner(
|
|||||||
|
|
||||||
// Golden path
|
// Golden path
|
||||||
val modelFile = File(config.models.get(modelName)!!)
|
val modelFile = File(config.models.get(modelName)!!)
|
||||||
val goldenFile = modelFile.parentFile!!.parentFile!!.resolve("golden/${testFullName}.png")
|
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 {
|
Thread {
|
||||||
try {
|
try {
|
||||||
@@ -245,14 +233,15 @@ class ValidationRunner(
|
|||||||
var diffMetric = 0f
|
var diffMetric = 0f
|
||||||
|
|
||||||
if (generateGoldens) {
|
if (generateGoldens) {
|
||||||
goldenFile.parentFile?.mkdirs()
|
val targetGolden = goldenFile ?: modelParent.parentFile?.resolve("golden/${testFullName}.png") ?: File(modelParent, "golden/${testFullName}.png")
|
||||||
FileOutputStream(goldenFile).use { out ->
|
targetGolden.parentFile?.mkdirs()
|
||||||
|
FileOutputStream(targetGolden).use { out ->
|
||||||
flipped.compress(Bitmap.CompressFormat.PNG, 100, out)
|
flipped.compress(Bitmap.CompressFormat.PNG, 100, out)
|
||||||
}
|
}
|
||||||
passed = true // Generating goldens always passes if successful
|
passed = true // Generating goldens always passes if successful
|
||||||
callback?.onStatusChanged("Golden generated")
|
callback?.onStatusChanged("Golden generated")
|
||||||
} else {
|
} else {
|
||||||
if (goldenFile.exists()) {
|
if (goldenFile != null && goldenFile.exists()) {
|
||||||
val golden = android.graphics.BitmapFactory.decodeFile(goldenFile.absolutePath)
|
val golden = android.graphics.BitmapFactory.decodeFile(goldenFile.absolutePath)
|
||||||
if (golden != null) {
|
if (golden != null) {
|
||||||
callback?.onImageResult("Golden", golden)
|
callback?.onImageResult("Golden", golden)
|
||||||
@@ -263,17 +252,61 @@ class ValidationRunner(
|
|||||||
passed = (result.status == ImageDiff.Result.Status.PASSED)
|
passed = (result.status == ImageDiff.Result.Status.PASSED)
|
||||||
diffMetric = result.failingPixelCount.toFloat()
|
diffMetric = result.failingPixelCount.toFloat()
|
||||||
|
|
||||||
if (!passed) {
|
if (!passed) {
|
||||||
if (result.diffImage != null) {
|
if (result.diffImage != null) {
|
||||||
callback?.onImageResult("Diff", result.diffImage!!)
|
val diffImg = result.diffImage!!
|
||||||
resultManager.saveImage("${testFullName}_diff", 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 {
|
} else {
|
||||||
callback?.onStatusChanged("Failed to load golden")
|
callback?.onStatusChanged("Failed to load golden")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.w("ValidationRunner", "Golden not found: ${goldenFile.absolutePath}")
|
Log.w("ValidationRunner", "Golden not found: ${goldenFile?.absolutePath}")
|
||||||
callback?.onStatusChanged("Golden not found")
|
callback?.onStatusChanged("Golden not found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -10,6 +10,8 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintDimensionRatio="1:1"
|
app:layout_constraintDimensionRatio="1:1"
|
||||||
|
app:layout_constraintWidth_percent="0.6"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent">
|
app:layout_constraintEnd_toEndOf="parent">
|
||||||
@@ -20,10 +22,110 @@
|
|||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent" />
|
||||||
</FrameLayout>
|
</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
|
<ScrollView
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintTop_toBottomOf="@id/surface_container"
|
app:layout_constraintTop_toBottomOf="@id/controls_container"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent">
|
app:layout_constraintEnd_toEndOf="parent">
|
||||||
@@ -32,41 +134,9 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="20dp">
|
android:paddingStart="20dp"
|
||||||
|
android:paddingEnd="20dp"
|
||||||
<TextView
|
android:paddingBottom="20dp">
|
||||||
android:id="@+id/status_text"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Initializing..."
|
|
||||||
android:textSize="14sp" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:paddingTop="10dp"
|
|
||||||
android:paddingBottom="10dp">
|
|
||||||
|
|
||||||
<Spinner
|
|
||||||
android:id="@+id/mode_spinner"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/run_button"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Run" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Test Results"
|
|
||||||
android:textSize="18sp"
|
|
||||||
android:paddingTop="20dp"
|
|
||||||
android:paddingBottom="10dp" />
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/results_container"
|
android:id="@+id/results_container"
|
||||||
@@ -74,7 +144,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical" />
|
android:orientation="vertical" />
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-plugin'
|
id 'com.google.android.filament-tools'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-plugin'
|
id 'com.google.android.filament-tools'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-plugin'
|
id 'com.google.android.filament-tools'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-plugin'
|
id 'com.google.android.filament-tools'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-plugin'
|
id 'com.google.android.filament-tools'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
// Filament tools plugin
|
||||||
|
pluginManagement {
|
||||||
|
includeBuild 'gradle-plugin'
|
||||||
|
}
|
||||||
|
|
||||||
// Libraries
|
// Libraries
|
||||||
include ':filament-android'
|
include ':filament-android'
|
||||||
include ':filamat-android'
|
include ':filamat-android'
|
||||||
|
|||||||
+6
-6
@@ -235,9 +235,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz",
|
||||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
"integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
},
|
},
|
||||||
@@ -429,9 +429,9 @@
|
|||||||
"integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q=="
|
"integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q=="
|
||||||
},
|
},
|
||||||
"minimatch": {
|
"minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz",
|
||||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
"integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ list(SORT NDK_VERSIONS)
|
|||||||
list(GET NDK_VERSIONS -1 NDK_VERSION)
|
list(GET NDK_VERSIONS -1 NDK_VERSION)
|
||||||
get_filename_component(NDK_VERSION ${NDK_VERSION} NAME)
|
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(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
|
# specify the cross compiler
|
||||||
set(COMPILER_SUFFIX)
|
set(COMPILER_SUFFIX)
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ list(SORT NDK_VERSIONS)
|
|||||||
list(GET NDK_VERSIONS -1 NDK_VERSION)
|
list(GET NDK_VERSIONS -1 NDK_VERSION)
|
||||||
get_filename_component(NDK_VERSION ${NDK_VERSION} NAME)
|
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(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
|
# specify the cross compiler
|
||||||
set(COMPILER_SUFFIX)
|
set(COMPILER_SUFFIX)
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ list(SORT NDK_VERSIONS)
|
|||||||
list(GET NDK_VERSIONS -1 NDK_VERSION)
|
list(GET NDK_VERSIONS -1 NDK_VERSION)
|
||||||
get_filename_component(NDK_VERSION ${NDK_VERSION} NAME)
|
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(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
|
# specify the cross compiler
|
||||||
set(COMPILER_SUFFIX)
|
set(COMPILER_SUFFIX)
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ list(SORT NDK_VERSIONS)
|
|||||||
list(GET NDK_VERSIONS -1 NDK_VERSION)
|
list(GET NDK_VERSIONS -1 NDK_VERSION)
|
||||||
get_filename_component(NDK_VERSION ${NDK_VERSION} NAME)
|
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(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
|
# specify the cross compiler
|
||||||
set(COMPILER_SUFFIX)
|
set(COMPILER_SUFFIX)
|
||||||
|
|||||||
+2
-2
@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.google.android.filament:filament-android:1.69.3'
|
implementation 'com.google.android.filament:filament-android:1.70.0'
|
||||||
}
|
}
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
|
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
|
||||||
@@ -195,7 +195,7 @@ dependencies {
|
|||||||
</div>
|
</div>
|
||||||
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
|
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
|
||||||
<p>iOS projects can use CocoaPods to install the latest release:</p>
|
<p>iOS projects can use CocoaPods to install the latest release:</p>
|
||||||
<pre><code class="language-shell">pod 'Filament', '~> 1.69.3'
|
<pre><code class="language-shell">pod 'Filament', '~> 1.70.0'
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
|
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
|
||||||
<ul>
|
<ul>
|
||||||
|
|||||||
@@ -225,9 +225,10 @@ into <strong>branch</strong> of <code>filament-assets</code>. This branch is pai
|
|||||||
<code>filament</code> repo.</p>
|
<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
|
<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
|
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>
|
<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>
|
<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.
|
<li>Run interactive mode in the <code>update_golden.py</code> script.
|
||||||
<pre><code>python3 test/renderdiff/src/update_golden.py
|
<pre><code>python3 test/renderdiff/src/update_golden.py
|
||||||
</code></pre>
|
</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>
|
<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>
|
to the <code>main</code> branch of the golden repo.</li>
|
||||||
</ul>
|
</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>
|
<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
|
<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
|
be used by pointing your browser to a localhost port. If you input the viewer with a PR or a
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -93,6 +93,7 @@ set(SRCS
|
|||||||
src/MorphTargetBuffer.cpp
|
src/MorphTargetBuffer.cpp
|
||||||
src/PostProcessManager.cpp
|
src/PostProcessManager.cpp
|
||||||
src/ProgramSpecialization.cpp
|
src/ProgramSpecialization.cpp
|
||||||
|
src/LocalProgramCache.cpp
|
||||||
src/RenderPass.cpp
|
src/RenderPass.cpp
|
||||||
src/RenderPrimitive.cpp
|
src/RenderPrimitive.cpp
|
||||||
src/RenderTarget.cpp
|
src/RenderTarget.cpp
|
||||||
@@ -186,6 +187,7 @@ set(PRIVATE_HDRS
|
|||||||
src/MaterialCache.h
|
src/MaterialCache.h
|
||||||
src/MaterialDefinition.h
|
src/MaterialDefinition.h
|
||||||
src/MaterialParser.h
|
src/MaterialParser.h
|
||||||
|
src/LocalProgramCache.h
|
||||||
src/MaterialInstanceManager.h
|
src/MaterialInstanceManager.h
|
||||||
src/PIDController.h
|
src/PIDController.h
|
||||||
src/PostProcessManager.h
|
src/PostProcessManager.h
|
||||||
|
|||||||
+152
-153
@@ -564,170 +564,169 @@ endif()
|
|||||||
# ==================================================================================================
|
# ==================================================================================================
|
||||||
# Test
|
# 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)
|
if (APPLE OR LINUX)
|
||||||
set(BACKEND_TEST_SRC
|
set(BACKEND_TEST_SRC
|
||||||
test/BackendTest.cpp
|
test/BackendTest.cpp
|
||||||
test/ShaderGenerator.cpp
|
test/ShaderGenerator.cpp
|
||||||
test/TrianglePrimitive.cpp
|
test/TrianglePrimitive.cpp
|
||||||
test/Arguments.cpp
|
test/Arguments.cpp
|
||||||
test/ImageExpectations.cpp
|
test/ImageExpectations.cpp
|
||||||
test/Lifetimes.cpp
|
test/Lifetimes.cpp
|
||||||
test/PlatformRunner.cpp
|
test/PlatformRunner.cpp
|
||||||
test/Shader.cpp
|
test/Shader.cpp
|
||||||
test/SharedShaders.cpp
|
test/SharedShaders.cpp
|
||||||
test/Skip.cpp
|
test/Skip.cpp
|
||||||
test/test_Autoresolve.cpp
|
test/test_Autoresolve.cpp
|
||||||
test/test_FeedbackLoops.cpp
|
test/test_FeedbackLoops.cpp
|
||||||
test/test_Blit.cpp
|
test/test_Blit.cpp
|
||||||
test/test_MissingRequiredAttributes.cpp
|
test/test_MissingRequiredAttributes.cpp
|
||||||
test/test_ReadPixels.cpp
|
test/test_ReadPixels.cpp
|
||||||
test/test_ReadTexture.cpp
|
test/test_ReadTexture.cpp
|
||||||
test/test_BufferUpdates.cpp
|
test/test_BufferUpdates.cpp
|
||||||
test/test_Callbacks.cpp
|
test/test_Callbacks.cpp
|
||||||
test/test_JobQueue.cpp
|
test/test_JobQueue.cpp
|
||||||
test/test_MemoryMappedBuffer.cpp
|
test/test_MemoryMappedBuffer.cpp
|
||||||
test/test_MsaaSwapChain.cpp
|
test/test_MsaaSwapChain.cpp
|
||||||
test/test_MRT.cpp
|
test/test_MRT.cpp
|
||||||
test/test_PushConstants.cpp
|
test/test_PushConstants.cpp
|
||||||
test/test_LoadImage.cpp
|
test/test_LoadImage.cpp
|
||||||
test/test_StencilBuffer.cpp
|
test/test_StencilBuffer.cpp
|
||||||
test/test_Scissor.cpp
|
test/test_Scissor.cpp
|
||||||
test/test_MipLevels.cpp
|
test/test_MipLevels.cpp
|
||||||
test/test_Handles.cpp
|
test/test_Handles.cpp
|
||||||
test/test_CircularBuffer.cpp
|
test/test_CircularBuffer.cpp
|
||||||
test/test_CommandBufferQueue.cpp
|
test/test_CommandBufferQueue.cpp
|
||||||
test/test_Template.cpp
|
test/test_Template.cpp
|
||||||
)
|
test/test_Platform.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
|
|
||||||
)
|
)
|
||||||
endif()
|
if (FILAMENT_SUPPORTS_WEBGPU)
|
||||||
set(BACKEND_TEST_LIBS
|
list(APPEND BACKEND_TEST_SRC
|
||||||
absl::str_format
|
test/test_WebGPUAsyncTaskCounter.cpp
|
||||||
backend
|
test/test_WebGPUComposeSwizzle.cpp)
|
||||||
getopt
|
endif()
|
||||||
gtest
|
if (APPLE)
|
||||||
imageio
|
# Metal-specific tests
|
||||||
filamat
|
list(APPEND BACKEND_TEST_SRC
|
||||||
SPIRV
|
test/test_MetalBlitter.mm
|
||||||
spirv-cross-glsl)
|
)
|
||||||
# Create input/output directories for test result images.
|
endif()
|
||||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/actual_images)
|
set(BACKEND_TEST_LIBS
|
||||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/diff_images)
|
absl::str_format
|
||||||
file(COPY test/expected_images DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/images)
|
backend
|
||||||
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
|
|
||||||
gtest
|
gtest
|
||||||
glslang
|
imageio
|
||||||
spirv-cross-core
|
filamat
|
||||||
spirv-cross-glsl
|
SPIRV
|
||||||
spirv-cross-msl)
|
spirv-cross-glsl)
|
||||||
|
# Create input/output directories for test result images.
|
||||||
if (NOT IOS)
|
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/actual_images)
|
||||||
target_link_libraries(backend_test PRIVATE image imageio)
|
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/diff_images)
|
||||||
list(APPEND BACKEND_TEST_DEPS image)
|
file(COPY test/expected_images DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/images)
|
||||||
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()
|
endif()
|
||||||
|
|
||||||
set(BACKEND_TEST_COMBINED_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/libbackendtest_combined.a")
|
# TODO: Disabling IOS test due to breakage wrt glslang update
|
||||||
combine_static_libs(backend_test "${BACKEND_TEST_COMBINED_OUTPUT}" "${BACKEND_TEST_DEPS}")
|
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)
|
if (NOT IOS)
|
||||||
install(FILES "${BACKEND_TEST_COMBINED_OUTPUT}" DESTINATION lib/${DIST_DIR} RENAME ${BACKEND_TEST_LIB_NAME})
|
target_link_libraries(backend_test PRIVATE image imageio)
|
||||||
install(FILES test/PlatformRunner.h DESTINATION include/backend_test)
|
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()
|
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)
|
if (APPLE AND NOT IOS)
|
||||||
add_executable(backend_test_mac test/mac_runner.mm)
|
add_executable(metal_utils_test test/MetalTest.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
|
target_compile_options(metal_utils_test PRIVATE "-fobjc-arc")
|
||||||
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_link_libraries(metal_utils_test PRIVATE
|
||||||
|
backend
|
||||||
|
gtest
|
||||||
|
)
|
||||||
|
|
||||||
|
set_target_properties(metal_utils_test PROPERTIES FOLDER Tests)
|
||||||
endif()
|
endif()
|
||||||
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,
|
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
|
* This controls the priority level for GPU work scheduling, which helps prioritize the
|
||||||
* submitted GPU work and enables preemption.
|
* submitted GPU work and enables preemption.
|
||||||
@@ -316,7 +328,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
StereoscopicType stereoscopicType = StereoscopicType::NONE;
|
StereoscopicType stereoscopicType = StereoscopicType::NONE;
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* The number of eyes to render when stereoscopic rendering is enabled. Supported values are
|
* The number of eyes to render when stereoscopic rendering is enabled. Supported values are
|
||||||
* between 1 and Engine::getMaxStereoscopicEyes() (inclusive).
|
* between 1 and Engine::getMaxStereoscopicEyes() (inclusive).
|
||||||
*/
|
*/
|
||||||
@@ -377,6 +389,16 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual int getOSVersion() const noexcept = 0;
|
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),
|
* Creates and initializes the low-level API (e.g. an OpenGL context or Vulkan instance),
|
||||||
* then creates the concrete Driver.
|
* then creates the concrete Driver.
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ protected:
|
|||||||
|
|
||||||
~OpenGLPlatform() noexcept override;
|
~OpenGLPlatform() noexcept override;
|
||||||
|
|
||||||
|
utils::CString getDeviceInfo(DeviceInfoType infoType,
|
||||||
|
Driver* UTILS_NULLABLE driver) const noexcept override;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
struct ExternalTexture {
|
struct ExternalTexture {
|
||||||
unsigned int target; // GLenum target
|
unsigned int target; // GLenum target
|
||||||
@@ -70,6 +73,12 @@ public:
|
|||||||
*/
|
*/
|
||||||
static utils::CString getRendererString(Driver const* UTILS_NONNULL driver);
|
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
|
* 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
|
* or buffers from initialization. This is for instance where `eglDestroyContext` would be
|
||||||
|
|||||||
@@ -159,6 +159,12 @@ protected:
|
|||||||
EGLConfig getEglConfig() const noexcept { return mEGLConfig; }
|
EGLConfig getEglConfig() const noexcept { return mEGLConfig; }
|
||||||
EGLConfig getSuitableConfigForSwapChain(uint64_t flags, bool window, bool pbuffer) const;
|
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
|
// supported extensions detected at runtime
|
||||||
struct {
|
struct {
|
||||||
struct {
|
struct {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ public:
|
|||||||
|
|
||||||
Driver* createDriver(void* sharedContext, const Platform::DriverConfig& driverConfig) override;
|
Driver* createDriver(void* sharedContext, const Platform::DriverConfig& driverConfig) override;
|
||||||
int getOSVersion() const noexcept override { return 0; }
|
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.
|
* Optionally initializes the Metal platform by acquiring resources necessary for rendering.
|
||||||
|
|||||||
@@ -31,7 +31,6 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <string>
|
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
@@ -69,7 +68,7 @@ public:
|
|||||||
|
|
||||||
struct ExtensionHashFn {
|
struct ExtensionHashFn {
|
||||||
std::size_t operator()(utils::CString const& s) const noexcept {
|
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.
|
// Note: utils::CString::operator== has an edge case that breaks for the extension set.
|
||||||
@@ -143,6 +142,8 @@ public:
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
utils::CString getDeviceInfo(DeviceInfoType infoType, Driver* driver) const noexcept override;
|
||||||
|
|
||||||
// ----------------------------------------------------
|
// ----------------------------------------------------
|
||||||
// ---------- Platform Customization options ----------
|
// ---------- Platform Customization options ----------
|
||||||
struct Customization {
|
struct Customization {
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ public:
|
|||||||
~WebGPUPlatform() override = default;
|
~WebGPUPlatform() override = default;
|
||||||
|
|
||||||
[[nodiscard]] int getOSVersion() const noexcept final { return 0; }
|
[[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; }
|
[[nodiscard]] wgpu::Instance& getInstance() noexcept { return mInstance; }
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,6 @@
|
|||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -66,7 +64,7 @@ public:
|
|||||||
|
|
||||||
// all commands buffers (Slices) written to this point are returned by waitForCommand(). This
|
// all commands buffers (Slices) written to this point are returned by waitForCommand(). This
|
||||||
// call blocks until the CircularBuffer has at least mRequiredSize bytes available.
|
// call blocks until the CircularBuffer has at least mRequiredSize bytes available.
|
||||||
void flush(std::function<void(void*, void*)> const& debugPrintHistogram = nullptr);
|
void flush();
|
||||||
|
|
||||||
// returns from waitForCommands() immediately.
|
// returns from waitForCommands() immediately.
|
||||||
void requestExit();
|
void requestExit();
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ namespace filament::backend {
|
|||||||
|
|
||||||
class CommandBase {
|
class CommandBase {
|
||||||
static constexpr size_t FILAMENT_OBJECT_ALIGNMENT = alignof(std::max_align_t);
|
static constexpr size_t FILAMENT_OBJECT_ALIGNMENT = alignof(std::max_align_t);
|
||||||
friend class CommandStream;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
using Execute = Dispatcher::Execute;
|
using Execute = Dispatcher::Execute;
|
||||||
@@ -169,8 +168,8 @@ struct CommandType<void (Driver::*)(ARGS...)> {
|
|||||||
|
|
||||||
class CustomCommand : public CommandBase {
|
class CustomCommand : public CommandBase {
|
||||||
std::function<void()> mCommand;
|
std::function<void()> mCommand;
|
||||||
public:
|
|
||||||
static void execute(Driver&, CommandBase* base, intptr_t* next);
|
static void execute(Driver&, CommandBase* base, intptr_t* next);
|
||||||
|
public:
|
||||||
CustomCommand(CustomCommand&& rhs) = default;
|
CustomCommand(CustomCommand&& rhs) = default;
|
||||||
|
|
||||||
explicit CustomCommand(std::function<void()> cmd)
|
explicit CustomCommand(std::function<void()> cmd)
|
||||||
@@ -180,12 +179,11 @@ public:
|
|||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
class NoopCommand : public CommandBase {
|
class NoopCommand : public CommandBase {
|
||||||
public:
|
|
||||||
intptr_t mNext;
|
intptr_t mNext;
|
||||||
static void execute(Driver&, CommandBase* self, intptr_t* next) noexcept {
|
static void execute(Driver&, CommandBase* self, intptr_t* next) noexcept {
|
||||||
*next = static_cast<NoopCommand*>(self)->mNext;
|
*next = static_cast<NoopCommand*>(self)->mNext;
|
||||||
}
|
}
|
||||||
|
public:
|
||||||
constexpr explicit NoopCommand(void* next) noexcept
|
constexpr explicit NoopCommand(void* next) noexcept
|
||||||
: CommandBase(execute), mNext(intptr_t((char *)next - (char *)this)) { }
|
: CommandBase(execute), mNext(intptr_t((char *)next - (char *)this)) { }
|
||||||
};
|
};
|
||||||
@@ -221,36 +219,6 @@ public:
|
|||||||
|
|
||||||
CircularBuffer const& getCircularBuffer() const noexcept { return mCurrentBuffer; }
|
CircularBuffer const& getCircularBuffer() const noexcept { return mCurrentBuffer; }
|
||||||
|
|
||||||
#if FILAMENT_DEBUG_COMMANDS_HISTOGRAM
|
|
||||||
using Execute = Dispatcher::Execute;
|
|
||||||
struct CommandInfo {
|
|
||||||
size_t size;
|
|
||||||
const char* name;
|
|
||||||
int index;
|
|
||||||
};
|
|
||||||
std::unordered_map<Execute, CommandInfo> mCommands;
|
|
||||||
|
|
||||||
void initializeLookup() {
|
|
||||||
int currentIndex = 0;
|
|
||||||
#define DECL_DRIVER_API_SYNCHRONOUS(RetType, methodName, paramsDecl, params)
|
|
||||||
#define DECL_DRIVER_API(methodName, paramsDecl, params) \
|
|
||||||
mCommands[mDispatcher.methodName##_] = { CommandBase::align(sizeof(COMMAND_TYPE(methodName))), \
|
|
||||||
#methodName, currentIndex++ };
|
|
||||||
#define DECL_DRIVER_API_RETURN(RetType, methodName, paramsDecl, params) \
|
|
||||||
mCommands[mDispatcher.methodName##_] = { \
|
|
||||||
CommandBase::align(sizeof(COMMAND_TYPE(methodName##R))), #methodName, currentIndex++ \
|
|
||||||
};
|
|
||||||
|
|
||||||
#include "private/backend/DriverAPI.inc"
|
|
||||||
|
|
||||||
mCommands[CustomCommand::execute] = { CommandBase::align(sizeof(CustomCommand)),
|
|
||||||
"CustomCommand", currentIndex++ };
|
|
||||||
|
|
||||||
// NoopCommands have variable size. We will handle them specially using their mNext pointer.
|
|
||||||
mCommands[NoopCommand::execute] = { 0, "NoopCommand", currentIndex++ };
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
#define DECL_DRIVER_API(methodName, paramsDecl, params) \
|
#define DECL_DRIVER_API(methodName, paramsDecl, params) \
|
||||||
inline void methodName(paramsDecl) noexcept { \
|
inline void methodName(paramsDecl) noexcept { \
|
||||||
@@ -295,13 +263,6 @@ public:
|
|||||||
|
|
||||||
void execute(void* buffer);
|
void execute(void* buffer);
|
||||||
|
|
||||||
#if FILAMENT_DEBUG_COMMANDS_HISTOGRAM
|
|
||||||
void debugIterateCommands(void* head, void* tail,
|
|
||||||
std::function<void(CommandInfo const& info)> const& callback);
|
|
||||||
|
|
||||||
void debugPrintHistogram(void* head, void* tail);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* queueCommand() allows to queue a lambda function as a command.
|
* queueCommand() allows to queue a lambda function as a command.
|
||||||
* This is much less efficient than using the Driver* API.
|
* This is much less efficient than using the Driver* API.
|
||||||
|
|||||||
@@ -48,11 +48,6 @@
|
|||||||
|
|
||||||
#define FILAMENT_DEBUG_COMMANDS FILAMENT_DEBUG_COMMANDS_NONE
|
#define FILAMENT_DEBUG_COMMANDS FILAMENT_DEBUG_COMMANDS_NONE
|
||||||
|
|
||||||
// Upon command stream overflow, print a histogram of commands
|
|
||||||
#ifndef FILAMENT_DEBUG_COMMANDS_HISTOGRAM
|
|
||||||
#define FILAMENT_DEBUG_COMMANDS_HISTOGRAM 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
class BufferDescriptor;
|
class BufferDescriptor;
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ bool CommandBufferQueue::isExitRequested() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void CommandBufferQueue::flush(std::function<void(void*, void*)> const& debugPrintHistogram) {
|
void CommandBufferQueue::flush() {
|
||||||
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
|
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
|
||||||
|
|
||||||
CircularBuffer& circularBuffer = mCircularBuffer;
|
CircularBuffer& circularBuffer = mCircularBuffer;
|
||||||
@@ -106,13 +106,6 @@ void CommandBufferQueue::flush(std::function<void(void*, void*)> const& debugPri
|
|||||||
std::unique_lock lock(mLock);
|
std::unique_lock lock(mLock);
|
||||||
|
|
||||||
// circular buffer is too small, we corrupted the stream
|
// circular buffer is too small, we corrupted the stream
|
||||||
#if FILAMENT_DEBUG_COMMANDS_HISTOGRAM
|
|
||||||
if (UTILS_VERY_UNLIKELY(used > mFreeSpace)) {
|
|
||||||
if (debugPrintHistogram) {
|
|
||||||
debugPrintHistogram(begin, end);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
FILAMENT_CHECK_POSTCONDITION(used <= mFreeSpace) <<
|
FILAMENT_CHECK_POSTCONDITION(used <= mFreeSpace) <<
|
||||||
"Backend CommandStream overflow. Commands are corrupted and unrecoverable.\n"
|
"Backend CommandStream overflow. Commands are corrupted and unrecoverable.\n"
|
||||||
"Please increase minCommandBufferSizeMB inside the Config passed to Engine::create.\n"
|
"Please increase minCommandBufferSizeMB inside the Config passed to Engine::create.\n"
|
||||||
|
|||||||
@@ -30,14 +30,9 @@
|
|||||||
#include <utils/ostream.h>
|
#include <utils/ostream.h>
|
||||||
#include <utils/sstream.h>
|
#include <utils/sstream.h>
|
||||||
|
|
||||||
#if FILAMENT_DEBUG_COMMANDS_HISTOGRAM
|
|
||||||
#include <algorithm>
|
|
||||||
#include <vector>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <string>
|
#include <string_view>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
@@ -88,10 +83,6 @@ CommandStream::CommandStream(Driver& driver, CircularBuffer& buffer) noexcept
|
|||||||
__system_property_get("debug.filament.perfcounters", property);
|
__system_property_get("debug.filament.perfcounters", property);
|
||||||
mUsePerformanceCounter = bool(atoi(property));
|
mUsePerformanceCounter = bool(atoi(property));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if FILAMENT_DEBUG_COMMANDS_HISTOGRAM
|
|
||||||
initializeLookup();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandStream::execute(void* buffer) {
|
void CommandStream::execute(void* buffer) {
|
||||||
@@ -135,71 +126,6 @@ void CommandStream::execute(void* buffer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if FILAMENT_DEBUG_COMMANDS_HISTOGRAM
|
|
||||||
void CommandStream::debugIterateCommands(void* head, void* tail,
|
|
||||||
std::function<void(CommandInfo const& info)> const& callback) {
|
|
||||||
CommandBase* UTILS_RESTRICT base = static_cast<CommandBase*>(head);
|
|
||||||
auto p = base;
|
|
||||||
while (UTILS_LIKELY(p)) {
|
|
||||||
if (p >= tail) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Execute e = p->mExecute;
|
|
||||||
|
|
||||||
if (e == NoopCommand::execute) {
|
|
||||||
NoopCommand* noop = static_cast<NoopCommand*>(p);
|
|
||||||
size_t size = noop->mNext;
|
|
||||||
int noopIndex = mCommands[NoopCommand::execute].index;
|
|
||||||
callback({ size, "NoopCommand", noopIndex });
|
|
||||||
p = reinterpret_cast<CommandBase*>(reinterpret_cast<char*>(p) + size);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto it = mCommands.find(e); it != mCommands.end()) {
|
|
||||||
size_t size = it->second.size;
|
|
||||||
callback(it->second);
|
|
||||||
p = reinterpret_cast<CommandBase*>(reinterpret_cast<char*>(p) + size);
|
|
||||||
} else {
|
|
||||||
LOG(ERROR) << "Cannot find command in lookup table";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CommandStream::debugPrintHistogram(void* head, void* tail) {
|
|
||||||
std::unordered_map<std::string_view, int> histogram;
|
|
||||||
std::unordered_map<int, int> index_histogram;
|
|
||||||
debugIterateCommands(head, tail, [&](CommandInfo const& info) {
|
|
||||||
histogram[std::string_view(info.name)]++;
|
|
||||||
index_histogram[info.index]++;
|
|
||||||
});
|
|
||||||
|
|
||||||
std::vector<std::pair<std::string_view, int>> sorted_histogram(histogram.begin(),
|
|
||||||
histogram.end());
|
|
||||||
std::sort(sorted_histogram.begin(), sorted_histogram.end(),
|
|
||||||
[](auto const& a, auto const& b) { return a.second > b.second; });
|
|
||||||
|
|
||||||
LOG(INFO) << "Command stream histogram:";
|
|
||||||
for (auto const& [name, count]: sorted_histogram) {
|
|
||||||
LOG(INFO) << name << ": " << count;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::pair<int, int>> sorted_index_histogram(index_histogram.begin(),
|
|
||||||
index_histogram.end());
|
|
||||||
std::sort(sorted_index_histogram.begin(), sorted_index_histogram.end(),
|
|
||||||
[](auto const& a, auto const& b) { return a.second > b.second; });
|
|
||||||
|
|
||||||
std::string short_histogram = "";
|
|
||||||
for (size_t i = 0, n = sorted_index_histogram.size(); i < n; ++i) {
|
|
||||||
short_histogram += std::to_string(sorted_index_histogram[i].first) + ":" +
|
|
||||||
std::to_string(sorted_index_histogram[i].second);
|
|
||||||
short_histogram += (i < n - 1) ? ";" : ".";
|
|
||||||
}
|
|
||||||
LOG(INFO) << "CS hist: " << short_histogram;
|
|
||||||
LOG(INFO) << "";
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void CommandStream::queueCommand(std::function<void()> command) {
|
void CommandStream::queueCommand(std::function<void()> command) {
|
||||||
new(allocateCommand(CustomCommand::align(sizeof(CustomCommand)))) CustomCommand(std::move(command));
|
new(allocateCommand(CustomCommand::align(sizeof(CustomCommand)))) CustomCommand(std::move(command));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,11 @@
|
|||||||
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
#include <pthread.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
namespace filament {
|
namespace filament {
|
||||||
|
|
||||||
using namespace utils;
|
using namespace utils;
|
||||||
@@ -105,8 +110,21 @@ JNIEnv* VirtualMachineEnv::getEnvironmentSlow() {
|
|||||||
FILAMENT_CHECK_PRECONDITION(mVirtualMachine)
|
FILAMENT_CHECK_PRECONDITION(mVirtualMachine)
|
||||||
<< "JNI_OnLoad() has not been called";
|
<< "JNI_OnLoad() has not been called";
|
||||||
|
|
||||||
#if defined(__ANDROID__)
|
#ifdef __ANDROID__
|
||||||
jint const result = mVirtualMachine->AttachCurrentThread(&mJniEnv, nullptr);
|
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
|
#else
|
||||||
jint const result = mVirtualMachine->AttachCurrentThread(reinterpret_cast<void**>(&mJniEnv), nullptr);
|
jint const result = mVirtualMachine->AttachCurrentThread(reinterpret_cast<void**>(&mJniEnv), nullptr);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class PlatformNoop final : public Platform {
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
int getOSVersion() const noexcept final { return 0; }
|
int getOSVersion() const noexcept final { return 0; }
|
||||||
|
utils::CString getDeviceInfo(DeviceInfoType, Driver*) const noexcept override { return {}; }
|
||||||
|
|
||||||
~PlatformNoop() noexcept override = default;
|
~PlatformNoop() noexcept override = default;
|
||||||
|
|
||||||
|
|||||||
@@ -341,7 +341,44 @@ void GLDescriptorSet::bind(
|
|||||||
if (arg.handle) {
|
if (arg.handle) {
|
||||||
GLTexture const* const t = handleAllocator.handle_cast<GLTexture*>(arg.handle);
|
GLTexture const* const t = handleAllocator.handle_cast<GLTexture*>(arg.handle);
|
||||||
gl.bindTexture(unit, t->gl.target, t->gl.id, t->gl.external);
|
gl.bindTexture(unit, t->gl.target, t->gl.id, t->gl.external);
|
||||||
SamplerParams const params = arg.params;
|
SamplerParams params = arg.params;
|
||||||
|
|
||||||
|
#if defined(__EMSCRIPTEN__)
|
||||||
|
// From https://registry.khronos.org/OpenGL-Refpages/es2.0/
|
||||||
|
// GLES 2.0 will draw mipmapped samplers as black if the following conditions are not met:
|
||||||
|
//
|
||||||
|
// "...if the width or height of a texture image are not powers of two and either the
|
||||||
|
// GL_TEXTURE_MIN_FILTER is set to one of the functions that requires mipmaps or the
|
||||||
|
// GL_TEXTURE_WRAP_S or GL_TEXTURE_WRAP_T is not set to GL_CLAMP_TO_EDGE, then the
|
||||||
|
// texture image unit will return (R, G, B, A) = (0, 0, 0, 1)."
|
||||||
|
//
|
||||||
|
// "If the texture has dimensions w × h , there are [floor(log2(max(w, h))) + 1] mipmap levels.
|
||||||
|
// Level 0 is the original texture; level [floor(log2(max(w, h)))] is the final 1 × 1 mipmap."
|
||||||
|
// "Suppose that a texture is accessed from a fragment shader or vertex shader and has set GL_TEXTURE_MIN_FILTER
|
||||||
|
// to one of the functions that requires mipmaps. If either the dimensions of the texture images currently
|
||||||
|
// defined (with previous calls to glTexImage2D, glCompressedTexImage2D, or glCopyTexImage2D) do not follow
|
||||||
|
// the proper sequence for mipmaps (described above), or there are fewer texture images defined than are needed,
|
||||||
|
// or the set of texture images were defined with different formats or types, then the texture image unit
|
||||||
|
// will return (R, G, B, A) = (0, 0, 0, 1)."
|
||||||
|
//
|
||||||
|
// So we force the sampler to a valid state in those cases.
|
||||||
|
auto isPowerOfTwo = [](uint32_t x) -> bool { return (x & (x - 1)) == 0 && x > 0; };
|
||||||
|
bool textureIsPowerOfTwo = isPowerOfTwo(t->width) && isPowerOfTwo(t->height);
|
||||||
|
|
||||||
|
uint32_t requiredMipLevels = (std::floor(std::log2(std::max(t->width, std::max(t->height, t->depth)))) + 1);
|
||||||
|
bool textureHasRequiredMipLevels = t->levels == requiredMipLevels;
|
||||||
|
|
||||||
|
bool samplerCanBeInvalid = params.filterMin > SamplerMinFilter::LINEAR;
|
||||||
|
if (samplerCanBeInvalid && (!textureIsPowerOfTwo || !textureHasRequiredMipLevels)) {
|
||||||
|
if (params.filterMin == SamplerMinFilter::NEAREST_MIPMAP_NEAREST
|
||||||
|
|| params.filterMin == SamplerMinFilter::LINEAR_MIPMAP_NEAREST) {
|
||||||
|
params.filterMin = SamplerMinFilter::NEAREST;
|
||||||
|
} else {
|
||||||
|
params.filterMin = SamplerMinFilter::LINEAR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
glTexParameteri(t->gl.target, GL_TEXTURE_MIN_FILTER,
|
glTexParameteri(t->gl.target, GL_TEXTURE_MIN_FILTER,
|
||||||
(GLint)GLUtils::getTextureFilter(params.filterMin));
|
(GLint)GLUtils::getTextureFilter(params.filterMin));
|
||||||
glTexParameteri(t->gl.target, GL_TEXTURE_MAG_FILTER,
|
glTexParameteri(t->gl.target, GL_TEXTURE_MAG_FILTER,
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ public:
|
|||||||
GLenum getIndicesType() const noexcept {
|
GLenum getIndicesType() const noexcept {
|
||||||
return indicesType;
|
return indicesType;
|
||||||
}
|
}
|
||||||
} gl;
|
};
|
||||||
|
|
||||||
static bool queryOpenGLVersion(GLint* major, GLint* minor) noexcept;
|
static bool queryOpenGLVersion(GLint* major, GLint* minor) noexcept;
|
||||||
|
|
||||||
|
|||||||
@@ -716,7 +716,11 @@ Handle<HwSwapChain> OpenGLDriver::createSwapChainHeadlessS() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwTimerQuery> OpenGLDriver::createTimerQueryS() noexcept {
|
Handle<HwTimerQuery> OpenGLDriver::createTimerQueryS() noexcept {
|
||||||
return initHandle<GLTimerQuery>();
|
Handle<HwTimerQuery> tqh = initHandle<GLTimerQuery>();
|
||||||
|
// The state must be constructed here, as a synchronous call to getTimerQueryValue might happen
|
||||||
|
// before createTimerQueryR is executed on the backend thread.
|
||||||
|
handle_cast<GLTimerQuery*>(tqh)->state = std::make_shared<GLTimerQuery::State>();
|
||||||
|
return tqh;
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwDescriptorSetLayout> OpenGLDriver::createDescriptorSetLayoutS() noexcept {
|
Handle<HwDescriptorSetLayout> OpenGLDriver::createDescriptorSetLayoutS() noexcept {
|
||||||
|
|||||||
@@ -250,6 +250,10 @@ private:
|
|||||||
return utils::CString{ mContext.state.renderer };
|
return utils::CString{ mContext.state.renderer };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
utils::CString getVersionString() const noexcept override {
|
||||||
|
return utils::CString{ mContext.state.version };
|
||||||
|
}
|
||||||
|
|
||||||
JobQueue* getJobQueue() const noexcept { return mJobQueue.get(); }
|
JobQueue* getJobQueue() const noexcept { return mJobQueue.get(); }
|
||||||
JobWorker* getJobWorker() const noexcept { return mJobWorker.get(); }
|
JobWorker* getJobWorker() const noexcept { return mJobWorker.get(); }
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ protected:
|
|||||||
public:
|
public:
|
||||||
virtual utils::CString getVendorString() const noexcept = 0;
|
virtual utils::CString getVendorString() const noexcept = 0;
|
||||||
virtual utils::CString getRendererString() const noexcept = 0;
|
virtual utils::CString getRendererString() const noexcept = 0;
|
||||||
|
virtual utils::CString getVersionString() const noexcept = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // filament::backend
|
} // filament::backend
|
||||||
|
|||||||
@@ -45,6 +45,21 @@ Driver* OpenGLPlatform::createDefaultDriver(OpenGLPlatform* platform,
|
|||||||
|
|
||||||
OpenGLPlatform::~OpenGLPlatform() noexcept = default;
|
OpenGLPlatform::~OpenGLPlatform() noexcept = default;
|
||||||
|
|
||||||
|
utils::CString OpenGLPlatform::getDeviceInfo(DeviceInfoType infoType,
|
||||||
|
Driver* driver) const noexcept {
|
||||||
|
switch (infoType) {
|
||||||
|
case DeviceInfoType::OPENGL_RENDERER:
|
||||||
|
return getRendererString(driver);
|
||||||
|
case DeviceInfoType::OPENGL_VENDOR:
|
||||||
|
return getVendorString(driver);
|
||||||
|
case DeviceInfoType::OPENGL_VERSION:
|
||||||
|
return getVersionString(driver);
|
||||||
|
default:
|
||||||
|
FILAMENT_CHECK_POSTCONDITION(false) << "Unsupported DeviceInfoType for OpenGLPlatform";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
utils::CString OpenGLPlatform::getVendorString(Driver const* driver) {
|
utils::CString OpenGLPlatform::getVendorString(Driver const* driver) {
|
||||||
auto const p = static_cast<OpenGLDriverBase const*>(driver);
|
auto const p = static_cast<OpenGLDriverBase const*>(driver);
|
||||||
#if UTILS_HAS_RTTI
|
#if UTILS_HAS_RTTI
|
||||||
@@ -63,6 +78,15 @@ utils::CString OpenGLPlatform::getRendererString(Driver const* driver) {
|
|||||||
return p->getRendererString();
|
return p->getRendererString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
utils::CString OpenGLPlatform::getVersionString(Driver const* driver) {
|
||||||
|
auto const p = static_cast<OpenGLDriverBase const*>(driver);
|
||||||
|
#if UTILS_HAS_RTTI
|
||||||
|
FILAMENT_CHECK_POSTCONDITION(dynamic_cast<OpenGLDriverBase const*>(driver))
|
||||||
|
<< "Driver* has not been allocated with OpenGLPlatform";
|
||||||
|
#endif
|
||||||
|
return p->getVersionString();
|
||||||
|
}
|
||||||
|
|
||||||
void OpenGLPlatform::makeCurrent(SwapChain* drawSwapChain, SwapChain* readSwapChain,
|
void OpenGLPlatform::makeCurrent(SwapChain* drawSwapChain, SwapChain* readSwapChain,
|
||||||
utils::Invocable<void()>, utils::Invocable<void(size_t)>) {
|
utils::Invocable<void()>, utils::Invocable<void(size_t)>) {
|
||||||
makeCurrent(getCurrentContextType(), drawSwapChain, readSwapChain);
|
makeCurrent(getCurrentContextType(), drawSwapChain, readSwapChain);
|
||||||
|
|||||||
@@ -88,15 +88,14 @@ TimerQueryFactoryInterface::~TimerQueryFactoryInterface() = default;
|
|||||||
// This is a backend synchronous call
|
// This is a backend synchronous call
|
||||||
TimerQueryResult TimerQueryFactoryInterface::getTimerQueryValue(
|
TimerQueryResult TimerQueryFactoryInterface::getTimerQueryValue(
|
||||||
GLTimerQuery* tq, uint64_t* elapsedTime) noexcept {
|
GLTimerQuery* tq, uint64_t* elapsedTime) noexcept {
|
||||||
if (UTILS_LIKELY(tq->state)) {
|
assert_invariant(tq->state);
|
||||||
int64_t const elapsed = tq->state->elapsed.load(std::memory_order_relaxed);
|
|
||||||
if (elapsed > 0) {
|
int64_t const elapsed = tq->state->elapsed.load(std::memory_order_relaxed);
|
||||||
*elapsedTime = elapsed;
|
if (elapsed > 0) {
|
||||||
return TimerQueryResult::AVAILABLE;
|
*elapsedTime = elapsed;
|
||||||
}
|
return TimerQueryResult::AVAILABLE;
|
||||||
return TimerQueryResult(elapsed);
|
|
||||||
}
|
}
|
||||||
return TimerQueryResult::ERROR;
|
return TimerQueryResult(elapsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
@@ -110,9 +109,8 @@ TimerQueryNativeFactory::TimerQueryNativeFactory(OpenGLContext& context)
|
|||||||
TimerQueryNativeFactory::~TimerQueryNativeFactory() = default;
|
TimerQueryNativeFactory::~TimerQueryNativeFactory() = default;
|
||||||
|
|
||||||
void TimerQueryNativeFactory::createTimerQuery(GLTimerQuery* tq) {
|
void TimerQueryNativeFactory::createTimerQuery(GLTimerQuery* tq) {
|
||||||
assert_invariant(!tq->state);
|
assert_invariant(tq->state);
|
||||||
|
|
||||||
tq->state = std::make_shared<GLTimerQuery::State>();
|
|
||||||
mContext.procs.genQueries(1u, &tq->state->gl.query);
|
mContext.procs.genQueries(1u, &tq->state->gl.query);
|
||||||
CHECK_GL_ERROR()
|
CHECK_GL_ERROR()
|
||||||
}
|
}
|
||||||
@@ -181,8 +179,7 @@ TimerQueryFenceFactory::~TimerQueryFenceFactory() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TimerQueryFenceFactory::createTimerQuery(GLTimerQuery* tq) {
|
void TimerQueryFenceFactory::createTimerQuery(GLTimerQuery* tq) {
|
||||||
assert_invariant(!tq->state);
|
assert_invariant(tq->state);
|
||||||
tq->state = std::make_shared<GLTimerQuery::State>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimerQueryFenceFactory::destroyTimerQuery(GLTimerQuery* tq) {
|
void TimerQueryFenceFactory::destroyTimerQuery(GLTimerQuery* tq) {
|
||||||
@@ -238,8 +235,7 @@ TimerQueryFallbackFactory::TimerQueryFallbackFactory() = default;
|
|||||||
TimerQueryFallbackFactory::~TimerQueryFallbackFactory() = default;
|
TimerQueryFallbackFactory::~TimerQueryFallbackFactory() = default;
|
||||||
|
|
||||||
void TimerQueryFallbackFactory::createTimerQuery(GLTimerQuery* tq) {
|
void TimerQueryFallbackFactory::createTimerQuery(GLTimerQuery* tq) {
|
||||||
assert_invariant(!tq->state);
|
assert_invariant(tq->state);
|
||||||
tq->state = std::make_shared<GLTimerQuery::State>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimerQueryFallbackFactory::destroyTimerQuery(GLTimerQuery* tq) {
|
void TimerQueryFallbackFactory::destroyTimerQuery(GLTimerQuery* tq) {
|
||||||
|
|||||||
@@ -117,6 +117,13 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
|
|||||||
cond.wait(l, [this] { return signaled; });
|
cond.wait(l, [this] { return signaled; });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used in THREAD_POOL mode. Returns true if the token is signaled, meaning it's ready to be
|
||||||
|
// used.
|
||||||
|
bool isReady() const noexcept {
|
||||||
|
std::unique_lock const l(lock);
|
||||||
|
return signaled;
|
||||||
|
}
|
||||||
|
|
||||||
// This is invoked upon token completion, which occurs after a successful `gl.program`
|
// This is invoked upon token completion, which occurs after a successful `gl.program`
|
||||||
// population or upon cancellation. In either scenario, the callback handle must be submitted
|
// population or upon cancellation. In either scenario, the callback handle must be submitted
|
||||||
// to notify the caller that resource loading has concluded.
|
// to notify the caller that resource loading has concluded.
|
||||||
@@ -524,8 +531,15 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ShaderCompilerService::ensureTokenIsReady(program_token_t const& token) {
|
void ShaderCompilerService::ensureTokenIsReady(program_token_t const& token) {
|
||||||
if (token->gl.program) {
|
if (mMode == Mode::THREAD_POOL) {
|
||||||
return;// It's ready.
|
// Check if `token->gl.program` is populated
|
||||||
|
if (token->isReady()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (token->gl.program) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (mMode) {
|
switch (mMode) {
|
||||||
@@ -935,6 +949,7 @@ void ShaderCompilerService::cancelPendingSynchronousProgram(program_token_t cons
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
token->retrievedFromBlobCache = true;
|
token->retrievedFromBlobCache = true;
|
||||||
|
token->signal(); // notify that `token->gl.program` is ready to use
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
|
|
||||||
#include <utils/Invocable.h>
|
#include <utils/Invocable.h>
|
||||||
#include <utils/Logger.h>
|
#include <utils/Logger.h>
|
||||||
|
#include <utils/Panic.h>
|
||||||
#include <utils/debug.h>
|
#include <utils/debug.h>
|
||||||
#include <utils/ostream.h>
|
#include <utils/ostream.h>
|
||||||
|
|
||||||
@@ -119,31 +120,41 @@ bool PlatformEGL::isOpenGL() const noexcept {
|
|||||||
|
|
||||||
PlatformEGL::ExternalImageEGL::~ExternalImageEGL() = default;
|
PlatformEGL::ExternalImageEGL::~ExternalImageEGL() = default;
|
||||||
|
|
||||||
|
void PlatformEGL::setEglDisplay(EGLDisplay display) noexcept {
|
||||||
|
FILAMENT_CHECK_PRECONDITION(mEGLDisplay == EGL_NO_DISPLAY)
|
||||||
|
<< "EGL Display has already been set.";
|
||||||
|
FILAMENT_CHECK_PRECONDITION(display != EGL_NO_DISPLAY)
|
||||||
|
<< "Must specify a valid EGL Display.";
|
||||||
|
mEGLDisplay = display;
|
||||||
|
}
|
||||||
|
|
||||||
Driver* PlatformEGL::createDriver(void* sharedContext, const DriverConfig& driverConfig) {
|
Driver* PlatformEGL::createDriver(void* sharedContext, const DriverConfig& driverConfig) {
|
||||||
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
if (mEGLDisplay == EGL_NO_DISPLAY) {
|
||||||
assert_invariant(mEGLDisplay != EGL_NO_DISPLAY);
|
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||||
|
assert_invariant(mEGLDisplay != EGL_NO_DISPLAY);
|
||||||
|
|
||||||
EGLint major, minor;
|
EGLint major, minor;
|
||||||
EGLBoolean initialized = eglInitialize(mEGLDisplay, &major, &minor);
|
EGLBoolean initialized = eglInitialize(mEGLDisplay, &major, &minor);
|
||||||
|
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
EGLDeviceEXT eglDevice;
|
EGLDeviceEXT eglDevice;
|
||||||
EGLint numDevices;
|
EGLint numDevices;
|
||||||
PFNEGLQUERYDEVICESEXTPROC const eglQueryDevicesEXT =
|
PFNEGLQUERYDEVICESEXTPROC const eglQueryDevicesEXT =
|
||||||
PFNEGLQUERYDEVICESEXTPROC(eglGetProcAddress("eglQueryDevicesEXT"));
|
PFNEGLQUERYDEVICESEXTPROC(eglGetProcAddress("eglQueryDevicesEXT"));
|
||||||
if (eglQueryDevicesEXT != nullptr) {
|
if (eglQueryDevicesEXT != nullptr) {
|
||||||
eglQueryDevicesEXT(1, &eglDevice, &numDevices);
|
eglQueryDevicesEXT(1, &eglDevice, &numDevices);
|
||||||
if(auto* getPlatformDisplay = reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
|
if(auto* getPlatformDisplay = reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
|
||||||
eglGetProcAddress("eglGetPlatformDisplay"))) {
|
eglGetProcAddress("eglGetPlatformDisplay"))) {
|
||||||
mEGLDisplay = getPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, eglDevice, nullptr);
|
mEGLDisplay = getPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, eglDevice, nullptr);
|
||||||
initialized = eglInitialize(mEGLDisplay, &major, &minor);
|
initialized = eglInitialize(mEGLDisplay, &major, &minor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (UTILS_UNLIKELY(!initialized)) {
|
if (UTILS_UNLIKELY(!initialized)) {
|
||||||
LOG(ERROR) << "eglInitialize failed";
|
LOG(ERROR) << "eglInitialize failed";
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(FILAMENT_IMPORT_ENTRY_POINTS)
|
#if defined(FILAMENT_IMPORT_ENTRY_POINTS)
|
||||||
@@ -706,10 +717,11 @@ bool PlatformEGL::setExternalImage(ExternalImageHandleRef externalImage,
|
|||||||
// -----------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
void PlatformEGL::initializeGlExtensions() noexcept {
|
void PlatformEGL::initializeGlExtensions() noexcept {
|
||||||
// We're guaranteed to be on an ES platform, since we're using EGL
|
|
||||||
const char* const extensions = (const char*)glGetString(GL_EXTENSIONS);
|
const char* const extensions = (const char*)glGetString(GL_EXTENSIONS);
|
||||||
GLUtils::unordered_string_set const glExtensions = GLUtils::split(extensions);
|
if (extensions) {
|
||||||
ext.gl.OES_EGL_image_external_essl3 = glExtensions.has("GL_OES_EGL_image_external_essl3");
|
GLUtils::unordered_string_set const glExtensions = GLUtils::split(extensions);
|
||||||
|
ext.gl.OES_EGL_image_external_essl3 = glExtensions.has("GL_OES_EGL_image_external_essl3");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EGLContext PlatformEGL::getContextForType(ContextType const type) const noexcept {
|
EGLContext PlatformEGL::getContextForType(ContextType const type) const noexcept {
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ using namespace bluevk;
|
|||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
|
std::shared_ptr<VulkanCmdFence> VulkanCmdFence::completed() noexcept {
|
||||||
|
auto cmdFence = std::make_shared<VulkanCmdFence>(VK_NULL_HANDLE);
|
||||||
|
cmdFence->mStatus = VK_SUCCESS;
|
||||||
|
return cmdFence;
|
||||||
|
}
|
||||||
|
|
||||||
FenceStatus VulkanCmdFence::wait(VkDevice device, uint64_t const timeout,
|
FenceStatus VulkanCmdFence::wait(VkDevice device, uint64_t const timeout,
|
||||||
std::chrono::steady_clock::time_point const until) {
|
std::chrono::steady_clock::time_point const until) {
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,13 @@ struct VulkanCmdFence {
|
|||||||
explicit VulkanCmdFence(VkFence fence) : mFence(fence) { }
|
explicit VulkanCmdFence(VkFence fence) : mFence(fence) { }
|
||||||
~VulkanCmdFence() = default;
|
~VulkanCmdFence() = default;
|
||||||
|
|
||||||
|
// Creates a VulkanCmdFence with its status set to VK_SUCCESS. It holds
|
||||||
|
// a null handle; it is assumed that any user of this object will avoid
|
||||||
|
// using the fence handle directly if getStatus() returns VK_SUCCESS, as
|
||||||
|
// in that case, it's likely the fence is being reused for other passes,
|
||||||
|
// and is not in the expected state anyway.
|
||||||
|
static std::shared_ptr<VulkanCmdFence> completed() noexcept;
|
||||||
|
|
||||||
void setStatus(VkResult const value) {
|
void setStatus(VkResult const value) {
|
||||||
std::lock_guard const l(mLock);
|
std::lock_guard const l(mLock);
|
||||||
mStatus = value;
|
mStatus = value;
|
||||||
|
|||||||
@@ -106,6 +106,12 @@ void VulkanBufferCache::gc() noexcept {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t totalBuffers = 0;
|
||||||
|
for (auto& bufferPool : mGpuBufferPools) {
|
||||||
|
totalBuffers += bufferPool.size();
|
||||||
|
}
|
||||||
|
FVK_LOGD << "VulkanBufferCache sizes: buffers=" << totalBuffers;
|
||||||
|
|
||||||
FVK_SYSTRACE_END();
|
FVK_SYSTRACE_END();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,6 +163,10 @@ VulkanGpuBuffer const* VulkanBufferCache::allocate(VulkanBufferBinding binding,
|
|||||||
UTILS_UNUSED_IN_RELEASE VkResult result = vmaCreateBuffer(mAllocator, &bufferInfo, &allocInfo,
|
UTILS_UNUSED_IN_RELEASE VkResult result = vmaCreateBuffer(mAllocator, &bufferInfo, &allocInfo,
|
||||||
&gpuBuffer->vkbuffer, &gpuBuffer->vmaAllocation, &gpuBuffer->allocationInfo);
|
&gpuBuffer->vkbuffer, &gpuBuffer->vmaAllocation, &gpuBuffer->allocationInfo);
|
||||||
|
|
||||||
|
FVK_LOGD << "VulkanBufferCache - allocated a vkBuffer " << gpuBuffer->vkbuffer
|
||||||
|
<< " of size " << numBytes << " and binding = " << static_cast<int>(binding)
|
||||||
|
<< " result=" << (int)result;
|
||||||
|
|
||||||
#if FVK_ENABLED(FVK_DEBUG_VULKAN_BUFFER_CACHE)
|
#if FVK_ENABLED(FVK_DEBUG_VULKAN_BUFFER_CACHE)
|
||||||
if (result != VK_SUCCESS) {
|
if (result != VK_SUCCESS) {
|
||||||
FVK_LOGE << "VulkanBufferCache - failed to allocate a new vkBuffer of size " << numBytes
|
FVK_LOGE << "VulkanBufferCache - failed to allocate a new vkBuffer of size " << numBytes
|
||||||
@@ -173,6 +183,7 @@ VulkanGpuBuffer const* VulkanBufferCache::allocate(VulkanBufferBinding binding,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanBufferCache::destroy(VulkanGpuBuffer const* gpuBuffer) noexcept {
|
void VulkanBufferCache::destroy(VulkanGpuBuffer const* gpuBuffer) noexcept {
|
||||||
|
FVK_LOGD << "VulkanBufferCache - Destroying vkBuffer " << gpuBuffer->vkbuffer;
|
||||||
vmaDestroyBuffer(mAllocator, gpuBuffer->vkbuffer, gpuBuffer->vmaAllocation);
|
vmaDestroyBuffer(mAllocator, gpuBuffer->vkbuffer, gpuBuffer->vmaAllocation);
|
||||||
delete gpuBuffer;
|
delete gpuBuffer;
|
||||||
gpuBuffer = nullptr;
|
gpuBuffer = nullptr;
|
||||||
|
|||||||
@@ -194,6 +194,7 @@ void VulkanCommandBuffer::begin() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fvkmemory::resource_ptr<VulkanSemaphore> VulkanCommandBuffer::submit() {
|
fvkmemory::resource_ptr<VulkanSemaphore> VulkanCommandBuffer::submit() {
|
||||||
|
FVK_LOGD << "Submitting VulkanCommandBuffer: " << mBuffer << " (age=" << age() << ")";
|
||||||
while (mMarkerCount > 0) {
|
while (mMarkerCount > 0) {
|
||||||
popMarker();
|
popMarker();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -277,7 +277,11 @@ private:
|
|||||||
fvkmemory::resource_ptr<VulkanSemaphore> mLastSubmit;
|
fvkmemory::resource_ptr<VulkanSemaphore> mLastSubmit;
|
||||||
|
|
||||||
VkFence mLastFence = VK_NULL_HANDLE;
|
VkFence mLastFence = VK_NULL_HANDLE;
|
||||||
std::shared_ptr<VulkanCmdFence> mLastFenceStatus;
|
// Start out with a completed fence, because if no commands have
|
||||||
|
// been queued or submited, then by definition, all pending work
|
||||||
|
// is complete.
|
||||||
|
std::shared_ptr<VulkanCmdFence> mLastFenceStatus =
|
||||||
|
VulkanCmdFence::completed();
|
||||||
|
|
||||||
VkPipelineStageFlags mInjectedDependencyWaitStage = 0;
|
VkPipelineStageFlags mInjectedDependencyWaitStage = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ VK_DEFINE_HANDLE(VmaPool)
|
|||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
struct VulkanCommandBuffer;
|
struct VulkanCommandBuffer;
|
||||||
|
struct VulkanRenderPass;
|
||||||
struct VulkanRenderTarget;
|
struct VulkanRenderTarget;
|
||||||
struct VulkanSwapChain;
|
struct VulkanSwapChain;
|
||||||
struct VulkanTexture;
|
struct VulkanTexture;
|
||||||
@@ -58,11 +59,11 @@ struct VulkanAttachment {
|
|||||||
VkImageSubresourceRange getSubresourceRange() const;
|
VkImageSubresourceRange getSubresourceRange() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VulkanRenderPass {
|
struct VulkanRenderPassContext {
|
||||||
// Between the begin and end command render pass we cache the command buffer
|
// Between the begin and end command render pass we cache the command buffer
|
||||||
VulkanCommandBuffer* commandBuffer;
|
VulkanCommandBuffer* commandBuffer;
|
||||||
fvkmemory::resource_ptr<VulkanRenderTarget> renderTarget;
|
fvkmemory::resource_ptr<VulkanRenderTarget> renderTarget;
|
||||||
VkRenderPass renderPass;
|
fvkmemory::resource_ptr<VulkanRenderPass> renderPass;
|
||||||
RenderPassParams params;
|
RenderPassParams params;
|
||||||
int currentSubpass;
|
int currentSubpass;
|
||||||
};
|
};
|
||||||
@@ -136,6 +137,14 @@ public:
|
|||||||
return mFenceExportFlags;
|
return mFenceExportFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline const char* getPhysicalDeviceName() const noexcept {
|
||||||
|
return mPhysicalDeviceProperties.properties.deviceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const char* getDriverName() const noexcept { return mDriverProperties.driverName; }
|
||||||
|
|
||||||
|
inline const char* getDriverInfo() const noexcept { return mDriverProperties.driverInfo; }
|
||||||
|
|
||||||
inline bool isImageCubeArraySupported() const noexcept {
|
inline bool isImageCubeArraySupported() const noexcept {
|
||||||
return mPhysicalDeviceFeatures.features.imageCubeArray == VK_TRUE;
|
return mPhysicalDeviceFeatures.features.imageCubeArray == VK_TRUE;
|
||||||
}
|
}
|
||||||
@@ -211,11 +220,16 @@ public:
|
|||||||
return mGlobalPrioritySupported;
|
return mGlobalPrioritySupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool isDriverPropertiesSupported() const noexcept { return mDriverPropertiesSupported; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
|
VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
|
||||||
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
|
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
|
||||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
|
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
|
||||||
};
|
};
|
||||||
|
VkPhysicalDeviceDriverProperties mDriverProperties = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES,
|
||||||
|
};
|
||||||
VkPhysicalDeviceVulkan11Features mPhysicalDeviceVk11Features = {
|
VkPhysicalDeviceVulkan11Features mPhysicalDeviceVk11Features = {
|
||||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES,
|
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES,
|
||||||
};
|
};
|
||||||
@@ -245,6 +259,7 @@ private:
|
|||||||
bool mProtectedMemorySupported = false;
|
bool mProtectedMemorySupported = false;
|
||||||
bool mVertexInputDynamicStateSupported = false;
|
bool mVertexInputDynamicStateSupported = false;
|
||||||
bool mGlobalPrioritySupported = false;
|
bool mGlobalPrioritySupported = false;
|
||||||
|
bool mDriverPropertiesSupported = false;
|
||||||
|
|
||||||
// These are options that can be enabled or disabled at an application level.
|
// These are options that can be enabled or disabled at an application level.
|
||||||
bool mAsyncPipelineCachePrewarmingEnabled = false;
|
bool mAsyncPipelineCachePrewarmingEnabled = false;
|
||||||
|
|||||||
@@ -355,35 +355,8 @@ void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescri
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||||
uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler,
|
uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture,
|
||||||
VkDescriptorSetLayout externalSamplerLayout) noexcept {
|
VkSampler sampler, VkDescriptorSetLayout externalSamplerLayout) noexcept {
|
||||||
|
|
||||||
// We have to update a bound set for two use cases
|
|
||||||
// - streaming API (a changing feed of AHardwareBuffer)
|
|
||||||
// - external samplers - potential changing of dataspace per-frame
|
|
||||||
|
|
||||||
// TODO: Fix the stream flow case base on the above comment!!
|
|
||||||
if (set->isAnExternalSamplerBound) {
|
|
||||||
auto layout = set->getLayout();
|
|
||||||
// Build a new descriptor set from the new layout
|
|
||||||
VkDescriptorSetLayout const genLayout = set->boundLayout;
|
|
||||||
VkDescriptorSet const newSet = getVkSet(layout->count, genLayout);
|
|
||||||
Bitmask const ubo = layout->bitmask.ubo | layout->bitmask.dynamicUbo;
|
|
||||||
Bitmask samplers = layout->bitmask.sampler;
|
|
||||||
samplers.unset(binding);
|
|
||||||
|
|
||||||
// Each bitmask denotes a binding index, and separated into two stages - vertex and buffer
|
|
||||||
// We fold the two stages into just the lower half of the bits to denote a combined set of
|
|
||||||
// bindings.
|
|
||||||
Bitmask const copyBindings = foldBitsInHalf(ubo | samplers);
|
|
||||||
VkDescriptorSet const srcSet = set->getVkSet();
|
|
||||||
copySet(srcSet, newSet, copyBindings);
|
|
||||||
set->addNewSet(newSet,
|
|
||||||
[this, layoutCount = layout->count, genLayout, newSet](VulkanDescriptorSet*) {
|
|
||||||
this->manualRecycle(layoutCount, genLayout, newSet);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
VkDescriptorSet const vkset = set->getVkSet();
|
VkDescriptorSet const vkset = set->getVkSet();
|
||||||
VkImageSubresourceRange range = texture->getPrimaryViewRange();
|
VkImageSubresourceRange range = texture->getPrimaryViewRange();
|
||||||
VkImageViewType const expectedType = texture->getViewType();
|
VkImageViewType const expectedType = texture->getViewType();
|
||||||
@@ -432,6 +405,28 @@ fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetCache::createSet
|
|||||||
return set;
|
return set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VulkanDescriptorSetCache::cloneSet(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||||
|
fvkutils::SamplerBitmask samplerMask) noexcept {
|
||||||
|
auto const& layout = set->getLayout();
|
||||||
|
// Build a new descriptor set from layout
|
||||||
|
VkDescriptorSetLayout const genLayout = set->boundLayout;
|
||||||
|
VkDescriptorSet const newSet = getVkSet(layout->count, genLayout);
|
||||||
|
|
||||||
|
// Each bitmask denotes a binding index, and separated into two stages - vertex and buffer
|
||||||
|
// We fold the two stages into just the lower half of the bits to denote a combined set of
|
||||||
|
// bindings.
|
||||||
|
Bitmask const ubo = layout->bitmask.ubo | layout->bitmask.dynamicUbo;
|
||||||
|
// Don't copy the samplers in the mask
|
||||||
|
Bitmask samplers = layout->bitmask.sampler ^ samplerMask;
|
||||||
|
Bitmask const copyBindings = foldBitsInHalf(ubo | samplers);
|
||||||
|
|
||||||
|
VkDescriptorSet const srcSet = set->getVkSet();
|
||||||
|
copySet(srcSet, newSet, copyBindings);
|
||||||
|
set->addNewSet(newSet,
|
||||||
|
[this, layoutCount = layout->count, genLayout, newSet](
|
||||||
|
VulkanDescriptorSet*) { this->manualRecycle(layoutCount, genLayout, newSet); });
|
||||||
|
}
|
||||||
|
|
||||||
VkDescriptorSet VulkanDescriptorSetCache::getVkSet(DescriptorCount const& count,
|
VkDescriptorSet VulkanDescriptorSetCache::getVkSet(DescriptorCount const& count,
|
||||||
VkDescriptorSetLayout vklayout) {
|
VkDescriptorSetLayout vklayout) {
|
||||||
return mDescriptorPool->obtainSet(count, vklayout);
|
return mDescriptorPool->obtainSet(count, vklayout);
|
||||||
|
|||||||
@@ -76,6 +76,11 @@ public:
|
|||||||
fvkmemory::resource_ptr<VulkanDescriptorSet> createSet(Handle<HwDescriptorSet> handle,
|
fvkmemory::resource_ptr<VulkanDescriptorSet> createSet(Handle<HwDescriptorSet> handle,
|
||||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
||||||
|
|
||||||
|
// Create and set as current a new VkDescriptorSet using the `set` currently bound layout and
|
||||||
|
// copy all the bindings and ignoring the samplers bindings in the `samplerMask`.
|
||||||
|
void cloneSet(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||||
|
fvkutils::SamplerBitmask samplerMask) noexcept;
|
||||||
|
|
||||||
// This method is meant to be used with external samplers
|
// This method is meant to be used with external samplers
|
||||||
VkDescriptorSet getVkSet(DescriptorCount const& count, VkDescriptorSetLayout vklayout);
|
VkDescriptorSet getVkSet(DescriptorCount const& count, VkDescriptorSetLayout vklayout);
|
||||||
|
|
||||||
|
|||||||
@@ -1913,9 +1913,6 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
|||||||
|
|
||||||
auto rt = resource_ptr<VulkanRenderTarget>::cast(&mResourceManager, rth);
|
auto rt = resource_ptr<VulkanRenderTarget>::cast(&mResourceManager, rth);
|
||||||
|
|
||||||
VulkanCommandBuffer* commandBuffer = rt->isProtected() ?
|
|
||||||
&mCommands.getProtected() : &mCommands.get();
|
|
||||||
|
|
||||||
// Filament has the expectation that the contents of the swap chain are not preserved on the
|
// Filament has the expectation that the contents of the swap chain are not preserved on the
|
||||||
// first render pass. Note however that its contents are often preserved on subsequent render
|
// first render pass. Note however that its contents are often preserved on subsequent render
|
||||||
// passes, due to multiple views.
|
// passes, due to multiple views.
|
||||||
@@ -1930,6 +1927,11 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note that this needs to come after the acquireNextswapchainImage() above because that path
|
||||||
|
// might flush the current command buffer.
|
||||||
|
VulkanCommandBuffer* commandBuffer =
|
||||||
|
rt->isProtected() ? &mCommands.getProtected() : &mCommands.get();
|
||||||
|
|
||||||
// Note that retrieving the extent must come after the acquireNextSwapchainImage() above;
|
// Note that retrieving the extent must come after the acquireNextSwapchainImage() above;
|
||||||
// otherwise it might be 0.
|
// otherwise it might be 0.
|
||||||
VkExtent2D const extent = rt->getExtent();
|
VkExtent2D const extent = rt->getExtent();
|
||||||
@@ -1975,17 +1977,19 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
|||||||
rpkey.initialDepthLayout = currentDepthLayout;
|
rpkey.initialDepthLayout = currentDepthLayout;
|
||||||
rpkey.subpassMask = uint8_t(params.subpassMask);
|
rpkey.subpassMask = uint8_t(params.subpassMask);
|
||||||
|
|
||||||
VkRenderPass renderPass = mFramebufferCache.getRenderPass(rpkey);
|
fvkmemory::resource_ptr<VulkanRenderPass> renderPass =
|
||||||
|
mFramebufferCache.getRenderPass(rpkey, &mResourceManager);
|
||||||
mPipelineCache.bindRenderPass(renderPass, 0);
|
mPipelineCache.bindRenderPass(renderPass, 0);
|
||||||
|
|
||||||
// Create the VkFramebuffer or fetch it from cache.
|
// Create the VkFramebuffer or fetch it from cache.
|
||||||
VulkanFboCache::FboKey fbkey = rt->getFboKey();
|
VulkanFboCache::FboKey fbkey = rt->getFboKey();
|
||||||
fbkey.renderPass = renderPass;
|
fbkey.renderPass = renderPass->getVkRenderPass();
|
||||||
fbkey.layers = 1;
|
fbkey.layers = 1;
|
||||||
|
|
||||||
rt->emitBarriersBeginRenderPass(*commandBuffer);
|
rt->emitBarriersBeginRenderPass(*commandBuffer);
|
||||||
|
|
||||||
VkFramebuffer vkfb = mFramebufferCache.getFramebuffer(fbkey);
|
fvkmemory::resource_ptr<VulkanFramebuffer> vkfb =
|
||||||
|
mFramebufferCache.getFramebuffer(fbkey, &mResourceManager, rt);
|
||||||
|
|
||||||
// Assign a label to the framebuffer for debugging purposes.
|
// Assign a label to the framebuffer for debugging purposes.
|
||||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS | FVK_DEBUG_DEBUG_UTILS)
|
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS | FVK_DEBUG_DEBUG_UTILS)
|
||||||
@@ -1998,12 +2002,14 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
|||||||
|
|
||||||
// The current command buffer now has references to the render target and its attachments.
|
// The current command buffer now has references to the render target and its attachments.
|
||||||
commandBuffer->acquire(rt);
|
commandBuffer->acquire(rt);
|
||||||
|
commandBuffer->acquire(renderPass);
|
||||||
|
commandBuffer->acquire(vkfb);
|
||||||
|
|
||||||
// Populate the structures required for vkCmdBeginRenderPass.
|
// Populate the structures required for vkCmdBeginRenderPass.
|
||||||
VkRenderPassBeginInfo renderPassInfo {
|
VkRenderPassBeginInfo renderPassInfo {
|
||||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||||
.renderPass = renderPass,
|
.renderPass = renderPass->getVkRenderPass(),
|
||||||
.framebuffer = vkfb,
|
.framebuffer = vkfb->getVkFramebuffer(),
|
||||||
|
|
||||||
// The renderArea field constrains the LoadOp, but scissoring does not.
|
// The renderArea field constrains the LoadOp, but scissoring does not.
|
||||||
// Therefore, we do not set the scissor rect here, we only need it in draw().
|
// Therefore, we do not set the scissor rect here, we only need it in draw().
|
||||||
@@ -2058,7 +2064,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
|||||||
mCurrentRenderPass = {
|
mCurrentRenderPass = {
|
||||||
.commandBuffer = commandBuffer,
|
.commandBuffer = commandBuffer,
|
||||||
.renderTarget = rt,
|
.renderTarget = rt,
|
||||||
.renderPass = renderPassInfo.renderPass,
|
.renderPass = renderPass,
|
||||||
.params = params,
|
.params = params,
|
||||||
.currentSubpass = 0,
|
.currentSubpass = 0,
|
||||||
};
|
};
|
||||||
@@ -2078,8 +2084,7 @@ void VulkanDriver::endRenderPass(int) {
|
|||||||
rt->emitBarriersEndRenderPass(*mCurrentRenderPass.commandBuffer);
|
rt->emitBarriersEndRenderPass(*mCurrentRenderPass.commandBuffer);
|
||||||
|
|
||||||
mCurrentRenderPass.renderTarget = {};
|
mCurrentRenderPass.renderTarget = {};
|
||||||
mCurrentRenderPass.renderPass = VK_NULL_HANDLE;
|
mCurrentRenderPass.renderPass = {};
|
||||||
|
|
||||||
mCurrentRenderPass.commandBuffer = nullptr;
|
mCurrentRenderPass.commandBuffer = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2244,7 +2249,7 @@ void VulkanDriver::resolve(
|
|||||||
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer) {
|
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer) {
|
||||||
FVK_SYSTRACE_SCOPE();
|
FVK_SYSTRACE_SCOPE();
|
||||||
|
|
||||||
FILAMENT_CHECK_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE)
|
FILAMENT_CHECK_PRECONDITION(!mCurrentRenderPass.renderPass)
|
||||||
<< "resolve() cannot be invoked inside a render pass.";
|
<< "resolve() cannot be invoked inside a render pass.";
|
||||||
|
|
||||||
auto srcTexture = resource_ptr<VulkanTexture>::cast(&mResourceManager, src);
|
auto srcTexture = resource_ptr<VulkanTexture>::cast(&mResourceManager, src);
|
||||||
@@ -2287,7 +2292,7 @@ void VulkanDriver::blit(
|
|||||||
math::uint2 size) {
|
math::uint2 size) {
|
||||||
FVK_SYSTRACE_SCOPE();
|
FVK_SYSTRACE_SCOPE();
|
||||||
|
|
||||||
FILAMENT_CHECK_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE)
|
FILAMENT_CHECK_PRECONDITION(!mCurrentRenderPass.renderPass)
|
||||||
<< "blit() cannot be invoked inside a render pass.";
|
<< "blit() cannot be invoked inside a render pass.";
|
||||||
|
|
||||||
auto srcTexture = resource_ptr<VulkanTexture>::cast(&mResourceManager, src);
|
auto srcTexture = resource_ptr<VulkanTexture>::cast(&mResourceManager, src);
|
||||||
@@ -2329,7 +2334,7 @@ void VulkanDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
|||||||
|
|
||||||
// Note: blitDEPRECATED is only used for Renderer::copyFrame()
|
// Note: blitDEPRECATED is only used for Renderer::copyFrame()
|
||||||
|
|
||||||
FILAMENT_CHECK_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE)
|
FILAMENT_CHECK_PRECONDITION(!mCurrentRenderPass.renderPass)
|
||||||
<< "blitDEPRECATED() cannot be invoked inside a render pass.";
|
<< "blitDEPRECATED() cannot be invoked inside a render pass.";
|
||||||
|
|
||||||
FILAMENT_CHECK_PRECONDITION(buffers == TargetBufferFlags::COLOR0)
|
FILAMENT_CHECK_PRECONDITION(buffers == TargetBufferFlags::COLOR0)
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ private:
|
|||||||
|
|
||||||
resource_ptr<VulkanSwapChain> mCurrentSwapChain;
|
resource_ptr<VulkanSwapChain> mCurrentSwapChain;
|
||||||
resource_ptr<VulkanRenderTarget> mDefaultRenderTarget;
|
resource_ptr<VulkanRenderTarget> mDefaultRenderTarget;
|
||||||
VulkanRenderPass mCurrentRenderPass = {};
|
VulkanRenderPassContext mCurrentRenderPass = {};
|
||||||
VmaAllocator mAllocator = VK_NULL_HANDLE;
|
VmaAllocator mAllocator = VK_NULL_HANDLE;
|
||||||
VkDebugReportCallbackEXT mDebugCallback = VK_NULL_HANDLE;
|
VkDebugReportCallbackEXT mDebugCallback = VK_NULL_HANDLE;
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,9 @@ void VulkanExternalImageManager::updateSetAndLayout(
|
|||||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> const& layout = set->getLayout();
|
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> const& layout = set->getLayout();
|
||||||
set->boundLayout = mDescriptorSetLayoutCache->getVkLayout(layout->bitmask,
|
set->boundLayout = mDescriptorSetLayoutCache->getVkLayout(layout->bitmask,
|
||||||
actualExternalSamplers, outSamplers);
|
actualExternalSamplers, outSamplers);
|
||||||
|
|
||||||
|
mDescriptorSetCache->cloneSet(set, actualExternalSamplers);
|
||||||
|
|
||||||
// Update the external samplers in the set
|
// Update the external samplers in the set
|
||||||
for (auto& [binding, sampler, image]: samplerAndBindings) {
|
for (auto& [binding, sampler, image]: samplerAndBindings) {
|
||||||
// We cannot call updateSamplerForExternalSamplerSet because some samplers are non NULL
|
// We cannot call updateSamplerForExternalSamplerSet because some samplers are non NULL
|
||||||
|
|||||||
@@ -17,13 +17,14 @@
|
|||||||
#include "VulkanFboCache.h"
|
#include "VulkanFboCache.h"
|
||||||
|
|
||||||
#include "VulkanConstants.h"
|
#include "VulkanConstants.h"
|
||||||
|
#include "VulkanHandles.h"
|
||||||
#include "vulkan/utils/Image.h"
|
#include "vulkan/utils/Image.h"
|
||||||
|
|
||||||
#include <utils/Panic.h>
|
#include <utils/Panic.h>
|
||||||
|
|
||||||
// If any VkRenderPass or VkFramebuffer is unused for more than TIME_BEFORE_EVICTION frames, it
|
// If any VkRenderPass or VkFramebuffer is unused for more than TIME_BEFORE_EVICTION frames, it
|
||||||
// is evicted from the cache.
|
// is evicted from the cache.
|
||||||
static constexpr uint32_t TIME_BEFORE_EVICTION = FVK_MAX_COMMAND_BUFFERS;
|
static constexpr uint32_t TIME_BEFORE_EVICTION = 3;
|
||||||
|
|
||||||
using namespace bluevk;
|
using namespace bluevk;
|
||||||
|
|
||||||
@@ -69,9 +70,11 @@ VulkanFboCache::~VulkanFboCache() {
|
|||||||
<< "Please explicitly call terminate() while the VkDevice is still alive.";
|
<< "Please explicitly call terminate() while the VkDevice is still alive.";
|
||||||
}
|
}
|
||||||
|
|
||||||
VkFramebuffer VulkanFboCache::getFramebuffer(FboKey const& config) noexcept {
|
fvkmemory::resource_ptr<VulkanFramebuffer> VulkanFboCache::getFramebuffer(FboKey const& config,
|
||||||
|
fvkmemory::ResourceManager* resManager,
|
||||||
|
fvkmemory::resource_ptr<VulkanRenderTarget> renderTarget) noexcept {
|
||||||
FboMap::iterator iter = mFramebufferCache.find(config);
|
FboMap::iterator iter = mFramebufferCache.find(config);
|
||||||
if (UTILS_LIKELY(iter != mFramebufferCache.end() && iter->second.handle != VK_NULL_HANDLE)) {
|
if (UTILS_LIKELY(iter != mFramebufferCache.end())) {
|
||||||
iter.value().timestamp = mCurrentTime;
|
iter.value().timestamp = mCurrentTime;
|
||||||
return iter->second.handle;
|
return iter->second.handle;
|
||||||
}
|
}
|
||||||
@@ -117,13 +120,19 @@ VkFramebuffer VulkanFboCache::getFramebuffer(FboKey const& config) noexcept {
|
|||||||
VkResult error = vkCreateFramebuffer(mDevice, &info, VKALLOC, &framebuffer);
|
VkResult error = vkCreateFramebuffer(mDevice, &info, VKALLOC, &framebuffer);
|
||||||
FILAMENT_CHECK_POSTCONDITION(error == VK_SUCCESS) << "Unable to create framebuffer."
|
FILAMENT_CHECK_POSTCONDITION(error == VK_SUCCESS) << "Unable to create framebuffer."
|
||||||
<< " error=" << static_cast<int32_t>(error);
|
<< " error=" << static_cast<int32_t>(error);
|
||||||
mFramebufferCache[config] = {framebuffer, mCurrentTime};
|
FVK_LOGD << "VulkanFboCache - Created VkFramebuffer " << framebuffer << " for renderTarget " << renderTarget.get()
|
||||||
return framebuffer;
|
<< " size=" << config.width << "x" << config.height;
|
||||||
|
fvkmemory::resource_ptr<VulkanFramebuffer> fbh =
|
||||||
|
fvkmemory::resource_ptr<VulkanFramebuffer>::construct(resManager, mDevice, framebuffer,
|
||||||
|
renderTarget);
|
||||||
|
mFramebufferCache[config] = { fbh, mCurrentTime };
|
||||||
|
return fbh;
|
||||||
}
|
}
|
||||||
|
|
||||||
VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey const& config) noexcept {
|
fvkmemory::resource_ptr<VulkanRenderPass> VulkanFboCache::getRenderPass(
|
||||||
|
RenderPassKey const& config, fvkmemory::ResourceManager* resManager) noexcept {
|
||||||
auto iter = mRenderPassCache.find(config);
|
auto iter = mRenderPassCache.find(config);
|
||||||
if (UTILS_LIKELY(iter != mRenderPassCache.end() && iter->second.handle != VK_NULL_HANDLE)) {
|
if (UTILS_LIKELY(iter != mRenderPassCache.end())) {
|
||||||
iter.value().timestamp = mCurrentTime;
|
iter.value().timestamp = mCurrentTime;
|
||||||
return iter->second.handle;
|
return iter->second.handle;
|
||||||
}
|
}
|
||||||
@@ -326,7 +335,9 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey const& config) noexcept
|
|||||||
VkResult error = vkCreateRenderPass(mDevice, &renderPassInfo, VKALLOC, &renderPass);
|
VkResult error = vkCreateRenderPass(mDevice, &renderPassInfo, VKALLOC, &renderPass);
|
||||||
FILAMENT_CHECK_POSTCONDITION(error == VK_SUCCESS) << "Unable to create render pass."
|
FILAMENT_CHECK_POSTCONDITION(error == VK_SUCCESS) << "Unable to create render pass."
|
||||||
<< " error=" << error;
|
<< " error=" << error;
|
||||||
mRenderPassCache[config] = {renderPass, mCurrentTime};
|
fvkmemory::resource_ptr<VulkanRenderPass> rph =
|
||||||
|
fvkmemory::resource_ptr<VulkanRenderPass>::construct(resManager, mDevice, renderPass);
|
||||||
|
mRenderPassCache[config] = {rph, mCurrentTime};
|
||||||
|
|
||||||
#if FVK_ENABLED(FVK_DEBUG_FBO_CACHE)
|
#if FVK_ENABLED(FVK_DEBUG_FBO_CACHE)
|
||||||
FVK_LOGD << "Created render pass " << renderPass << " with ";
|
FVK_LOGD << "Created render pass " << renderPass << " with ";
|
||||||
@@ -343,13 +354,12 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey const& config) noexcept
|
|||||||
<< "colorAttachmentCount[0] = " << subpasses[0].colorAttachmentCount;
|
<< "colorAttachmentCount[0] = " << subpasses[0].colorAttachmentCount;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return renderPass;
|
return rph;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanFboCache::resetFramebuffers() noexcept {
|
void VulkanFboCache::resetFramebuffers() noexcept {
|
||||||
for (const auto& pair: mFramebufferCache) {
|
for (const auto& pair: mFramebufferCache) {
|
||||||
mRenderPassRefCount[pair.first.renderPass]--;
|
mRenderPassRefCount[pair.first.renderPass]--;
|
||||||
vkDestroyFramebuffer(mDevice, pair.second.handle, VKALLOC);
|
|
||||||
}
|
}
|
||||||
mFramebufferCache.clear();
|
mFramebufferCache.clear();
|
||||||
}
|
}
|
||||||
@@ -357,9 +367,6 @@ void VulkanFboCache::resetFramebuffers() noexcept {
|
|||||||
void VulkanFboCache::terminate() noexcept {
|
void VulkanFboCache::terminate() noexcept {
|
||||||
resetFramebuffers();
|
resetFramebuffers();
|
||||||
|
|
||||||
for (const auto& pair: mRenderPassCache) {
|
|
||||||
vkDestroyRenderPass(mDevice, pair.second.handle, VKALLOC);
|
|
||||||
}
|
|
||||||
mRenderPassRefCount.clear();
|
mRenderPassRefCount.clear();
|
||||||
mRenderPassCache.clear();
|
mRenderPassCache.clear();
|
||||||
}
|
}
|
||||||
@@ -380,8 +387,6 @@ void VulkanFboCache::gc() noexcept {
|
|||||||
const FboVal fbo = iter->second;
|
const FboVal fbo = iter->second;
|
||||||
if (fbo.timestamp < evictTime && fbo.handle) {
|
if (fbo.timestamp < evictTime && fbo.handle) {
|
||||||
mRenderPassRefCount[iter->first.renderPass]--;
|
mRenderPassRefCount[iter->first.renderPass]--;
|
||||||
vkDestroyFramebuffer(mDevice, fbo.handle, VKALLOC);
|
|
||||||
iter.value().handle = VK_NULL_HANDLE;
|
|
||||||
|
|
||||||
// erase(iterator) returns the iterator to the next element.
|
// erase(iterator) returns the iterator to the next element.
|
||||||
iter = mFramebufferCache.erase(iter);
|
iter = mFramebufferCache.erase(iter);
|
||||||
@@ -391,11 +396,8 @@ void VulkanFboCache::gc() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (RenderPassMap::iterator iter = mRenderPassCache.begin(); iter != mRenderPassCache.end(); ) {
|
for (RenderPassMap::iterator iter = mRenderPassCache.begin(); iter != mRenderPassCache.end(); ) {
|
||||||
const VkRenderPass handle = iter->second.handle;
|
const VkRenderPass handle = iter->second.handle->getVkRenderPass();
|
||||||
if (iter->second.timestamp < evictTime && handle && mRenderPassRefCount[handle] == 0) {
|
if (iter->second.timestamp < evictTime && handle && mRenderPassRefCount[handle] == 0) {
|
||||||
vkDestroyRenderPass(mDevice, handle, VKALLOC);
|
|
||||||
iter.value().handle = VK_NULL_HANDLE;
|
|
||||||
|
|
||||||
// erase(iterator) returns the iterator to the next element.
|
// erase(iterator) returns the iterator to the next element.
|
||||||
iter = mRenderPassCache.erase(iter);
|
iter = mRenderPassCache.erase(iter);
|
||||||
mRenderPassRefCount.erase(handle);
|
mRenderPassRefCount.erase(handle);
|
||||||
@@ -404,6 +406,8 @@ void VulkanFboCache::gc() noexcept {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FVK_LOGD << "VulkanFboCache sizes: framebuffers=" << mFramebufferCache.size() << " renderpasses=" << mRenderPassCache.size();
|
||||||
|
|
||||||
FVK_SYSTRACE_END();
|
FVK_SYSTRACE_END();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user