Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b9308817c |
@@ -27,24 +27,17 @@ runs:
|
||||
echo "$HASH" > /tmp/commit_hash.txt
|
||||
- name: Find commit message (PR)
|
||||
shell: bash
|
||||
id: checkout_code
|
||||
if: github.event_name == 'pull_request'
|
||||
run: |
|
||||
PR_NUMBER="${{ github.event.pull_request.number }}"
|
||||
# Fetch the head of the PR explicitly to handle forks. Depth 50 ensures we can traverse past recent merge commits.
|
||||
git fetch --depth=50 origin "pull/$PR_NUMBER/head:pr-head"
|
||||
|
||||
COMMIT_HASH=$(git log -1 --no-merges pr-head --format=%H)
|
||||
AUTHOR_NAME=$(git log -1 --no-merges pr-head --format=%an)
|
||||
AUTHOR_EMAIL=$(git log -1 --no-merges pr-head --format=%ae)
|
||||
TSTAMP=$(git log -1 --no-merges pr-head --format=%aI)
|
||||
COMMIT_MESSAGE=$(git log -1 --no-merges pr-head --format=%B)
|
||||
|
||||
echo "commit $COMMIT_HASH" > /tmp/commit_msg.txt
|
||||
echo "Author: ${AUTHOR_NAME}<${AUTHOR_EMAIL}>" >> /tmp/commit_msg.txt
|
||||
echo "Date: ${TSTAMP}" >> /tmp/commit_msg.txt
|
||||
echo "" >> /tmp/commit_msg.txt
|
||||
echo "$COMMIT_MESSAGE" >> /tmp/commit_msg.txt
|
||||
echo "$COMMIT_HASH" > /tmp/commit_hash.txt
|
||||
BEFORE_HASH=$(git rev-parse HEAD)
|
||||
echo "hash=$BEFORE_HASH" >> "$GITHUB_OUTPUT"
|
||||
# Next we will checkout the actual head (not the merge commits) of the PR
|
||||
AFTER_HASH="${{ github.event.pull_request.head.sha }}"
|
||||
git checkout $AFTER_HASH
|
||||
COMMIT_MESSAGE=$(git log -1 --no-merges)
|
||||
echo "$COMMIT_MESSAGE" > /tmp/commit_msg.txt
|
||||
echo "$AFTER_HASH" > /tmp/commit_hash.txt
|
||||
- shell: bash
|
||||
id: action_output
|
||||
run: |
|
||||
@@ -54,4 +47,9 @@ runs:
|
||||
cat /tmp/commit_msg.txt >> "$GITHUB_OUTPUT"
|
||||
echo "$DELIMITER" >> "$GITHUB_OUTPUT"
|
||||
# Get the commit hash
|
||||
echo "hash=$(cat /tmp/commit_hash.txt)" >> "$GITHUB_OUTPUT"
|
||||
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 }}
|
||||
@@ -9,7 +9,7 @@ runs:
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: mesa
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-mesa-deps-${{ env.GITHUB_MESA_VERSION }}
|
||||
key: ${{ runner.os }}-mesa-deps-${{ env.GITHUB_MESA_VERSION }}
|
||||
- name: Get Mesa
|
||||
run: |
|
||||
bash build/common/get-mesa.sh
|
||||
|
||||
@@ -9,7 +9,7 @@ runs:
|
||||
id: cache-vulkan-sdk
|
||||
with:
|
||||
path: ~/VulkanSDK
|
||||
key: vulkansdk-${{ env.GITHUB_VULKANSDK_VERSION }}-2-${{ runner.os }}-${{ runner.arch }}
|
||||
key: vulkansdk-${{ env.GITHUB_VULKANSDK_VERSION }}-2-${{ runner.os }}
|
||||
- name: Download Vulkan SDK
|
||||
if: steps.cache-vulkan-sdk.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
@@ -23,7 +23,6 @@ runs:
|
||||
unpack_vulkan_installer
|
||||
shell: bash
|
||||
- name: Run Vulkan SDK setup
|
||||
if: runner.os != 'Linux'
|
||||
run: |
|
||||
pushd .
|
||||
cd ~/VulkanSDK/${GITHUB_VULKANSDK_VERSION}
|
||||
|
||||
@@ -11,13 +11,24 @@ runs:
|
||||
echo "set man-db/auto-update false" | sudo debconf-communicate
|
||||
sudo dpkg-reconfigure man-db
|
||||
|
||||
# Install ninja
|
||||
source ./build/common/get-ninja.sh
|
||||
|
||||
# Install CMake
|
||||
mkdir -p cmake
|
||||
cd cmake
|
||||
|
||||
sudo wget https://github.com/Kitware/CMake/releases/download/v$GITHUB_CMAKE_VERSION/cmake-$GITHUB_CMAKE_VERSION-Linux-x86_64.sh
|
||||
sudo chmod +x ./cmake-$GITHUB_CMAKE_VERSION-Linux-x86_64.sh
|
||||
sudo ./cmake-$GITHUB_CMAKE_VERSION-Linux-x86_64.sh --skip-license > /dev/null
|
||||
sudo update-alternatives --install /usr/bin/cmake cmake $(pwd)/bin/cmake 1000 --force
|
||||
|
||||
cd ..
|
||||
|
||||
sudo wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
||||
sudo apt-get update
|
||||
sudo apt-get install clang-$GITHUB_CLANG_VERSION libc++-$GITHUB_CLANG_VERSION-dev libc++abi-$GITHUB_CLANG_VERSION-dev
|
||||
sudo apt-get install mesa-common-dev libxi-dev libxxf86vm-dev
|
||||
sudo apt-get install cmake ninja-build
|
||||
|
||||
# For dawn
|
||||
sudo apt-get install libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libx11-xcb-dev
|
||||
|
||||
@@ -8,15 +8,9 @@ runs:
|
||||
- uses: ./.github/actions/get-mesa
|
||||
- uses: ./.github/actions/get-vulkan-sdk
|
||||
- name: Prerequisites
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
# Must have at least clang-16 for a webgpu/dawn build.
|
||||
sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer
|
||||
# this enforces the gltf_viewer build to use apple clang as oppose the brew clang used for get-mesa
|
||||
echo "CC=/usr/bin/clang" >> $GITHUB_ENV
|
||||
echo "CXX=/usr/bin/clang++" >> $GITHUB_ENV
|
||||
echo "/usr/local/bin" >> $GITHUB_PATH
|
||||
echo "/usr/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
- name: Generate images
|
||||
run: |
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM python:3.12.13
|
||||
FROM python:3.10.4
|
||||
|
||||
RUN pip3 install pygithub==1.55
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: 'Web Prerequisites'
|
||||
name: 'Web Preqrequisites'
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
@@ -8,7 +8,7 @@ runs:
|
||||
uses: actions/cache@v4 # Use a specific version
|
||||
with:
|
||||
path: emsdk
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-emsdk-${{ env.GITHUB_EMSDK_VERSION }}
|
||||
key: ${{ runner.os }}-emsdk-${{ env.GITHUB_EMSDK_VERSION }}
|
||||
- name: Install Web Prerequisites
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
# a branch on filament-assets.
|
||||
update-renderdiff-goldens:
|
||||
name: update-renderdiff-goldens
|
||||
runs-on: 'macos-14'
|
||||
runs-on: 'macos-14-xlarge'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -90,7 +90,7 @@ jobs:
|
||||
# filament-assets
|
||||
update-sizeguard:
|
||||
name: update-sizeguard
|
||||
runs-on: 'ubuntu-24.04-4core'
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
|
||||
@@ -12,7 +12,8 @@ jobs:
|
||||
name: build-android
|
||||
# We intentially use a larger runner here to enable larger disk space
|
||||
# (standard linux runner will fail on disk space and faster build time).
|
||||
runs-on: 'ubuntu-24.04-4core'
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -25,7 +26,8 @@ jobs:
|
||||
|
||||
build-ios:
|
||||
name: build-ios
|
||||
runs-on: 'macos-14'
|
||||
runs-on: macos-14-xlarge
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -44,7 +46,8 @@ jobs:
|
||||
|
||||
build-linux:
|
||||
name: build-linux
|
||||
runs-on: 'arm-ubuntu-24.04-16core'
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -55,12 +58,13 @@ jobs:
|
||||
cd build/linux && printf "y" | ./build.sh continuous
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: filament-arm-linux
|
||||
path: out/filament-release-arm-linux.tgz
|
||||
name: filament-linux
|
||||
path: out/filament-release-linux.tgz
|
||||
|
||||
build-mac:
|
||||
name: build-mac
|
||||
runs-on: 'macos-14'
|
||||
runs-on: macos-14-xlarge
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -73,10 +77,14 @@ jobs:
|
||||
with:
|
||||
name: filament-mac
|
||||
path: out/filament-release-darwin.tgz
|
||||
- name: Check public headers
|
||||
run: |
|
||||
test/check-headers/test.sh out/release/filament/include
|
||||
|
||||
build-web:
|
||||
name: build-web
|
||||
runs-on: 'arm-ubuntu-24.04-16core'
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -93,7 +101,7 @@ jobs:
|
||||
|
||||
build-windows:
|
||||
name: build-windows
|
||||
runs-on: 'windows-2022'
|
||||
runs-on: windows-2022-32core
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
|
||||
+17
-178
@@ -8,54 +8,10 @@ on:
|
||||
branches:
|
||||
- main
|
||||
|
||||
# This will cancel in-flight runs when there is an update to a PR
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
# The conditional 'if' on each job is meant to skip presubmit jobs when a commit is pushed to main
|
||||
# and that commit is cryptographically "verified". Typically, a verified commit (like a GitHub UI
|
||||
# squash-and-merge) has already passed through presubmit during the Pull Request phase.
|
||||
# The conditional explicitly checks:
|
||||
# 1. always() && !cancelled(): Ensures the job runs even if 'check-verification' is skipped, but
|
||||
# aborts if the workflow was manually cancelled.
|
||||
# 2. github.event_name == 'pull_request': Presubmits should always run normally on PRs.
|
||||
# 3. github.event_name == 'push' && ...: If it's a push to main, it only runs if the
|
||||
# 'check-verification' job confirmed the commit is NOT verified.
|
||||
|
||||
jobs:
|
||||
check-verification:
|
||||
if: github.event_name == 'push'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
verified: ${{ steps.check.outputs.verified }}
|
||||
steps:
|
||||
- name: Check commit verification
|
||||
id: check
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const { data } = await github.rest.repos.getCommit({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: context.sha
|
||||
});
|
||||
core.setOutput('verified', data.commit.verification.verified);
|
||||
|
||||
build-desktop-mac:
|
||||
name: build-mac
|
||||
runs-on: 'macos-14-xlarge'
|
||||
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'
|
||||
)
|
||||
)
|
||||
runs-on: macos-14-xlarge
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -64,30 +20,13 @@ jobs:
|
||||
- name: Run build script
|
||||
run: |
|
||||
cd build/mac && printf "y" | ./build.sh presubmit-with-test
|
||||
- name: Test - material parser
|
||||
- name: Test material parser
|
||||
run: |
|
||||
out/cmake-release/filament/test/test_material_parser
|
||||
- name: Test - public headers
|
||||
run: |
|
||||
# out/cmake-release should have the artifacts ready for installation. Here we install it
|
||||
# to test the public headers
|
||||
ninja -C out/cmake-release install
|
||||
test/check-headers/test.sh out/release/filament/include
|
||||
|
||||
build-desktop-linux:
|
||||
name: build-linux
|
||||
runs-on: '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'
|
||||
)
|
||||
)
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -96,24 +35,13 @@ jobs:
|
||||
- name: Run build script
|
||||
run: |
|
||||
cd build/linux && printf "y" | ./build.sh presubmit
|
||||
- name: Test - material parser
|
||||
- name: Test material parser
|
||||
run: |
|
||||
out/cmake-release/filament/test/test_material_parser
|
||||
|
||||
build-windows:
|
||||
name: build-windows
|
||||
runs-on: 'windows-2022'
|
||||
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'
|
||||
)
|
||||
)
|
||||
runs-on: windows-2022-32core
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -125,18 +53,7 @@ jobs:
|
||||
|
||||
build-android:
|
||||
name: build-android
|
||||
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'
|
||||
)
|
||||
)
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -163,18 +80,7 @@ jobs:
|
||||
|
||||
build-ios:
|
||||
name: build-iOS
|
||||
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'
|
||||
)
|
||||
)
|
||||
runs-on: macos-14-xlarge
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -189,18 +95,7 @@ jobs:
|
||||
|
||||
build-web:
|
||||
name: build-web
|
||||
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'
|
||||
)
|
||||
)
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -213,18 +108,7 @@ jobs:
|
||||
|
||||
validate-docs:
|
||||
name: validate-docs
|
||||
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'
|
||||
)
|
||||
)
|
||||
runs-on: 'ubuntu-24.04-4core'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -237,18 +121,7 @@ jobs:
|
||||
|
||||
test-renderdiff:
|
||||
name: test-renderdiff
|
||||
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'
|
||||
)
|
||||
)
|
||||
runs-on: macos-14-xlarge
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -266,10 +139,10 @@ jobs:
|
||||
echo "accept=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
shell: bash
|
||||
- name: Renderdiff generate
|
||||
- name: Renderdiff Generate
|
||||
if: steps.check_accept.outputs.accept != 'true'
|
||||
uses: ./.github/actions/renderdiff-generate
|
||||
- name: Compare rendered images
|
||||
- name: Render and compare
|
||||
if: steps.check_accept.outputs.accept != 'true'
|
||||
id: render_compare
|
||||
env:
|
||||
@@ -301,13 +174,12 @@ jobs:
|
||||
echo "$DELIMITER" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
shell: bash
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: steps.check_accept.outputs.accept != 'true'
|
||||
with:
|
||||
name: presubmit-renderdiff-result
|
||||
path: ./out/renderdiff
|
||||
- name: Check results
|
||||
- name: Compare result
|
||||
if: steps.check_accept.outputs.accept != 'true'
|
||||
run: |
|
||||
ERROR_STR="${{ steps.render_compare.outputs.err }}"
|
||||
@@ -316,20 +188,9 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
build-wgsl-webgpu:
|
||||
validate-wgsl-webgpu:
|
||||
name: validate-wgsl-webgpu
|
||||
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'
|
||||
)
|
||||
)
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -343,17 +204,6 @@ jobs:
|
||||
test-code-correctness:
|
||||
name: test-code-correctness
|
||||
runs-on: 'macos-14-xlarge'
|
||||
needs: [check-verification]
|
||||
if: >
|
||||
always() && !cancelled() &&
|
||||
(
|
||||
github.event_name == 'pull_request' ||
|
||||
(
|
||||
github.event_name == 'push' &&
|
||||
needs.check-verification.result == 'success' &&
|
||||
needs.check-verification.outputs.verified == 'false'
|
||||
)
|
||||
)
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
@@ -374,18 +224,7 @@ jobs:
|
||||
|
||||
test-backend:
|
||||
name: test-backend
|
||||
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'
|
||||
)
|
||||
)
|
||||
runs-on: macos-14-xlarge
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
|
||||
@@ -63,39 +63,6 @@ jobs:
|
||||
const globber = await glob.create('out/*.tgz');
|
||||
await upload({ github, context }, await globber.glob(), TAG);
|
||||
|
||||
build-arm-linux:
|
||||
name: build-arm-linux
|
||||
runs-on: 'arm-ubuntu-24.04-16core'
|
||||
if: github.event_name == 'release' || github.event.inputs.platform == 'desktop'
|
||||
steps:
|
||||
- name: Decide Git ref
|
||||
id: git_ref
|
||||
run: |
|
||||
REF=${RELEASE_TAG:-${GITHUB_REF}}
|
||||
TAG=${REF##*/}
|
||||
echo "ref=${REF}" >> $GITHUB_OUTPUT
|
||||
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
ref: ${{ steps.git_ref.outputs.ref }}
|
||||
- uses: ./.github/actions/linux-prereq
|
||||
- name: Run build script
|
||||
env:
|
||||
TAG: ${{ steps.git_ref.outputs.tag }}
|
||||
run: |
|
||||
cd build/linux && printf "y" | ./build.sh release
|
||||
cd ../..
|
||||
mv out/filament-release-linux.tgz out/filament-${TAG}-arm-linux.tgz
|
||||
- uses: actions/github-script@v6
|
||||
env:
|
||||
TAG: ${{ steps.git_ref.outputs.tag }}
|
||||
with:
|
||||
script: |
|
||||
const upload = require('./build/common/upload-release-assets');
|
||||
const { TAG } = process.env;
|
||||
const globber = await glob.create('out/*.tgz');
|
||||
await upload({ github, context }, await globber.glob(), TAG);
|
||||
|
||||
build-mac:
|
||||
name: build-mac
|
||||
runs-on: macos-14-xlarge
|
||||
@@ -132,7 +99,7 @@ jobs:
|
||||
|
||||
build-web:
|
||||
name: build-web
|
||||
runs-on: 'arm-ubuntu-24.04-16core'
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
if: github.event_name == 'release' || github.event.inputs.platform == 'web'
|
||||
|
||||
steps:
|
||||
@@ -221,54 +188,6 @@ jobs:
|
||||
const globber = await glob.create(['out/*.aar', 'out/*.apk', 'out/*.tgz'].join('\n'));
|
||||
await upload({ github, context }, await globber.glob(), TAG);
|
||||
|
||||
sonatype-publish:
|
||||
name: sonatype-publish
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
# Depends on the the Android build for the Android binaries.
|
||||
# Depends on the Mac, Linux, and Windows builds for host tools.
|
||||
needs: [build-mac, build-linux, build-windows, build-android]
|
||||
if: github.event_name == 'release' || github.event.inputs.platform == 'android'
|
||||
|
||||
steps:
|
||||
- name: Decide Git ref
|
||||
id: git_ref
|
||||
run: |
|
||||
REF=${RELEASE_TAG:-${GITHUB_REF}}
|
||||
TAG=${REF##*/}
|
||||
echo "ref=${REF}" >> $GITHUB_OUTPUT
|
||||
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
ref: ${{ steps.git_ref.outputs.ref }}
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
- uses: ./.github/actions/linux-prereq
|
||||
- name: Download Android Release
|
||||
run: |
|
||||
gh release download ${TAG} \
|
||||
--repo ${{ github.repository }} \
|
||||
--pattern 'filament-*-android-native.tgz'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ steps.git_ref.outputs.tag }}
|
||||
- name: Unzip Android Release
|
||||
run: |
|
||||
mkdir -p out/android-release
|
||||
tar -xzvf filament-${TAG}-android-native.tgz -C out/android-release/
|
||||
env:
|
||||
TAG: ${{ steps.git_ref.outputs.tag }}
|
||||
- name: Publish To Sonatype
|
||||
run: |
|
||||
cd android
|
||||
./gradlew publishToSonatype closeSonatypeStagingRepository
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.SONATYPE_USERNAME }}
|
||||
ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.SONATYPE_PASSWORD }}
|
||||
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.MAVEN_SIGNING_KEY }}
|
||||
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.MAVEN_SIGNING_PASSWORD }}
|
||||
|
||||
build-ios:
|
||||
name: build-ios
|
||||
runs-on: macos-14-xlarge
|
||||
|
||||
@@ -11,11 +11,14 @@ jobs:
|
||||
name: Verify Release Notes
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
# We *only* need to check out the .github/ directory.
|
||||
# The verify RELEASE_NOTES script uses the GitHub API to verify that RELEASE_NOTES was
|
||||
# modified.
|
||||
- name: Check out action
|
||||
uses: Bhacaz/checkout-files@73e17cfbe8d7e0c6b2672b20cb05a718e20d18d4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
sparse-checkout: |
|
||||
.github
|
||||
files: .github
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Verify release notes
|
||||
uses: ./.github/actions/verify-release-notes
|
||||
with:
|
||||
|
||||
+3
-10
@@ -326,13 +326,11 @@ endif()
|
||||
if (FILAMENT_ENABLE_LTO)
|
||||
include(CheckIPOSupported)
|
||||
|
||||
check_ipo_supported(RESULT IPO_SUPPORT OUTPUT IPO_ERROR)
|
||||
check_ipo_supported(RESULT IPO_SUPPORT)
|
||||
|
||||
if (IPO_SUPPORT)
|
||||
message(STATUS "IPO / LTO is enabled")
|
||||
message(STATUS "LTO support is enabled")
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
else()
|
||||
message(WARNING "IPO / LTO is not supported by this architecture: ${IPO_ERROR}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -869,12 +867,6 @@ endfunction()
|
||||
# Sub-projects
|
||||
# ==================================================================================================
|
||||
|
||||
include(CheckSymbolExists)
|
||||
check_symbol_exists(getopt_long "getopt.h" HAS_SYSTEM_GETOPT)
|
||||
if (NOT HAS_SYSTEM_GETOPT)
|
||||
add_subdirectory(${EXTERNAL}/getopt)
|
||||
endif()
|
||||
|
||||
# Common to all platforms
|
||||
add_subdirectory(${EXTERNAL}/libgtest/tnt)
|
||||
add_subdirectory(${LIBRARIES}/camutils)
|
||||
@@ -910,6 +902,7 @@ add_subdirectory(${EXTERNAL}/cgltf/tnt)
|
||||
add_subdirectory(${EXTERNAL}/draco/tnt)
|
||||
add_subdirectory(${EXTERNAL}/jsmn/tnt)
|
||||
add_subdirectory(${EXTERNAL}/stb/tnt)
|
||||
add_subdirectory(${EXTERNAL}/getopt)
|
||||
add_subdirectory(${EXTERNAL}/perfetto/tnt)
|
||||
add_subdirectory(${EXTERNAL}/basisu/tnt)
|
||||
|
||||
|
||||
@@ -6,3 +6,6 @@
|
||||
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||
|
||||
## Release notes for next branch cut
|
||||
|
||||
- engine: fix crash when using variance shadow maps
|
||||
- materials: better shadow normal-bias calculations [⚠️ **New Material Version**]
|
||||
|
||||
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.70.2'
|
||||
implementation 'com.google.android.filament:filament-android:1.69.4'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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:
|
||||
|
||||
```shell
|
||||
pod 'Filament', '~> 1.70.2'
|
||||
pod 'Filament', '~> 1.69.4'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -7,17 +7,6 @@ A new header is inserted each time a *tag* is created.
|
||||
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||
|
||||
## v1.70.2
|
||||
|
||||
|
||||
## v1.70.1
|
||||
|
||||
|
||||
## v1.70.0
|
||||
|
||||
- engine: fix crash when using variance shadow maps
|
||||
- materials: better shadow normal-bias calculations [⚠️ **New Material Version**]
|
||||
|
||||
## v1.69.5
|
||||
|
||||
- engine: fix crash when using variance shadow maps
|
||||
|
||||
@@ -40,21 +40,15 @@
|
||||
// - Build and upload artifacts with ./gradlew publish
|
||||
// - Close and release staging repo on Nexus with ./gradlew closeAndReleaseStagingRepository
|
||||
//
|
||||
// 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):
|
||||
// The following is needed in ~/gradle/gradle.properties:
|
||||
//
|
||||
// sonatypeUsername=nexus_user
|
||||
// sonatypePassword=nexus_password
|
||||
//
|
||||
// To sign with a key ring file:
|
||||
// signing.keyId=pgp_key_id
|
||||
// signing.password=pgp_key_password
|
||||
// signing.secretKeyRingFile=/Users/user/.gnupg/maven_signing.key
|
||||
//
|
||||
// To sign with in-memory keys (useful for CI):,
|
||||
// signingKey=ASCII armored key (begins with -----BEGIN PGP PRIVATE KEY BLOCK-----)
|
||||
// signingPassword=key password
|
||||
//
|
||||
|
||||
buildscript {
|
||||
def path = providers
|
||||
@@ -200,7 +194,7 @@ subprojects {
|
||||
google()
|
||||
}
|
||||
|
||||
if (!name.startsWith("sample") && name != "filament-tools" && name != "gradle-plugin") {
|
||||
if (!name.startsWith("sample") && name != "filament-tools") {
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
|
||||
@@ -25,7 +25,7 @@ configured, the corresponding task will be disabled.
|
||||
|
||||
```groovy
|
||||
plugins {
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
filament {
|
||||
@@ -0,0 +1,21 @@
|
||||
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"
|
||||
}
|
||||
+2
-3
@@ -60,10 +60,9 @@ class ToolsLocator {
|
||||
def classifier =
|
||||
project.extensions.getByType(com.google.gradle.osdetector.OsDetector).classifier
|
||||
|
||||
// If com.google.android.filament.tools-dir is set to a non-empty string, we'll use it as
|
||||
// the tool's base path.
|
||||
// If com.google.android.filament.tools-dir is set, we'll use it as the tool's base path.
|
||||
def toolsDirProp = project.providers.gradleProperty("com.google.android.filament.tools-dir")
|
||||
if (toolsDirProp.isPresent() && !toolsDirProp.get().trim().isEmpty()) {
|
||||
if (toolsDirProp.isPresent()) {
|
||||
def toolsDir = toolsDirProp.get()
|
||||
def path = OperatingSystem.current().isWindows() ?
|
||||
"${toolsDir}/bin/${name}.exe" :
|
||||
@@ -2121,7 +2121,7 @@ public class View {
|
||||
*/
|
||||
public boolean highPrecision = false;
|
||||
/**
|
||||
* @deprecated has no effect.
|
||||
* VSM minimum variance scale, must be positive.
|
||||
*/
|
||||
public float minVarianceScale = 0.5f;
|
||||
/**
|
||||
|
||||
@@ -61,7 +61,6 @@ set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size
|
||||
add_library(filament-utils-jni SHARED
|
||||
src/main/cpp/AutomationEngine.cpp
|
||||
src/main/cpp/Bookmark.cpp
|
||||
src/main/cpp/DeviceUtils.cpp
|
||||
src/main/cpp/HDRLoader.cpp
|
||||
src/main/cpp/IBLPrefilterContext.cpp
|
||||
src/main/cpp/Utils.cpp
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2026 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <filament/Engine.h>
|
||||
|
||||
#include <backend/Platform.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
using namespace filament;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::array<backend::Platform::DeviceInfoType, 3> VULKAN_INFO = {
|
||||
backend::Platform::DeviceInfoType::VULKAN_DEVICE_NAME,
|
||||
backend::Platform::DeviceInfoType::VULKAN_DRIVER_NAME,
|
||||
backend::Platform::DeviceInfoType::VULKAN_DRIVER_INFO,
|
||||
};
|
||||
|
||||
constexpr std::array<backend::Platform::DeviceInfoType, 3> GL_INFO = {
|
||||
backend::Platform::DeviceInfoType::OPENGL_VENDOR,
|
||||
backend::Platform::DeviceInfoType::OPENGL_RENDERER,
|
||||
backend::Platform::DeviceInfoType::OPENGL_VERSION,
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" JNIEXPORT jstring JNICALL
|
||||
Java_com_google_android_filament_utils_DeviceUtils_nGetGpuDriverInfo(JNIEnv* env, jclass,
|
||||
jlong nativeEngine) {
|
||||
auto emptyStr = [env]() { return env->NewStringUTF(""); };
|
||||
Engine* engine = (Engine*) nativeEngine;
|
||||
if (!engine) {
|
||||
return emptyStr();
|
||||
}
|
||||
|
||||
backend::Platform* platform = engine->getPlatform();
|
||||
if (!platform) {
|
||||
return emptyStr();
|
||||
}
|
||||
|
||||
std::array<backend::Platform::DeviceInfoType, 3> infoTypes;
|
||||
switch (engine->getBackend()) {
|
||||
case backend::Backend::VULKAN:
|
||||
infoTypes = VULKAN_INFO;
|
||||
break;
|
||||
case backend::Backend::OPENGL:
|
||||
infoTypes = GL_INFO;
|
||||
break;
|
||||
default:
|
||||
return emptyStr();
|
||||
}
|
||||
|
||||
backend::Driver* driver = const_cast<backend::Driver*>(engine->getDriver());
|
||||
utils::CString fullInfo;
|
||||
std::for_each(infoTypes.begin(), infoTypes.end(),
|
||||
[&](backend::Platform::DeviceInfoType infoType) {
|
||||
utils::CString const newInfo = platform->getDeviceInfo(infoType, driver);
|
||||
if (!newInfo.empty()) {
|
||||
if (!fullInfo.empty()) {
|
||||
fullInfo += " | ";
|
||||
}
|
||||
fullInfo += newInfo.c_str();
|
||||
}
|
||||
});
|
||||
return env->NewStringUTF(fullInfo.c_str());
|
||||
}
|
||||
@@ -20,6 +20,8 @@
|
||||
#include <imagediff/ImageDiff.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace imagediff;
|
||||
using namespace utils;
|
||||
|
||||
@@ -100,48 +102,30 @@ jobject createResult(JNIEnv* env, ImageDiffResult const& result, bool generateDi
|
||||
|
||||
if (generateDiff && result.diffImage.getWidth() > 0) {
|
||||
jclass bitmapClass = env->FindClass("android/graphics/Bitmap");
|
||||
jmethodID createBitmap = env->GetStaticMethodID(bitmapClass, "createBitmap",
|
||||
jmethodID createBitmap = env->GetStaticMethodID(bitmapClass, "createBitmap",
|
||||
"(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
|
||||
|
||||
|
||||
jclass configClass = env->FindClass("android/graphics/Bitmap$Config");
|
||||
jfieldID argb8888 = env->GetStaticFieldID(configClass, "ARGB_8888", "Landroid/graphics/Bitmap$Config;");
|
||||
jobject configObj = env->GetStaticObjectField(configClass, argb8888);
|
||||
|
||||
uint32_t width = result.diffImage.getWidth();
|
||||
uint32_t height = result.diffImage.getHeight();
|
||||
jobject diffBitmap = env->CallStaticObjectMethod(bitmapClass, createBitmap, (jint) width,
|
||||
(jint) height, configObj);
|
||||
|
||||
jobject diffBitmap = env->CallStaticObjectMethod(bitmapClass, createBitmap, (jint)width, (jint)height, configObj);
|
||||
|
||||
if (diffBitmap) {
|
||||
// We need to transport the bit differences accurately to the java side, so set
|
||||
// premultiplied to false. From the java-side, if the bitmap is used to draw to a
|
||||
// canvas, then client needs to set premultiplied to true again.
|
||||
jmethodID setPremultiplied = env->GetMethodID(bitmapClass, "setPremultiplied", "(Z)V");
|
||||
if (setPremultiplied) {
|
||||
env->CallVoidMethod(diffBitmap, setPremultiplied, JNI_FALSE);
|
||||
}
|
||||
|
||||
void* diffPixels;
|
||||
if (AndroidBitmap_lockPixels(env, diffBitmap, &diffPixels) == 0) {
|
||||
AndroidBitmapInfo info;
|
||||
AndroidBitmap_getInfo(env, diffBitmap, &info);
|
||||
|
||||
float const* src = result.diffImage.getPixelRef();
|
||||
uint8_t* dst = (uint8_t*) diffPixels;
|
||||
uint32_t const channels = result.diffImage.getChannels(); // usually 4
|
||||
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
uint8_t* row = dst + y * info.stride;
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
size_t srcIdx = (y * width + x) * channels;
|
||||
for (int c = 0; c < 4; ++c) {
|
||||
float v = 0.0f;
|
||||
if (c < channels) v = src[srcIdx + c];
|
||||
if (c == 3 && channels < 4) v = 1.0f; // Alpha 1.0 if missing
|
||||
|
||||
row[x * 4 + c] = uint8_t(
|
||||
std::min(255.0f, std::max(0.0f, std::round(v * 255.0f))));
|
||||
}
|
||||
uint32_t channels = result.diffImage.getChannels(); // usually 4
|
||||
|
||||
for (size_t i = 0; i < width * height; ++i) {
|
||||
for (int c = 0; c < 4; ++c) {
|
||||
float v = 0.0f;
|
||||
if (c < channels) v = src[i * channels + c];
|
||||
if (c == 3 && channels < 4) v = 1.0f; // Alpha 1.0 if missing
|
||||
dst[i * 4 + c] = (uint8_t) std::min(255.0f, std::max(0.0f, v * 255.0f));
|
||||
}
|
||||
}
|
||||
AndroidBitmap_unlockPixels(env, diffBitmap);
|
||||
@@ -149,7 +133,7 @@ jobject createResult(JNIEnv* env, ImageDiffResult const& result, bool generateDi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return resultObj;
|
||||
}
|
||||
|
||||
@@ -163,7 +147,7 @@ Java_com_google_android_filament_utils_ImageDiff_nCompareBasic(JNIEnv* env, jcla
|
||||
BitmapLock maskArg(env, maskBitmap);
|
||||
|
||||
if (!refArg.isValid() || !candArg.isValid()) {
|
||||
ImageDiffResult emptyResult;
|
||||
ImageDiffResult emptyResult;
|
||||
emptyResult.status = ImageDiffResult::Status::SIZE_MISMATCH; // or ERROR
|
||||
return createResult(env, emptyResult, false);
|
||||
}
|
||||
@@ -191,13 +175,13 @@ Java_com_google_android_filament_utils_ImageDiff_nCompareBasic(JNIEnv* env, jcla
|
||||
extern "C" JNIEXPORT jobject JNICALL
|
||||
Java_com_google_android_filament_utils_ImageDiff_nCompareJson(JNIEnv* env, jclass,
|
||||
jobject refBitmap, jobject candBitmap, jstring jsonConfig, jobject maskBitmap) {
|
||||
|
||||
|
||||
BitmapLock refArg(env, refBitmap);
|
||||
BitmapLock candArg(env, candBitmap);
|
||||
BitmapLock maskArg(env, maskBitmap);
|
||||
|
||||
if (!refArg.isValid() || !candArg.isValid()) {
|
||||
ImageDiffResult emptyResult;
|
||||
ImageDiffResult emptyResult;
|
||||
emptyResult.status = ImageDiffResult::Status::SIZE_MISMATCH; // or ERROR
|
||||
return createResult(env, emptyResult, false);
|
||||
}
|
||||
@@ -205,7 +189,7 @@ Java_com_google_android_filament_utils_ImageDiff_nCompareJson(JNIEnv* env, jclas
|
||||
ImageDiffConfig config;
|
||||
const char* nativeJson = env->GetStringUTFChars(jsonConfig, 0);
|
||||
size_t length = env->GetStringUTFLength(jsonConfig);
|
||||
|
||||
|
||||
bool parsed = parseConfig(nativeJson, length, &config);
|
||||
env->ReleaseStringUTFChars(jsonConfig, nativeJson);
|
||||
|
||||
@@ -230,3 +214,4 @@ Java_com_google_android_filament_utils_ImageDiff_nCompareJson(JNIEnv* env, jclas
|
||||
|
||||
return createResult(env, result, generateDiff);
|
||||
}
|
||||
|
||||
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2026 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.utils;
|
||||
|
||||
import com.google.android.filament.Engine;
|
||||
|
||||
public class DeviceUtils {
|
||||
public static String getGpuDriverInfo(Engine engine) {
|
||||
return nGetGpuDriverInfo(engine.getNativeObject());
|
||||
}
|
||||
private static native String nGetGpuDriverInfo(long nativeEngine);
|
||||
}
|
||||
+20
-54
@@ -106,8 +106,8 @@ class ModelViewer(
|
||||
var skyboxCubemap: Texture? = null
|
||||
|
||||
private lateinit var displayHelper: DisplayHelper
|
||||
private var cameraManipulator: Manipulator? = null
|
||||
private var gestureDetector: GestureDetector? = null
|
||||
private lateinit var cameraManipulator: Manipulator
|
||||
private lateinit var gestureDetector: GestureDetector
|
||||
private var surfaceView: SurfaceView? = null
|
||||
private var textureView: TextureView? = null
|
||||
|
||||
@@ -157,13 +157,15 @@ class ModelViewer(
|
||||
surfaceView: SurfaceView,
|
||||
engine: Engine = Engine.create(),
|
||||
uiHelper: UiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK),
|
||||
manipulator: Manipulator? = defaultCameraManipulator(surfaceView.width, surfaceView.height)
|
||||
manipulator: Manipulator? = null
|
||||
) : 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
|
||||
cameraManipulator = manipulator
|
||||
cameraManipulator?.let { c ->
|
||||
gestureDetector = GestureDetector(surfaceView, c)
|
||||
}
|
||||
gestureDetector = GestureDetector(surfaceView, cameraManipulator)
|
||||
displayHelper = DisplayHelper(surfaceView.context)
|
||||
uiHelper.renderCallback = SurfaceCallback()
|
||||
uiHelper.attachTo(surfaceView)
|
||||
@@ -175,14 +177,15 @@ class ModelViewer(
|
||||
textureView: TextureView,
|
||||
engine: Engine = Engine.create(),
|
||||
uiHelper: UiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK),
|
||||
manipulator: Manipulator? = defaultCameraManipulator(textureView.width, textureView.height)
|
||||
manipulator: Manipulator? = null
|
||||
) : this(engine, uiHelper) {
|
||||
cameraManipulator = manipulator
|
||||
cameraManipulator = manipulator ?: Manipulator.Builder()
|
||||
.targetPosition(kDefaultObjectPosition.x, kDefaultObjectPosition.y, kDefaultObjectPosition.z)
|
||||
.viewport(textureView.width, textureView.height)
|
||||
.build(Manipulator.Mode.ORBIT)
|
||||
|
||||
this.textureView = textureView
|
||||
cameraManipulator = manipulator
|
||||
cameraManipulator?.let { c ->
|
||||
gestureDetector = GestureDetector(textureView, c)
|
||||
}
|
||||
gestureDetector = GestureDetector(textureView, cameraManipulator)
|
||||
displayHelper = DisplayHelper(textureView.context)
|
||||
uiHelper.renderCallback = SurfaceCallback()
|
||||
uiHelper.attachTo(textureView)
|
||||
@@ -266,35 +269,6 @@ class ModelViewer(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the model's transform, animation, and camera state to defaults.
|
||||
* Call this when reusing the same model across multiple tests.
|
||||
*/
|
||||
fun resetToDefaultState() {
|
||||
// 1. Reset Camera parameters
|
||||
cameraFocalLength = 28f
|
||||
cameraNear = kNearPlane
|
||||
cameraFar = kFarPlane
|
||||
updateCameraProjection()
|
||||
|
||||
// 2. Reset the manipulator's look-at vectors to initial state
|
||||
cameraManipulator?.let { cm ->
|
||||
cm.jumpToBookmark(cm.homeBookmark)
|
||||
}
|
||||
|
||||
// 3. Reset Animations
|
||||
animator?.let {
|
||||
if (it.animationCount > 0) {
|
||||
it.applyAnimation(0, 0.0f)
|
||||
}
|
||||
it.updateBoneMatrices()
|
||||
}
|
||||
|
||||
// 4. Re-apply the unit cube transform to clear custom scaling/translation
|
||||
clearRootTransform()
|
||||
transformToUnitCube()
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees all entities associated with the most recently-loaded model.
|
||||
*/
|
||||
@@ -328,13 +302,11 @@ class ModelViewer(
|
||||
asset?.let { populateScene(it) }
|
||||
|
||||
// Extract the camera basis from the helper and push it to the Filament camera.
|
||||
cameraManipulator?.let { cm ->
|
||||
cm.getLookAt(eyePos, target, upward)
|
||||
camera.lookAt(
|
||||
cameraManipulator.getLookAt(eyePos, target, upward)
|
||||
camera.lookAt(
|
||||
eyePos[0], eyePos[1], eyePos[2],
|
||||
target[0], target[1], target[2],
|
||||
upward[0], upward[1], upward[2])
|
||||
}
|
||||
|
||||
// Render the scene, unless the renderer wants to skip the frame.
|
||||
if (renderer.beginFrame(swapChain!!, frameTimeNanos)) {
|
||||
@@ -426,7 +398,7 @@ class ModelViewer(
|
||||
* Handles a [MotionEvent] to enable one-finger orbit, two-finger pan, and pinch-to-zoom.
|
||||
*/
|
||||
fun onTouchEvent(event: MotionEvent) {
|
||||
gestureDetector?.onTouchEvent(event)
|
||||
gestureDetector.onTouchEvent(event)
|
||||
}
|
||||
|
||||
@SuppressWarnings("ClickableViewAccessibility")
|
||||
@@ -479,7 +451,7 @@ class ModelViewer(
|
||||
|
||||
override fun onResized(width: Int, height: Int) {
|
||||
view.viewport = Viewport(0, 0, width, height)
|
||||
cameraManipulator?.setViewport(width, height)
|
||||
cameraManipulator.setViewport(width, height)
|
||||
updateCameraProjection()
|
||||
synchronizePendingFrames(engine)
|
||||
}
|
||||
@@ -496,11 +468,5 @@ class ModelViewer(
|
||||
|
||||
companion object {
|
||||
private val kDefaultObjectPosition = Float3(0.0f, 0.0f, -4.0f)
|
||||
private fun defaultCameraManipulator(width: Int, height: Int) : Manipulator {
|
||||
return Manipulator.Builder()
|
||||
.targetPosition(kDefaultObjectPosition.x, kDefaultObjectPosition.y, kDefaultObjectPosition.z)
|
||||
.viewport(width, height)
|
||||
.build(Manipulator.Mode.ORBIT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
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"
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.70.2
|
||||
VERSION_NAME=1.69.4
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -94,13 +94,6 @@ afterEvaluate { project ->
|
||||
}
|
||||
|
||||
signing {
|
||||
def signingKey = findProperty("signingKey")
|
||||
def signingPassword = findProperty("signingPassword")
|
||||
if (signingKey && signingPassword) {
|
||||
println("Signing with in-memory keys")
|
||||
useInMemoryPgpKeys(signingKey, signingPassword)
|
||||
}
|
||||
|
||||
publishing.publications.all { publication ->
|
||||
sign publication
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
@@ -26,8 +26,7 @@ tasks.register('copyDamagedHelmetGltf', Copy) {
|
||||
preBuild.dependsOn copyDamagedHelmetGltf
|
||||
|
||||
clean.doFirst {
|
||||
delete "src/main/assets/envs"
|
||||
delete "src/main/assets/models"
|
||||
delete "src/main/assets"
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -49,7 +48,6 @@ android {
|
||||
dependencies {
|
||||
implementation deps.kotlin
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'com.google.android.material:material:1.10.0'
|
||||
implementation deps.coroutines.core
|
||||
implementation project(':filament-android')
|
||||
implementation project(':gltfio-android')
|
||||
|
||||
@@ -12,11 +12,10 @@
|
||||
android:supportsRtl="true"
|
||||
android:largeHeap="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:theme="@style/Theme.Material3.DayNight.NoActionBar">
|
||||
android:theme="@android:style/Theme.NoTitleBar">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
|
||||
android:screenOrientation="fullSensor">
|
||||
<intent-filter>
|
||||
|
||||
@@ -1,899 +0,0 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+93
-501
@@ -17,35 +17,36 @@
|
||||
package com.google.android.filament.validation
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.text.Html
|
||||
import android.util.Log
|
||||
import android.view.Choreographer
|
||||
import android.view.SurfaceView
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Spinner
|
||||
import android.widget.ScrollView
|
||||
import android.widget.TextView
|
||||
import com.google.android.filament.utils.KTX1Loader
|
||||
import com.google.android.filament.utils.ModelViewer
|
||||
import com.google.android.filament.utils.Utils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import com.google.android.filament.utils.KTX1Loader
|
||||
import com.google.android.filament.IndirectLight
|
||||
import com.google.android.filament.Skybox
|
||||
import android.graphics.Color
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.nio.ByteBuffer
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Button
|
||||
import android.widget.Spinner
|
||||
import android.widget.AdapterView
|
||||
|
||||
class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
|
||||
@@ -61,33 +62,13 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
private lateinit var choreographer: Choreographer
|
||||
private lateinit var modelViewer: ModelViewer
|
||||
private lateinit var statusTextView: TextView
|
||||
private lateinit var testResultsHeader: TextView
|
||||
private lateinit var resultsContainer: LinearLayout
|
||||
private lateinit var inputManager: ValidationInputManager
|
||||
private var currentInput: ValidationInputManager.ValidationInput? = null
|
||||
|
||||
private var currentAlphaDiffBitmap: Bitmap? = null
|
||||
private var globalEnhancementFactor: Float = 1.0f
|
||||
|
||||
private data class TestImages(
|
||||
val testName: String,
|
||||
val golden: Bitmap?,
|
||||
val rendered: Bitmap?,
|
||||
val diff: Bitmap?,
|
||||
val alphaDiff: Bitmap?
|
||||
)
|
||||
|
||||
private val diffImageViews = mutableListOf<ImageView>()
|
||||
|
||||
// UI Elements
|
||||
private lateinit var modeSpinner: Spinner
|
||||
private lateinit var runButton: Button
|
||||
private lateinit var loadButton: Button
|
||||
private lateinit var optionsButton: Button
|
||||
private lateinit var enhancementContainer: LinearLayout
|
||||
private lateinit var enhancementLabel: TextView
|
||||
private lateinit var enhancementSlider: android.widget.SeekBar
|
||||
|
||||
private var resultManager: ValidationResultManager? = null
|
||||
|
||||
private var validationRunner: ValidationRunner? = null
|
||||
|
||||
// Frame callback
|
||||
@@ -108,74 +89,29 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
surfaceView.holder.setFixedSize(512, 512)
|
||||
|
||||
statusTextView = findViewById(R.id.status_text)
|
||||
testResultsHeader = findViewById(R.id.test_results_header)
|
||||
modeSpinner = findViewById(R.id.mode_spinner)
|
||||
runButton = findViewById(R.id.run_button)
|
||||
resultsContainer = findViewById(R.id.results_container)
|
||||
|
||||
runButton = findViewById(R.id.run_button)
|
||||
loadButton = findViewById(R.id.load_button)
|
||||
optionsButton = findViewById(R.id.options_button)
|
||||
enhancementContainer = findViewById(R.id.enhancement_container)
|
||||
enhancementLabel = findViewById(R.id.enhancement_label)
|
||||
enhancementSlider = findViewById(R.id.enhancement_slider)
|
||||
|
||||
enhancementSlider.setOnSeekBarChangeListener(object : android.widget.SeekBar.OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(seekBar: android.widget.SeekBar?, progress: Int, fromUser: Boolean) {
|
||||
globalEnhancementFactor = 1.0f + (progress / 100f) * 49.0f
|
||||
enhancementLabel.text = String.format(Locale.US, "Enhancement: %.1fx", globalEnhancementFactor)
|
||||
applyGlobalEnhancement()
|
||||
}
|
||||
override fun onStartTrackingTouch(seekBar: android.widget.SeekBar?) {}
|
||||
override fun onStopTrackingTouch(seekBar: android.widget.SeekBar?) {}
|
||||
})
|
||||
// Setup Spinner
|
||||
val modes = arrayOf("Run Validation", "Generate Goldens")
|
||||
val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, modes)
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
modeSpinner.adapter = adapter
|
||||
|
||||
// Setup Run Button
|
||||
runButton.setOnClickListener {
|
||||
currentInput?.let { input ->
|
||||
// Always use the generateGoldens flag from the intent/input
|
||||
startValidation(input)
|
||||
val generateGoldens = modeSpinner.selectedItemPosition == 1
|
||||
val newInput = input.copy(generateGoldens = generateGoldens)
|
||||
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)
|
||||
|
||||
choreographer = Choreographer.getInstance()
|
||||
modelViewer = ModelViewer(surfaceView=surfaceView, manipulator=null)
|
||||
modelViewer = ModelViewer(surfaceView)
|
||||
inputManager = ValidationInputManager(this)
|
||||
|
||||
// Initialize IBL
|
||||
@@ -184,135 +120,6 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
handleIntent()
|
||||
}
|
||||
|
||||
private fun showLoadDialog() {
|
||||
val exportDir = getExternalFilesDir(null) ?: filesDir
|
||||
// Filter out result zips (starting with "results_") to only show test bundles
|
||||
val zips = exportDir.listFiles { _, name ->
|
||||
name.endsWith(".zip") && !name.startsWith("results_")
|
||||
}?.sortedByDescending { it.lastModified() } ?: emptyList()
|
||||
|
||||
if (zips.isEmpty()) {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle("Load Test")
|
||||
.setMessage("No test bundles found.")
|
||||
.setPositiveButton("OK", null)
|
||||
.show()
|
||||
return
|
||||
}
|
||||
|
||||
val builder = AlertDialog.Builder(this)
|
||||
builder.setTitle("Select Test Bundle")
|
||||
|
||||
val items = zips.map { it.name }.toTypedArray()
|
||||
|
||||
builder.setItems(items) { dialog, which ->
|
||||
val selectedFile = zips[which]
|
||||
loadZipBundle(selectedFile)
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
builder.setNegativeButton("Cancel", null)
|
||||
builder.show()
|
||||
}
|
||||
|
||||
private fun showTestAdbInfo() {
|
||||
val exportDir = getExternalFilesDir(null) ?: filesDir
|
||||
val path = exportDir.absolutePath
|
||||
val isInternal = path.startsWith(filesDir.absolutePath)
|
||||
val message = StringBuilder()
|
||||
|
||||
message.append("Storage Path: $path<br><br>")
|
||||
|
||||
message.append("<b>--- PULL FROM DEVICE ---</b><br>")
|
||||
if (isInternal) {
|
||||
message.append("<tt>adb shell \"run-as $packageName cat files/<filename>\" > <filename></tt><br><br>")
|
||||
} else {
|
||||
message.append("<tt>adb pull $path/<filename> .</tt><br><br>")
|
||||
}
|
||||
|
||||
message.append("<b>--- PUSH TO DEVICE ---</b><br>")
|
||||
if (isInternal) {
|
||||
message.append("1. <tt>adb push <filename> /sdcard/Download/</tt><br>")
|
||||
message.append("2. <tt>adb shell \"run-as $packageName cp /sdcard/Download/<filename> files/\"</tt><br>")
|
||||
} else {
|
||||
message.append("<tt>adb push <filename> $path/</tt><br>")
|
||||
}
|
||||
message.append("<br>Note: Use underscores instead of spaces in <filename>.")
|
||||
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle("Test Bundle ADB Info")
|
||||
.setMessage(Html.fromHtml(message.toString(), Html.FROM_HTML_MODE_LEGACY))
|
||||
.setPositiveButton("OK", null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun showResultAdbInfo() {
|
||||
val exportDir = getExternalFilesDir(null) ?: filesDir
|
||||
val path = exportDir.absolutePath
|
||||
val isInternal = path.startsWith(filesDir.absolutePath)
|
||||
val message = StringBuilder()
|
||||
|
||||
message.append("<b>--- PULL RESULTS ---</b><br>")
|
||||
if (isInternal) {
|
||||
message.append("<tt>adb shell \"run-as $packageName cat files/<filename>\" > <filename></tt><br><br>")
|
||||
} else {
|
||||
message.append("<tt>adb pull $path/<filename> .</tt><br><br>")
|
||||
}
|
||||
|
||||
message.append("<b>--- AVAILABLE RESULTS ---</b><br>")
|
||||
val zips = exportDir.listFiles { _, name ->
|
||||
name.endsWith(".zip") && name.startsWith("results_")
|
||||
}?.sortedByDescending { it.lastModified() } ?: emptyList()
|
||||
|
||||
if (zips.isEmpty()) {
|
||||
message.append("No result zips found.<br>")
|
||||
} else {
|
||||
zips.forEach { file ->
|
||||
message.append("${file.name}<br>")
|
||||
}
|
||||
}
|
||||
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle("Result ADB Info")
|
||||
.setMessage(Html.fromHtml(message.toString(), Html.FROM_HTML_MODE_LEGACY))
|
||||
.setPositiveButton("OK", null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun loadZipBundle(file: File) {
|
||||
statusTextView.text = "Loading ${file.name}..."
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
try {
|
||||
val config = inputManager.loadFromZip(file)
|
||||
val baseDir = getExternalFilesDir(null) ?: filesDir
|
||||
val outputDir = File(baseDir, "validation_results").apply { mkdirs() }
|
||||
|
||||
// Clear existing results UI and state
|
||||
resultsContainer.removeAllViews()
|
||||
diffImageViews.clear()
|
||||
resultManager = null
|
||||
|
||||
val newInput = ValidationInputManager.ValidationInput(
|
||||
config = config,
|
||||
outputDir = outputDir,
|
||||
generateGoldens = false,
|
||||
autoRun = false,
|
||||
autoExport = false,
|
||||
autoExportResults = false,
|
||||
sourceZip = file
|
||||
)
|
||||
|
||||
currentInput = newInput
|
||||
statusTextView.text = "Loaded ${config.name}"
|
||||
Log.i(TAG, "Setting header to: Test Results: ${config.name}")
|
||||
testResultsHeader.text = "${config.name}"
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to load zip", e)
|
||||
statusTextView.text = "Error: ${e.message}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createIndirectLight() {
|
||||
try {
|
||||
val engine = modelViewer.engine
|
||||
@@ -349,18 +156,12 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
try {
|
||||
val input = inputManager.resolveConfig(intent)
|
||||
|
||||
// Update header
|
||||
Log.i(TAG, "handleIntent: Setting header to: Test Results: ${input.config.name}")
|
||||
testResultsHeader.text = "${input.config.name}"
|
||||
currentInput = input
|
||||
|
||||
if (input.autoRun) {
|
||||
startValidation(input)
|
||||
} else {
|
||||
// Just show status
|
||||
statusTextView.text = "Ready: ${input.config.name}"
|
||||
}
|
||||
// Sync spinner with intent
|
||||
modeSpinner.setSelection(if (input.generateGoldens) 1 else 0)
|
||||
|
||||
startValidation(input)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to resolve config", e)
|
||||
statusTextView.text = "Error: ${e.message}"
|
||||
@@ -368,35 +169,22 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
}
|
||||
}
|
||||
|
||||
private fun createResultManager(outputDir: File): ValidationResultManager {
|
||||
val gpuDriverInfo = com.google.android.filament.utils.DeviceUtils.getGpuDriverInfo(modelViewer.engine)
|
||||
return ValidationResultManager(
|
||||
outputDir = outputDir,
|
||||
gpuDriverInfo = gpuDriverInfo,
|
||||
deviceName = android.os.Build.MODEL,
|
||||
deviceCodeName = android.os.Build.DEVICE,
|
||||
androidVersion = android.os.Build.VERSION.RELEASE,
|
||||
androidBuildNumber = android.os.Build.DISPLAY
|
||||
)
|
||||
}
|
||||
|
||||
private fun startValidation(input: ValidationInputManager.ValidationInput) {
|
||||
try {
|
||||
resultsContainer.removeAllViews()
|
||||
diffImageViews.clear()
|
||||
enhancementSlider.isEnabled = false
|
||||
Log.i(TAG, "Starting validation with config: ${input.config.name}")
|
||||
Log.i(TAG, "Output dir: ${input.outputDir.absolutePath}")
|
||||
|
||||
testResultsHeader.text = "${input.config.name}"
|
||||
|
||||
resultManager = createResultManager(input.outputDir)
|
||||
resultManager = ValidationResultManager(input.outputDir)
|
||||
|
||||
validationRunner = ValidationRunner(this, modelViewer, input.config, resultManager!!)
|
||||
validationRunner?.callback = this
|
||||
validationRunner?.generateGoldens = input.generateGoldens
|
||||
validationRunner?.start()
|
||||
|
||||
// Sync spinner in case it was called programmatically or changed implicitly
|
||||
modeSpinner.setSelection(if (input.generateGoldens) 1 else 0)
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to start validation", e)
|
||||
statusTextView.text = "Error: ${e.message}"
|
||||
@@ -408,12 +196,6 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
choreographer.postFrameCallback(frameScheduler)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
setIntent(intent)
|
||||
handleIntent()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
choreographer.removeFrameCallback(frameScheduler)
|
||||
@@ -439,44 +221,19 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
resultContainer.orientation = LinearLayout.VERTICAL
|
||||
resultContainer.setPadding(0, 10, 0, 20)
|
||||
|
||||
// Header Layout
|
||||
val headerRow = LinearLayout(this)
|
||||
headerRow.orientation = LinearLayout.HORIZONTAL
|
||||
headerRow.gravity = android.view.Gravity.CENTER_VERTICAL
|
||||
|
||||
// Status Icon + Name
|
||||
val statusView = TextView(this)
|
||||
val icon = if (result.passed) "✔" else "✖"
|
||||
|
||||
statusView.text = "$icon ${result.testName}"
|
||||
statusView.setTextColor(
|
||||
if (result.passed) Color.parseColor("#4CAF50") else Color.parseColor("#F44336")
|
||||
)
|
||||
statusView.textSize = 12f
|
||||
statusView.setTypeface(null, android.graphics.Typeface.BOLD)
|
||||
headerRow.addView(statusView)
|
||||
|
||||
// Diff Metric (only show if it's relevant/there's a diff or we just always show it like before)
|
||||
val diffView = TextView(this)
|
||||
diffView.text = " (Diff: ${result.diffMetric})"
|
||||
diffView.textSize = 12f
|
||||
diffView.setTextColor(Color.GRAY)
|
||||
headerRow.addView(diffView)
|
||||
resultContainer.addView(headerRow)
|
||||
// Header
|
||||
val resultView = TextView(this)
|
||||
resultView.text = "${result.testName}: ${if(result.passed) "PASS" else "FAIL"} (Diff: ${result.diffMetric})"
|
||||
resultView.setTextColor(if(result.passed) Color.GREEN else Color.RED)
|
||||
resultView.textSize = 16f
|
||||
resultView.setTypeface(null, android.graphics.Typeface.BOLD)
|
||||
resultContainer.addView(resultView)
|
||||
|
||||
// Images Row
|
||||
val imagesRow = LinearLayout(this)
|
||||
imagesRow.orientation = LinearLayout.HORIZONTAL
|
||||
|
||||
val testImages = TestImages(
|
||||
testName = result.testName,
|
||||
golden = currentGoldenBitmap,
|
||||
rendered = currentRenderedBitmap,
|
||||
diff = currentDiffBitmap,
|
||||
alphaDiff = currentAlphaDiffBitmap
|
||||
)
|
||||
|
||||
fun addImage(label: String, bitmap: Bitmap?, isDiff: Boolean) {
|
||||
fun addImage(label: String, bitmap: Bitmap?) {
|
||||
if (bitmap != null) {
|
||||
val container = LinearLayout(this)
|
||||
container.orientation = LinearLayout.VERTICAL
|
||||
@@ -484,7 +241,7 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
|
||||
val labelView = TextView(this)
|
||||
labelView.text = label
|
||||
labelView.textSize = 9f
|
||||
labelView.textSize = 12f
|
||||
container.addView(labelView)
|
||||
|
||||
val iv = ImageView(this)
|
||||
@@ -492,29 +249,16 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
iv.layoutParams = LinearLayout.LayoutParams(250, 250) // Smaller thumbnails
|
||||
iv.scaleType = ImageView.ScaleType.FIT_CENTER
|
||||
iv.setBackgroundColor(0xFF404040.toInt())
|
||||
|
||||
if (isDiff) {
|
||||
diffImageViews.add(iv)
|
||||
applyEnhancementToView(iv, globalEnhancementFactor)
|
||||
}
|
||||
|
||||
iv.setOnClickListener {
|
||||
showImageDialog(testImages, label)
|
||||
}
|
||||
|
||||
container.addView(iv)
|
||||
|
||||
imagesRow.addView(container)
|
||||
}
|
||||
}
|
||||
|
||||
addImage("Rendered", currentRenderedBitmap, false)
|
||||
addImage("Golden", currentGoldenBitmap, false)
|
||||
addImage("Rendered", currentRenderedBitmap)
|
||||
addImage("Golden", currentGoldenBitmap)
|
||||
if (!result.passed) {
|
||||
addImage("Diff", currentDiffBitmap, true)
|
||||
}
|
||||
if (currentAlphaDiffBitmap != null) {
|
||||
addImage("Alpha Diff", currentAlphaDiffBitmap, true)
|
||||
addImage("Diff", currentDiffBitmap)
|
||||
}
|
||||
|
||||
resultContainer.addView(imagesRow)
|
||||
@@ -524,54 +268,13 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
currentRenderedBitmap = null
|
||||
currentGoldenBitmap = null
|
||||
currentDiffBitmap = null
|
||||
currentAlphaDiffBitmap = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAllTestsFinished() {
|
||||
runOnUiThread {
|
||||
statusTextView.text = "All tests finished!"
|
||||
enhancementSlider.isEnabled = true
|
||||
Log.i(TAG, "All tests finished " + if (currentInput?.autoExport == true) "Exporting bundle" else "x")
|
||||
|
||||
if (currentInput?.autoExport == true) {
|
||||
exportTestBundleAction()
|
||||
}
|
||||
if (currentInput?.autoExportResults == true) {
|
||||
exportTestResultsAction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun exportTestBundleAction() {
|
||||
currentInput?.let { input ->
|
||||
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
|
||||
val rm = resultManager ?: createResultManager(input.outputDir)
|
||||
val zip = rm.exportTestBundle(input.config, timestamp)
|
||||
if (zip != null) {
|
||||
val msg = "Exported Bundle: ${zip.name}"
|
||||
statusTextView.text = msg
|
||||
Log.i(TAG, "Exported test bundle to ${zip.absolutePath}")
|
||||
} else {
|
||||
statusTextView.text = "Export Bundle failed"
|
||||
Log.e(TAG, "Export Bundle failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun exportTestResultsAction() {
|
||||
currentInput?.let { input ->
|
||||
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
|
||||
val rm = resultManager ?: createResultManager(input.outputDir)
|
||||
val zip = rm.exportTestResults(input.sourceZip, timestamp)
|
||||
if (zip != null) {
|
||||
val msg = "Exported Results: ${zip.name}"
|
||||
statusTextView.text = msg
|
||||
Log.i(TAG, "Exported results to ${zip.absolutePath}")
|
||||
} else {
|
||||
statusTextView.text = "Export Results failed"
|
||||
Log.e(TAG, "Export Results failed")
|
||||
}
|
||||
Log.i(TAG, "All tests finished")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,165 +297,54 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
"Diff" -> {
|
||||
currentDiffBitmap = bitmap
|
||||
}
|
||||
"Alpha Diff" -> {
|
||||
currentAlphaDiffBitmap = bitmap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyEnhancementToView(iv: ImageView, factor: Float) {
|
||||
val cm = android.graphics.ColorMatrix()
|
||||
cm.setScale(factor, factor, factor, 1.0f)
|
||||
iv.colorFilter = android.graphics.ColorMatrixColorFilter(cm)
|
||||
}
|
||||
|
||||
private fun applyGlobalEnhancement() {
|
||||
for (iv in diffImageViews) {
|
||||
applyEnhancementToView(iv, globalEnhancementFactor)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showImageDialog(images: TestImages, initialLabel: String) {
|
||||
val dialogView = layoutInflater.inflate(R.layout.dialog_image_viewer, null)
|
||||
val dialog = AlertDialog.Builder(this)
|
||||
.setView(dialogView)
|
||||
.create()
|
||||
|
||||
val titleView = dialogView.findViewById<TextView>(R.id.dialog_title)
|
||||
val typeView = dialogView.findViewById<TextView>(R.id.dialog_image_type)
|
||||
val imageView = dialogView.findViewById<ImageView>(R.id.dialog_image)
|
||||
val btnClose = dialogView.findViewById<View>(R.id.btn_close)
|
||||
val btnReset = dialogView.findViewById<View>(R.id.btn_reset)
|
||||
val btnPrev = dialogView.findViewById<View>(R.id.btn_prev)
|
||||
val btnNext = dialogView.findViewById<View>(R.id.btn_next)
|
||||
|
||||
val enhancementContainer = dialogView.findViewById<View>(R.id.dialog_enhancement_container)
|
||||
val enhancementLabel = dialogView.findViewById<TextView>(R.id.dialog_enhancement_label)
|
||||
val enhancementSlider = dialogView.findViewById<android.widget.SeekBar>(R.id.dialog_enhancement_slider)
|
||||
|
||||
titleView.text = images.testName
|
||||
|
||||
val availableImages = mutableListOf<Pair<String, Bitmap>>()
|
||||
images.rendered?.let { availableImages.add(Pair("Rendered", it)) }
|
||||
images.golden?.let { availableImages.add(Pair("Golden", it)) }
|
||||
images.diff?.let { availableImages.add(Pair("Diff", it)) }
|
||||
images.alphaDiff?.let { availableImages.add(Pair("Alpha Diff", it)) }
|
||||
|
||||
if (availableImages.isEmpty()) return
|
||||
|
||||
var currentIndex = availableImages.indexOfFirst { it.first == initialLabel }
|
||||
if (currentIndex == -1) currentIndex = 0
|
||||
|
||||
var currentDialogEnhancement = globalEnhancementFactor
|
||||
|
||||
val matrix = android.graphics.Matrix()
|
||||
// Save initial values for translation tracking
|
||||
var lastTouchX = 0f
|
||||
var lastTouchY = 0f
|
||||
var isDragging = false
|
||||
|
||||
val scaleDetector = android.view.ScaleGestureDetector(this, object : android.view.ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
||||
override fun onScale(detector: android.view.ScaleGestureDetector): Boolean {
|
||||
matrix.postScale(detector.scaleFactor, detector.scaleFactor, detector.focusX, detector.focusY)
|
||||
imageView.imageMatrix = matrix
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
imageView.setOnTouchListener { _, event ->
|
||||
scaleDetector.onTouchEvent(event)
|
||||
when (event.actionMasked) {
|
||||
android.view.MotionEvent.ACTION_DOWN -> {
|
||||
lastTouchX = event.x
|
||||
lastTouchY = event.y
|
||||
isDragging = true
|
||||
}
|
||||
android.view.MotionEvent.ACTION_MOVE -> {
|
||||
if (isDragging && !scaleDetector.isInProgress) {
|
||||
val dx = event.x - lastTouchX
|
||||
val dy = event.y - lastTouchY
|
||||
matrix.postTranslate(dx, dy)
|
||||
imageView.imageMatrix = matrix
|
||||
}
|
||||
lastTouchX = event.x
|
||||
lastTouchY = event.y
|
||||
}
|
||||
android.view.MotionEvent.ACTION_UP, android.view.MotionEvent.ACTION_CANCEL -> {
|
||||
isDragging = false
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fun updateView() {
|
||||
val (label, bitmap) = availableImages[currentIndex]
|
||||
typeView.text = label
|
||||
imageView.setImageBitmap(bitmap)
|
||||
(imageView.drawable as? android.graphics.drawable.BitmapDrawable)?.setAntiAlias(false)
|
||||
(imageView.drawable as? android.graphics.drawable.BitmapDrawable)?.setFilterBitmap(false)
|
||||
imageView.imageMatrix = matrix
|
||||
|
||||
if (label == "Diff" || label == "Alpha Diff") {
|
||||
enhancementContainer.visibility = View.VISIBLE
|
||||
applyEnhancementToView(imageView, currentDialogEnhancement)
|
||||
} else {
|
||||
enhancementContainer.visibility = View.GONE
|
||||
imageView.colorFilter = null
|
||||
}
|
||||
}
|
||||
|
||||
fun resetMatrix() {
|
||||
val drawable = imageView.drawable ?: return
|
||||
val width = imageView.width.toFloat()
|
||||
val height = imageView.height.toFloat()
|
||||
val dw = drawable.intrinsicWidth.toFloat()
|
||||
val dh = drawable.intrinsicHeight.toFloat()
|
||||
|
||||
val scaleX = width / dw
|
||||
val scaleY = height / dh
|
||||
val scale = Math.min(scaleX, scaleY)
|
||||
|
||||
val dx = (width - dw * scale) / 2f
|
||||
val dy = (height - dh * scale) / 2f
|
||||
|
||||
matrix.reset()
|
||||
matrix.postScale(scale, scale)
|
||||
matrix.postTranslate(dx, dy)
|
||||
imageView.imageMatrix = matrix
|
||||
}
|
||||
|
||||
btnClose.setOnClickListener { dialog.dismiss() }
|
||||
btnReset.setOnClickListener { resetMatrix() }
|
||||
btnPrev.setOnClickListener {
|
||||
currentIndex = (currentIndex - 1 + availableImages.size) % availableImages.size
|
||||
updateView()
|
||||
}
|
||||
btnNext.setOnClickListener {
|
||||
currentIndex = (currentIndex + 1) % availableImages.size
|
||||
updateView()
|
||||
}
|
||||
|
||||
val defaultProgress = ((currentDialogEnhancement - 1.0f) / 49.0f * 100).toInt()
|
||||
val safeProgress = Math.max(0, Math.min(100, defaultProgress))
|
||||
enhancementSlider.progress = safeProgress
|
||||
enhancementLabel.text = String.format(Locale.US, "Enhance: %.1fx", currentDialogEnhancement)
|
||||
|
||||
enhancementSlider.setOnSeekBarChangeListener(object : android.widget.SeekBar.OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(seekBar: android.widget.SeekBar?, progress: Int, fromUser: Boolean) {
|
||||
currentDialogEnhancement = 1.0f + (progress / 100f) * 49.0f
|
||||
enhancementLabel.text = String.format(Locale.US, "Enhance: %.1fx", currentDialogEnhancement)
|
||||
updateView()
|
||||
}
|
||||
override fun onStartTrackingTouch(seekBar: android.widget.SeekBar?) {}
|
||||
override fun onStopTrackingTouch(seekBar: android.widget.SeekBar?) {}
|
||||
})
|
||||
|
||||
imageView.post {
|
||||
resetMatrix()
|
||||
}
|
||||
|
||||
updateView()
|
||||
dialog.show()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Scripts for reference:
|
||||
*
|
||||
* generate_goldens.sh:
|
||||
* --------------------
|
||||
* #!/bin/bash
|
||||
* set -e
|
||||
*
|
||||
* # Config path (on device)
|
||||
* CONFIG_PATH=$1
|
||||
* if [ -z "$CONFIG_PATH" ]; then
|
||||
* echo "Usage: $0 <device_config_path>"
|
||||
* echo "Example: $0 /sdcard/Android/data/com.google.android.filament.validation/files/default_test.json"
|
||||
* exit 1
|
||||
* fi
|
||||
*
|
||||
* echo "Starting Golden Generation for $CONFIG_PATH..."
|
||||
* adb shell am force-stop com.google.android.filament.validation
|
||||
* adb shell am start -n com.google.android.filament.validation/.MainActivity \
|
||||
* -e test_config "$CONFIG_PATH" \
|
||||
* --ez generate_goldens true
|
||||
*
|
||||
* echo "Check device or logcat for progress."
|
||||
* echo "adb logcat -s FilamentValidation:I ValidationRunner:I"
|
||||
* echo "To pull results: ./samples/sample-render-validation/pull_goldens.sh"
|
||||
*
|
||||
* pull_goldens.sh:
|
||||
* ----------------
|
||||
* #!/bin/bash
|
||||
* set -e
|
||||
*
|
||||
* # Default destination is local golden directory relative to script
|
||||
* SCRIPT_DIR=$(cd $(dirname $0); pwd)
|
||||
* DEST_DIR=${1:-"$SCRIPT_DIR/golden"}
|
||||
*
|
||||
* echo "Pulling goldens to $DEST_DIR..."
|
||||
* mkdir -p "$DEST_DIR"
|
||||
*
|
||||
* # Path on device
|
||||
* DEVICE_GOLDEN_DIR="/storage/emulated/0/Android/data/com.google.android.filament.validation/files/golden/."
|
||||
*
|
||||
* adb pull "$DEVICE_GOLDEN_DIR" "$DEST_DIR"
|
||||
*
|
||||
* echo "Done."
|
||||
* ls -l "$DEST_DIR"
|
||||
*/
|
||||
|
||||
+7
-101
@@ -26,7 +26,6 @@ import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
/**
|
||||
* Handles the retrieval and preparation of test configuration and assets.
|
||||
@@ -43,11 +42,7 @@ class ValidationInputManager(private val context: Context) {
|
||||
data class ValidationInput(
|
||||
val config: RenderTestConfig,
|
||||
val outputDir: File,
|
||||
val generateGoldens: Boolean,
|
||||
val autoRun: Boolean = false,
|
||||
val autoExport: Boolean = false,
|
||||
val autoExportResults: Boolean = false,
|
||||
val sourceZip: File? = null
|
||||
val generateGoldens: Boolean
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -58,111 +53,22 @@ class ValidationInputManager(private val context: Context) {
|
||||
val testConfigPath = intent.getStringExtra("test_config")
|
||||
val urlConfig = intent.getStringExtra("url_config")
|
||||
val urlModelsBase = intent.getStringExtra("url_models_base")
|
||||
val zipPath = intent.getStringExtra("zip_path")
|
||||
val generateGoldens = intent.getBooleanExtra("generate_goldens", false)
|
||||
val autoRun = intent.getBooleanExtra("auto_run", false)
|
||||
val autoExport = intent.getBooleanExtra("auto_export", false)
|
||||
val autoExportResults = intent.getBooleanExtra("auto_export_results", false)
|
||||
val outputPath = intent.getStringExtra("output_path")
|
||||
|
||||
Log.i(TAG, "Resolving config with outputPath: $outputPath")
|
||||
|
||||
val baseDir = context.getExternalFilesDir(null) ?: context.filesDir
|
||||
Log.i(TAG, "Base directory for resolution: ${baseDir.absolutePath}")
|
||||
|
||||
val outputDir = if (!outputPath.isNullOrBlank()) {
|
||||
val file = File(outputPath)
|
||||
val resolved = if (file.isAbsolute) {
|
||||
file
|
||||
} else {
|
||||
File(baseDir, outputPath)
|
||||
}
|
||||
|
||||
// Critical check: if resolved is root or very short, it's likely wrong
|
||||
if (resolved.absolutePath == "/" || resolved.parent == null) {
|
||||
Log.w(TAG, "Resolved outputDir is root ($resolved), defaulting to app-specific dir")
|
||||
File(baseDir, "validation_results")
|
||||
} else {
|
||||
resolved
|
||||
}
|
||||
val outputDir = if (outputPath != null) {
|
||||
File(outputPath).apply { mkdirs() }
|
||||
} else {
|
||||
File(baseDir, "validation_results")
|
||||
}
|
||||
|
||||
if (!outputDir.exists() && !outputDir.mkdirs()) {
|
||||
Log.e(TAG, "Failed to create outputDir: ${outputDir.absolutePath}")
|
||||
}
|
||||
Log.i(TAG, "Final outputDir: ${outputDir.absolutePath}")
|
||||
|
||||
val sourceZipFile = if (zipPath != null) {
|
||||
val file = File(zipPath)
|
||||
if (file.isAbsolute) {
|
||||
file
|
||||
} else {
|
||||
File(baseDir, zipPath)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
File(context.getExternalFilesDir(null), "validation_results").apply { mkdirs() }
|
||||
}
|
||||
|
||||
val config = when {
|
||||
sourceZipFile != null && sourceZipFile.exists() -> loadFromZip(sourceZipFile)
|
||||
urlConfig != null -> downloadConfig(urlConfig, urlModelsBase)
|
||||
testConfigPath != null -> ConfigParser.parseFromPath(testConfigPath)
|
||||
else -> extractDefaultAssets()
|
||||
}
|
||||
|
||||
return@withContext ValidationInput(config, outputDir, generateGoldens, autoRun, autoExport, autoExportResults, sourceZipFile)
|
||||
}
|
||||
|
||||
suspend fun loadFromZip(zipFile: File): RenderTestConfig {
|
||||
Log.i(TAG, "Unzipping validation bundle: ${zipFile.absolutePath}")
|
||||
// Use a unique cache dir based on timestamp or just overwrite a common one
|
||||
// Overwriting is safer to avoid filling up disk, but we must ensure we don't conflict with current run
|
||||
val baseCacheDir = context.externalCacheDir ?: context.cacheDir
|
||||
val cacheDir = File(baseCacheDir, "unzipped_validation")
|
||||
if (cacheDir.exists()) cacheDir.deleteRecursively()
|
||||
cacheDir.mkdirs()
|
||||
|
||||
Log.i(TAG, "Using cacheDir: ${cacheDir.absolutePath}")
|
||||
|
||||
ZipFile(zipFile).use { zip ->
|
||||
val entries = zip.entries()
|
||||
while (entries.hasMoreElements()) {
|
||||
val entry = entries.nextElement()
|
||||
val entryFile = File(cacheDir, entry.name)
|
||||
// specific check to avoid zip slip vulnerability (though low risk here)
|
||||
if (!entryFile.canonicalPath.startsWith(cacheDir.canonicalPath)) {
|
||||
throw SecurityException("Zip entry is outside of the target dir: ${entry.name}")
|
||||
}
|
||||
|
||||
if (entry.isDirectory) {
|
||||
entryFile.mkdirs()
|
||||
} else {
|
||||
entryFile.parentFile?.mkdirs()
|
||||
zip.getInputStream(entry).use { input ->
|
||||
FileOutputStream(entryFile).use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find config.json
|
||||
// We look for a file ending in .json within the unzipped structure
|
||||
// Exclude results.json if it happened to be there
|
||||
val jsonFiles = cacheDir.walkTopDown()
|
||||
.filter { it.isFile && it.extension == "json" && it.name != "results.json" }
|
||||
.toList()
|
||||
|
||||
if (jsonFiles.isEmpty()) throw IllegalStateException("No config.json found in zip")
|
||||
|
||||
// Prefer one named config.json or take the first one
|
||||
val configFile = jsonFiles.find { it.name == "config.json" } ?: jsonFiles.first()
|
||||
|
||||
Log.i(TAG, "Parsed config from ${configFile.absolutePath}")
|
||||
return ConfigParser.parseFromPath(configFile.absolutePath)
|
||||
return@withContext ValidationInput(config, outputDir, generateGoldens)
|
||||
}
|
||||
|
||||
private suspend fun extractDefaultAssets(): RenderTestConfig {
|
||||
@@ -184,9 +90,9 @@ class ValidationInputManager(private val context: Context) {
|
||||
// Copy DamagedHelmet.glb
|
||||
val modelsDir = File(filesDir, "models")
|
||||
modelsDir.mkdirs()
|
||||
val modelOut = File(modelsDir, "helmet.glb")
|
||||
val modelOut = File(modelsDir, "DamagedHelmet.glb")
|
||||
|
||||
assetManager.open("models/helmet.glb").use { input ->
|
||||
assetManager.open("DamagedHelmet.glb").use { input ->
|
||||
FileOutputStream(modelOut).use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
|
||||
+13
-211
@@ -31,14 +31,7 @@ data class ValidationResult(
|
||||
val diffMetric: Float = 0f
|
||||
)
|
||||
|
||||
class ValidationResultManager(
|
||||
private val outputDir: File,
|
||||
private val gpuDriverInfo: String,
|
||||
private val deviceName: String,
|
||||
private val deviceCodeName: String,
|
||||
private val androidVersion: String,
|
||||
private val androidBuildNumber: String
|
||||
) {
|
||||
class ValidationResultManager(private val outputDir: File) {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ValidationResultManager"
|
||||
@@ -71,220 +64,30 @@ class ValidationResultManager(
|
||||
return outputDir
|
||||
}
|
||||
|
||||
fun finalizeResults(totalTimeMs: Long): File? {
|
||||
fun finalizeResults(): File? {
|
||||
// Write results JSON
|
||||
writeResultsJson(totalTimeMs)
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports a zip containing:
|
||||
* - results.json
|
||||
* - input test bundle (as nested zip), if provided
|
||||
* - diff images (if any failure)
|
||||
*/
|
||||
fun exportTestResults(sourceZip: File?, timestamp: String): File? {
|
||||
// Safe parent dir resolution
|
||||
val parentDir = outputDir.canonicalFile.parentFile ?: outputDir.parentFile
|
||||
if (parentDir == null) return null
|
||||
|
||||
val resultZipName = "results_$timestamp"
|
||||
val zipFile = File(parentDir, "$resultZipName.zip")
|
||||
|
||||
Log.i(TAG, "Exporting results to ${zipFile.absolutePath}")
|
||||
writeResultsJson()
|
||||
|
||||
// Zip results
|
||||
val zipFile = File(outputDir, "results.zip")
|
||||
try {
|
||||
ZipOutputStream(FileOutputStream(zipFile)).use { zos ->
|
||||
// 1. Add results.json
|
||||
val resultsJson = File(outputDir, "results.json")
|
||||
if (resultsJson.exists()) {
|
||||
zos.putNextEntry(ZipEntry("results.json"))
|
||||
resultsJson.inputStream().use { it.copyTo(zos) }
|
||||
zos.closeEntry()
|
||||
}
|
||||
|
||||
// 2. Add source zip if exists
|
||||
if (sourceZip != null && sourceZip.exists()) {
|
||||
zos.putNextEntry(ZipEntry(sourceZip.name))
|
||||
sourceZip.inputStream().use { it.copyTo(zos) }
|
||||
zos.closeEntry()
|
||||
}
|
||||
|
||||
// 3. Add images (only rendered images, exclude diffs)
|
||||
outputDir.listFiles { _, name -> name.endsWith(".png") && !name.endsWith("_diff.png") }?.forEach { imgFile ->
|
||||
zos.putNextEntry(ZipEntry(imgFile.name))
|
||||
imgFile.inputStream().use { it.copyTo(zos) }
|
||||
outputDir.walkTopDown().filter { it.isFile && it.name != "results.zip" }.forEach { file ->
|
||||
val entryName = file.relativeTo(outputDir).path
|
||||
zos.putNextEntry(ZipEntry(entryName))
|
||||
file.inputStream().use { it.copyTo(zos) }
|
||||
zos.closeEntry()
|
||||
}
|
||||
}
|
||||
Log.i(TAG, "Exported results to ${zipFile.absolutePath}")
|
||||
Log.i(TAG, "Zipped results to ${zipFile.absolutePath}")
|
||||
return zipFile
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to export results", e)
|
||||
Log.e(TAG, "Failed to zip results", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports a zip bundle containing:
|
||||
* - Modified config.json (with updated name and relative paths)
|
||||
* - Models (in models/ subdirectory)
|
||||
* - Golden images (in goldens/ subdirectory)
|
||||
*
|
||||
* Structure:
|
||||
* test_name_TIMESTAMP/
|
||||
* config.json
|
||||
* models/
|
||||
* model.glb
|
||||
* goldens/
|
||||
* test_result.png
|
||||
*/
|
||||
fun exportTestBundle(config: RenderTestConfig, timestamp: String): File? {
|
||||
Log.i(TAG, "Starting exportTestBundle for ${config.name} at $timestamp")
|
||||
Log.i(TAG, "OutputDir: ${outputDir.absolutePath}")
|
||||
|
||||
val parentDir = outputDir.canonicalFile.parentFile ?: outputDir.parentFile
|
||||
if (parentDir == null) {
|
||||
Log.e(TAG, "OutputDir parent is null: ${outputDir.absolutePath}")
|
||||
return null
|
||||
}
|
||||
Log.i(TAG, "Using parentDir for export: ${parentDir.absolutePath}")
|
||||
|
||||
val testNameWithTimestamp = "${config.name}_$timestamp"
|
||||
val exportNameNoSpaces = testNameWithTimestamp.replace(" ", "_")
|
||||
|
||||
val exportDir = File(parentDir, "export_temp_$timestamp")
|
||||
|
||||
Log.i(TAG, "Creating export temp dir: ${exportDir.absolutePath}")
|
||||
if (exportDir.exists()) exportDir.deleteRecursively()
|
||||
if (!exportDir.mkdirs()) {
|
||||
Log.e(TAG, "Failed to create export dir: ${exportDir.absolutePath}")
|
||||
return null
|
||||
}
|
||||
|
||||
val rootDir = File(exportDir, exportNameNoSpaces)
|
||||
rootDir.mkdirs()
|
||||
|
||||
val modelsDir = File(rootDir, "models")
|
||||
modelsDir.mkdirs()
|
||||
|
||||
val goldensDir = File(rootDir, "goldens")
|
||||
goldensDir.mkdirs()
|
||||
|
||||
try {
|
||||
// 1. Copy Models and update config map
|
||||
val newModelsMap = mutableMapOf<String, String>()
|
||||
Log.i(TAG, "Copying models...")
|
||||
for ((modelName, modelPath) in config.models) {
|
||||
val sourceFile = File(modelPath)
|
||||
if (sourceFile.exists()) {
|
||||
val destFile = File(modelsDir, sourceFile.name)
|
||||
Log.d(TAG, "Copying model $modelName: $modelPath -> ${destFile.name}")
|
||||
sourceFile.copyTo(destFile, overwrite = true)
|
||||
// Use relative path for the new config
|
||||
newModelsMap[modelName] = "models/${sourceFile.name}"
|
||||
} else {
|
||||
Log.w(TAG, "Model file not found: $modelPath")
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Copy Golden Images
|
||||
// We assume goldens are in outputDir with .png extension
|
||||
Log.i(TAG, "Copying goldens from ${outputDir.absolutePath}...")
|
||||
outputDir.listFiles { _, name -> name.endsWith(".png") }?.forEach { file ->
|
||||
Log.d(TAG, "Copying golden: ${file.name}")
|
||||
file.copyTo(File(goldensDir, file.name), overwrite = true)
|
||||
}
|
||||
|
||||
// 3. Create modified config JSON
|
||||
Log.i(TAG, "Creating config.json...")
|
||||
val newConfigJson = JSONObject()
|
||||
newConfigJson.put("name", testNameWithTimestamp) // Keep spaces in JSON name
|
||||
|
||||
// Reconstruct backends
|
||||
val backendsArray = JSONArray()
|
||||
config.backends.forEach { backendsArray.put(it) }
|
||||
newConfigJson.put("backends", backendsArray)
|
||||
|
||||
// Reconstruct models
|
||||
val modelsJson = JSONObject()
|
||||
for ((k, v) in newModelsMap) {
|
||||
modelsJson.put(k, v)
|
||||
}
|
||||
newConfigJson.put("models", modelsJson)
|
||||
|
||||
// Reconstruct tests
|
||||
val testsArray = JSONArray()
|
||||
for (test in config.tests) {
|
||||
val testJson = JSONObject()
|
||||
testJson.put("name", test.name)
|
||||
if (test.description != null) testJson.put("description", test.description)
|
||||
|
||||
// Models for this test (set of strings)
|
||||
val testModelsArray = JSONArray()
|
||||
test.models.forEach { testModelsArray.put(it) }
|
||||
testJson.put("models", testModelsArray)
|
||||
|
||||
// Rendering settings
|
||||
testJson.put("rendering", test.rendering)
|
||||
|
||||
// Tolerance
|
||||
if (test.tolerance != null) {
|
||||
testJson.put("tolerance", test.tolerance)
|
||||
}
|
||||
|
||||
// Backends (optional override)
|
||||
val testBackends = JSONArray()
|
||||
test.backends.forEach { testBackends.put(it) }
|
||||
testJson.put("backends", testBackends)
|
||||
|
||||
testsArray.put(testJson)
|
||||
}
|
||||
newConfigJson.put("tests", testsArray)
|
||||
|
||||
// Write config.json
|
||||
File(rootDir, "config.json").writeText(newConfigJson.toString(4))
|
||||
|
||||
// 4. Zip it
|
||||
val zipFile = File(parentDir, "$exportNameNoSpaces.zip")
|
||||
Log.i(TAG, "Zipping to ${zipFile.absolutePath}...")
|
||||
|
||||
ZipOutputStream(FileOutputStream(zipFile)).use { zos ->
|
||||
rootDir.walkTopDown().forEach { file ->
|
||||
if (file.isFile) {
|
||||
val entryName = file.relativeTo(exportDir).path
|
||||
zos.putNextEntry(ZipEntry(entryName))
|
||||
file.inputStream().use { it.copyTo(zos) }
|
||||
zos.closeEntry()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup temp dir
|
||||
exportDir.deleteRecursively()
|
||||
|
||||
Log.i(TAG, "Exported test bundle to ${zipFile.absolutePath}")
|
||||
return zipFile
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to export test bundle", e)
|
||||
exportDir.deleteRecursively()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun writeResultsJson(totalTimeMs: Long) {
|
||||
val rootObject = JSONObject()
|
||||
|
||||
val metadataObject = JSONObject()
|
||||
metadataObject.put("gpu_driver_info", gpuDriverInfo ?: "")
|
||||
metadataObject.put("total_time_ms", totalTimeMs)
|
||||
metadataObject.put("device_name", deviceName ?: "")
|
||||
metadataObject.put("device_code_name", deviceCodeName ?: "")
|
||||
metadataObject.put("android_version", androidVersion ?: "")
|
||||
metadataObject.put("android_build_number", androidBuildNumber ?: "")
|
||||
rootObject.put("metadata", metadataObject)
|
||||
|
||||
private fun writeResultsJson() {
|
||||
val jsonArray = JSONArray()
|
||||
for (result in results) {
|
||||
val jsonObject = JSONObject()
|
||||
@@ -293,12 +96,11 @@ class ValidationResultManager(
|
||||
jsonObject.put("diff_metric", result.diffMetric)
|
||||
jsonArray.put(jsonObject)
|
||||
}
|
||||
rootObject.put("results", jsonArray)
|
||||
|
||||
val jsonFile = File(outputDir, "results.json")
|
||||
try {
|
||||
FileOutputStream(jsonFile).use { out ->
|
||||
out.write(rootObject.toString(4).toByteArray())
|
||||
out.write(jsonArray.toString(4).toByteArray())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to write results.json", e)
|
||||
|
||||
+60
-109
@@ -41,14 +41,18 @@ class ValidationRunner(
|
||||
private var currentTestConfig: TestConfig? = null
|
||||
private var currentModelName: String? = null
|
||||
|
||||
private var loadStartFence: com.google.android.filament.Fence? = null
|
||||
private var loadStartTime = 0L
|
||||
private var frameCounter = 0
|
||||
private var suiteStartTime: Long = 0
|
||||
|
||||
enum class State {
|
||||
IDLE,
|
||||
LOADING_MODEL,
|
||||
WAITING_FOR_FENCE,
|
||||
WAITING_FOR_RESOURCES,
|
||||
WARMUP,
|
||||
RUNNING_TEST
|
||||
RUNNING_TEST,
|
||||
COMPARING
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
@@ -66,7 +70,6 @@ class ValidationRunner(
|
||||
callback?.onAllTestsFinished()
|
||||
return
|
||||
}
|
||||
suiteStartTime = System.currentTimeMillis()
|
||||
currentTestIndex = 0
|
||||
currentModelIndex = 0
|
||||
startTest(config.tests[0])
|
||||
@@ -83,17 +86,6 @@ class ValidationRunner(
|
||||
}
|
||||
|
||||
private fun startModel(modelName: String) {
|
||||
if (currentModelName == modelName) {
|
||||
Log.i("ValidationRunner", "Reusing model $modelName")
|
||||
callback?.onStatusChanged("Reusing $modelName for ${currentTestConfig?.name}")
|
||||
|
||||
modelViewer.resetToDefaultState()
|
||||
|
||||
frameCounter = 0
|
||||
currentState = State.WARMUP
|
||||
return
|
||||
}
|
||||
|
||||
currentModelName = modelName
|
||||
val modelPath = config.models[modelName]
|
||||
if (modelPath == null) {
|
||||
@@ -101,6 +93,8 @@ class ValidationRunner(
|
||||
nextModel()
|
||||
return
|
||||
}
|
||||
|
||||
currentState = State.LOADING_MODEL
|
||||
callback?.onStatusChanged("Loading $modelName for ${currentTestConfig?.name}")
|
||||
|
||||
// Load model on main thread (required by ModelViewer)
|
||||
@@ -116,20 +110,51 @@ class ValidationRunner(
|
||||
Log.i("ValidationRunner", "Loading GLB buffer... (${bytes.size} bytes)")
|
||||
val buffer = ByteBuffer.wrap(bytes)
|
||||
modelViewer.loadModelGlb(buffer)
|
||||
Log.i("ValidationRunner", "Model loaded.")
|
||||
Log.i("ValidationRunner", "Model loaded. initializing fence.")
|
||||
modelViewer.transformToUnitCube()
|
||||
currentState = State.WAITING_FOR_RESOURCES
|
||||
frameCounter = 0
|
||||
Log.i("ValidationRunner", "State set to WAITING_FOR_RESOURCES")
|
||||
loadStartFence = modelViewer.engine.createFence()
|
||||
loadStartTime = System.nanoTime()
|
||||
currentState = State.WAITING_FOR_FENCE
|
||||
frameCounter = 0 // Reset for fence timeout tracking
|
||||
Log.i("ValidationRunner", "State set to WAITING_FOR_FENCE")
|
||||
} catch (e: Exception) {
|
||||
Log.e("ValidationRunner", "Failed to load $path", e)
|
||||
nextModel()
|
||||
Log.e("ValidationRunner", "Failed to load $path", e)
|
||||
nextModel()
|
||||
}
|
||||
}
|
||||
|
||||
fun onFrame(frameTimeNanos: Long) {
|
||||
if (frameCounter % 60 == 0) {
|
||||
Log.i("ValidationRunner", "onFrame: $currentState (frame: $frameCounter)")
|
||||
}
|
||||
|
||||
when (currentState) {
|
||||
State.IDLE -> {}
|
||||
State.WAITING_FOR_FENCE -> {
|
||||
frameCounter++
|
||||
if (frameCounter > 600) {
|
||||
Log.w("ValidationRunner", "Fence timed out after 600 frames! Forcing proceed.")
|
||||
modelViewer.engine.destroyFence(loadStartFence!!)
|
||||
loadStartFence = null
|
||||
currentState = State.WAITING_FOR_RESOURCES
|
||||
return
|
||||
}
|
||||
|
||||
loadStartFence?.let { fence ->
|
||||
if (fence.wait(com.google.android.filament.Fence.Mode.FLUSH, 0) ==
|
||||
com.google.android.filament.Fence.FenceStatus.CONDITION_SATISFIED) {
|
||||
modelViewer.engine.destroyFence(fence)
|
||||
loadStartFence = null
|
||||
|
||||
// Compile materials (simplified)
|
||||
modelViewer.scene.forEach { entity ->
|
||||
// ... existing material compilation logic ...
|
||||
}
|
||||
|
||||
currentState = State.WAITING_FOR_RESOURCES
|
||||
}
|
||||
}
|
||||
}
|
||||
State.WAITING_FOR_RESOURCES -> {
|
||||
val progress = modelViewer.progress
|
||||
if (progress >= 1.0f) {
|
||||
@@ -141,11 +166,12 @@ class ValidationRunner(
|
||||
State.WARMUP -> {
|
||||
frameCounter++
|
||||
if (frameCounter > 5) { // 5 frames warmup
|
||||
startAutomation()
|
||||
startAutomation()
|
||||
}
|
||||
}
|
||||
State.RUNNING_TEST -> {
|
||||
currentEngine?.let { engine ->
|
||||
// Log.i("ValidationRunner", "Running test...")
|
||||
currentEngine?.let { engine ->
|
||||
val content = AutomationEngine.ViewerContent()
|
||||
content.view = modelViewer.view
|
||||
content.renderer = modelViewer.renderer
|
||||
@@ -160,11 +186,13 @@ class ValidationRunner(
|
||||
if (engine.shouldClose()) {
|
||||
Log.i("ValidationRunner", "Finishing test (frames: $frameCounter)")
|
||||
// Test finished (for this spec)
|
||||
currentState = State.IDLE
|
||||
currentState = State.COMPARING
|
||||
captureAndCompare()
|
||||
}
|
||||
}
|
||||
}
|
||||
State.COMPARING -> {} // Busy
|
||||
State.LOADING_MODEL -> {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,36 +233,7 @@ class ValidationRunner(
|
||||
|
||||
// Golden path
|
||||
val modelFile = File(config.models.get(modelName)!!)
|
||||
val modelParent = modelFile.parentFile!!
|
||||
|
||||
// Search for golden in:
|
||||
// 1. ../golden/ (standard structure)
|
||||
// 2. ../goldens/ (exported structure, sibling of models)
|
||||
// 3. ./goldens/ (backup)
|
||||
|
||||
val searchPaths = mutableListOf<File>()
|
||||
modelParent.parentFile?.let {
|
||||
searchPaths.add(it.resolve("golden"))
|
||||
searchPaths.add(it.resolve("goldens"))
|
||||
}
|
||||
searchPaths.add(modelParent.resolve("goldens"))
|
||||
|
||||
var goldenFile: File? = null
|
||||
for (path in searchPaths) {
|
||||
val f = path.resolve("${testFullName}.png")
|
||||
if (f.exists()) {
|
||||
goldenFile = f
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (goldenFile != null) {
|
||||
Log.i("ValidationRunner", "Found golden at ${goldenFile.absolutePath}")
|
||||
} else {
|
||||
Log.w("ValidationRunner", "Golden not found for $testFullName. Searched in: ${searchPaths.joinToString { it.absolutePath }}")
|
||||
// Fallback to old behavior for reference if everything else fails
|
||||
goldenFile = modelParent.parentFile?.resolve("golden/${testFullName}.png") ?: File("nonexistent")
|
||||
}
|
||||
val goldenFile = modelFile.parentFile!!.parentFile!!.resolve("golden/${testFullName}.png")
|
||||
|
||||
Thread {
|
||||
try {
|
||||
@@ -246,15 +245,14 @@ class ValidationRunner(
|
||||
var diffMetric = 0f
|
||||
|
||||
if (generateGoldens) {
|
||||
val targetGolden = goldenFile ?: modelParent.parentFile?.resolve("golden/${testFullName}.png") ?: File(modelParent, "golden/${testFullName}.png")
|
||||
targetGolden.parentFile?.mkdirs()
|
||||
FileOutputStream(targetGolden).use { out ->
|
||||
goldenFile.parentFile?.mkdirs()
|
||||
FileOutputStream(goldenFile).use { out ->
|
||||
flipped.compress(Bitmap.CompressFormat.PNG, 100, out)
|
||||
}
|
||||
passed = true // Generating goldens always passes if successful
|
||||
callback?.onStatusChanged("Golden generated")
|
||||
} else {
|
||||
if (goldenFile != null && goldenFile.exists()) {
|
||||
if (goldenFile.exists()) {
|
||||
val golden = android.graphics.BitmapFactory.decodeFile(goldenFile.absolutePath)
|
||||
if (golden != null) {
|
||||
callback?.onImageResult("Golden", golden)
|
||||
@@ -265,61 +263,17 @@ class ValidationRunner(
|
||||
passed = (result.status == ImageDiff.Result.Status.PASSED)
|
||||
diffMetric = result.failingPixelCount.toFloat()
|
||||
|
||||
if (!passed) {
|
||||
if (!passed) {
|
||||
if (result.diffImage != null) {
|
||||
val diffImg = result.diffImage!!
|
||||
val width = diffImg.width
|
||||
val height = diffImg.height
|
||||
val pixels = IntArray(width * height)
|
||||
diffImg.getPixels(pixels, 0, width, 0, 0, width, height)
|
||||
|
||||
var hasAlphaDiff = false
|
||||
val alphaPixels = IntArray(width * height)
|
||||
|
||||
for (i in pixels.indices) {
|
||||
val color = pixels[i]
|
||||
|
||||
val a = android.graphics.Color.alpha(color)
|
||||
val r = android.graphics.Color.red(color)
|
||||
val g = android.graphics.Color.green(color)
|
||||
val b = android.graphics.Color.blue(color)
|
||||
|
||||
if (a > 0) {
|
||||
hasAlphaDiff = true
|
||||
}
|
||||
|
||||
// Map alpha diff to grayscale RGB
|
||||
alphaPixels[i] = android.graphics.Color.argb(255, a, a, a)
|
||||
|
||||
// Force main diff image alpha to 255
|
||||
pixels[i] = android.graphics.Color.argb(255, r, g, b)
|
||||
}
|
||||
|
||||
// Apply updated pixels to diff image
|
||||
diffImg.setPixels(pixels, 0, width, 0, 0, width, height)
|
||||
|
||||
// The C++ ImageDiff code sets isPremultiplied to false so Android
|
||||
// doesn't erase RGB diff values when Alpha diff is 0. However, Android's
|
||||
// Canvas will crash if we try to draw a non-premultiplied bitmap.
|
||||
// Since we just forced all alpha values to 255 (fully opaque) in the
|
||||
// loop above, we can safely mark it as premultiplied again here.
|
||||
diffImg.isPremultiplied = true
|
||||
callback?.onImageResult("Diff", diffImg)
|
||||
resultManager.saveImage("${testFullName}_diff", diffImg)
|
||||
|
||||
if (hasAlphaDiff) {
|
||||
val alphaDiffImg = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
alphaDiffImg.setPixels(alphaPixels, 0, width, 0, 0, width, height)
|
||||
callback?.onImageResult("Alpha Diff", alphaDiffImg)
|
||||
resultManager.saveImage("${testFullName}_alpha_diff", alphaDiffImg)
|
||||
}
|
||||
callback?.onImageResult("Diff", result.diffImage!!)
|
||||
resultManager.saveImage("${testFullName}_diff", result.diffImage!!)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
callback?.onStatusChanged("Failed to load golden")
|
||||
}
|
||||
} else {
|
||||
Log.w("ValidationRunner", "Golden not found: ${goldenFile?.absolutePath}")
|
||||
Log.w("ValidationRunner", "Golden not found: ${goldenFile.absolutePath}")
|
||||
callback?.onStatusChanged("Golden not found")
|
||||
}
|
||||
}
|
||||
@@ -358,10 +312,7 @@ class ValidationRunner(
|
||||
startTest(config.tests[currentTestIndex])
|
||||
} else {
|
||||
currentState = State.IDLE
|
||||
|
||||
val totalTimeMs = System.currentTimeMillis() - suiteStartTime
|
||||
|
||||
resultManager.finalizeResults(totalTimeMs)
|
||||
resultManager.finalizeResults()
|
||||
callback?.onAllTestsFinished()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
@@ -10,8 +10,6 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintWidth_percent="0.6"
|
||||
android:layout_marginTop="32dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
@@ -22,110 +20,10 @@
|
||||
android:layout_height="match_parent" />
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/controls_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:paddingTop="20dp"
|
||||
android:paddingBottom="0dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/surface_container"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Initializing..."
|
||||
android:textSize="12sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingBottom="10dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/run_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
style="@style/Widget.Material3.Button"
|
||||
android:text="Run Test" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/load_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:text="Load Test"
|
||||
android:layout_marginStart="8dp"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/options_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.Material3.Button.IconButton.Outlined"
|
||||
app:icon="@android:drawable/ic_menu_more"
|
||||
android:contentDescription="More Options"
|
||||
android:layout_marginStart="8dp"/>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Global Enhancement Controls (Hidden by default) -->
|
||||
<LinearLayout
|
||||
android:id="@+id/enhancement_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:visibility="gone"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/enhancement_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Enhancement: 1.0x"
|
||||
android:textSize="12sp"
|
||||
android:minWidth="120dp" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/enhancement_slider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:max="100"
|
||||
android:progress="0" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/test_results_header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Test Results"
|
||||
android:gravity="center"
|
||||
android:textSize="14sp"
|
||||
android:paddingTop="0dp"
|
||||
android:paddingBottom="8dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/controls_container"
|
||||
app:layout_constraintTop_toBottomOf="@id/surface_container"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
@@ -134,9 +32,41 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:paddingBottom="20dp">
|
||||
android:padding="20dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Initializing..."
|
||||
android:textSize="14sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingBottom="10dp">
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/mode_spinner"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/run_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Run" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Test Results"
|
||||
android:textSize="18sp"
|
||||
android:paddingTop="20dp"
|
||||
android:paddingBottom="10dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/results_container"
|
||||
@@ -144,6 +74,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" />
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
<?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 {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
// Filament tools plugin
|
||||
pluginManagement {
|
||||
includeBuild 'gradle-plugin'
|
||||
}
|
||||
|
||||
// Libraries
|
||||
include ':filament-android'
|
||||
include ':filamat-android'
|
||||
|
||||
@@ -54,13 +54,8 @@ if [[ "$OS_NAME" == "Linux" ]]; then
|
||||
# is constantly being updated and sometimes not compatible with the current
|
||||
# linux platform.
|
||||
# Note that we assume this platform is compatible with ubuntu-22.04 x86_64
|
||||
DPKG_ARCH=$(dpkg --print-architecture)
|
||||
EXTRA_PACKAGES=""
|
||||
if [[ "$DPKG_ARCH" == "amd64" ]]; then
|
||||
EXTRA_PACKAGES="lib32gcc-s1 lib32stdc++6 libc6-i386"
|
||||
fi
|
||||
sudo apt-get -y install \
|
||||
autoconf automake autopoint autotools-dev bindgen bison build-essential bzip2 cpp cpp-11 debhelper debugedit dh-autoreconf dh-strip-nondeterminism diffstat directx-headers-dev dpkg-dev dwz flex g++ g++-11 gcc gcc-11 gcc-11-base:${DPKG_ARCH} gettext glslang-tools icu-devtools intltool-debian ${EXTRA_PACKAGES} libarchive-zip-perl libasan6:${DPKG_ARCH} libatomic1:${DPKG_ARCH} libc-dev-bin libc6-dbg:${DPKG_ARCH} libc6-dev:${DPKG_ARCH} libcc1-0:${DPKG_ARCH} libclang-${GITHUB_CLANG_VERSION}-dev libclang-common-${GITHUB_CLANG_VERSION}-dev libclang-cpp${GITHUB_CLANG_VERSION} libclang-cpp${GITHUB_CLANG_VERSION}-dev libclang1-14 libclang1-${GITHUB_CLANG_VERSION} libclc-${GITHUB_CLANG_VERSION} libclc-${GITHUB_CLANG_VERSION}-dev libcrypt-dev:${DPKG_ARCH} libdebhelper-perl libdpkg-perl libdrm-amdgpu1:${DPKG_ARCH} libdrm-dev:${DPKG_ARCH} libdrm-intel1:${DPKG_ARCH} libdrm-nouveau2:${DPKG_ARCH} libdrm-radeon1:${DPKG_ARCH} libelf-dev:${DPKG_ARCH} libexpat1-dev:${DPKG_ARCH} libffi-dev:${DPKG_ARCH} libfile-stripnondeterminism-perl libgc1:${DPKG_ARCH} libgcc-11-dev:${DPKG_ARCH} libgl1:${DPKG_ARCH} libgl1-mesa-dri:${DPKG_ARCH} libglapi-mesa:${DPKG_ARCH} libglvnd-core-dev:${DPKG_ARCH} libglvnd0:${DPKG_ARCH} libglx-mesa0:${DPKG_ARCH} libglx0:${DPKG_ARCH} libgomp1:${DPKG_ARCH} libicu-dev:${DPKG_ARCH} libisl23:${DPKG_ARCH} libitm1:${DPKG_ARCH} libllvm14:${DPKG_ARCH} libllvm${GITHUB_CLANG_VERSION}:${DPKG_ARCH} libllvmspirvlib-${GITHUB_CLANG_VERSION}-dev:${DPKG_ARCH} libllvmspirvlib${GITHUB_CLANG_VERSION}:${DPKG_ARCH} liblsan0:${DPKG_ARCH} libmpc3:${DPKG_ARCH} libncurses-dev:${DPKG_ARCH} libnsl-dev:${DPKG_ARCH} libobjc-11-dev:${DPKG_ARCH} libobjc4:${DPKG_ARCH} libpciaccess-dev:${DPKG_ARCH} libpciaccess0f:${DPKG_ARCH} libpfm4:${DPKG_ARCH} libpthread-stubs0-dev:${DPKG_ARCH} libquadmath0:${DPKG_ARCH} libsensors-config libsensors-dev:${DPKG_ARCH} libsensors5:${DPKG_ARCH} libset-scalar-perl libstd-rust-1.75:${DPKG_ARCH} libstd-rust-dev:${DPKG_ARCH} libstdc++-11-dev:${DPKG_ARCH} libsub-override-perl libtinfo-dev:${DPKG_ARCH} libtirpc-dev:${DPKG_ARCH} libtool libtsan0:${DPKG_ARCH} libubsan1:${DPKG_ARCH} libva-dev:${DPKG_ARCH} libva-drm2:${DPKG_ARCH} libva-glx2:${DPKG_ARCH} libva-wayland2:${DPKG_ARCH} libva-x11-2:${DPKG_ARCH} libva2:${DPKG_ARCH} libvdpau-dev:${DPKG_ARCH} libvdpau1:${DPKG_ARCH} libvulkan-dev:${DPKG_ARCH} libvulkan1:${DPKG_ARCH} libwayland-bin libwayland-client0:${DPKG_ARCH} libwayland-cursor0:${DPKG_ARCH} libwayland-dev:${DPKG_ARCH} libwayland-egl-backend-dev:${DPKG_ARCH} libwayland-egl1:${DPKG_ARCH} libwayland-server0:${DPKG_ARCH} libx11-dev:${DPKG_ARCH} libx11-xcb-dev:${DPKG_ARCH} libx11-xcb1:${DPKG_ARCH} libxau-dev:${DPKG_ARCH} libxcb-dri2-0:${DPKG_ARCH} libxcb-dri2-0-dev:${DPKG_ARCH} libxcb-dri3-0:${DPKG_ARCH} libxcb-dri3-dev:${DPKG_ARCH} libxcb-glx0:${DPKG_ARCH} libxcb-glx0-dev:${DPKG_ARCH} libxcb-present-dev:${DPKG_ARCH} libxcb-present0:${DPKG_ARCH} libxcb-randr0:${DPKG_ARCH} libxcb-randr0-dev:${DPKG_ARCH} libxcb-render0:${DPKG_ARCH} libxcb-render0-dev:${DPKG_ARCH} libxcb-shape0:${DPKG_ARCH} libxcb-shape0-dev:${DPKG_ARCH} libxcb-shm0:${DPKG_ARCH} libxcb-shm0-dev:${DPKG_ARCH} libxcb-sync-dev:${DPKG_ARCH} libxcb-sync1:${DPKG_ARCH} libxcb-xfixes0:${DPKG_ARCH} libxcb-xfixes0-dev:${DPKG_ARCH} libxcb1-dev:${DPKG_ARCH} libxdmcp-dev:${DPKG_ARCH} libxext-dev:${DPKG_ARCH} libxfixes-dev:${DPKG_ARCH} libxfixes3:${DPKG_ARCH} libxml2-dev:${DPKG_ARCH} libxrandr-dev:${DPKG_ARCH} libxrandr2:${DPKG_ARCH} libxrender-dev:${DPKG_ARCH} libxrender1:${DPKG_ARCH} libxshmfence-dev:${DPKG_ARCH} libxshmfence1:${DPKG_ARCH} libxxf86vm-dev:${DPKG_ARCH} libxxf86vm1:${DPKG_ARCH} libz3-4:${DPKG_ARCH} libz3-dev:${DPKG_ARCH} libzstd-dev:${DPKG_ARCH} linux-libc-dev:${DPKG_ARCH} llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev llvm-${LLVM_VERSION}-linker-tools llvm-${LLVM_VERSION}-runtime llvm-${LLVM_VERSION}-tools llvm-spirv-${LLVM_VERSION} lto-disabled-list m4 make meson ninja-build pkg-config po-debconf python3-mako python3-ply python3-pygments quilt rpcsvc-proto rustc spirv-tools valgrind wayland-protocols x11proto-dev xorg-sgml-doctools xtrans-dev zlib1g-dev:${DPKG_ARCH} \
|
||||
autoconf automake autopoint autotools-dev bindgen bison build-essential bzip2 cpp cpp-11 debhelper debugedit dh-autoreconf dh-strip-nondeterminism diffstat directx-headers-dev dpkg-dev dwz flex g++ g++-11 gcc gcc-11 gcc-11-base:amd64 gettext glslang-tools icu-devtools intltool-debian lib32gcc-s1 lib32stdc++6 libarchive-zip-perl libasan6:amd64 libatomic1:amd64 libc-dev-bin libc6-dbg:amd64 libc6-dev:amd64 libc6-i386 libcc1-0:amd64 libclang-${GITHUB_CLANG_VERSION}-dev libclang-common-${GITHUB_CLANG_VERSION}-dev libclang-cpp${GITHUB_CLANG_VERSION} libclang-cpp${GITHUB_CLANG_VERSION}-dev libclang1-14 libclang1-${GITHUB_CLANG_VERSION} libclc-${GITHUB_CLANG_VERSION} libclc-${GITHUB_CLANG_VERSION}-dev libcrypt-dev:amd64 libdebhelper-perl libdpkg-perl libdrm-amdgpu1:amd64 libdrm-dev:amd64 libdrm-intel1:amd64 libdrm-nouveau2:amd64 libdrm-radeon1:amd64 libelf-dev:amd64 libexpat1-dev:amd64 libffi-dev:amd64 libfile-stripnondeterminism-perl libgc1:amd64 libgcc-11-dev:amd64 libgl1:amd64 libgl1-mesa-dri:amd64 libglapi-mesa:amd64 libglvnd-core-dev:amd64 libglvnd0:amd64 libglx-mesa0:amd64 libglx0:amd64 libgomp1:amd64 libicu-dev:amd64 libisl23:amd64 libitm1:amd64 libllvm14:amd64 libllvm${GITHUB_CLANG_VERSION}:amd64 libllvmspirvlib-${GITHUB_CLANG_VERSION}-dev:amd64 libllvmspirvlib${GITHUB_CLANG_VERSION}:amd64 liblsan0:amd64 libmpc3:amd64 libncurses-dev:amd64 libnsl-dev:amd64 libobjc-11-dev:amd64 libobjc4:amd64 libpciaccess-dev:amd64 libpciaccess0f:amd64 libpfm4:amd64 libpthread-stubs0-dev:amd64 libquadmath0:amd64 libsensors-config libsensors-dev:amd64 libsensors5:amd64 libset-scalar-perl libstd-rust-1.75:amd64 libstd-rust-dev:amd64 libstdc++-11-dev:amd64 libsub-override-perl libtinfo-dev:amd64 libtirpc-dev:amd64 libtool libtsan0:amd64 libubsan1:amd64 libva-dev:amd64 libva-drm2:amd64 libva-glx2:amd64 libva-wayland2:amd64 libva-x11-2:amd64 libva2:amd64 libvdpau-dev:amd64 libvdpau1:amd64 libvulkan-dev:amd64 libvulkan1:amd64 libwayland-bin libwayland-client0:amd64 libwayland-cursor0:amd64 libwayland-dev:amd64 libwayland-egl-backend-dev:amd64 libwayland-egl1:amd64 libwayland-server0:amd64 libx11-dev:amd64 libx11-xcb-dev:amd64 libx11-xcb1:amd64 libxau-dev:amd64 libxcb-dri2-0:amd64 libxcb-dri2-0-dev:amd64 libxcb-dri3-0:amd64 libxcb-dri3-dev:amd64 libxcb-glx0:amd64 libxcb-glx0-dev:amd64 libxcb-present-dev:amd64 libxcb-present0:amd64 libxcb-randr0:amd64 libxcb-randr0-dev:amd64 libxcb-render0:amd64 libxcb-render0-dev:amd64 libxcb-shape0:amd64 libxcb-shape0-dev:amd64 libxcb-shm0:amd64 libxcb-shm0-dev:amd64 libxcb-sync-dev:amd64 libxcb-sync1:amd64 libxcb-xfixes0:amd64 libxcb-xfixes0-dev:amd64 libxcb1-dev:amd64 libxdmcp-dev:amd64 libxext-dev:amd64 libxfixes-dev:amd64 libxfixes3:amd64 libxml2-dev:amd64 libxrandr-dev:amd64 libxrandr2:amd64 libxrender-dev:amd64 libxrender1:amd64 libxshmfence-dev:amd64 libxshmfence1:amd64 libxxf86vm-dev:amd64 libxxf86vm1:amd64 libz3-4:amd64 libz3-dev:amd64 libzstd-dev:amd64 linux-libc-dev:amd64 llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev llvm-${LLVM_VERSION}-linker-tools llvm-${LLVM_VERSION}-runtime llvm-${LLVM_VERSION}-tools llvm-spirv-${LLVM_VERSION} lto-disabled-list m4 make meson ninja-build pkg-config po-debconf python3-mako python3-ply python3-pygments quilt rpcsvc-proto rustc spirv-tools valgrind wayland-protocols x11proto-dev xorg-sgml-doctools xtrans-dev zlib1g-dev:amd64 \
|
||||
clang-$GITHUB_CLANG_VERSION libc++-$GITHUB_CLANG_VERSION-dev libc++abi-$GITHUB_CLANG_VERSION-dev
|
||||
|
||||
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang-${GITHUB_CLANG_VERSION} 100
|
||||
@@ -85,7 +80,6 @@ elif [[ "$OS_NAME" == "Darwin" ]]; then
|
||||
if command -v brew > /dev/null 2>&1; then
|
||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=true brew install autoconf automake libx11 libxext libxrandr \
|
||||
llvm@${LLVM_VERSION} ninja meson pkg-config libxshmfence
|
||||
brew link --overwrite llvm@${LLVM_VERSION}
|
||||
# For reasons unknown, this is necessary for pkg-config to find homebrew's packages
|
||||
LOCAL_PKG_CONFIG_PATH="/opt/homebrew/lib/pkgconfig:$PKG_CONFIG_PATH"
|
||||
elif command -v port > /dev/null 2>&1; then
|
||||
|
||||
@@ -63,10 +63,6 @@ function _preferred_os_filename() {
|
||||
|
||||
function download_vulkan_installer() {
|
||||
local os=$(_get_os)
|
||||
if [[ "$os" == "linux" ]]; then
|
||||
echo "Linux uses apt to install vulkan dependencies, skipping tarball download." >&2
|
||||
return 0
|
||||
fi
|
||||
local dl_filename=$(_os_filename ${VULKAN_SDK_VERSION})
|
||||
local filename=$(_preferred_os_filename)
|
||||
local url=https://sdk.lunarg.com/sdk/download/$VULKAN_SDK_VERSION/$os/$dl_filename?Human=true
|
||||
@@ -83,22 +79,15 @@ function download_vulkan_installer() {
|
||||
|
||||
function unpack_vulkan_installer() {
|
||||
local os=$(_get_os)
|
||||
if [[ "$os" == "linux" ]]; then
|
||||
install_${os}
|
||||
return 0
|
||||
fi
|
||||
local filename=$(_preferred_os_filename $os)
|
||||
test -f $filename
|
||||
install_${os}
|
||||
}
|
||||
|
||||
function install_linux() {
|
||||
echo "Installing Vulkan dependencies via apt for Linux..." >&2
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y libvulkan-dev vulkan-validationlayers glslang-tools spirv-tools
|
||||
|
||||
# Create a dummy SDK dir to satisfy scripts expecting a VulkanSDK folder structure
|
||||
mkdir -p ~/VulkanSDK/${VULKAN_SDK_VERSION}
|
||||
test -d $VULKAN_SDK_DIR && test -f vulkan_sdk.tar.gz
|
||||
echo "extract just the SDK's prebuilt binaries ($VULKAN_SDK_VERSION/x86_64) from vulkan_sdk.tar.gz into $VULKAN_SDK" >&2
|
||||
tar -C "$VULKAN_SDK_DIR" --strip-components 2 -xf vulkan_sdk.tar.gz $VULKAN_SDK_VERSION/x86_64
|
||||
}
|
||||
|
||||
function install_mac() {
|
||||
|
||||
@@ -46,7 +46,6 @@ list(SORT NDK_VERSIONS)
|
||||
list(GET NDK_VERSIONS -1 NDK_VERSION)
|
||||
get_filename_component(NDK_VERSION ${NDK_VERSION} NAME)
|
||||
set(TOOLCHAIN ${ANDROID_HOME_UNIX}/ndk/${NDK_VERSION}/toolchains/llvm/prebuilt/${HOST_NAME_L}-x86_64)
|
||||
set(CMAKE_ANDROID_NDK_VERSION ${NDK_VERSION})
|
||||
|
||||
# specify the cross compiler
|
||||
set(COMPILER_SUFFIX)
|
||||
|
||||
@@ -47,7 +47,6 @@ list(SORT NDK_VERSIONS)
|
||||
list(GET NDK_VERSIONS -1 NDK_VERSION)
|
||||
get_filename_component(NDK_VERSION ${NDK_VERSION} NAME)
|
||||
set(TOOLCHAIN ${ANDROID_HOME_UNIX}/ndk/${NDK_VERSION}/toolchains/llvm/prebuilt/${HOST_NAME_L}-x86_64)
|
||||
set(CMAKE_ANDROID_NDK_VERSION ${NDK_VERSION})
|
||||
|
||||
# specify the cross compiler
|
||||
set(COMPILER_SUFFIX)
|
||||
|
||||
@@ -46,7 +46,6 @@ list(SORT NDK_VERSIONS)
|
||||
list(GET NDK_VERSIONS -1 NDK_VERSION)
|
||||
get_filename_component(NDK_VERSION ${NDK_VERSION} NAME)
|
||||
set(TOOLCHAIN ${ANDROID_HOME_UNIX}/ndk/${NDK_VERSION}/toolchains/llvm/prebuilt/${HOST_NAME_L}-x86_64)
|
||||
set(CMAKE_ANDROID_NDK_VERSION ${NDK_VERSION})
|
||||
|
||||
# specify the cross compiler
|
||||
set(COMPILER_SUFFIX)
|
||||
|
||||
@@ -46,7 +46,6 @@ list(SORT NDK_VERSIONS)
|
||||
list(GET NDK_VERSIONS -1 NDK_VERSION)
|
||||
get_filename_component(NDK_VERSION ${NDK_VERSION} NAME)
|
||||
set(TOOLCHAIN ${ANDROID_HOME_UNIX}/ndk/${NDK_VERSION}/toolchains/llvm/prebuilt/${HOST_NAME_L}-x86_64)
|
||||
set(CMAKE_ANDROID_NDK_VERSION ${NDK_VERSION})
|
||||
|
||||
# specify the cross compiler
|
||||
set(COMPILER_SUFFIX)
|
||||
|
||||
+2
-2
@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.70.0'
|
||||
implementation 'com.google.android.filament:filament-android:1.69.4'
|
||||
}
|
||||
</code></pre>
|
||||
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
|
||||
@@ -195,7 +195,7 @@ dependencies {
|
||||
</div>
|
||||
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
|
||||
<p>iOS projects can use CocoaPods to install the latest release:</p>
|
||||
<pre><code class="language-shell">pod 'Filament', '~> 1.70.0'
|
||||
<pre><code class="language-shell">pod 'Filament', '~> 1.69.4'
|
||||
</code></pre>
|
||||
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
|
||||
<ul>
|
||||
|
||||
+199
-199
@@ -504,9 +504,9 @@ D_{GGX}(h,\alpha) = \frac{\aa}{\pi ( (\NoH)^2 (\aa - 1) + 1)^2}
|
||||
\end{equation}$$
|
||||
</p><p>
|
||||
The GLSL implementation of the NDF, shown in <a href="#listing_speculard">listing 1</a>, is simple and efficient.
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">float</span> <span class="hljs-title">D_GGX</span><span class="hljs-params">(<span class="hljs-type">float</span> NoH, <span class="hljs-type">float</span> roughness)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> a = NoH * roughness;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> k = roughness / (<span class="hljs-number">1.0</span> - NoH * NoH + a * a);</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-built_in">float</span> <span class="hljs-title">D_GGX</span>(<span class="hljs-params"><span class="hljs-built_in">float</span> NoH, <span class="hljs-built_in">float</span> roughness</span>)</span> {</span>
|
||||
<span class="line"> <span class="hljs-built_in">float</span> a = NoH * roughness;</span>
|
||||
<span class="line"> <span class="hljs-built_in">float</span> k = roughness / (<span class="hljs-number">1.0</span> - NoH * NoH + a * a);</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> k * k * (<span class="hljs-number">1.0</span> / PI);</span>
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_speculard"> </a><b style="font-style:normal;">Listing 1:</b> Implementation of the specular D term in GLSL</div></center>
|
||||
<p>
|
||||
@@ -590,10 +590,10 @@ V(v,l,\alpha) = \frac{0.5}{\NoL \sqrt{(\NoV)^2 (1 - \aa) + \aa} + \NoV \sqrt{(\N
|
||||
\end{equation}$$
|
||||
</p><p>
|
||||
The GLSL implementation of the visibility term, shown in <a href="#listing_specularv">listing 3</a>, is a bit more expensive than we would like since it requires two <code>sqrt</code> operations.
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">float</span> <span class="hljs-title">V_SmithGGXCorrelated</span><span class="hljs-params">(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> NoL, <span class="hljs-type">float</span> roughness)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> a2 = roughness * roughness;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> GGXV = NoL * <span class="hljs-built_in">sqrt</span>(NoV * NoV * (<span class="hljs-number">1.0</span> - a2) + a2);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> GGXL = NoV * <span class="hljs-built_in">sqrt</span>(NoL * NoL * (<span class="hljs-number">1.0</span> - a2) + a2);</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">V_SmithGGXCorrelated</span><span class="hljs-params">(<span class="hljs-keyword">float</span> NoV, <span class="hljs-keyword">float</span> NoL, <span class="hljs-keyword">float</span> roughness)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> a2 = roughness * roughness;</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> GGXV = NoL * <span class="hljs-built_in">sqrt</span>(NoV * NoV * (<span class="hljs-number">1.0</span> - a2) + a2);</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> GGXL = NoV * <span class="hljs-built_in">sqrt</span>(NoL * NoL * (<span class="hljs-number">1.0</span> - a2) + a2);</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">0.5</span> / (GGXV + GGXL);</span>
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_specularv"> </a><b style="font-style:normal;">Listing 3:</b> Implementation of the specular V term in GLSL</div></center>
|
||||
<p>
|
||||
@@ -604,10 +604,10 @@ V(v,l,\alpha) = \frac{0.5}{\NoL (\NoV (1 - \alpha) + \alpha) + \NoV (\NoL (1 - \
|
||||
\end{equation}$$
|
||||
</p><p>
|
||||
This approximation is mathematically wrong but saves two square root operations and is good enough for real-time mobile applications, as shown in <a href="#listing_approximatedspecularv">listing 4</a>.
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">float</span> <span class="hljs-title">V_SmithGGXCorrelatedFast</span><span class="hljs-params">(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> NoL, <span class="hljs-type">float</span> roughness)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> a = roughness;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> GGXV = NoL * (NoV * (<span class="hljs-number">1.0</span> - a) + a);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> GGXL = NoV * (NoL * (<span class="hljs-number">1.0</span> - a) + a);</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-built_in">float</span> <span class="hljs-title">V_SmithGGXCorrelatedFast</span>(<span class="hljs-params"><span class="hljs-built_in">float</span> NoV, <span class="hljs-built_in">float</span> NoL, <span class="hljs-built_in">float</span> roughness</span>)</span> {</span>
|
||||
<span class="line"> <span class="hljs-built_in">float</span> a = roughness;</span>
|
||||
<span class="line"> <span class="hljs-built_in">float</span> GGXV = NoL * (NoV * (<span class="hljs-number">1.0</span> - a) + a);</span>
|
||||
<span class="line"> <span class="hljs-built_in">float</span> GGXL = NoV * (NoL * (<span class="hljs-number">1.0</span> - a) + a);</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">0.5</span> / (GGXV + GGXL);</span>
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_approximatedspecularv"> </a><b style="font-style:normal;">Listing 4:</b> Implementation of the approximated specular V term in GLSL</div></center>
|
||||
<p>
|
||||
@@ -659,7 +659,7 @@ $$\begin{equation}
|
||||
\end{equation}$$
|
||||
</p><p>
|
||||
In practice, the diffuse reflectance \(\sigma\) is multiplied later, as shown in <a href="#listing_diffusebrdf">listing 8</a>.
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-built_in">float</span> <span class="hljs-title">Fd_Lambert</span>()</span> {</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-built_in">float</span> <span class="hljs-title">Fd_Lambert</span>(<span class="hljs-params"></span>)</span> {</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">1.0</span> / PI;</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
@@ -680,14 +680,14 @@ Where:
|
||||
$$\begin{equation}
|
||||
\fGrazing=0.5 + 2 \cdot \alpha cos^2(\theta_d)
|
||||
\end{equation}$$
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">float</span> <span class="hljs-title function_">F_Schlick</span><span class="hljs-params">(<span class="hljs-type">float</span> u, <span class="hljs-type">float</span> f0, <span class="hljs-type">float</span> f90)</span> {</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> f0 + (f90 - f0) * pow(<span class="hljs-number">1.0</span> - u, <span class="hljs-number">5.0</span>);</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">F_Schlick</span><span class="hljs-params">(<span class="hljs-keyword">float</span> u, <span class="hljs-keyword">float</span> f0, <span class="hljs-keyword">float</span> f90)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> f0 + (f90 - f0) * <span class="hljs-built_in">pow</span>(<span class="hljs-number">1.0</span> - u, <span class="hljs-number">5.0</span>);</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-type">float</span> <span class="hljs-title function_">Fd_Burley</span><span class="hljs-params">(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> NoL, <span class="hljs-type">float</span> LoH, <span class="hljs-type">float</span> roughness)</span> {</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">f90</span> <span class="hljs-operator">=</span> <span class="hljs-number">0.5</span> + <span class="hljs-number">2.0</span> * roughness * LoH * LoH;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">lightScatter</span> <span class="hljs-operator">=</span> F_Schlick(NoL, <span class="hljs-number">1.0</span>, f90);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">viewScatter</span> <span class="hljs-operator">=</span> F_Schlick(NoV, <span class="hljs-number">1.0</span>, f90);</span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">Fd_Burley</span><span class="hljs-params">(<span class="hljs-keyword">float</span> NoV, <span class="hljs-keyword">float</span> NoL, <span class="hljs-keyword">float</span> LoH, <span class="hljs-keyword">float</span> roughness)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> f90 = <span class="hljs-number">0.5</span> + <span class="hljs-number">2.0</span> * roughness * LoH * LoH;</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> lightScatter = F_Schlick(NoL, <span class="hljs-number">1.0</span>, f90);</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> viewScatter = F_Schlick(NoV, <span class="hljs-number">1.0</span>, f90);</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> lightScatter * viewScatter * (<span class="hljs-number">1.0</span> / PI);</span>
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_diffusebrdf"> </a><b style="font-style:normal;">Listing 8:</b> Implementation of the diffuse Disney BRDF in GLSL</div></center>
|
||||
<p>
|
||||
@@ -704,47 +704,47 @@ We could allow artists/developers to choose the Disney diffuse BRDF depending on
|
||||
<strong class="asterisk">Diffuse term</strong>: a Lambertian diffuse model.
|
||||
</p><p>
|
||||
The full GLSL implementation of the standard model is shown in <a href="#listing_glslbrdf">listing 9</a>.
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">float</span> <span class="hljs-title function_">D_GGX</span><span class="hljs-params">(<span class="hljs-type">float</span> NoH, <span class="hljs-type">float</span> a)</span> {</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">a2</span> <span class="hljs-operator">=</span> a * a;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">f</span> <span class="hljs-operator">=</span> (NoH * a2 - NoH) * NoH + <span class="hljs-number">1.0</span>;</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">float</span> D_GGX(<span class="hljs-type">float</span> NoH, <span class="hljs-type">float</span> a) {</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> a2 = a * a;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> f = (NoH * a2 - NoH) * NoH + <span class="hljs-number">1.0</span>;</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> a2 / (PI * f * f);</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line">vec3 <span class="hljs-title function_">F_Schlick</span><span class="hljs-params">(<span class="hljs-type">float</span> u, vec3 f0)</span> {</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> f0 + (vec3(<span class="hljs-number">1.0</span>) - f0) * pow(<span class="hljs-number">1.0</span> - u, <span class="hljs-number">5.0</span>);</span>
|
||||
<span class="line"><span class="hljs-type">vec3</span> F_Schlick(<span class="hljs-type">float</span> u, <span class="hljs-type">vec3</span> f0) {</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> f0 + (<span class="hljs-type">vec3</span>(<span class="hljs-number">1.0</span>) - f0) * <span class="hljs-built_in">pow</span>(<span class="hljs-number">1.0</span> - u, <span class="hljs-number">5.0</span>);</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-type">float</span> <span class="hljs-title function_">V_SmithGGXCorrelated</span><span class="hljs-params">(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> NoL, <span class="hljs-type">float</span> a)</span> {</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">a2</span> <span class="hljs-operator">=</span> a * a;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">GGXL</span> <span class="hljs-operator">=</span> NoV * sqrt((-NoL * a2 + NoL) * NoL + a2);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">GGXV</span> <span class="hljs-operator">=</span> NoL * sqrt((-NoV * a2 + NoV) * NoV + a2);</span>
|
||||
<span class="line"><span class="hljs-type">float</span> V_SmithGGXCorrelated(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> NoL, <span class="hljs-type">float</span> a) {</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> a2 = a * a;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> GGXL = NoV * <span class="hljs-built_in">sqrt</span>((-NoL * a2 + NoL) * NoL + a2);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> GGXV = NoL * <span class="hljs-built_in">sqrt</span>((-NoV * a2 + NoV) * NoV + a2);</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">0.5</span> / (GGXV + GGXL);</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-type">float</span> <span class="hljs-title function_">Fd_Lambert</span><span class="hljs-params">()</span> {</span>
|
||||
<span class="line"><span class="hljs-type">float</span> Fd_Lambert() {</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">1.0</span> / PI;</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-keyword">void</span> <span class="hljs-title function_">BRDF</span><span class="hljs-params">(...)</span> {</span>
|
||||
<span class="line"> <span class="hljs-type">vec3</span> <span class="hljs-variable">h</span> <span class="hljs-operator">=</span> normalize(v + l);</span>
|
||||
<span class="line"><span class="hljs-type">void</span> BRDF(...) {</span>
|
||||
<span class="line"> <span class="hljs-type">vec3</span> h = <span class="hljs-built_in">normalize</span>(v + l);</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">NoV</span> <span class="hljs-operator">=</span> abs(dot(n, v)) + <span class="hljs-number">1e-5</span>;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">NoL</span> <span class="hljs-operator">=</span> clamp(dot(n, l), <span class="hljs-number">0.0</span>, <span class="hljs-number">1.0</span>);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">NoH</span> <span class="hljs-operator">=</span> clamp(dot(n, h), <span class="hljs-number">0.0</span>, <span class="hljs-number">1.0</span>);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">LoH</span> <span class="hljs-operator">=</span> clamp(dot(l, h), <span class="hljs-number">0.0</span>, <span class="hljs-number">1.0</span>);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> NoV = <span class="hljs-built_in">abs</span>(<span class="hljs-built_in">dot</span>(n, v)) + <span class="hljs-number">1e-5</span>;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> NoL = <span class="hljs-built_in">clamp</span>(<span class="hljs-built_in">dot</span>(n, l), <span class="hljs-number">0.0</span>, <span class="hljs-number">1.0</span>);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> NoH = <span class="hljs-built_in">clamp</span>(<span class="hljs-built_in">dot</span>(n, h), <span class="hljs-number">0.0</span>, <span class="hljs-number">1.0</span>);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> LoH = <span class="hljs-built_in">clamp</span>(<span class="hljs-built_in">dot</span>(l, h), <span class="hljs-number">0.0</span>, <span class="hljs-number">1.0</span>);</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-comment">// perceptually linear roughness to roughness (see parameterization)</span></span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">roughness</span> <span class="hljs-operator">=</span> perceptualRoughness * perceptualRoughness;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> roughness = perceptualRoughness * perceptualRoughness;</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">D</span> <span class="hljs-operator">=</span> D_GGX(NoH, roughness);</span>
|
||||
<span class="line"> <span class="hljs-type">vec3</span> <span class="hljs-variable">F</span> <span class="hljs-operator">=</span> F_Schlick(LoH, f0);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">V</span> <span class="hljs-operator">=</span> V_SmithGGXCorrelated(NoV, NoL, roughness);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> D = D_GGX(NoH, roughness);</span>
|
||||
<span class="line"> <span class="hljs-type">vec3</span> F = F_Schlick(LoH, f0);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> V = V_SmithGGXCorrelated(NoV, NoL, roughness);</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-comment">// specular BRDF</span></span>
|
||||
<span class="line"> <span class="hljs-type">vec3</span> <span class="hljs-variable">Fr</span> <span class="hljs-operator">=</span> (D * V) * F;</span>
|
||||
<span class="line"> <span class="hljs-type">vec3</span> Fr = (D * V) * F;</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-comment">// diffuse BRDF</span></span>
|
||||
<span class="line"> <span class="hljs-type">vec3</span> <span class="hljs-variable">Fd</span> <span class="hljs-operator">=</span> diffuseColor * Fd_Lambert();</span>
|
||||
<span class="line"> <span class="hljs-type">vec3</span> Fd = diffuseColor * Fd_Lambert();</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-comment">// apply lighting...</span></span>
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_glslbrdf"> </a><b style="font-style:normal;">Listing 9:</b> Evaluation of the BRDF in GLSL</div></center>
|
||||
@@ -965,7 +965,7 @@ $$\begin{equation}
|
||||
\end{equation}$$
|
||||
</p><p>
|
||||
<a href="#listing_fnormal">Listing 12</a> shows how \(\fNormal\) is computed for both dielectric and metallic materials. It shows that the color of the specular reflectance is derived from the base color in the metallic case.
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-symbol">vec3</span> <span class="hljs-built_in">f0</span> = <span class="hljs-number">0</span>.<span class="hljs-number">16</span> * reflectance * reflectance * (<span class="hljs-number">1</span>.<span class="hljs-number">0</span> - metallic) + baseColor * metallic<span class="hljs-comment">;</span></span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_fnormal"> </a><b style="font-style:normal;">Listing 12:</b> Computing \(\fNormal\) for dielectric and metallic materials in GLSL</div></center>
|
||||
</p><pre class="listing tilde"><code><span class="line">vec3 f0 = 0.16 <span class="hljs-emphasis">* reflectance *</span> reflectance <span class="hljs-emphasis">* (1.0 - metallic) + baseColor *</span> metallic;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_fnormal"> </a><b style="font-style:normal;">Listing 12:</b> Computing \(\fNormal\) for dielectric and metallic materials in GLSL</div></center>
|
||||
<a class="target" name="roughnessremappingandclamping"> </a><a class="target" name="materialsystem/parameterization/remapping/roughnessremappingandclamping"> </a><a class="target" name="toc4.8.3.3"> </a><h4 id="roughness-remapping-and-clamping"><a class="header" href="#roughness-remapping-and-clamping">Roughness remapping and clamping</a></h4>
|
||||
<p>
|
||||
<p>The roughness set by the user, called <code>perceptualRoughness</code> here, is remapped to a perceptually linear range using the following formulation:</p>
|
||||
@@ -1054,7 +1054,7 @@ V(l,h) = \frac{1}{4(\LoH)^2}
|
||||
This masking-shadowing function is not physically based, as shown in [<a href="#citation-heitz14">Heitz14</a>], but its simplicity makes it desirable for real-time rendering.
|
||||
</p><p>
|
||||
In summary, our clear coat BRDF is a Cook-Torrance specular microfacet model, with a GGX normal distribution function, a Kelemen visibility function, and a Schlick Fresnel function. <a href="#listing_kelemen">Listing 13</a> shows how trivial the GLSL implementation is.
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">float</span> <span class="hljs-title">V_Kelemen</span><span class="hljs-params">(<span class="hljs-type">float</span> LoH)</span> </span>{</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-built_in">float</span> <span class="hljs-title">V_Kelemen</span>(<span class="hljs-params"><span class="hljs-built_in">float</span> LoH</span>)</span> {</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">0.25</span> / (LoH * LoH);</span>
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_kelemen"> </a><b style="font-style:normal;">Listing 13:</b> Implementation of the Kelemen visibility term in GLSL</div></center>
|
||||
<p>
|
||||
@@ -1097,18 +1097,18 @@ The clear coat roughness parameter is remapped and clamped in a similar way to t
|
||||
<center><div class="image" style=""><a href="../images/material_clear_coat2.png" target="_blank"><img class="markdeep" src="../images/material_clear_coat2.png" /></a><center><span class="imagecaption"><a class="target" name="figure_clearcoatroughness"> </a><b style="font-style:normal;">Figure 26:</b> Clear coat roughness varying from 0.0 (left) to 1.0 (right) with metallic set to 1.0, roughness to 0.8 and clear coat to 1.0</span></center></div></center>
|
||||
</p><p>
|
||||
<a href="#listing_clearcoatbrdf">Listing 14</a> shows the GLSL implementation of the clear coat material model after remapping, parameterization and integration in the standard surface response.
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">BRDF</span><span class="hljs-params">(...)</span> </span>{</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">BRDF</span>(<span class="hljs-params">...</span>)</span> {</span>
|
||||
<span class="line"> <span class="hljs-comment">// compute Fd and Fr from standard model</span></span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-comment">// remapping and linearization of clear coat roughness</span></span>
|
||||
<span class="line"> clearCoatPerceptualRoughness = <span class="hljs-built_in">clamp</span>(clearCoatPerceptualRoughness, <span class="hljs-number">0.089</span>, <span class="hljs-number">1.0</span>);</span>
|
||||
<span class="line"> clearCoatPerceptualRoughness = clamp(clearCoatPerceptualRoughness, <span class="hljs-number">0.089</span>, <span class="hljs-number">1.0</span>);</span>
|
||||
<span class="line"> clearCoatRoughness = clearCoatPerceptualRoughness * clearCoatPerceptualRoughness;</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-comment">// clear coat BRDF</span></span>
|
||||
<span class="line"> <span class="hljs-type">float</span> Dc = <span class="hljs-built_in">D_GGX</span>(clearCoatRoughness, NoH);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> Vc = <span class="hljs-built_in">V_Kelemen</span>(clearCoatRoughness, LoH);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> Fc = <span class="hljs-built_in">F_Schlick</span>(<span class="hljs-number">0.04</span>, LoH) * clearCoat; <span class="hljs-comment">// clear coat strength</span></span>
|
||||
<span class="line"> <span class="hljs-type">float</span> Frc = (Dc * Vc) * Fc;</span>
|
||||
<span class="line"> <span class="hljs-built_in">float</span> Dc = D_GGX(clearCoatRoughness, NoH);</span>
|
||||
<span class="line"> <span class="hljs-built_in">float</span> Vc = V_Kelemen(clearCoatRoughness, LoH);</span>
|
||||
<span class="line"> <span class="hljs-built_in">float</span> Fc = F_Schlick(<span class="hljs-number">0.04</span>, LoH) * clearCoat; <span class="hljs-comment">// clear coat strength</span></span>
|
||||
<span class="line"> <span class="hljs-built_in">float</span> Frc = (Dc * Vc) * Fc;</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-comment">// account for energy loss in the base layer</span></span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> color * ((Fd + Fr * (<span class="hljs-number">1.0</span> - Fc)) * (<span class="hljs-number">1.0</span> - Fc) + Frc);</span>
|
||||
@@ -1294,14 +1294,14 @@ f_{r}(v,h,\alpha) = \frac{D_{velvet}(v,h,\alpha)}{4(\NoL + \NoV - (\NoL)(\NoV))}
|
||||
\end{equation}$$
|
||||
</p><p>
|
||||
The implementation of the velvet NDF is presented in <a href="#listing_clothbrdf">listing 17</a>, optimized to properly fit in half float formats and to avoid computing a costly cotangent, relying instead on trigonometric identities. Note that we removed the Fresnel component from this BRDF.
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">float</span> <span class="hljs-title">D_Ashikhmin</span><span class="hljs-params">(<span class="hljs-type">float</span> roughness, <span class="hljs-type">float</span> NoH)</span> </span>{</span>
|
||||
</p><pre class="listing tilde"><code><span class="line">float D_Ashikhmin(float roughness, float NoH) {</span>
|
||||
<span class="line"> <span class="hljs-comment">// Ashikhmin 2007, "Distribution-based BRDFs"</span></span>
|
||||
<span class="line"> <span class="hljs-type">float</span> a2 = roughness * roughness;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> cos2h = NoH * NoH;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> sin2h = <span class="hljs-built_in">max</span>(<span class="hljs-number">1.0</span> - cos2h, <span class="hljs-number">0.0078125</span>); <span class="hljs-comment">// 2^(-14/2), so sin2h^2 > 0 in fp16</span></span>
|
||||
<span class="line"> <span class="hljs-type">float</span> sin4h = sin2h * sin2h;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> cot2 = -cos2h / (a2 * sin2h);</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">1.0</span> / (PI * (<span class="hljs-number">4.0</span> * a2 + <span class="hljs-number">1.0</span>) * sin4h) * (<span class="hljs-number">4.0</span> * <span class="hljs-built_in">exp</span>(cot2) + sin4h);</span>
|
||||
<span class="line"> float a<span class="hljs-number">2</span> = roughness * roughness;</span>
|
||||
<span class="line"> float <span class="hljs-keyword">cos</span><span class="hljs-number">2</span>h = NoH * NoH;</span>
|
||||
<span class="line"> float <span class="hljs-keyword">sin</span><span class="hljs-number">2</span>h = <span class="hljs-keyword">max</span>(<span class="hljs-number">1.0</span> - <span class="hljs-keyword">cos</span><span class="hljs-number">2</span>h, <span class="hljs-number">0.0078125</span>); <span class="hljs-comment">// 2^(-14/2), so sin2h^2 > 0 in fp16</span></span>
|
||||
<span class="line"> float <span class="hljs-keyword">sin</span><span class="hljs-number">4</span>h = <span class="hljs-keyword">sin</span><span class="hljs-number">2</span>h * <span class="hljs-keyword">sin</span><span class="hljs-number">2</span>h;</span>
|
||||
<span class="line"> float cot<span class="hljs-number">2</span> = -<span class="hljs-keyword">cos</span><span class="hljs-number">2</span>h / (a<span class="hljs-number">2</span> * <span class="hljs-keyword">sin</span><span class="hljs-number">2</span>h);</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">1.0</span> / (PI * (<span class="hljs-number">4.0</span> * a<span class="hljs-number">2</span> + <span class="hljs-number">1.0</span>) * <span class="hljs-keyword">sin</span><span class="hljs-number">4</span>h) * (<span class="hljs-number">4.0</span> * exp(cot<span class="hljs-number">2</span>) + <span class="hljs-keyword">sin</span><span class="hljs-number">4</span>h);</span>
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_clothbrdf"> </a><b style="font-style:normal;">Listing 17:</b> Implementation of Ashikhmin's velvet NDF in GLSL</div></center>
|
||||
<p>
|
||||
<p>In [<a href="#citation-estevez17">Estevez17</a>] Estevez and Kulla propose a different NDF (called the “Charlie” sheen) that is based on an exponentiated sinusoidal instead of an inverted Gaussian. This NDF is appealing for several reasons: its parameterization feels more natural and intuitive, it provides a softer appearance and, as shown in equation (\ref{charlieNDF}), its implementation is simpler:</p>
|
||||
@@ -1312,11 +1312,11 @@ D(m) = \frac{(2 + \frac{1}{\alpha}) sin(\theta)^{\frac{1}{\alpha}}}{2 \pi}
|
||||
</p><p>
|
||||
[<a href="#citation-estevez17">Estevez17</a>] also presents a new shadowing term that we omit here because of its cost. We instead rely on the visibility term from [<a href="#citation-neubelt13">Neubelt13</a>] (shown in equation \(\ref{clothSpecularBRDF}\) above).
|
||||
The implementation of this NDF is presented in <a href="#listing_clothcharliebrdf">listing 18</a>, optimized to properly fit in half float formats.
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">float</span> <span class="hljs-title">D_Charlie</span><span class="hljs-params">(<span class="hljs-type">float</span> roughness, <span class="hljs-type">float</span> NoH)</span> </span>{</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">D_Charlie</span><span class="hljs-params">(<span class="hljs-keyword">float</span> roughness, <span class="hljs-keyword">float</span> NoH)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-comment">// Estevez and Kulla 2017, "Production Friendly Microfacet Sheen BRDF"</span></span>
|
||||
<span class="line"> <span class="hljs-type">float</span> invAlpha = <span class="hljs-number">1.0</span> / roughness;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> cos2h = NoH * NoH;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> sin2h = <span class="hljs-built_in">max</span>(<span class="hljs-number">1.0</span> - cos2h, <span class="hljs-number">0.0078125</span>); <span class="hljs-comment">// 2^(-14/2), so sin2h^2 > 0 in fp16</span></span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> invAlpha = <span class="hljs-number">1.0</span> / roughness;</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> cos2h = NoH * NoH;</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> sin2h = max(<span class="hljs-number">1.0</span> - cos2h, <span class="hljs-number">0.0078125</span>); <span class="hljs-comment">// 2^(-14/2), so sin2h^2 > 0 in fp16</span></span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> (<span class="hljs-number">2.0</span> + invAlpha) * <span class="hljs-built_in">pow</span>(sin2h, invAlpha * <span class="hljs-number">0.5</span>) / (<span class="hljs-number">2.0</span> * PI);</span>
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_clothcharliebrdf"> </a><b style="font-style:normal;">Listing 18:</b> Implementation of the “Charlie” NDF in GLSL</div></center>
|
||||
<a class="target" name="sheencolor"> </a><a class="target" name="materialsystem/clothmodel/clothspecularbrdf/sheencolor"> </a><a class="target" name="toc4.12.1.1"> </a><h4 id="sheen-color"><a class="header" href="#sheen-color">Sheen color</a></h4>
|
||||
@@ -1745,21 +1745,21 @@ The photometric attenuation function can be easily implemented in GLSL by adding
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_glslphotometricpunctuallight"> </a><b style="font-style:normal;">Listing 22:</b> Implementation of attenuation from photometric profiles in GLSL</div></center>
|
||||
<p>
|
||||
<p>The light intensity is computed CPU-side (<a href="#listing_photometriclightintensity">listing 23</a>) and depends on whether the photometric profile is used as a mask.</p>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">float</span> multiplier;</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-keyword">float</span> multiplier;</span>
|
||||
<span class="line"><span class="hljs-comment">// Photometric profile used as a mask</span></span>
|
||||
<span class="line"><span class="hljs-keyword">if</span> (photometricLight.<span class="hljs-built_in">isMasked</span>()) {</span>
|
||||
<span class="line"><span class="hljs-keyword">if</span> (photometricLight.isMasked()) {</span>
|
||||
<span class="line"> <span class="hljs-comment">// The desired intensity is set by the artist</span></span>
|
||||
<span class="line"> <span class="hljs-comment">// The integrated intensity comes from a Monte-Carlo</span></span>
|
||||
<span class="line"> <span class="hljs-comment">// integration over the unit sphere around the luminaire</span></span>
|
||||
<span class="line"> multiplier = photometricLight.<span class="hljs-built_in">getDesiredIntensity</span>() /</span>
|
||||
<span class="line"> photometricLight.<span class="hljs-built_in">getIntegratedIntensity</span>();</span>
|
||||
<span class="line"> multiplier = photometricLight.getDesiredIntensity() /</span>
|
||||
<span class="line"> photometricLight.getIntegratedIntensity();</span>
|
||||
<span class="line">} <span class="hljs-keyword">else</span> {</span>
|
||||
<span class="line"> <span class="hljs-comment">// Multiplier provided for convenience, set to 1.0 by default</span></span>
|
||||
<span class="line"> multiplier = photometricLight.<span class="hljs-built_in">getMultiplier</span>();</span>
|
||||
<span class="line"> multiplier = photometricLight.getMultiplier();</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-comment">// The max intensity in cd comes from the IES profile</span></span>
|
||||
<span class="line"><span class="hljs-type">float</span> lightIntensity = photometricLight.<span class="hljs-built_in">getMaxIntensity</span>() * multiplier;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_photometriclightintensity"> </a><b style="font-style:normal;">Listing 23:</b> Computing the intensity of a photometric light on the CPU</div></center>
|
||||
<span class="line"><span class="hljs-keyword">float</span> lightIntensity = photometricLight.getMaxIntensity() * multiplier;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_photometriclightintensity"> </a><b style="font-style:normal;">Listing 23:</b> Computing the intensity of a photometric light on the CPU</div></center>
|
||||
<p>
|
||||
<div class="endnote"><a class="target" name="endnote-xarrowintensity"> </a><sup>4</sup> The XArrow profile declares a luminous intensity of 1,750 lm but a Monte-Carlo integration shows an intensity of only 350 lm.
|
||||
</div>
|
||||
@@ -2058,19 +2058,19 @@ In practice only 4 or 9 coefficients (i.e.: 2 or 3 bands) are enough for \(\cosT
|
||||
<center><div class="image" style=""><a href="../images/ibl/ibl_irradiance_sh2.png" target="_blank"><img class="markdeep" src="../images/ibl/ibl_irradiance_sh2.png" style="max-width:100%;" /></a><center><span class="imagecaption"><a class="target" name="figure_iblsh2"> </a><b style="font-style:normal;">Figure 52:</b> 2 bands (4 coefficients)</span></center></div></center>
|
||||
</p><p>
|
||||
In practice we pre-convolve \(\Lt\) with \(\cosTheta\) and pre-scale these coefficients by the basis scaling factors \(K_l^m\) so that the reconstruction code is as simple as possible in the shader:
|
||||
</p><pre class="listing tilde"><code><span class="line">vec3 irradianceSH(vec3 n) {</span>
|
||||
<span class="line"> // uniform vec3 sphericalHarmonics<span class="hljs-selector-attr">[9]</span></span>
|
||||
<span class="line"> // We can <span class="hljs-selector-tag">use</span> only the first <span class="hljs-number">2</span> bands for better performance</span>
|
||||
<span class="line"> return</span>
|
||||
<span class="line"> sphericalHarmonics<span class="hljs-selector-attr">[0]</span></span>
|
||||
<span class="line"> + sphericalHarmonics<span class="hljs-selector-attr">[1]</span> * (n<span class="hljs-selector-class">.y</span>)</span>
|
||||
<span class="line"> + sphericalHarmonics<span class="hljs-selector-attr">[2]</span> * (n<span class="hljs-selector-class">.z</span>)</span>
|
||||
<span class="line"> + sphericalHarmonics<span class="hljs-selector-attr">[3]</span> * (n<span class="hljs-selector-class">.x</span>)</span>
|
||||
<span class="line"> + sphericalHarmonics<span class="hljs-selector-attr">[4]</span> * (n<span class="hljs-selector-class">.y</span> * n<span class="hljs-selector-class">.x</span>)</span>
|
||||
<span class="line"> + sphericalHarmonics<span class="hljs-selector-attr">[5]</span> * (n<span class="hljs-selector-class">.y</span> * n<span class="hljs-selector-class">.z</span>)</span>
|
||||
<span class="line"> + sphericalHarmonics<span class="hljs-selector-attr">[6]</span> * (<span class="hljs-number">3.0</span> * n<span class="hljs-selector-class">.z</span> * n<span class="hljs-selector-class">.z</span> - <span class="hljs-number">1.0</span>)</span>
|
||||
<span class="line"> + sphericalHarmonics<span class="hljs-selector-attr">[7]</span> * (n<span class="hljs-selector-class">.z</span> * n<span class="hljs-selector-class">.x</span>)</span>
|
||||
<span class="line"> + sphericalHarmonics<span class="hljs-selector-attr">[8]</span> * (n<span class="hljs-selector-class">.x</span> * n<span class="hljs-selector-class">.x</span> - n<span class="hljs-selector-class">.y</span> * n<span class="hljs-selector-class">.y</span>);</span>
|
||||
</p><pre class="listing tilde"><code><span class="line">vec3 <span class="hljs-function"><span class="hljs-title">irradianceSH</span>(<span class="hljs-params">vec3 n</span>)</span> {</span>
|
||||
<span class="line"> <span class="hljs-comment">// uniform vec3 sphericalHarmonics[9]</span></span>
|
||||
<span class="line"> <span class="hljs-comment">// We can use only the first 2 bands for better performance</span></span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span></span>
|
||||
<span class="line"> sphericalHarmonics[<span class="hljs-number">0</span>]</span>
|
||||
<span class="line"> + sphericalHarmonics[<span class="hljs-number">1</span>] * (n.y)</span>
|
||||
<span class="line"> + sphericalHarmonics[<span class="hljs-number">2</span>] * (n.z)</span>
|
||||
<span class="line"> + sphericalHarmonics[<span class="hljs-number">3</span>] * (n.x)</span>
|
||||
<span class="line"> + sphericalHarmonics[<span class="hljs-number">4</span>] * (n.y * n.x)</span>
|
||||
<span class="line"> + sphericalHarmonics[<span class="hljs-number">5</span>] * (n.y * n.z)</span>
|
||||
<span class="line"> + sphericalHarmonics[<span class="hljs-number">6</span>] * (<span class="hljs-number">3.0</span> * n.z * n.z - <span class="hljs-number">1.0</span>)</span>
|
||||
<span class="line"> + sphericalHarmonics[<span class="hljs-number">7</span>] * (n.z * n.x)</span>
|
||||
<span class="line"> + sphericalHarmonics[<span class="hljs-number">8</span>] * (n.x * n.x - n.y * n.y);</span>
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_irradiancesh"> </a><b style="font-style:normal;">Listing 26:</b> GLSL code to reconstruct the irradiance from the pre-scaled SH</div></center>
|
||||
<p>
|
||||
<p>Note that with 2 bands, the computation above becomes a single (4 \times 4) matrix-by-vector multiply.</p>
|
||||
@@ -2443,7 +2443,7 @@ LD(n, \alpha) &= \frac{\sum_i^N V(l_i, n,
|
||||
$$
|
||||
</p><p>
|
||||
These two new \(DFG\) terms simply need to replace the ones used in the implementation shown in section <a href="#toc9.5">9.5</a>:
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">float</span> Fc = <span class="hljs-built_in">pow</span>(<span class="hljs-number">1</span> - VoH, <span class="hljs-number">5.0f</span>);</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-keyword">float</span> Fc = <span class="hljs-built_in">pow</span>(<span class="hljs-number">1</span> - VoH, <span class="hljs-number">5.0f</span>);</span>
|
||||
<span class="line">r.x += Gv * Fc;</span>
|
||||
<span class="line">r.y += Gv;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_multiscatteriblpreintegration"> </a><b style="font-style:normal;">Listing 29:</b> C++ implementation of the \(L_{DFG}\) term for multiscattering</div></center>
|
||||
<p>
|
||||
@@ -2507,11 +2507,11 @@ using an environment made of colored vertical stripes (skybox hidden).</span></c
|
||||
<p>
|
||||
<p>When sampling the IBL, the clear coat layer is calculated as a second specular lobe. This specular lobe is oriented along the view direction since we cannot reasonably integrate over the hemisphere. <a href="#listing_clearcoatibl">Listing 31</a> demonstrates this approximation in practice. It also shows the energy conservation step. It is important to note that this second specular lobe is computed exactly the same way as the main specular lobe, using the same DFG approximation.</p>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-comment">// clearCoat_NoV == shading_NoV if the clear coat layer doesn't have its own normal map</span></span>
|
||||
<span class="line"><span class="hljs-type">float</span> Fc = <span class="hljs-built_in">F_Schlick</span>(<span class="hljs-number">0.04</span>, <span class="hljs-number">1.0</span>, clearCoat_NoV) * clearCoat;</span>
|
||||
<span class="line"><span class="hljs-built_in">float</span> Fc = F_Schlick(<span class="hljs-number">0.04</span>, <span class="hljs-number">1.0</span>, clearCoat_NoV) * clearCoat;</span>
|
||||
<span class="line"><span class="hljs-comment">// base layer attenuation for energy compensation</span></span>
|
||||
<span class="line">iblDiffuse *= <span class="hljs-number">1.0</span> - Fc;</span>
|
||||
<span class="line">iblSpecular *= <span class="hljs-built_in">sq</span>(<span class="hljs-number">1.0</span> - Fc);</span>
|
||||
<span class="line">iblSpecular += <span class="hljs-built_in">specularIBL</span>(r, clearCoatPerceptualRoughness) * Fc;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_clearcoatibl"> </a><b style="font-style:normal;">Listing 31:</b> GLSL implementation of the clear coat specular lobe for image-based lighting</div></center>
|
||||
<span class="line">iblSpecular *= sq(<span class="hljs-number">1.0</span> - Fc);</span>
|
||||
<span class="line">iblSpecular += specularIBL(r, clearCoatPerceptualRoughness) * Fc;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_clearcoatibl"> </a><b style="font-style:normal;">Listing 31:</b> GLSL implementation of the clear coat specular lobe for image-based lighting</div></center>
|
||||
<a class="target" name="anisotropy"> </a><a class="target" name="lighting/imagebasedlights/anisotropy"> </a><a class="target" name="toc5.3.6"> </a><h3 id="anisotropy"><a class="header" href="#anisotropy">Anisotropy </a></h3>
|
||||
<p>
|
||||
<p>[<a href="#citation-mcauley15">McAuley15</a>] describes a technique called “bent reflection vector”, based [<a href="#citation-revie12">Revie12</a>]. The bent reflection vector is a rough approximation of anisotropic lighting but the alternative is to use importance sampling. This approximation is sufficiently cheap to compute and provides good results, as shown in <a href="#figure_anisotropicibl1">figure 59</a> and <a href="#figure_anisotropicibl2">figure 60</a>.</p>
|
||||
@@ -2550,17 +2550,17 @@ The DG term is generated using uniform sampling as recommended in [<a href="#cit
|
||||
<center><div class="image" style=""><a href="../images/ibl/dfg_cloth.png" target="_blank"><img class="markdeep" src="../images/ibl/dfg_cloth.png" /></a><center><span class="imagecaption"><a class="target" name="figure_dfgclothlut"> </a><b style="font-style:normal;">Figure 62:</b> DFG LUT with a 3rd channel encoding the DG term of the cloth BRDF</span></center></div></center>
|
||||
</p><p>
|
||||
The remainder of the image-based lighting implementation follows the same steps as the implementation of regular lights, including the optional subsurface scattering term and its wrap diffuse component. Just as with the clear coat IBL implementation, we cannot integrate over the hemisphere and use the view direction as the dominant light direction to compute the wrap diffuse component.
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">float</span> diffuse = <span class="hljs-built_in">Fd_Lambert</span>() * ambientOcclusion;</span>
|
||||
<span class="line"><span class="hljs-meta">#<span class="hljs-keyword">if</span> defined(SHADING_MODEL_CLOTH)</span></span>
|
||||
<span class="line"><span class="hljs-meta">#<span class="hljs-keyword">if</span> defined(MATERIAL_HAS_SUBSURFACE_COLOR)</span></span>
|
||||
<span class="line">diffuse *= <span class="hljs-built_in">saturate</span>((NoV + <span class="hljs-number">0.5</span>) / <span class="hljs-number">2.25</span>);</span>
|
||||
<span class="line"><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span></span>
|
||||
<span class="line"><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span></span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-built_in">float</span> diffuse = Fd_Lambert() * ambientOcclusion;</span>
|
||||
<span class="line"><span class="hljs-meta">#<span class="hljs-meta-keyword">if</span> defined(SHADING_MODEL_CLOTH)</span></span>
|
||||
<span class="line"><span class="hljs-meta">#<span class="hljs-meta-keyword">if</span> defined(MATERIAL_HAS_SUBSURFACE_COLOR)</span></span>
|
||||
<span class="line">diffuse *= saturate((NoV + <span class="hljs-number">0.5</span>) / <span class="hljs-number">2.25</span>);</span>
|
||||
<span class="line"><span class="hljs-meta">#<span class="hljs-meta-keyword">endif</span></span></span>
|
||||
<span class="line"><span class="hljs-meta">#<span class="hljs-meta-keyword">endif</span></span></span>
|
||||
<span class="line"></span>
|
||||
<span class="line">vec3 indirectDiffuse = <span class="hljs-built_in">irradianceIBL</span>(n) * diffuse;</span>
|
||||
<span class="line"><span class="hljs-meta">#<span class="hljs-keyword">if</span> defined(SHADING_MODEL_CLOTH) && defined(MATERIAL_HAS_SUBSURFACE_COLOR)</span></span>
|
||||
<span class="line">indirectDiffuse *= <span class="hljs-built_in">saturate</span>(subsurfaceColor + NoV);</span>
|
||||
<span class="line"><span class="hljs-meta">#<span class="hljs-keyword">endif</span></span></span>
|
||||
<span class="line">vec3 indirectDiffuse = irradianceIBL(n) * diffuse;</span>
|
||||
<span class="line"><span class="hljs-meta">#<span class="hljs-meta-keyword">if</span> defined(SHADING_MODEL_CLOTH) && defined(MATERIAL_HAS_SUBSURFACE_COLOR)</span></span>
|
||||
<span class="line">indirectDiffuse *= saturate(subsurfaceColor + NoV);</span>
|
||||
<span class="line"><span class="hljs-meta">#<span class="hljs-meta-keyword">endif</span></span></span>
|
||||
<span class="line"></span>
|
||||
<span class="line">vec3 ibl = diffuseColor * indirectDiffuse + indirectSpecular * specularColor;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_clothapprox"> </a><b style="font-style:normal;">Listing 34:</b> GLSL implementation of the DFG approximation for the cloth NDF</div></center>
|
||||
<p>
|
||||
@@ -2982,20 +2982,20 @@ L_{max} &= 2^{EV_{100}} \times 1.2
|
||||
<span class="line"><span class="hljs-comment">// aperture in f-stops</span></span>
|
||||
<span class="line"><span class="hljs-comment">// shutterSpeed in seconds</span></span>
|
||||
<span class="line"><span class="hljs-comment">// sensitivity in ISO</span></span>
|
||||
<span class="line"><span class="hljs-type">float</span> <span class="hljs-title function_">exposureSettings</span><span class="hljs-params">(<span class="hljs-type">float</span> aperture, <span class="hljs-type">float</span> shutterSpeed, <span class="hljs-type">float</span> sensitivity)</span> {</span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">exposureSettings</span><span class="hljs-params">(<span class="hljs-keyword">float</span> aperture, <span class="hljs-keyword">float</span> shutterSpeed, <span class="hljs-keyword">float</span> sensitivity)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> log2((aperture * aperture) / shutterSpeed * <span class="hljs-number">100.0</span> / sensitivity);</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-comment">// Computes the exposure normalization factor from</span></span>
|
||||
<span class="line"><span class="hljs-comment">// the camera's EV100</span></span>
|
||||
<span class="line"><span class="hljs-type">float</span> <span class="hljs-title function_">exposure</span><span class="hljs-params">(<span class="hljs-type">float</span> ev100)</span> {</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">1.0</span> / (pow(<span class="hljs-number">2.0</span>, ev100) * <span class="hljs-number">1.2</span>);</span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">exposure</span><span class="hljs-params">(<span class="hljs-keyword">float</span> ev100)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">1.0</span> / (<span class="hljs-built_in">pow</span>(<span class="hljs-number">2.0</span>, ev100) * <span class="hljs-number">1.2</span>);</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-type">float</span> <span class="hljs-variable">ev100</span> <span class="hljs-operator">=</span> exposureSettings(aperture, shutterSpeed, sensitivity);</span>
|
||||
<span class="line"><span class="hljs-type">float</span> <span class="hljs-variable">exposure</span> <span class="hljs-operator">=</span> exposure(ev100);</span>
|
||||
<span class="line"><span class="hljs-keyword">float</span> ev100 = exposureSettings(aperture, shutterSpeed, sensitivity);</span>
|
||||
<span class="line"><span class="hljs-keyword">float</span> exposure = exposure(ev100);</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-type">vec4</span> <span class="hljs-variable">color</span> <span class="hljs-operator">=</span> evaluateLighting();</span>
|
||||
<span class="line">vec4 color = evaluateLighting();</span>
|
||||
<span class="line">color.rgb *= exposure;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_fragmentexposure"> </a><b style="font-style:normal;">Listing 42:</b> Implementation of exposure in GLSL</div></center>
|
||||
<p>
|
||||
<p>In practice the exposure factor can be pre-computed on the CPU to save shader instructions.</p>
|
||||
@@ -3466,9 +3466,9 @@ Our implementation is presented in <a href="#listing_specularcolorimpl">listing&
|
||||
<span class="line"><span class="hljs-comment">// Data source:</span></span>
|
||||
<span class="line"><span class="hljs-comment">// http://cvrl.ioo.ucl.ac.uk/cmfs.htm</span></span>
|
||||
<span class="line"><span class="hljs-comment">// http://cvrl.ioo.ucl.ac.uk/database/text/cmfs/ciexyz31.htm</span></span>
|
||||
<span class="line"><span class="hljs-type">const</span> <span class="hljs-type">size_t</span> CIE_XYZ_START = <span class="hljs-number">360</span>;</span>
|
||||
<span class="line"><span class="hljs-type">const</span> <span class="hljs-type">size_t</span> CIE_XYZ_COUNT = <span class="hljs-number">471</span>;</span>
|
||||
<span class="line"><span class="hljs-type">const</span> float3 CIE_XYZ[CIE_XYZ_COUNT] = { ... };</span>
|
||||
<span class="line"><span class="hljs-keyword">const</span> <span class="hljs-keyword">size_t</span> CIE_XYZ_START = <span class="hljs-number">360</span>;</span>
|
||||
<span class="line"><span class="hljs-keyword">const</span> <span class="hljs-keyword">size_t</span> CIE_XYZ_COUNT = <span class="hljs-number">471</span>;</span>
|
||||
<span class="line"><span class="hljs-keyword">const</span> float3 CIE_XYZ[CIE_XYZ_COUNT] = { ... };</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-comment">// CIE Standard Illuminant D65 relative spectral power distribution,</span></span>
|
||||
<span class="line"><span class="hljs-comment">// from 300nm to 830, at 5nm intervals</span></span>
|
||||
@@ -3476,51 +3476,51 @@ Our implementation is presented in <a href="#listing_specularcolorimpl">listing&
|
||||
<span class="line"><span class="hljs-comment">// Data source:</span></span>
|
||||
<span class="line"><span class="hljs-comment">// https://en.wikipedia.org/wiki/Illuminant_D65</span></span>
|
||||
<span class="line"><span class="hljs-comment">// https://cielab.xyz/pdf/CIE_sel_colorimetric_tables.xls</span></span>
|
||||
<span class="line"><span class="hljs-type">const</span> <span class="hljs-type">size_t</span> CIE_D65_INTERVAL = <span class="hljs-number">5</span>;</span>
|
||||
<span class="line"><span class="hljs-type">const</span> <span class="hljs-type">size_t</span> CIE_D65_START = <span class="hljs-number">300</span>;</span>
|
||||
<span class="line"><span class="hljs-type">const</span> <span class="hljs-type">size_t</span> CIE_D65_END = <span class="hljs-number">830</span>;</span>
|
||||
<span class="line"><span class="hljs-type">const</span> <span class="hljs-type">size_t</span> CIE_D65_COUNT = <span class="hljs-number">107</span>;</span>
|
||||
<span class="line"><span class="hljs-type">const</span> <span class="hljs-type">float</span> CIE_D65[CIE_D65_COUNT] = { ... };</span>
|
||||
<span class="line"><span class="hljs-keyword">const</span> <span class="hljs-keyword">size_t</span> CIE_D65_INTERVAL = <span class="hljs-number">5</span>;</span>
|
||||
<span class="line"><span class="hljs-keyword">const</span> <span class="hljs-keyword">size_t</span> CIE_D65_START = <span class="hljs-number">300</span>;</span>
|
||||
<span class="line"><span class="hljs-keyword">const</span> <span class="hljs-keyword">size_t</span> CIE_D65_END = <span class="hljs-number">830</span>;</span>
|
||||
<span class="line"><span class="hljs-keyword">const</span> <span class="hljs-keyword">size_t</span> CIE_D65_COUNT = <span class="hljs-number">107</span>;</span>
|
||||
<span class="line"><span class="hljs-keyword">const</span> <span class="hljs-keyword">float</span> CIE_D65[CIE_D65_COUNT] = { ... };</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-keyword">struct</span> <span class="hljs-title class_">Sample</span> {</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> w = <span class="hljs-number">0.0f</span>; <span class="hljs-comment">// wavelength</span></span>
|
||||
<span class="line"> std::complex<<span class="hljs-type">float</span>> ior; <span class="hljs-comment">// complex IOR, n + ik</span></span>
|
||||
<span class="line"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Sample</span> {</span></span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> w = <span class="hljs-number">0.0f</span>; <span class="hljs-comment">// wavelength</span></span>
|
||||
<span class="line"> <span class="hljs-built_in">std</span>::<span class="hljs-built_in">complex</span><<span class="hljs-keyword">float</span>> ior; <span class="hljs-comment">// complex IOR, n + ik</span></span>
|
||||
<span class="line">};</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-type">float</span> <span class="hljs-title">illuminantD65</span><span class="hljs-params">(<span class="hljs-type">float</span> w)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">auto</span> i0 = <span class="hljs-built_in">size_t</span>((w - CIE_D65_START) / CIE_D65_INTERVAL);</span>
|
||||
<span class="line"> uint2 indexBounds{i0, std::<span class="hljs-built_in">min</span>(i0 + <span class="hljs-number">1</span>, CIE_D65_END)};</span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">float</span> <span class="hljs-title">illuminantD65</span><span class="hljs-params">(<span class="hljs-keyword">float</span> w)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">auto</span> i0 = <span class="hljs-keyword">size_t</span>((w - CIE_D65_START) / CIE_D65_INTERVAL);</span>
|
||||
<span class="line"> uint2 indexBounds{i0, <span class="hljs-built_in">std</span>::min(i0 + <span class="hljs-number">1</span>, CIE_D65_END)};</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> float2 wavelengthBounds = CIE_D65_START + float2{indexBounds} * CIE_D65_INTERVAL;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> t = (w - wavelengthBounds.x) / (wavelengthBounds.y - wavelengthBounds.x);</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-built_in">lerp</span>(CIE_D65[indexBounds.x], CIE_D65[indexBounds.y], t);</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> t = (w - wavelengthBounds.x) / (wavelengthBounds.y - wavelengthBounds.x);</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> lerp(CIE_D65[indexBounds.x], CIE_D65[indexBounds.y], t);</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-comment">// For std::lower_bound</span></span>
|
||||
<span class="line"><span class="hljs-type">bool</span> <span class="hljs-keyword">operator</span><(<span class="hljs-type">const</span> Sample& lhs, <span class="hljs-type">const</span> Sample& rhs) {</span>
|
||||
<span class="line"><span class="hljs-keyword">bool</span> <span class="hljs-keyword">operator</span><(<span class="hljs-keyword">const</span> Sample& lhs, <span class="hljs-keyword">const</span> Sample& rhs) {</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> lhs.w < rhs.w;</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-comment">// The wavelength w must be between 360nm and 830nm</span></span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-type">static</span> std::complex<<span class="hljs-type">float</span>> <span class="hljs-title">findSample</span><span class="hljs-params">(<span class="hljs-type">const</span> std::vector<sample>& samples, <span class="hljs-type">float</span> w)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">auto</span> i1 = std::<span class="hljs-built_in">lower_bound</span>(</span>
|
||||
<span class="line"> samples.<span class="hljs-built_in">begin</span>(), samples.<span class="hljs-built_in">end</span>(), Sample{w, <span class="hljs-number">0.0f</span> + <span class="hljs-number">0.0</span><span class="hljs-keyword">if</span>});</span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-built_in">std</span>::<span class="hljs-built_in">complex</span><<span class="hljs-keyword">float</span>> <span class="hljs-title">findSample</span><span class="hljs-params">(<span class="hljs-keyword">const</span> <span class="hljs-built_in">std</span>::<span class="hljs-built_in">vector</span><sample>& samples, <span class="hljs-keyword">float</span> w)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">auto</span> i1 = <span class="hljs-built_in">std</span>::lower_bound(</span>
|
||||
<span class="line"> samples.begin(), samples.end(), Sample{w, <span class="hljs-number">0.0f</span> + <span class="hljs-number">0.0</span><span class="hljs-keyword">if</span>});</span>
|
||||
<span class="line"> <span class="hljs-keyword">auto</span> i0 = i1 - <span class="hljs-number">1</span>;</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-comment">// Interpolate the complex IORs</span></span>
|
||||
<span class="line"> <span class="hljs-type">float</span> t = (w - i0->w) / (i1->w - i0->w);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> n = <span class="hljs-built_in">lerp</span>(i0->ior.<span class="hljs-built_in">real</span>(), i1->ior.<span class="hljs-built_in">real</span>(), t);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> k = <span class="hljs-built_in">lerp</span>(i0->ior.<span class="hljs-built_in">imag</span>(), i1->ior.<span class="hljs-built_in">imag</span>(), t);</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> t = (w - i0->w) / (i1->w - i0->w);</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> n = lerp(i0->ior.real(), i1->ior.real(), t);</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> k = lerp(i0->ior.imag(), i1->ior.imag(), t);</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> { n, k };</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-type">float</span> <span class="hljs-title">fresnel</span><span class="hljs-params">(<span class="hljs-type">const</span> std::complex<<span class="hljs-type">float</span>>& sample)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> (((sample - (<span class="hljs-number">1.0f</span> + <span class="hljs-number">0</span><span class="hljs-keyword">if</span>)) * (std::<span class="hljs-built_in">conj</span>(sample) - (<span class="hljs-number">1.0f</span> + <span class="hljs-number">0</span><span class="hljs-keyword">if</span>))) /</span>
|
||||
<span class="line"> ((sample + (<span class="hljs-number">1.0f</span> + <span class="hljs-number">0</span><span class="hljs-keyword">if</span>)) * (std::<span class="hljs-built_in">conj</span>(sample) + (<span class="hljs-number">1.0f</span> + <span class="hljs-number">0</span><span class="hljs-keyword">if</span>)))).<span class="hljs-built_in">real</span>();</span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">float</span> <span class="hljs-title">fresnel</span><span class="hljs-params">(<span class="hljs-keyword">const</span> <span class="hljs-built_in">std</span>::<span class="hljs-built_in">complex</span><<span class="hljs-keyword">float</span>>& sample)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> (((sample - (<span class="hljs-number">1.0f</span> + <span class="hljs-number">0</span><span class="hljs-keyword">if</span>)) * (<span class="hljs-built_in">std</span>::conj(sample) - (<span class="hljs-number">1.0f</span> + <span class="hljs-number">0</span><span class="hljs-keyword">if</span>))) /</span>
|
||||
<span class="line"> ((sample + (<span class="hljs-number">1.0f</span> + <span class="hljs-number">0</span><span class="hljs-keyword">if</span>)) * (<span class="hljs-built_in">std</span>::conj(sample) + (<span class="hljs-number">1.0f</span> + <span class="hljs-number">0</span><span class="hljs-keyword">if</span>)))).real();</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-type">static</span> float3 <span class="hljs-title">XYZ_to_sRGB</span><span class="hljs-params">(<span class="hljs-type">const</span> float3& v)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-type">const</span> mat3f XYZ_sRGB{</span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-keyword">static</span> float3 <span class="hljs-title">XYZ_to_sRGB</span><span class="hljs-params">(<span class="hljs-keyword">const</span> float3& v)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">const</span> mat3f XYZ_sRGB{</span>
|
||||
<span class="line"> <span class="hljs-number">3.2404542f</span>, <span class="hljs-number">-0.9692660f</span>, <span class="hljs-number">0.0556434f</span>,</span>
|
||||
<span class="line"> <span class="hljs-number">-1.5371385f</span>, <span class="hljs-number">1.8760108f</span>, <span class="hljs-number">-0.2040259f</span>,</span>
|
||||
<span class="line"> <span class="hljs-number">-0.4985314f</span>, <span class="hljs-number">0.0415560f</span>, <span class="hljs-number">1.0572252f</span></span>
|
||||
@@ -3529,21 +3529,21 @@ Our implementation is presented in <a href="#listing_specularcolorimpl">listing&
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-comment">// Outputs a linear sRGB color</span></span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-type">static</span> float3 <span class="hljs-title">computeColor</span><span class="hljs-params">(<span class="hljs-type">const</span> std::vector<sample>& samples)</span> </span>{</span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-keyword">static</span> float3 <span class="hljs-title">computeColor</span><span class="hljs-params">(<span class="hljs-keyword">const</span> <span class="hljs-built_in">std</span>::<span class="hljs-built_in">vector</span><sample>& samples)</span> </span>{</span>
|
||||
<span class="line"> float3 xyz{<span class="hljs-number">0.0f</span>};</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> y = <span class="hljs-number">0.0f</span>;</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> y = <span class="hljs-number">0.0f</span>;</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-type">size_t</span> i = <span class="hljs-number">0</span>; i < CIE_XYZ_COUNT; i++) {</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">size_t</span> i = <span class="hljs-number">0</span>; i < CIE_XYZ_COUNT; i++) {</span>
|
||||
<span class="line"> <span class="hljs-comment">// Current wavelength</span></span>
|
||||
<span class="line"> <span class="hljs-type">float</span> w = CIE_XYZ_START + i;</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> w = CIE_XYZ_START + i;</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-comment">// Find most appropriate CIE XYZ sample for the wavelength</span></span>
|
||||
<span class="line"> <span class="hljs-keyword">auto</span> sample = <span class="hljs-built_in">findSample</span>(samples, w);</span>
|
||||
<span class="line"> <span class="hljs-keyword">auto</span> sample = findSample(samples, w);</span>
|
||||
<span class="line"> <span class="hljs-comment">// Compute Fresnel reflectance at normal incidence</span></span>
|
||||
<span class="line"> <span class="hljs-type">float</span> f0 = <span class="hljs-built_in">fresnel</span>(sample);</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> f0 = fresnel(sample);</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-comment">// We need to multiply by the spectral power distribution of the illuminant</span></span>
|
||||
<span class="line"> <span class="hljs-type">float</span> d65 = <span class="hljs-built_in">illuminantD65</span>(w);</span>
|
||||
<span class="line"> <span class="hljs-keyword">float</span> d65 = illuminantD65(w);</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> xyz += f0 * CIE_XYZ[i] * d65;</span>
|
||||
<span class="line"> y += CIE_XYZ[i].y * d65;</span>
|
||||
@@ -3552,10 +3552,10 @@ Our implementation is presented in <a href="#listing_specularcolorimpl">listing&
|
||||
<span class="line"> <span class="hljs-comment">// Normalize so that 100% reflectance at every wavelength yields Y=1</span></span>
|
||||
<span class="line"> xyz /= y;</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> float3 linear = <span class="hljs-built_in">XYZ_to_sRGB</span>(xyz);</span>
|
||||
<span class="line"> float3 linear = XYZ_to_sRGB(xyz);</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-comment">// Normalize out-of-gamut values</span></span>
|
||||
<span class="line"> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">any</span>(<span class="hljs-built_in">greaterThan</span>(linear, float3{<span class="hljs-number">1.0f</span>}))) linear *= <span class="hljs-number">1.0f</span> / <span class="hljs-built_in">max</span>(linear);</span>
|
||||
<span class="line"> <span class="hljs-keyword">if</span> (any(greaterThan(linear, float3{<span class="hljs-number">1.0f</span>}))) linear *= <span class="hljs-number">1.0f</span> / max(linear);</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> linear;</span>
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_specularcolorimpl"> </a><b style="font-style:normal;">Listing 46:</b> C++ implementation to compute the base color of a metallic surface from spectral data</div></center>
|
||||
@@ -3735,47 +3735,47 @@ l &= \{ cos\phi sin\theta,sin\phi sin\theta,cos\theta \}
|
||||
<pre class="listing tilde"><code><span class="line">vec2f hammersley(uint i, <span class="hljs-built_in">float</span> numSamples) {</span>
|
||||
<span class="line"> uint bits = i;</span>
|
||||
<span class="line"> bits = (bits << <span class="hljs-string">16) | (bits >> 16</span>);</span>
|
||||
<span class="line"> bits = ((bits & <span class="hljs-number">0</span>x55555555) << <span class="hljs-number">1</span>) | ((bits & <span class="hljs-number">0</span>xAAAAAAAA) >> <span class="hljs-number">1</span>);</span>
|
||||
<span class="line"> bits = ((bits & <span class="hljs-number">0</span>x33333333) << <span class="hljs-number">2</span>) | ((bits & <span class="hljs-number">0</span>xCCCCCCCC) >> <span class="hljs-number">2</span>);</span>
|
||||
<span class="line"> bits = ((bits & <span class="hljs-number">0</span>x0F0F0F0F) << <span class="hljs-number">4</span>) | ((bits & <span class="hljs-number">0</span>xF0F0F0F0) >> <span class="hljs-number">4</span>);</span>
|
||||
<span class="line"> bits = ((bits & <span class="hljs-number">0</span>x00FF00FF) << <span class="hljs-number">8</span>) | ((bits & <span class="hljs-number">0</span>xFF00FF00) >> <span class="hljs-number">8</span>);</span>
|
||||
<span class="line"> return vec2f(i / numSamples, bits / exp2(<span class="hljs-number">32</span>));</span>
|
||||
<span class="line"> bits = ((bits & 0x55555555) << <span class="hljs-string">1) | ((bits & 0xAAAAAAAA) >> 1</span>);</span>
|
||||
<span class="line"> bits = ((bits & 0x33333333) << <span class="hljs-string">2) | ((bits & 0xCCCCCCCC) >> 2</span>);</span>
|
||||
<span class="line"> bits = ((bits & 0x0F0F0F0F) << <span class="hljs-string">4) | ((bits & 0xF0F0F0F0) >> 4</span>);</span>
|
||||
<span class="line"> bits = ((bits & 0x00FF00FF) << <span class="hljs-string">8) | ((bits & 0xFF00FF00) >> 8</span>);</span>
|
||||
<span class="line"> <span class="hljs-built_in">return</span> vec2f(i / numSamples, bits / exp2(32));</span>
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde">C++ implementation of a Hammersley sequence generator</div></center>
|
||||
<a class="target" name="precomputinglforimage-basedlighting"> </a><a class="target" name="annex/precomputinglforimage-basedlighting"> </a><a class="target" name="toc9.5"> </a><h2 id="precomputing-l-for-image-based-lighting"><a class="header" href="#precomputing-l-for-image-based-lighting">Precomputing L for image-based lighting</a></h2>
|
||||
<p>
|
||||
<p>The term ( L_{DFG} ) is only dependent on ( \NoV ). Below, the normal is arbitrarily set to ( n=\left[0, 0, 1\right] ) and (v) is chosen to satisfy ( \NoV ). The vector ( h_i ) is the ( D_{GGX}(\alpha) ) important direction sample (i).</p>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">float</span> <span class="hljs-title function_">GDFG</span><span class="hljs-params">(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> NoL, <span class="hljs-type">float</span> a)</span> {</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">a2</span> <span class="hljs-operator">=</span> a * a;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">GGXL</span> <span class="hljs-operator">=</span> NoV * sqrt((-NoL * a2 + NoL) * NoL + a2);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">GGXV</span> <span class="hljs-operator">=</span> NoL * sqrt((-NoV * a2 + NoV) * NoV + a2);</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">float</span> GDFG(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> NoL, <span class="hljs-type">float</span> a) {</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> a2 = a * a;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> GGXL = NoV * <span class="hljs-built_in">sqrt</span>((-NoL * a2 + NoL) * NoL + a2);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> GGXV = NoL * <span class="hljs-built_in">sqrt</span>((-NoV * a2 + NoV) * NoV + a2);</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> (<span class="hljs-number">2</span> * NoL) / (GGXV + GGXL);</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line">float2 <span class="hljs-title function_">DFG</span><span class="hljs-params">(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> a)</span> {</span>
|
||||
<span class="line">float2 DFG(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> a) {</span>
|
||||
<span class="line"> float3 V;</span>
|
||||
<span class="line"> V.x = sqrt(<span class="hljs-number">1.0f</span> - NoV*NoV);</span>
|
||||
<span class="line"> V.y = <span class="hljs-number">0.0f</span>;</span>
|
||||
<span class="line"> V.x = <span class="hljs-built_in">sqrt</span>(<span class="hljs-number">1.0</span>f - NoV*NoV);</span>
|
||||
<span class="line"> V.y = <span class="hljs-number">0.0</span>f;</span>
|
||||
<span class="line"> V.z = NoV;</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-type">float2</span> <span class="hljs-variable">r</span> <span class="hljs-operator">=</span> <span class="hljs-number">0.0f</span>;</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-type">uint</span> <span class="hljs-variable">i</span> <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i < sampleCount; i++) {</span>
|
||||
<span class="line"> <span class="hljs-type">float2</span> <span class="hljs-variable">Xi</span> <span class="hljs-operator">=</span> hammersley(i, sampleCount);</span>
|
||||
<span class="line"> <span class="hljs-type">float3</span> <span class="hljs-variable">H</span> <span class="hljs-operator">=</span> importanceSampleGGX(Xi, a, N);</span>
|
||||
<span class="line"> <span class="hljs-type">float3</span> <span class="hljs-variable">L</span> <span class="hljs-operator">=</span> <span class="hljs-number">2.0f</span> * dot(V, H) * H - V;</span>
|
||||
<span class="line"> float2 r = <span class="hljs-number">0.0</span>f;</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-type">uint</span> i = <span class="hljs-number">0</span>; i < sampleCount; i++) {</span>
|
||||
<span class="line"> float2 Xi = hammersley(i, sampleCount);</span>
|
||||
<span class="line"> float3 H = importanceSampleGGX(Xi, a, N);</span>
|
||||
<span class="line"> float3 L = <span class="hljs-number">2.0</span>f * <span class="hljs-built_in">dot</span>(V, H) * H - V;</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">VoH</span> <span class="hljs-operator">=</span> saturate(dot(V, H));</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">NoL</span> <span class="hljs-operator">=</span> saturate(L.z);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">NoH</span> <span class="hljs-operator">=</span> saturate(H.z);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> VoH = saturate(<span class="hljs-built_in">dot</span>(V, H));</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> NoL = saturate(L.z);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> NoH = saturate(H.z);</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"> <span class="hljs-keyword">if</span> (NoL > <span class="hljs-number">0.0f</span>) {</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">G</span> <span class="hljs-operator">=</span> GDFG(NoV, NoL, a);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">Gv</span> <span class="hljs-operator">=</span> G * VoH / NoH;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> <span class="hljs-variable">Fc</span> <span class="hljs-operator">=</span> pow(<span class="hljs-number">1</span> - VoH, <span class="hljs-number">5.0f</span>);</span>
|
||||
<span class="line"> <span class="hljs-keyword">if</span> (NoL > <span class="hljs-number">0.0</span>f) {</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> G = GDFG(NoV, NoL, a);</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> Gv = G * VoH / NoH;</span>
|
||||
<span class="line"> <span class="hljs-type">float</span> Fc = <span class="hljs-built_in">pow</span>(<span class="hljs-number">1</span> - VoH, <span class="hljs-number">5.0</span>f);</span>
|
||||
<span class="line"> r.x += Gv * (<span class="hljs-number">1</span> - Fc);</span>
|
||||
<span class="line"> r.y += Gv * Fc;</span>
|
||||
<span class="line"> }</span>
|
||||
<span class="line"> }</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> r * (<span class="hljs-number">1.0f</span> / sampleCount);</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> r * (<span class="hljs-number">1.0</span>f / sampleCount);</span>
|
||||
<span class="line">}</span></code></pre><center><div class="listingcaption tilde">C++ implementation of the \( L_{DFG} \) term</div></center>
|
||||
<a class="target" name="sphericalharmonics"> </a><a class="target" name="annex/sphericalharmonics"> </a><a class="target" name="toc9.6"> </a><h2 id="spherical-harmonics"><a class="header" href="#spherical-harmonics">Spherical Harmonics</a></h2>
|
||||
<p>
|
||||
@@ -3851,56 +3851,56 @@ sin(m \phi + \phi) &= sin(m \phi) cos(\phi) + cos(m \phi) sin(\phi) \Leftrightar
|
||||
\end{align*}$$
|
||||
</p><p>
|
||||
<a href="#listing_nonnormalizedshbasis">Listing 47</a> shows the C++ code to compute the non-normalized SH basis \(\frac{y^m_l(s)}{\sqrt{2} K^m_l}\):
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-keyword">inline</span> <span class="hljs-type">size_t</span> <span class="hljs-title">SHindex</span><span class="hljs-params">(<span class="hljs-type">ssize_t</span> m, <span class="hljs-type">size_t</span> l)</span> </span>{</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">inline</span> <span class="hljs-keyword">size_t</span> <span class="hljs-title">SHindex</span><span class="hljs-params">(<span class="hljs-keyword">ssize_t</span> m, <span class="hljs-keyword">size_t</span> l)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> l * (l + <span class="hljs-number">1</span>) + m;</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">computeShBasis</span><span class="hljs-params">(</span>
|
||||
<span class="line"> <span class="hljs-type">double</span>* <span class="hljs-type">const</span> SHb,</span>
|
||||
<span class="line"> <span class="hljs-type">size_t</span> numBands,</span>
|
||||
<span class="line"> <span class="hljs-type">const</span> vec3& s)</span></span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">computeShBasis</span><span class="hljs-params">(</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span>* <span class="hljs-keyword">const</span> SHb,</span>
|
||||
<span class="line"> <span class="hljs-keyword">size_t</span> numBands,</span>
|
||||
<span class="line"> <span class="hljs-keyword">const</span> vec3& s)</span></span>
|
||||
<span class="line"></span>{</span>
|
||||
<span class="line"> <span class="hljs-comment">// handle m=0 separately, since it produces only one coefficient</span></span>
|
||||
<span class="line"> <span class="hljs-type">double</span> Pml_2 = <span class="hljs-number">0</span>;</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> Pml_1 = <span class="hljs-number">1</span>;</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> Pml_2 = <span class="hljs-number">0</span>;</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> Pml_1 = <span class="hljs-number">1</span>;</span>
|
||||
<span class="line"> SHb[<span class="hljs-number">0</span>] = Pml_1;</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-type">ssize_t</span> l = <span class="hljs-number">1</span>; l < numBands; l++) {</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> Pml = ((<span class="hljs-number">2</span> * l - <span class="hljs-number">1</span>) * Pml_1 * s.z - (l - <span class="hljs-number">1</span>) * Pml_2) / l;</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">ssize_t</span> l = <span class="hljs-number">1</span>; l < numBands; l++) {</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> Pml = ((<span class="hljs-number">2</span> * l - <span class="hljs-number">1</span>) * Pml_1 * s.z - (l - <span class="hljs-number">1</span>) * Pml_2) / l;</span>
|
||||
<span class="line"> Pml_2 = Pml_1;</span>
|
||||
<span class="line"> Pml_1 = Pml;</span>
|
||||
<span class="line"> SHb[<span class="hljs-built_in">SHindex</span>(<span class="hljs-number">0</span>, l)] = Pml;</span>
|
||||
<span class="line"> SHb[SHindex(<span class="hljs-number">0</span>, l)] = Pml;</span>
|
||||
<span class="line"> }</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> Pmm = <span class="hljs-number">1</span>;</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-type">ssize_t</span> m = <span class="hljs-number">1</span>; m < numBands ; m++) {</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> Pmm = <span class="hljs-number">1</span>;</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">ssize_t</span> m = <span class="hljs-number">1</span>; m < numBands ; m++) {</span>
|
||||
<span class="line"> Pmm = (<span class="hljs-number">1</span> - <span class="hljs-number">2</span> * m) * Pmm;</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> Pml_2 = Pmm;</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> Pml_1 = (<span class="hljs-number">2</span> * m + <span class="hljs-number">1</span>)*Pmm*s.z;</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> Pml_2 = Pmm;</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> Pml_1 = (<span class="hljs-number">2</span> * m + <span class="hljs-number">1</span>)*Pmm*s.z;</span>
|
||||
<span class="line"> <span class="hljs-comment">// l == m</span></span>
|
||||
<span class="line"> SHb[<span class="hljs-built_in">SHindex</span>(-m, m)] = Pml_2;</span>
|
||||
<span class="line"> SHb[<span class="hljs-built_in">SHindex</span>( m, m)] = Pml_2;</span>
|
||||
<span class="line"> SHb[SHindex(-m, m)] = Pml_2;</span>
|
||||
<span class="line"> SHb[SHindex( m, m)] = Pml_2;</span>
|
||||
<span class="line"> <span class="hljs-keyword">if</span> (m + <span class="hljs-number">1</span> < numBands) {</span>
|
||||
<span class="line"> <span class="hljs-comment">// l == m+1</span></span>
|
||||
<span class="line"> SHb[<span class="hljs-built_in">SHindex</span>(-m, m + <span class="hljs-number">1</span>)] = Pml_1;</span>
|
||||
<span class="line"> SHb[<span class="hljs-built_in">SHindex</span>( m, m + <span class="hljs-number">1</span>)] = Pml_1;</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-type">ssize_t</span> l = m + <span class="hljs-number">2</span>; l < numBands; l++) {</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> Pml = ((<span class="hljs-number">2</span> * l - <span class="hljs-number">1</span>) * Pml_1 * s.z - (l + m - <span class="hljs-number">1</span>) * Pml_2)</span>
|
||||
<span class="line"> SHb[SHindex(-m, m + <span class="hljs-number">1</span>)] = Pml_1;</span>
|
||||
<span class="line"> SHb[SHindex( m, m + <span class="hljs-number">1</span>)] = Pml_1;</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">ssize_t</span> l = m + <span class="hljs-number">2</span>; l < numBands; l++) {</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> Pml = ((<span class="hljs-number">2</span> * l - <span class="hljs-number">1</span>) * Pml_1 * s.z - (l + m - <span class="hljs-number">1</span>) * Pml_2)</span>
|
||||
<span class="line"> / (l - m);</span>
|
||||
<span class="line"> Pml_2 = Pml_1;</span>
|
||||
<span class="line"> Pml_1 = Pml;</span>
|
||||
<span class="line"> SHb[<span class="hljs-built_in">SHindex</span>(-m, l)] = Pml;</span>
|
||||
<span class="line"> SHb[<span class="hljs-built_in">SHindex</span>( m, l)] = Pml;</span>
|
||||
<span class="line"> SHb[SHindex(-m, l)] = Pml;</span>
|
||||
<span class="line"> SHb[SHindex( m, l)] = Pml;</span>
|
||||
<span class="line"> }</span>
|
||||
<span class="line"> }</span>
|
||||
<span class="line"> }</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> Cm = s.x;</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> Sm = s.y;</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-type">ssize_t</span> m = <span class="hljs-number">1</span>; m <= numBands ; m++) {</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-type">ssize_t</span> l = m; l < numBands ; l++) {</span>
|
||||
<span class="line"> SHb[<span class="hljs-built_in">SHindex</span>(-m, l)] *= Sm;</span>
|
||||
<span class="line"> SHb[<span class="hljs-built_in">SHindex</span>( m, l)] *= Cm;</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> Cm = s.x;</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> Sm = s.y;</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">ssize_t</span> m = <span class="hljs-number">1</span>; m <= numBands ; m++) {</span>
|
||||
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">ssize_t</span> l = m; l < numBands ; l++) {</span>
|
||||
<span class="line"> SHb[SHindex(-m, l)] *= Sm;</span>
|
||||
<span class="line"> SHb[SHindex( m, l)] *= Cm;</span>
|
||||
<span class="line"> }</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> Cm1 = Cm * s.x - Sm * s.y;</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> Sm1 = Sm * s.x + Cm * s.y;</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> Cm1 = Cm * s.x - Sm * s.y;</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> Sm1 = Sm * s.x + Cm * s.y;</span>
|
||||
<span class="line"> Cm = Cm1;</span>
|
||||
<span class="line"> Sm = Sm1;</span>
|
||||
<span class="line"> }</span>
|
||||
@@ -3979,10 +3979,10 @@ $$\begin{equation}
|
||||
\end{equation}$$
|
||||
</p><p>
|
||||
Here is the C++ code to compute \(\hat{C}_l\):
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-type">static</span> <span class="hljs-type">double</span> <span class="hljs-title">factorial</span><span class="hljs-params">(<span class="hljs-type">size_t</span> n, <span class="hljs-type">size_t</span> d = <span class="hljs-number">1</span>)</span></span>;</span>
|
||||
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">double</span> <span class="hljs-title">factorial</span><span class="hljs-params">(<span class="hljs-keyword">size_t</span> n, <span class="hljs-keyword">size_t</span> d = <span class="hljs-number">1</span>)</span></span>;</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-comment">// < cos(theta) > SH coefficients pre-multiplied by 1 / K(0,l)</span></span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-type">double</span> <span class="hljs-title">computeTruncatedCosSh</span><span class="hljs-params">(<span class="hljs-type">size_t</span> l)</span> </span>{</span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-keyword">double</span> <span class="hljs-title">computeTruncatedCosSh</span><span class="hljs-params">(<span class="hljs-keyword">size_t</span> l)</span> </span>{</span>
|
||||
<span class="line"> <span class="hljs-keyword">if</span> (l == <span class="hljs-number">0</span>) {</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> M_PI;</span>
|
||||
<span class="line"> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (l == <span class="hljs-number">1</span>) {</span>
|
||||
@@ -3990,17 +3990,17 @@ Here is the C++ code to compute \(\hat{C}_l\):
|
||||
<span class="line"> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (l & <span class="hljs-number">1</span>) {</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;</span>
|
||||
<span class="line"> }</span>
|
||||
<span class="line"> <span class="hljs-type">const</span> <span class="hljs-type">size_t</span> l_2 = l / <span class="hljs-number">2</span>;</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> A0 = ((l_2 & <span class="hljs-number">1</span>) ? <span class="hljs-number">1.0</span> : <span class="hljs-number">-1.0</span>) / ((l + <span class="hljs-number">2</span>) * (l - <span class="hljs-number">1</span>));</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> A1 = <span class="hljs-built_in">factorial</span>(l, l_2) / (<span class="hljs-built_in">factorial</span>(l_2) * (<span class="hljs-number">1</span> << l));</span>
|
||||
<span class="line"> <span class="hljs-keyword">const</span> <span class="hljs-keyword">size_t</span> l_2 = l / <span class="hljs-number">2</span>;</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> A0 = ((l_2 & <span class="hljs-number">1</span>) ? <span class="hljs-number">1.0</span> : <span class="hljs-number">-1.0</span>) / ((l + <span class="hljs-number">2</span>) * (l - <span class="hljs-number">1</span>));</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> A1 = factorial(l, l_2) / (factorial(l_2) * (<span class="hljs-number">1</span> << l));</span>
|
||||
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">2</span> * M_PI * A0 * A1;</span>
|
||||
<span class="line">}</span>
|
||||
<span class="line"></span>
|
||||
<span class="line"><span class="hljs-comment">// returns n! / d!</span></span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-type">double</span> <span class="hljs-title">factorial</span><span class="hljs-params">(<span class="hljs-type">size_t</span> n, <span class="hljs-type">size_t</span> d )</span> </span>{</span>
|
||||
<span class="line"> d = std::<span class="hljs-built_in">max</span>(<span class="hljs-built_in">size_t</span>(<span class="hljs-number">1</span>), d);</span>
|
||||
<span class="line"> n = std::<span class="hljs-built_in">max</span>(<span class="hljs-built_in">size_t</span>(<span class="hljs-number">1</span>), n);</span>
|
||||
<span class="line"> <span class="hljs-type">double</span> r = <span class="hljs-number">1.0</span>;</span>
|
||||
<span class="line"><span class="hljs-function"><span class="hljs-keyword">double</span> <span class="hljs-title">factorial</span><span class="hljs-params">(<span class="hljs-keyword">size_t</span> n, <span class="hljs-keyword">size_t</span> d )</span> </span>{</span>
|
||||
<span class="line"> d = <span class="hljs-built_in">std</span>::max(<span class="hljs-keyword">size_t</span>(<span class="hljs-number">1</span>), d);</span>
|
||||
<span class="line"> n = <span class="hljs-built_in">std</span>::max(<span class="hljs-keyword">size_t</span>(<span class="hljs-number">1</span>), n);</span>
|
||||
<span class="line"> <span class="hljs-keyword">double</span> r = <span class="hljs-number">1.0</span>;</span>
|
||||
<span class="line"> <span class="hljs-keyword">if</span> (n == d) {</span>
|
||||
<span class="line"> <span class="hljs-comment">// intentionally left blank</span></span>
|
||||
<span class="line"> } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (n > d) {</span>
|
||||
|
||||
+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,7 +93,6 @@ set(SRCS
|
||||
src/MorphTargetBuffer.cpp
|
||||
src/PostProcessManager.cpp
|
||||
src/ProgramSpecialization.cpp
|
||||
src/LocalProgramCache.cpp
|
||||
src/RenderPass.cpp
|
||||
src/RenderPrimitive.cpp
|
||||
src/RenderTarget.cpp
|
||||
@@ -187,7 +186,6 @@ set(PRIVATE_HDRS
|
||||
src/MaterialCache.h
|
||||
src/MaterialDefinition.h
|
||||
src/MaterialParser.h
|
||||
src/LocalProgramCache.h
|
||||
src/MaterialInstanceManager.h
|
||||
src/PIDController.h
|
||||
src/PostProcessManager.h
|
||||
|
||||
+153
-152
@@ -564,169 +564,170 @@ endif()
|
||||
# ==================================================================================================
|
||||
# Test
|
||||
# ==================================================================================================
|
||||
if (FILAMENT_BUILD_TESTING)
|
||||
option(INSTALL_BACKEND_TEST "Install the backend test library so it can be consumed on iOS" OFF)
|
||||
option(INSTALL_BACKEND_TEST "Install the backend test library so it can be consumed on iOS" OFF)
|
||||
|
||||
if (APPLE OR LINUX)
|
||||
set(BACKEND_TEST_SRC
|
||||
test/BackendTest.cpp
|
||||
test/ShaderGenerator.cpp
|
||||
test/TrianglePrimitive.cpp
|
||||
test/Arguments.cpp
|
||||
test/ImageExpectations.cpp
|
||||
test/Lifetimes.cpp
|
||||
test/PlatformRunner.cpp
|
||||
test/Shader.cpp
|
||||
test/SharedShaders.cpp
|
||||
test/Skip.cpp
|
||||
test/test_Autoresolve.cpp
|
||||
test/test_FeedbackLoops.cpp
|
||||
test/test_Blit.cpp
|
||||
test/test_MissingRequiredAttributes.cpp
|
||||
test/test_ReadPixels.cpp
|
||||
test/test_ReadTexture.cpp
|
||||
test/test_BufferUpdates.cpp
|
||||
test/test_Callbacks.cpp
|
||||
test/test_JobQueue.cpp
|
||||
test/test_MemoryMappedBuffer.cpp
|
||||
test/test_MsaaSwapChain.cpp
|
||||
test/test_MRT.cpp
|
||||
test/test_PushConstants.cpp
|
||||
test/test_LoadImage.cpp
|
||||
test/test_StencilBuffer.cpp
|
||||
test/test_Scissor.cpp
|
||||
test/test_MipLevels.cpp
|
||||
test/test_Handles.cpp
|
||||
test/test_CircularBuffer.cpp
|
||||
test/test_CommandBufferQueue.cpp
|
||||
test/test_Template.cpp
|
||||
test/test_Platform.cpp
|
||||
)
|
||||
if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
list(APPEND BACKEND_TEST_SRC
|
||||
test/test_WebGPUAsyncTaskCounter.cpp
|
||||
test/test_WebGPUComposeSwizzle.cpp)
|
||||
endif()
|
||||
if (APPLE)
|
||||
# Metal-specific tests
|
||||
list(APPEND BACKEND_TEST_SRC
|
||||
test/test_MetalBlitter.mm
|
||||
)
|
||||
endif()
|
||||
set(BACKEND_TEST_LIBS
|
||||
absl::str_format
|
||||
backend
|
||||
gtest
|
||||
imageio
|
||||
filamat
|
||||
SPIRV
|
||||
spirv-cross-glsl)
|
||||
# Create input/output directories for test result images.
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/actual_images)
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/diff_images)
|
||||
file(COPY test/expected_images DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/images)
|
||||
endif()
|
||||
|
||||
# TODO: Disabling IOS test due to breakage wrt glslang update
|
||||
if (APPLE AND NOT IOS)
|
||||
# TODO: we should expand this test to Linux and other platforms.
|
||||
if (APPLE OR LINUX)
|
||||
set(BACKEND_TEST_SRC
|
||||
test/BackendTest.cpp
|
||||
test/ShaderGenerator.cpp
|
||||
test/TrianglePrimitive.cpp
|
||||
test/Arguments.cpp
|
||||
test/ImageExpectations.cpp
|
||||
test/Lifetimes.cpp
|
||||
test/PlatformRunner.cpp
|
||||
test/Shader.cpp
|
||||
test/SharedShaders.cpp
|
||||
test/Skip.cpp
|
||||
test/test_Autoresolve.cpp
|
||||
test/test_FeedbackLoops.cpp
|
||||
test/test_Blit.cpp
|
||||
test/test_MissingRequiredAttributes.cpp
|
||||
test/test_ReadPixels.cpp
|
||||
test/test_ReadTexture.cpp
|
||||
test/test_BufferUpdates.cpp
|
||||
test/test_Callbacks.cpp
|
||||
test/test_JobQueue.cpp
|
||||
test/test_MemoryMappedBuffer.cpp
|
||||
test/test_MsaaSwapChain.cpp
|
||||
test/test_MRT.cpp
|
||||
test/test_PushConstants.cpp
|
||||
test/test_LoadImage.cpp
|
||||
test/test_StencilBuffer.cpp
|
||||
test/test_Scissor.cpp
|
||||
test/test_MipLevels.cpp
|
||||
test/test_Handles.cpp
|
||||
test/test_CircularBuffer.cpp
|
||||
test/test_CommandBufferQueue.cpp
|
||||
test/test_Template.cpp
|
||||
)
|
||||
if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
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")
|
||||
test/test_WebGPUAsyncTaskCounter.cpp
|
||||
test/test_WebGPUComposeSwizzle.cpp)
|
||||
endif()
|
||||
if (APPLE)
|
||||
# Metal-specific tests
|
||||
list(APPEND BACKEND_TEST_SRC
|
||||
test/test_MetalBlitter.mm
|
||||
)
|
||||
endif()
|
||||
set(BACKEND_TEST_LIBS
|
||||
absl::str_format
|
||||
backend
|
||||
getopt
|
||||
gtest
|
||||
imageio
|
||||
filamat
|
||||
SPIRV
|
||||
spirv-cross-glsl)
|
||||
# Create input/output directories for test result images.
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/actual_images)
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/diff_images)
|
||||
file(COPY test/expected_images DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/images)
|
||||
endif()
|
||||
|
||||
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)
|
||||
# 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")
|
||||
|
||||
if (NOT IOS)
|
||||
target_link_libraries(backend_test PRIVATE image imageio)
|
||||
list(APPEND BACKEND_TEST_DEPS image)
|
||||
endif()
|
||||
if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
target_link_libraries(backend_test PRIVATE webgpu_dawn dawncpp_headers)
|
||||
list(APPEND BACKEND_TEST_DEPS webgpu_dawn dawncpp_headers)
|
||||
endif()
|
||||
set(BACKEND_TEST_DEPS
|
||||
OSDependent
|
||||
SPIRV
|
||||
SPIRV-Tools
|
||||
SPIRV-Tools-opt
|
||||
backend_test
|
||||
getopt
|
||||
gtest
|
||||
glslang
|
||||
spirv-cross-core
|
||||
spirv-cross-glsl
|
||||
spirv-cross-msl)
|
||||
|
||||
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()
|
||||
if (NOT IOS)
|
||||
target_link_libraries(backend_test PRIVATE image imageio)
|
||||
list(APPEND BACKEND_TEST_DEPS image)
|
||||
endif()
|
||||
if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
target_link_libraries(backend_test PRIVATE webgpu_dawn dawncpp_headers)
|
||||
list(APPEND BACKEND_TEST_DEPS webgpu_dawn dawncpp_headers)
|
||||
endif()
|
||||
|
||||
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()
|
||||
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()
|
||||
|
||||
# ==================================================================================================
|
||||
# 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
|
||||
set_target_properties(backend_test PROPERTIES FOLDER Tests)
|
||||
|
||||
if (APPLE AND NOT IOS)
|
||||
add_executable(metal_utils_test test/MetalTest.mm)
|
||||
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)
|
||||
|
||||
target_compile_options(metal_utils_test PRIVATE "-fobjc-arc")
|
||||
|
||||
target_link_libraries(metal_utils_test PRIVATE
|
||||
backend
|
||||
gtest
|
||||
)
|
||||
|
||||
set_target_properties(metal_utils_test PROPERTIES FOLDER Tests)
|
||||
# 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()
|
||||
|
||||
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()
|
||||
|
||||
@@ -1706,6 +1706,8 @@ enum class Workaround : uint16_t {
|
||||
DISABLE_DEPTH_PRECACHE_FOR_DEFAULT_MATERIAL,
|
||||
// Emulate an sRGB swapchain in shader code.
|
||||
EMULATE_SRGB_SWAPCHAIN,
|
||||
// Workaround for AMD Vulkan drivers on Windows crashing when SSR history is detached.
|
||||
BLIT_SSR_HISTORY,
|
||||
};
|
||||
|
||||
using StereoscopicType = Platform::StereoscopicType;
|
||||
|
||||
@@ -216,18 +216,6 @@ public:
|
||||
MULTIVIEW,
|
||||
};
|
||||
|
||||
/**
|
||||
* Types of device/driver information that can be queried from the platform.
|
||||
*/
|
||||
enum class DeviceInfoType {
|
||||
OPENGL_RENDERER, //!< glGetString(GL_RENDERER)
|
||||
OPENGL_VENDOR, //!< glGetString(GL_VENDOR)
|
||||
OPENGL_VERSION, //!< glGetString(GL_VERSION)
|
||||
VULKAN_DEVICE_NAME, //!< VkPhysicalDeviceProperties::deviceName
|
||||
VULKAN_DRIVER_NAME, //!< VkPhysicalDeviceDriverProperties::driverName
|
||||
VULKAN_DRIVER_INFO, //!< VkPhysicalDeviceDriverProperties::driverInfo
|
||||
};
|
||||
|
||||
/**
|
||||
* This controls the priority level for GPU work scheduling, which helps prioritize the
|
||||
* submitted GPU work and enables preemption.
|
||||
@@ -328,7 +316,7 @@ public:
|
||||
*/
|
||||
StereoscopicType stereoscopicType = StereoscopicType::NONE;
|
||||
|
||||
/**
|
||||
/*
|
||||
* The number of eyes to render when stereoscopic rendering is enabled. Supported values are
|
||||
* between 1 and Engine::getMaxStereoscopicEyes() (inclusive).
|
||||
*/
|
||||
@@ -389,16 +377,6 @@ public:
|
||||
*/
|
||||
virtual int getOSVersion() const noexcept = 0;
|
||||
|
||||
/**
|
||||
* Queries device/driver information of the graphics API.
|
||||
* @param infoType the type of information to query.
|
||||
* @param driver a pointer to the current driver.
|
||||
* @return a CString containing the requested information.
|
||||
*/
|
||||
virtual utils::CString getDeviceInfo(DeviceInfoType infoType,
|
||||
Driver* UTILS_NULLABLE driver) const noexcept = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Creates and initializes the low-level API (e.g. an OpenGL context or Vulkan instance),
|
||||
* then creates the concrete Driver.
|
||||
|
||||
@@ -52,9 +52,6 @@ protected:
|
||||
|
||||
~OpenGLPlatform() noexcept override;
|
||||
|
||||
utils::CString getDeviceInfo(DeviceInfoType infoType,
|
||||
Driver* UTILS_NULLABLE driver) const noexcept override;
|
||||
|
||||
public:
|
||||
struct ExternalTexture {
|
||||
unsigned int target; // GLenum target
|
||||
@@ -73,12 +70,6 @@ public:
|
||||
*/
|
||||
static utils::CString getRendererString(Driver const* UTILS_NONNULL driver);
|
||||
|
||||
/**
|
||||
* Return the OpenGL version string of the specified Driver instance.
|
||||
* @return The GL_VERSION string
|
||||
*/
|
||||
static utils::CString getVersionString(Driver const* UTILS_NONNULL driver);
|
||||
|
||||
/**
|
||||
* Called by the driver to destroy the OpenGL context. This should clean up any windows
|
||||
* or buffers from initialization. This is for instance where `eglDestroyContext` would be
|
||||
|
||||
@@ -159,12 +159,6 @@ protected:
|
||||
EGLConfig getEglConfig() const noexcept { return mEGLConfig; }
|
||||
EGLConfig getSuitableConfigForSwapChain(uint64_t flags, bool window, bool pbuffer) const;
|
||||
|
||||
// Sets the EGLDisplay to be used by this platform. This should only be called by derived
|
||||
// classes before invoking createDriver. Calling it after that point will result in
|
||||
// undefined behaviour. This class will take ownership of the display and call eglTerminate
|
||||
// on it during shutdown.
|
||||
void setEglDisplay(EGLDisplay display) noexcept;
|
||||
|
||||
// supported extensions detected at runtime
|
||||
struct {
|
||||
struct {
|
||||
|
||||
@@ -185,7 +185,7 @@ private:
|
||||
};
|
||||
|
||||
int mOSVersion;
|
||||
ExternalStreamManagerAndroid* mExternalStreamManager = nullptr;
|
||||
ExternalStreamManagerAndroid& mExternalStreamManager;
|
||||
AndroidDetails& mAndroidDetails;
|
||||
utils::PerformanceHintManager mPerformanceHintManager;
|
||||
utils::PerformanceHintManager::Session mPerformanceHintSession;
|
||||
|
||||
@@ -38,7 +38,6 @@ public:
|
||||
|
||||
Driver* createDriver(void* sharedContext, const Platform::DriverConfig& driverConfig) override;
|
||||
int getOSVersion() const noexcept override { return 0; }
|
||||
utils::CString getDeviceInfo(DeviceInfoType, Driver*) const noexcept override { return {}; }
|
||||
|
||||
/**
|
||||
* Optionally initializes the Metal platform by acquiring resources necessary for rendering.
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <cstring>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <unordered_set>
|
||||
|
||||
@@ -68,7 +69,7 @@ public:
|
||||
|
||||
struct ExtensionHashFn {
|
||||
std::size_t operator()(utils::CString const& s) const noexcept {
|
||||
return std::hash<utils::CString>{}(s.data());
|
||||
return std::hash<std::string>{}(s.data());
|
||||
}
|
||||
};
|
||||
// Note: utils::CString::operator== has an edge case that breaks for the extension set.
|
||||
@@ -142,8 +143,6 @@ public:
|
||||
return 0;
|
||||
}
|
||||
|
||||
utils::CString getDeviceInfo(DeviceInfoType infoType, Driver* driver) const noexcept override;
|
||||
|
||||
// ----------------------------------------------------
|
||||
// ---------- Platform Customization options ----------
|
||||
struct Customization {
|
||||
@@ -176,12 +175,6 @@ public:
|
||||
* presentation. Default is true.
|
||||
*/
|
||||
bool transitionSwapChainImageLayoutForPresent = true;
|
||||
|
||||
/**
|
||||
* The number of frames before an unused framebuffer is evicted from the cache.
|
||||
* Default is 3.
|
||||
*/
|
||||
uint32_t timeBeforeEvictionFbo = 3;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,9 +46,6 @@ public:
|
||||
~WebGPUPlatform() override = default;
|
||||
|
||||
[[nodiscard]] int getOSVersion() const noexcept final { return 0; }
|
||||
[[nodiscard]] utils::CString getDeviceInfo(DeviceInfoType, Driver*) const noexcept override {
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] wgpu::Instance& getInstance() noexcept { return mInstance; }
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#ifdef __ANDROID__
|
||||
|
||||
@@ -26,7 +26,6 @@ class PlatformNoop final : public Platform {
|
||||
public:
|
||||
|
||||
int getOSVersion() const noexcept final { return 0; }
|
||||
utils::CString getDeviceInfo(DeviceInfoType, Driver*) const noexcept override { return {}; }
|
||||
|
||||
~PlatformNoop() noexcept override = default;
|
||||
|
||||
|
||||
@@ -341,44 +341,7 @@ void GLDescriptorSet::bind(
|
||||
if (arg.handle) {
|
||||
GLTexture const* const t = handleAllocator.handle_cast<GLTexture*>(arg.handle);
|
||||
gl.bindTexture(unit, t->gl.target, t->gl.id, t->gl.external);
|
||||
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
|
||||
|
||||
SamplerParams const params = arg.params;
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_MIN_FILTER,
|
||||
(GLint)GLUtils::getTextureFilter(params.filterMin));
|
||||
glTexParameteri(t->gl.target, GL_TEXTURE_MAG_FILTER,
|
||||
|
||||
@@ -222,15 +222,6 @@ OpenGLContext::OpenGLContext(OpenGLPlatform& platform,
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_disjoint_timer_query)
|
||||
if (ext.EXT_disjoint_timer_query) {
|
||||
mGpuTimeSupported = true;
|
||||
} else
|
||||
#endif
|
||||
if (platform.canCreateFence()) {
|
||||
mGpuTimeSupported = true;
|
||||
}
|
||||
|
||||
// in practice KHR_Debug has never been useful, and actually is confusing. We keep this
|
||||
// only for our own debugging, in case we need it some day.
|
||||
#if false && !defined(NDEBUG) && defined(GL_KHR_debug)
|
||||
|
||||
@@ -158,10 +158,8 @@ public:
|
||||
constexpr static inline size_t getIndexForBufferTarget(GLenum target) noexcept;
|
||||
|
||||
ShaderModel getShaderModel() const noexcept { return mShaderModel; }
|
||||
|
||||
|
||||
void resetState() noexcept;
|
||||
|
||||
bool isGpuTimeSupported() const noexcept { return mGpuTimeSupported; }
|
||||
|
||||
inline void useProgram(GLuint program) noexcept;
|
||||
|
||||
@@ -544,8 +542,6 @@ private:
|
||||
|
||||
Platform::DriverConfig const mDriverConfig;
|
||||
|
||||
bool mGpuTimeSupported = false;
|
||||
|
||||
void bindFramebufferResolved(GLenum target, GLuint buffer) noexcept;
|
||||
|
||||
const std::array<std::tuple<bool const&, char const*, char const*>, sizeof(bugs)> mBugDatabase{{
|
||||
|
||||
@@ -467,8 +467,7 @@ void OpenGLDriver::bindTexture(GLuint const unit, GLTexture const* t) noexcept {
|
||||
bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
|
||||
bool success = true;
|
||||
if (mBoundProgram != p) {
|
||||
// compile/link the program if needed and call glUseProgram.
|
||||
// This call may block until the program linking process is complete.
|
||||
// compile/link the program if needed and call glUseProgram
|
||||
success = p->use(this, mContext);
|
||||
assert_invariant(success == p->isValid());
|
||||
if (success) {
|
||||
@@ -717,11 +716,7 @@ Handle<HwSwapChain> OpenGLDriver::createSwapChainHeadlessS() noexcept {
|
||||
}
|
||||
|
||||
Handle<HwTimerQuery> OpenGLDriver::createTimerQueryS() noexcept {
|
||||
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;
|
||||
return initHandle<GLTimerQuery>();
|
||||
}
|
||||
|
||||
Handle<HwDescriptorSetLayout> OpenGLDriver::createDescriptorSetLayoutS() noexcept {
|
||||
@@ -2860,7 +2855,7 @@ bool OpenGLDriver::isFrameBufferFetchMultiSampleSupported() {
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isFrameTimeSupported() {
|
||||
return mContext.isGpuTimeSupported();
|
||||
return TimerQueryFactory::isGpuTimeSupported();
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isAutoDepthResolveSupported() {
|
||||
|
||||
@@ -250,10 +250,6 @@ private:
|
||||
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(); }
|
||||
JobWorker* getJobWorker() const noexcept { return mJobWorker.get(); }
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ protected:
|
||||
public:
|
||||
virtual utils::CString getVendorString() const noexcept = 0;
|
||||
virtual utils::CString getRendererString() const noexcept = 0;
|
||||
virtual utils::CString getVersionString() const noexcept = 0;
|
||||
};
|
||||
|
||||
} // filament::backend
|
||||
|
||||
@@ -45,21 +45,6 @@ Driver* OpenGLPlatform::createDefaultDriver(OpenGLPlatform* platform,
|
||||
|
||||
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) {
|
||||
auto const p = static_cast<OpenGLDriverBase const*>(driver);
|
||||
#if UTILS_HAS_RTTI
|
||||
@@ -78,15 +63,6 @@ utils::CString OpenGLPlatform::getRendererString(Driver const* driver) {
|
||||
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,
|
||||
utils::Invocable<void()>, utils::Invocable<void(size_t)>) {
|
||||
makeCurrent(getCurrentContextType(), drawSwapChain, readSwapChain);
|
||||
|
||||
@@ -48,7 +48,7 @@ class OpenGLDriver;
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
bool TimerQueryFactory::mGpuTimeSupported = false;
|
||||
|
||||
TimerQueryFactoryInterface* TimerQueryFactory::init(
|
||||
OpenGLPlatform& platform, OpenGLContext& context) {
|
||||
@@ -65,14 +65,17 @@ TimerQueryFactoryInterface* TimerQueryFactory::init(
|
||||
} else {
|
||||
impl = new(std::nothrow) TimerQueryNativeFactory(context);
|
||||
}
|
||||
mGpuTimeSupported = true;
|
||||
} else
|
||||
#endif
|
||||
if (platform.canCreateFence()) {
|
||||
// no timer queries, but we can use fences
|
||||
impl = new(std::nothrow) TimerQueryFenceFactory(platform);
|
||||
mGpuTimeSupported = true;
|
||||
} else {
|
||||
// no queries, no fences -- that's a problem
|
||||
impl = new(std::nothrow) TimerQueryFallbackFactory();
|
||||
mGpuTimeSupported = false;
|
||||
}
|
||||
assert_invariant(impl);
|
||||
return impl;
|
||||
@@ -85,14 +88,15 @@ TimerQueryFactoryInterface::~TimerQueryFactoryInterface() = default;
|
||||
// This is a backend synchronous call
|
||||
TimerQueryResult TimerQueryFactoryInterface::getTimerQueryValue(
|
||||
GLTimerQuery* tq, uint64_t* elapsedTime) noexcept {
|
||||
assert_invariant(tq->state);
|
||||
|
||||
int64_t const elapsed = tq->state->elapsed.load(std::memory_order_relaxed);
|
||||
if (elapsed > 0) {
|
||||
*elapsedTime = elapsed;
|
||||
return TimerQueryResult::AVAILABLE;
|
||||
if (UTILS_LIKELY(tq->state)) {
|
||||
int64_t const elapsed = tq->state->elapsed.load(std::memory_order_relaxed);
|
||||
if (elapsed > 0) {
|
||||
*elapsedTime = elapsed;
|
||||
return TimerQueryResult::AVAILABLE;
|
||||
}
|
||||
return TimerQueryResult(elapsed);
|
||||
}
|
||||
return TimerQueryResult(elapsed);
|
||||
return TimerQueryResult::ERROR;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
@@ -106,8 +110,9 @@ TimerQueryNativeFactory::TimerQueryNativeFactory(OpenGLContext& context)
|
||||
TimerQueryNativeFactory::~TimerQueryNativeFactory() = default;
|
||||
|
||||
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);
|
||||
CHECK_GL_ERROR()
|
||||
}
|
||||
@@ -176,7 +181,8 @@ TimerQueryFenceFactory::~TimerQueryFenceFactory() {
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -232,7 +238,8 @@ TimerQueryFallbackFactory::TimerQueryFallbackFactory() = default;
|
||||
TimerQueryFallbackFactory::~TimerQueryFallbackFactory() = default;
|
||||
|
||||
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) {
|
||||
|
||||
@@ -61,9 +61,14 @@ struct GLTimerQuery : public HwTimerQuery {
|
||||
*/
|
||||
|
||||
class TimerQueryFactory {
|
||||
static bool mGpuTimeSupported;
|
||||
public:
|
||||
static TimerQueryFactoryInterface* init(
|
||||
OpenGLPlatform& platform, OpenGLContext& context);
|
||||
|
||||
static bool isGpuTimeSupported() noexcept {
|
||||
return mGpuTimeSupported;
|
||||
}
|
||||
};
|
||||
|
||||
class TimerQueryFactoryInterface {
|
||||
|
||||
@@ -117,13 +117,6 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
|
||||
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`
|
||||
// population or upon cancellation. In either scenario, the callback handle must be submitted
|
||||
// to notify the caller that resource loading has concluded.
|
||||
@@ -367,15 +360,14 @@ void ShaderCompilerService::compileProgram(
|
||||
<< " failed to link or compile";
|
||||
#endif
|
||||
}
|
||||
// The program blob is cached prior to signaling to ensure data integrity.
|
||||
// Since the receiving thread may immediately modify gl.program (e.g., via
|
||||
// glUniformBlockBinding) upon receipt of the signal, caching must be
|
||||
// finalized first.
|
||||
tryCachingProgram(mBlobCache, mDriver.mPlatform, token);
|
||||
// Now `token->gl.program` must be populated, so we signal the completion
|
||||
// of the linking. We don't need to check the result of the program here
|
||||
// because it'll be done in the engine thread.
|
||||
token->signal();
|
||||
// We try caching the program blob after sending the signal. This allows us
|
||||
// to unblock the engine thread as soon as the token is ready while
|
||||
// performing an expensive caching operation still in the pool.
|
||||
tryCachingProgram(mBlobCache, mDriver.mPlatform, token);
|
||||
// Updates the token's state. If the token is canceled while this function
|
||||
// executes, this update notifies `tick` that GL resource loading is
|
||||
// complete, allowing `tick` to proceed with resource destruction.
|
||||
@@ -532,15 +524,8 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) {
|
||||
}
|
||||
|
||||
void ShaderCompilerService::ensureTokenIsReady(program_token_t const& token) {
|
||||
if (mMode == Mode::THREAD_POOL) {
|
||||
// Check if `token->gl.program` is populated
|
||||
if (token->isReady()) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (token->gl.program) {
|
||||
return;
|
||||
}
|
||||
if (token->gl.program) {
|
||||
return;// It's ready.
|
||||
}
|
||||
|
||||
switch (mMode) {
|
||||
@@ -950,7 +935,6 @@ void ShaderCompilerService::cancelPendingSynchronousProgram(program_token_t cons
|
||||
return false;
|
||||
}
|
||||
token->retrievedFromBlobCache = true;
|
||||
token->signal(); // notify that `token->gl.program` is ready to use
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -80,9 +80,6 @@ JNIEnv* ExternalStreamManagerAndroid::getEnvironmentSlow() noexcept {
|
||||
|
||||
Stream* ExternalStreamManagerAndroid::acquire(jobject surfaceTexture) noexcept {
|
||||
// note: This is called on the application thread (not the GL thread)
|
||||
// The application thread *MUST* be attached to the JVM to pass the java 'surfaceTexture' down
|
||||
// to filament natively, so we are guaranteed to get a valid JNIEnv from getThreadEnvironment()
|
||||
// without needing AttachCurrentThread.
|
||||
JNIEnv* env = VirtualMachineEnv::getThreadEnvironment();
|
||||
if (!env) {
|
||||
return nullptr; // this should not happen
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
|
||||
#include <utils/Invocable.h>
|
||||
#include <utils/Logger.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
@@ -120,41 +119,31 @@ bool PlatformEGL::isOpenGL() const noexcept {
|
||||
|
||||
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) {
|
||||
if (mEGLDisplay == EGL_NO_DISPLAY) {
|
||||
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
assert_invariant(mEGLDisplay != EGL_NO_DISPLAY);
|
||||
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
assert_invariant(mEGLDisplay != EGL_NO_DISPLAY);
|
||||
|
||||
EGLint major, minor;
|
||||
EGLBoolean initialized = eglInitialize(mEGLDisplay, &major, &minor);
|
||||
EGLint major, minor;
|
||||
EGLBoolean initialized = eglInitialize(mEGLDisplay, &major, &minor);
|
||||
|
||||
if (!initialized) {
|
||||
EGLDeviceEXT eglDevice;
|
||||
EGLint numDevices;
|
||||
PFNEGLQUERYDEVICESEXTPROC const eglQueryDevicesEXT =
|
||||
PFNEGLQUERYDEVICESEXTPROC(eglGetProcAddress("eglQueryDevicesEXT"));
|
||||
if (eglQueryDevicesEXT != nullptr) {
|
||||
eglQueryDevicesEXT(1, &eglDevice, &numDevices);
|
||||
if(auto* getPlatformDisplay = reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
|
||||
eglGetProcAddress("eglGetPlatformDisplay"))) {
|
||||
mEGLDisplay = getPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, eglDevice, nullptr);
|
||||
initialized = eglInitialize(mEGLDisplay, &major, &minor);
|
||||
}
|
||||
if (!initialized) {
|
||||
EGLDeviceEXT eglDevice;
|
||||
EGLint numDevices;
|
||||
PFNEGLQUERYDEVICESEXTPROC const eglQueryDevicesEXT =
|
||||
PFNEGLQUERYDEVICESEXTPROC(eglGetProcAddress("eglQueryDevicesEXT"));
|
||||
if (eglQueryDevicesEXT != nullptr) {
|
||||
eglQueryDevicesEXT(1, &eglDevice, &numDevices);
|
||||
if(auto* getPlatformDisplay = reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
|
||||
eglGetProcAddress("eglGetPlatformDisplay"))) {
|
||||
mEGLDisplay = getPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, eglDevice, nullptr);
|
||||
initialized = eglInitialize(mEGLDisplay, &major, &minor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(!initialized)) {
|
||||
LOG(ERROR) << "eglInitialize failed";
|
||||
return nullptr;
|
||||
}
|
||||
if (UTILS_UNLIKELY(!initialized)) {
|
||||
LOG(ERROR) << "eglInitialize failed";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#if defined(FILAMENT_IMPORT_ENTRY_POINTS)
|
||||
@@ -717,11 +706,10 @@ bool PlatformEGL::setExternalImage(ExternalImageHandleRef externalImage,
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
|
||||
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);
|
||||
if (extensions) {
|
||||
GLUtils::unordered_string_set const glExtensions = GLUtils::split(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 {
|
||||
|
||||
@@ -120,7 +120,8 @@ struct PlatformEGLAndroid::AndroidDetails {
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
PlatformEGLAndroid::PlatformEGLAndroid() noexcept
|
||||
: mAndroidDetails(*(new(std::nothrow) AndroidDetails{})) {
|
||||
: mExternalStreamManager(ExternalStreamManagerAndroid::create()),
|
||||
mAndroidDetails(*(new(std::nothrow) AndroidDetails{})) {
|
||||
mOSVersion = android_get_device_api_level();
|
||||
if (mOSVersion < 0) {
|
||||
mOSVersion = __ANDROID_API_FUTURE__;
|
||||
@@ -134,10 +135,7 @@ PlatformEGLAndroid::~PlatformEGLAndroid() noexcept {
|
||||
void PlatformEGLAndroid::terminate() noexcept {
|
||||
mPerformanceHintManager.terminate();
|
||||
mAndroidDetails.androidFrameCallback.terminate();
|
||||
if (mExternalStreamManager) {
|
||||
ExternalStreamManagerAndroid::destroy(mExternalStreamManager);
|
||||
mExternalStreamManager = nullptr;
|
||||
}
|
||||
ExternalStreamManagerAndroid::destroy(&mExternalStreamManager);
|
||||
PlatformEGL::terminate();
|
||||
}
|
||||
|
||||
@@ -227,10 +225,6 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
|
||||
mPerformanceHintSession = PerformanceHintManager::Session{
|
||||
mPerformanceHintManager, &tid, 1, 16'666'667 };
|
||||
|
||||
mExternalStreamManager = &ExternalStreamManagerAndroid::create();
|
||||
FILAMENT_CHECK_POSTCONDITION(mExternalStreamManager)
|
||||
<< "Failed to create ExternalStreamManagerAndroid";
|
||||
|
||||
Driver* driver = PlatformEGL::createDriver(sharedContext, driverConfig);
|
||||
auto const extensions = GLUtils::split(eglQueryString(getEglDisplay(), EGL_EXTENSIONS));
|
||||
|
||||
@@ -617,13 +611,11 @@ void PlatformEGLAndroid::setPresentationTime(int64_t const presentationTimeInNan
|
||||
}
|
||||
|
||||
Platform::Stream* PlatformEGLAndroid::createStream(void* nativeStream) noexcept {
|
||||
assert_invariant(mExternalStreamManager);
|
||||
return mExternalStreamManager->acquire(static_cast<jobject>(nativeStream));
|
||||
return mExternalStreamManager.acquire(static_cast<jobject>(nativeStream));
|
||||
}
|
||||
|
||||
void PlatformEGLAndroid::destroyStream(Stream* stream) noexcept {
|
||||
assert_invariant(mExternalStreamManager);
|
||||
mExternalStreamManager->release(stream);
|
||||
mExternalStreamManager.release(stream);
|
||||
}
|
||||
|
||||
Platform::Sync* PlatformEGLAndroid::createSync() noexcept {
|
||||
@@ -675,23 +667,19 @@ void PlatformEGLAndroid::destroySync(Sync* sync) noexcept {
|
||||
}
|
||||
|
||||
void PlatformEGLAndroid::attach(Stream* stream, intptr_t const tname) noexcept {
|
||||
assert_invariant(mExternalStreamManager);
|
||||
mExternalStreamManager->attach(stream, tname);
|
||||
mExternalStreamManager.attach(stream, tname);
|
||||
}
|
||||
|
||||
void PlatformEGLAndroid::detach(Stream* stream) noexcept {
|
||||
assert_invariant(mExternalStreamManager);
|
||||
mExternalStreamManager->detach(stream);
|
||||
mExternalStreamManager.detach(stream);
|
||||
}
|
||||
|
||||
void PlatformEGLAndroid::updateTexImage(Stream* stream, int64_t* timestamp) noexcept {
|
||||
assert_invariant(mExternalStreamManager);
|
||||
mExternalStreamManager->updateTexImage(stream, timestamp);
|
||||
mExternalStreamManager.updateTexImage(stream, timestamp);
|
||||
}
|
||||
|
||||
math::mat3f PlatformEGLAndroid::getTransformMatrix(Stream* stream) noexcept {
|
||||
assert_invariant(mExternalStreamManager);
|
||||
return mExternalStreamManager->getTransformMatrix(stream);
|
||||
return mExternalStreamManager.getTransformMatrix(stream);
|
||||
}
|
||||
|
||||
int PlatformEGLAndroid::getOSVersion() const noexcept {
|
||||
|
||||
@@ -29,12 +29,6 @@ using namespace bluevk;
|
||||
|
||||
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,
|
||||
std::chrono::steady_clock::time_point const until) {
|
||||
|
||||
|
||||
@@ -40,13 +40,6 @@ struct VulkanCmdFence {
|
||||
explicit VulkanCmdFence(VkFence fence) : mFence(fence) { }
|
||||
~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) {
|
||||
std::lock_guard const l(mLock);
|
||||
mStatus = value;
|
||||
|
||||
@@ -277,11 +277,7 @@ private:
|
||||
fvkmemory::resource_ptr<VulkanSemaphore> mLastSubmit;
|
||||
|
||||
VkFence mLastFence = VK_NULL_HANDLE;
|
||||
// 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();
|
||||
std::shared_ptr<VulkanCmdFence> mLastFenceStatus;
|
||||
|
||||
VkPipelineStageFlags mInjectedDependencyWaitStage = 0;
|
||||
};
|
||||
|
||||
@@ -121,6 +121,10 @@ public:
|
||||
return mPhysicalDeviceProperties.properties.vendorID;
|
||||
}
|
||||
|
||||
inline char const* getDriverName() const noexcept {
|
||||
return mDriverProperties.driverName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a list of pre-registered external formats for prewarming the Vulkan
|
||||
* pipeline cache.
|
||||
@@ -137,14 +141,6 @@ public:
|
||||
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 {
|
||||
return mPhysicalDeviceFeatures.features.imageCubeArray == VK_TRUE;
|
||||
}
|
||||
@@ -220,8 +216,6 @@ public:
|
||||
return mGlobalPrioritySupported;
|
||||
}
|
||||
|
||||
inline bool isDriverPropertiesSupported() const noexcept { return mDriverPropertiesSupported; }
|
||||
|
||||
private:
|
||||
VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
|
||||
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
|
||||
@@ -259,7 +253,6 @@ private:
|
||||
bool mProtectedMemorySupported = false;
|
||||
bool mVertexInputDynamicStateSupported = false;
|
||||
bool mGlobalPrioritySupported = false;
|
||||
bool mDriverPropertiesSupported = false;
|
||||
|
||||
// These are options that can be enabled or disabled at an application level.
|
||||
bool mAsyncPipelineCachePrewarmingEnabled = false;
|
||||
|
||||
@@ -355,8 +355,35 @@ void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescri
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture,
|
||||
VkSampler sampler, VkDescriptorSetLayout externalSamplerLayout) noexcept {
|
||||
uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture, 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();
|
||||
VkImageSubresourceRange range = texture->getPrimaryViewRange();
|
||||
VkImageViewType const expectedType = texture->getViewType();
|
||||
@@ -405,28 +432,6 @@ fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetCache::createSet
|
||||
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,
|
||||
VkDescriptorSetLayout vklayout) {
|
||||
return mDescriptorPool->obtainSet(count, vklayout);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user