Compare commits

...
Author SHA1 Message Date
Powei Feng c5462621eb vk: adding logs just for debugging [DO NOT COMMIT] 2026-03-18 10:15:56 -07:00
Mathias Agopian e2b195706a fix overdraw visualization in gltf_viewer (#9805) 2026-03-18 09:01:18 -07:00
Powei Feng 3ebb2086ed github: use arm linux instead of x86 whenever possible (#9794)
- Simplify getting cmake, ninja for linux
 - Move linux, web, webgpu builds to use arm (more cores for less)
 - Android cannot be moved due to Android SDK not available on
   arm linux yet.
 - Add Arm Linux release artifacts

Fixes #8674
2026-03-18 07:19:38 +00:00
Mathias Agopian 052092f553 remove the remaining instances of std::string (#9802)
libfilament (including libutils and libmath) are 100% std::string
free.

std::string is pulled in the .so (on android) through libc++ for 
exception handling, even if we're not using them. There is not much
we can do here, but at least, it's not because of us!

utils::ostream still references it but only as an inline function,
so if the inline is not called, std::string won't be pulled in.

It's also referenced from Path.cpp, but that's not included in
libfilament.
2026-03-17 15:27:12 -07:00
Mathias Agopian 7163aaa627 don't output the full shader when matc fails (#9803)
This made it nearly impossible to find the actual error. Now, we
output only the error from the compiler + the material name, variant
and shader stage all in one line.
2026-03-17 13:37:14 -07:00
Mathias Agopian 0c7ff3fcf4 store shadow atlas resolution in frameUniforms (#9804) 2026-03-17 13:36:58 -07:00
Powei Feng 962756e283 gl: add getter for GL_VERSION (#9798)
On Android, GL_VERSION contains information about the driver
version number.  This can be useful when debugging or running
tests.
2026-03-17 17:56:51 +00:00
Sungun Park 48ee727c8d fix: Resolve a TSan warning for OpenGLTimerQuery (#9790)
The main thread invokes `getTimerQueryValue` synchronously, reading
`tq->state`. Meanwhile, the driver thread handles `createTimerQuery`
asynchronously and initializes `tq->state = std::make_shared<State>()`.

Eliminate this race by initializing the state in createTimerQueryS.

BUGS=[491522442]
2026-03-16 20:48:31 +00:00
Powei Feng 2d1e4b8ce2 github: fix broken header check (#9800)
- Move header check from postsubmit to presubmit
 - Install third_party/getop if needed
 - Modify check-headers test to use system getopt when it's available.
2026-03-16 10:26:59 -07:00
Powei Feng 749b03ed2a utils: refactor getopt into utils namespace (#9796)
On certain linux, macOS environment, there is already a system
getopt. This often creates conflict when compiling filament.
Here we alias utils::getopt to either the system getopt (if
present) or third_party/getopt.

Fixes #7551
2026-03-13 17:22:44 -07:00
Mathias Agopian 497a1cc42d fix android CMake so LTO can be turned on (#9797)
our custom build system didn't set CMAKE_ANDROID_NDK_VERSION which
is needed for CMake's check_ipo_supported.
2026-03-13 16:32:59 -07:00
Run Yu 256a494cd0 webgpu: implement the scissor backend function (#9783)
also enable ScissorViewportRegion backend test.
It was previously skipped, now it is passing.

BUGS=474393992
2026-03-13 21:37:54 +00:00
Powei Feng b2531fff15 android: [sample-render-val] add difference/output viewer (#9781)
- Add viewer for closer examination
 - Add slider to enhance difference
 - Fixed ImageDiff jni bug to account for stride and
   premultiplication by alpha
2026-03-13 18:39:22 +00:00
Powei Feng f18afe1d3e Try to fix verify-notes breakage (#9795)
- Update python3 version to 3.12.13
 - Use action/checkout instead of Bhacaz/checkout-files because action/checkout can
   use the action/files of the current PR, where as checkout-files is checking out
   a file on the main branch
 - action/checkout seems to run in less than a minute, so acceptable performance
   difference.

--------------------------------------------------------------------

Breakage is:

Traceback (most recent call last):
  File "/usr/local/lib/python3.10/site-packages/jwt/algorithms.py", line 36, in <module>
    from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm
ModuleNotFoundError: No module named 'cryptography'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/verify_release_notes.py", line 17, in <module>
    from github import Github
  File "/usr/local/lib/python3.10/site-packages/github/__init__.py", line 56, in <module>
    from github.MainClass import Github, GithubIntegration
  File "/usr/local/lib/python3.10/site-packages/github/MainClass.py", line 54, in <module>
    import jwt
  File "/usr/local/lib/python3.10/site-packages/jwt/__init__.py", line 1, in <module>
    from .api_jwk import PyJWK, PyJWKSet
  File "/usr/local/lib/python3.10/site-packages/jwt/api_jwk.py", line 8, in <module>
    from .algorithms import get_default_algorithms, has_crypto, requires_cryptography
  File "/usr/local/lib/python3.10/site-packages/jwt/algorithms.py", line 113, in <module>
    from typing_extensions import Never
ModuleNotFoundError: No module named 'typing_extensions'
2026-03-13 11:06:55 -07:00
Sungun Park 771eb4c1a4 fix: Resolve a TSan warning for ShaderCompilerService (#9792)
When the backend thread checks the availability of the token, it
directly accesses `gl.program`, which caused a TSan warning.

BUGS=[491819760]
2026-03-12 17:19:11 +00:00
Powei Feng 70e7cb2a27 github: fix bad conditional in presubmit (#9793)
The previous conditional used
github.event.head_commit.verification.verified, which is not a
real field. Hence, the attempt to reduce presubmit runs did not
actually succeed.  Here we replace it with a more correct
verification step.
2026-03-11 16:09:29 -07:00
Powei Feng f95f127495 github: reduce presubmit runs (#9791)
We make two adjustments to make the presubmit workflows run less.
 - Only run on commit merge when the commit is not "verified".
   While not a 1-1 definition, "verified" commit typically implies
   that the commit came from a PR merge (which requires that
   presubmit passes). A commit could also be made by owners on
   main directly, in which case, the commit would not be verified.
 - Cancel any in progress presubmit runs are "overwritten" by an
   update to the PR.
2026-03-11 18:43:39 +00:00
Evan Mezeske c0d63e826d Metal: fix crash from exceeding max texture dimensions for Bloom buffer (#9784)
I recently added a new fuzz test for my Filament-based app, which creates a window and then rapidly issues changes such as toggling View features, changing the window size, and other operations that the users of my app can do.

It quickly found a crash when Bloom is enabled and the window dimensions get resized to weird aspect ratios on a large monitor. Eventually I narrowed it down to the Bloom buffer's width exceeding the Metal max texture dimension (on my machine, 16K). Applying this patch fixes the crash.
2026-03-11 10:39:35 -07:00
haroonq 36583618f1 Allow child classes to configure the EGLDisplay. (#9779) 2026-03-10 23:42:17 +00:00
Powei Feng 9d322e7208 github: reduce action runner costs (#9789)
This is a speculative reduction of github runner costs by using
less costly machines. The usage time will be longer, but
hopefully the total cost will still be lower.

Note we disabled most of the large runners for postsubmit builds,
since we should have caught all breakages in presubmit.
2026-03-10 21:30:47 +00:00
Filament Bot b897d22d20 [automated] Updating /docs due to commit e5fe3d4
Full commit hash is e5fe3d495e

DOCS_ALLOW_DIRECT_EDITS
2026-03-10 20:49:10 +00:00
Benjamin Doherty e5fe3d495e Release Filament 1.70.0 2026-03-10 13:43:15 -07:00
Mathias Agopian 35f501b3d5 Debug Shadow Cascades: remove dependency on frameUniforms (#9782)
The DebugShadowCasacade material was dependent on frameUniforms,
instead we use material parameters. We also pass all the informations
relative to cascades (it's not used yet, but will be in the future).
2026-03-09 17:14:07 -07:00
Powei Feng 5b799928c3 webgpu: fixes for deadlock, over-flushing (#9776) (#9767)
- In finish(), instead of block to wait for read pixels to finish,
   we poll the state of the counter to ensure that all the readbacks
   are complete.  This allows us to complete the readpixel callbacks
   by advancing webgpu's internal callback counter.
 - Remove all the redundant flushes that are no longer needed now
   that buffer upload is put on the command queue.
 - Add a convenience method for getting swapchain dimensions. Will
   be useful.
 - Add webgpu as a valid backend for running backend tests on linux
   (now that it no longer deadlocks).
2026-03-09 13:05:18 -07:00
Powei Feng f7f586caff webgpu: fixes for deadlock, over-flushing (#9776)
- In finish(), instead of block to wait for read pixels to finish,
   we poll the state of the counter to ensure that all the readbacks
   are complete.  This allows us to complete the readpixel callbacks
   by advancing webgpu's internal callback counter.
 - Remove all the redundant flushes that are no longer needed now
   that buffer upload is put on the command queue.
 - Add a convenience method for getting swapchain dimensions. Will
   be useful.
 - Add webgpu as a valid backend for running backend tests on linux
   (now that it no longer deadlocks).
2026-03-09 18:14:26 +00:00
Mathias Agopian 5428812b93 remove an unnecessary assert in the froxelixer (#9785) 2026-03-09 10:41:43 -07:00
Patrick Ribas e57f2f5c02 Add workaround for GLES2 mipmap requirements (#9770) 2026-03-06 16:36:03 -08:00
Mathias Agopian fd9bcaa735 Fix heap buffer overflow in HDRDecoder RLE decoding (Issue #9748) (#9777)
This commit addresses a critical security vulnerability (OOB write) and 
several stability issues in the Radiance HDR parser.

Primary Fix:
* Fixed a heap buffer overflow in the RLE decoding loop (Issue #9748). 
  The decoder previously failed to verify if a run-length chunk exceeded 
  the remaining space in the scanline buffer. Added strict bounds checking 
  (`num_bytes + run_length > width`) before executing `memset` or 
  `mStream.read` to prevent arbitrary memory corruption.

Additional Security & Stability Improvements:
* Prevented an infinite loop (DoS) in header parsing. Replaced the 
  `do { ... } while(true);` loop with proper stream state checking 
  (`while (mStream.getline(...))`) to handle unexpected EOFs gracefully.
* Mitigated integer overflow and Out-Of-Memory (OOM) vulnerabilities by 
  enforcing maximum sane dimensions (`MAX_IMAGE_DIMENSION` and 
  `MAX_IMAGE_PIXELS`). This prevents catastrophic memory allocations 
  triggered by maliciously crafted width/height values.
* Initialized local variables and buffers (`buf`, `gamma`, `exposure`) to 
  prevent undefined behavior and parsing of stack garbage upon stream read 
  failures.

Fixes #9748
2026-03-06 16:35:24 -08:00
Powei Feng 9a14e54fc2 matdbg: prefer msl when running on metal (#9780)
Keeping ShaderLanguage::UNSPECIFIED will return metal library
as the preferred language on metal.  Here we just make it more
explicit that matdbg needs msl.

Fixes #9372
2026-03-06 21:34:21 +00:00
Powei Feng d78bb294ed gltf-viewer: enable animation by default (#9778)
The toggle for animation was added recently to Settings and default 
to false. This differs from before where animation is always assumed
to be enabled.

For gltf_viewer's interactive mode, we enable animation. (Batch mode
will still have animation off).

Fixes #9775
2026-03-06 17:46:13 +00:00
haroonq 0b8dbe9b0a Small fix to allow PlatformEGL to work on non-Android targets. (#9724)
- Include gl_headers.h since it defines BACKEND_OPENGL_VERSION_GL(ES)
    depending on which headers are available.
- Don't assume glGetString(GL_EXTENSIONS) returns a non-null value as
    null is a possible return value for OpenGL (desktop).
2026-03-05 15:38:49 -08:00
Powei Feng e595fd4b79 android-utils: ModelViewer camera manipulator can be null (#9766)
We add the ability for the maniuplator to be null. If it is null,
then the camera settings won't be determined by the manipulator.
This is useful for when we want to set the camera parameters
outside of the modelviewer, but still use it to do everything else.
2026-03-05 20:05:55 +00:00
Ben Doherty c5d36cff7f Prepare Gradle plugin for publishing (#9773) 2026-03-05 10:50:15 -08:00
Powei Feng f392e8be54 ppm: move stencil check right before driver calls (#9769)
The init block of addPass will be executed immediately, which means
that even for passes that are culled, we will trip on incorrect format
given to the pass.

We move the stencil check to the execute block of addPass so that
it'll only assert when the pass is actually present in the graph.

FIXES=489437881
2026-03-05 18:11:33 +00:00
Eliza 83653fb358 engine: encapsulate material cache handling (#9663)
* engine: encapsulate material cache handling

We will soon allow `MaterialInstance` to override the value of spec constants.
To avoid code duplication, we introduce a new class `MaterialPrograms` which
handles the chunky bits of managing the program cache and values of the spec
constants.

* MaterialPrograms: add explicit initialize method

* MaterialPrograms: backport fixes, address comments

* MaterialPrograms: set all constants at once

* MaterialPrograms: rename to LocalProgramCache
2026-03-05 06:42:05 +00:00
Eliza 8a3c48fef1 utils: add LRU cache to RefCountedMap (#9730)
* utils: add LRU cache to RefCountedMap

This change introduces a new data structure LruCache and uses it in
RefCountedMap to keep a fixed number of cache entries alive after their
reference count has dropped to zero in the main map.

* utils: address LRU cache comments
2026-03-05 06:04:16 +00:00
Powei Feng 7f6b9bb144 vk: reference rendertarget as part of fbo cache (#9771)
The FBO cache uses imageviews as its cache key. To ensure that these
views are valid for the lifetime of a key-value pair in the cache, we
need to also reference the rendertarget in the value part of the pair.
The RT contains attachments, which are wrappers around
which own the image views.

Fixes #9680
2026-03-04 09:59:05 -08:00
rafadevai 687c42583b VK: Only make a single copy of the descriptor with external samplers (#9765)
When more than one external sampler are present the
flow will clone the descriptor set twice and make a copy
of a binding that requires an immutable sampler. The copy
of this binding will cause a crash in some adreno GPUs.

This change will make sure the descriptor set is only cloned one
and after that only update binding operations are done for the
external sampled bindings.
2026-03-03 18:34:40 -08:00
Mathias Agopian 71e8cab08a Fix a race when garbage-collecting components (#9768)
Not all component managers are thread-safe for garbage-collection. In
particular, some need to access the DriverAPI which is never
thread-safe.

We refactor the garbage-collection code into FEngine, so it's not
duplicated in FRenderer. We make it more explicit that 
FRenderableManager::destroyComponent() needs the DriverAPI, and we
don't call its gc() from a job.

Besides the refactoring, the only change in this CL is that 
FRenderableManager::gc() is no longer called from a job.


FIXES=[489134910]
2026-03-03 13:21:06 -08:00
Ben Doherty afae31a975 Add Sonatype publishing step to release workflow (#9764) 2026-03-03 12:41:55 -08:00
Powei Feng 5ac5dc4c95 ci: fetch PR ref explicitly to extract commit message for forks (#9756) 2026-03-03 17:24:00 +00:00
Filament Bot e975572972 [automated] Updating /docs due to commit 29e91f0
Full commit hash is 29e91f0d3a

DOCS_ALLOW_DIRECT_EDITS
2026-03-03 16:21:50 +00:00
208 changed files with 4050 additions and 1879 deletions
+17 -15
View File
@@ -27,17 +27,24 @@ runs:
echo "$HASH" > /tmp/commit_hash.txt
- name: Find commit message (PR)
shell: bash
id: checkout_code
if: github.event_name == 'pull_request'
run: |
BEFORE_HASH=$(git rev-parse HEAD)
echo "hash=$BEFORE_HASH" >> "$GITHUB_OUTPUT"
# Next we will checkout the actual head (not the merge commits) of the PR
AFTER_HASH="${{ github.event.pull_request.head.sha }}"
git checkout $AFTER_HASH
COMMIT_MESSAGE=$(git log -1 --no-merges)
echo "$COMMIT_MESSAGE" > /tmp/commit_msg.txt
echo "$AFTER_HASH" > /tmp/commit_hash.txt
PR_NUMBER="${{ github.event.pull_request.number }}"
# Fetch the head of the PR explicitly to handle forks. Depth 50 ensures we can traverse past recent merge commits.
git fetch --depth=50 origin "pull/$PR_NUMBER/head:pr-head"
COMMIT_HASH=$(git log -1 --no-merges pr-head --format=%H)
AUTHOR_NAME=$(git log -1 --no-merges pr-head --format=%an)
AUTHOR_EMAIL=$(git log -1 --no-merges pr-head --format=%ae)
TSTAMP=$(git log -1 --no-merges pr-head --format=%aI)
COMMIT_MESSAGE=$(git log -1 --no-merges pr-head --format=%B)
echo "commit $COMMIT_HASH" > /tmp/commit_msg.txt
echo "Author: ${AUTHOR_NAME}<${AUTHOR_EMAIL}>" >> /tmp/commit_msg.txt
echo "Date: ${TSTAMP}" >> /tmp/commit_msg.txt
echo "" >> /tmp/commit_msg.txt
echo "$COMMIT_MESSAGE" >> /tmp/commit_msg.txt
echo "$COMMIT_HASH" > /tmp/commit_hash.txt
- shell: bash
id: action_output
run: |
@@ -47,9 +54,4 @@ runs:
cat /tmp/commit_msg.txt >> "$GITHUB_OUTPUT"
echo "$DELIMITER" >> "$GITHUB_OUTPUT"
# Get the commit hash
echo "hash=$(cat /tmp/commit_hash.txt)" >> "$GITHUB_OUTPUT"
- name: Cleanup Find commit message (PR)
shell: bash
if: github.event_name == 'pull_request'
run: |
git checkout ${{ steps.checkout_code.outputs.hash }}
echo "hash=$(cat /tmp/commit_hash.txt)" >> "$GITHUB_OUTPUT"
+1 -12
View File
@@ -11,24 +11,13 @@ runs:
echo "set man-db/auto-update false" | sudo debconf-communicate
sudo dpkg-reconfigure man-db
# Install ninja
source ./build/common/get-ninja.sh
# Install CMake
mkdir -p cmake
cd cmake
sudo wget https://github.com/Kitware/CMake/releases/download/v$GITHUB_CMAKE_VERSION/cmake-$GITHUB_CMAKE_VERSION-Linux-x86_64.sh
sudo chmod +x ./cmake-$GITHUB_CMAKE_VERSION-Linux-x86_64.sh
sudo ./cmake-$GITHUB_CMAKE_VERSION-Linux-x86_64.sh --skip-license > /dev/null
sudo update-alternatives --install /usr/bin/cmake cmake $(pwd)/bin/cmake 1000 --force
cd ..
sudo wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo apt-get update
sudo apt-get install clang-$GITHUB_CLANG_VERSION libc++-$GITHUB_CLANG_VERSION-dev libc++abi-$GITHUB_CLANG_VERSION-dev
sudo apt-get install mesa-common-dev libxi-dev libxxf86vm-dev
sudo apt-get install cmake ninja-build
# For dawn
sudo apt-get install libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libx11-xcb-dev
@@ -1,4 +1,4 @@
FROM python:3.10.4
FROM python:3.12.13
RUN pip3 install pygithub==1.55
+1 -1
View File
@@ -8,7 +8,7 @@ runs:
uses: actions/cache@v4 # Use a specific version
with:
path: emsdk
key: ${{ runner.os }}-emsdk-${{ env.GITHUB_EMSDK_VERSION }}
key: ${{ runner.os }}-${{ runner.arch }}-emsdk-${{ env.GITHUB_EMSDK_VERSION }}
- name: Install Web Prerequisites
shell: bash
run: |
+2 -2
View File
@@ -10,7 +10,7 @@ jobs:
# a branch on filament-assets.
update-renderdiff-goldens:
name: update-renderdiff-goldens
runs-on: 'macos-14-xlarge'
runs-on: 'macos-14'
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-16core'
runs-on: 'ubuntu-24.04-4core'
steps:
- uses: actions/checkout@v4.1.6
with:
+8 -16
View File
@@ -12,8 +12,7 @@ jobs:
name: build-android
# We intentially use a larger runner here to enable larger disk space
# (standard linux runner will fail on disk space and faster build time).
runs-on: 'ubuntu-24.04-16core'
runs-on: 'ubuntu-24.04-4core'
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -26,8 +25,7 @@ jobs:
build-ios:
name: build-ios
runs-on: macos-14-xlarge
runs-on: 'macos-14'
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -46,8 +44,7 @@ jobs:
build-linux:
name: build-linux
runs-on: 'ubuntu-24.04-16core'
runs-on: 'arm-ubuntu-24.04-16core'
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -58,13 +55,12 @@ jobs:
cd build/linux && printf "y" | ./build.sh continuous
- uses: actions/upload-artifact@v4
with:
name: filament-linux
path: out/filament-release-linux.tgz
name: filament-arm-linux
path: out/filament-release-arm-linux.tgz
build-mac:
name: build-mac
runs-on: macos-14-xlarge
runs-on: 'macos-14'
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -77,14 +73,10 @@ jobs:
with:
name: filament-mac
path: out/filament-release-darwin.tgz
- name: Check public headers
run: |
test/check-headers/test.sh out/release/filament/include
build-web:
name: build-web
runs-on: 'ubuntu-24.04-16core'
runs-on: 'arm-ubuntu-24.04-16core'
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -101,7 +93,7 @@ jobs:
build-windows:
name: build-windows
runs-on: windows-2022-32core
runs-on: 'windows-2022'
steps:
- uses: actions/checkout@v4.1.6
+171 -11
View File
@@ -8,10 +8,54 @@ on:
branches:
- main
# This will cancel in-flight runs when there is an update to a PR
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# The conditional 'if' on each job is meant to skip presubmit jobs when a commit is pushed to main
# and that commit is cryptographically "verified". Typically, a verified commit (like a GitHub UI
# squash-and-merge) has already passed through presubmit during the Pull Request phase.
# The conditional explicitly checks:
# 1. always() && !cancelled(): Ensures the job runs even if 'check-verification' is skipped, but
# aborts if the workflow was manually cancelled.
# 2. github.event_name == 'pull_request': Presubmits should always run normally on PRs.
# 3. github.event_name == 'push' && ...: If it's a push to main, it only runs if the
# 'check-verification' job confirmed the commit is NOT verified.
jobs:
check-verification:
if: github.event_name == 'push'
runs-on: ubuntu-latest
outputs:
verified: ${{ steps.check.outputs.verified }}
steps:
- name: Check commit verification
id: check
uses: actions/github-script@v7
with:
script: |
const { data } = await github.rest.repos.getCommit({
owner: context.repo.owner,
repo: context.repo.repo,
ref: context.sha
});
core.setOutput('verified', data.commit.verification.verified);
build-desktop-mac:
name: build-mac
runs-on: macos-14-xlarge
runs-on: 'macos-14-xlarge'
needs: [check-verification]
if: >
always() && !cancelled() &&
(
github.event_name == 'pull_request' ||
(
github.event_name == 'push' &&
needs.check-verification.result == 'success' &&
needs.check-verification.outputs.verified == 'false'
)
)
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -20,13 +64,30 @@ jobs:
- name: Run build script
run: |
cd build/mac && printf "y" | ./build.sh presubmit-with-test
- name: Test material parser
- name: Test - material parser
run: |
out/cmake-release/filament/test/test_material_parser
- name: Test - public headers
run: |
# out/cmake-release should have the artifacts ready for installation. Here we install it
# to test the public headers
ninja -C out/cmake-release install
test/check-headers/test.sh out/release/filament/include
build-desktop-linux:
name: build-linux
runs-on: 'ubuntu-24.04-16core'
runs-on: 'arm-ubuntu-24.04-16core'
needs: [check-verification]
if: >
always() && !cancelled() &&
(
github.event_name == 'pull_request' ||
(
github.event_name == 'push' &&
needs.check-verification.result == 'success' &&
needs.check-verification.outputs.verified == 'false'
)
)
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -41,7 +102,18 @@ jobs:
build-windows:
name: build-windows
runs-on: windows-2022-32core
runs-on: 'windows-2022'
needs: [check-verification]
if: >
always() && !cancelled() &&
(
github.event_name == 'pull_request' ||
(
github.event_name == 'push' &&
needs.check-verification.result == 'success' &&
needs.check-verification.outputs.verified == 'false'
)
)
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -53,7 +125,18 @@ jobs:
build-android:
name: build-android
runs-on: 'ubuntu-24.04-16core'
runs-on: 'ubuntu-24.04-4core'
needs: [check-verification]
if: >
always() && !cancelled() &&
(
github.event_name == 'pull_request' ||
(
github.event_name == 'push' &&
needs.check-verification.result == 'success' &&
needs.check-verification.outputs.verified == 'false'
)
)
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -80,7 +163,18 @@ jobs:
build-ios:
name: build-iOS
runs-on: macos-14-xlarge
runs-on: 'macos-14-xlarge'
needs: [check-verification]
if: >
always() && !cancelled() &&
(
github.event_name == 'pull_request' ||
(
github.event_name == 'push' &&
needs.check-verification.result == 'success' &&
needs.check-verification.outputs.verified == 'false'
)
)
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -95,7 +189,18 @@ jobs:
build-web:
name: build-web
runs-on: 'ubuntu-24.04-16core'
runs-on: 'arm-ubuntu-24.04-16core'
needs: [check-verification]
if: >
always() && !cancelled() &&
(
github.event_name == 'pull_request' ||
(
github.event_name == 'push' &&
needs.check-verification.result == 'success' &&
needs.check-verification.outputs.verified == 'false'
)
)
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -108,7 +213,18 @@ jobs:
validate-docs:
name: validate-docs
runs-on: 'ubuntu-24.04-4core'
runs-on: 'ubuntu-24.04'
needs: [check-verification]
if: >
always() && !cancelled() &&
(
github.event_name == 'pull_request' ||
(
github.event_name == 'push' &&
needs.check-verification.result == 'success' &&
needs.check-verification.outputs.verified == 'false'
)
)
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -121,7 +237,18 @@ jobs:
test-renderdiff:
name: test-renderdiff
runs-on: macos-14-xlarge
runs-on: 'macos-14-xlarge'
needs: [check-verification]
if: >
always() && !cancelled() &&
(
github.event_name == 'pull_request' ||
(
github.event_name == 'push' &&
needs.check-verification.result == 'success' &&
needs.check-verification.outputs.verified == 'false'
)
)
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -190,7 +317,18 @@ jobs:
validate-wgsl-webgpu:
name: validate-wgsl-webgpu
runs-on: 'ubuntu-24.04-16core'
runs-on: 'arm-ubuntu-24.04-16core'
needs: [check-verification]
if: >
always() && !cancelled() &&
(
github.event_name == 'pull_request' ||
(
github.event_name == 'push' &&
needs.check-verification.result == 'success' &&
needs.check-verification.outputs.verified == 'false'
)
)
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -204,6 +342,17 @@ jobs:
test-code-correctness:
name: test-code-correctness
runs-on: 'macos-14-xlarge'
needs: [check-verification]
if: >
always() && !cancelled() &&
(
github.event_name == 'pull_request' ||
(
github.event_name == 'push' &&
needs.check-verification.result == 'success' &&
needs.check-verification.outputs.verified == 'false'
)
)
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -224,7 +373,18 @@ jobs:
test-backend:
name: test-backend
runs-on: macos-14-xlarge
runs-on: 'macos-14-xlarge'
needs: [check-verification]
if: >
always() && !cancelled() &&
(
github.event_name == 'pull_request' ||
(
github.event_name == 'push' &&
needs.check-verification.result == 'success' &&
needs.check-verification.outputs.verified == 'false'
)
)
steps:
- uses: actions/checkout@v4.1.6
with:
+82 -1
View File
@@ -63,6 +63,39 @@ jobs:
const globber = await glob.create('out/*.tgz');
await upload({ github, context }, await globber.glob(), TAG);
build-arm-linux:
name: build-arm-linux
runs-on: 'arm-ubuntu-24.04-16core'
if: github.event_name == 'release' || github.event.inputs.platform == 'desktop'
steps:
- name: Decide Git ref
id: git_ref
run: |
REF=${RELEASE_TAG:-${GITHUB_REF}}
TAG=${REF##*/}
echo "ref=${REF}" >> $GITHUB_OUTPUT
echo "tag=${TAG}" >> $GITHUB_OUTPUT
- uses: actions/checkout@v4.1.6
with:
ref: ${{ steps.git_ref.outputs.ref }}
- uses: ./.github/actions/linux-prereq
- name: Run build script
env:
TAG: ${{ steps.git_ref.outputs.tag }}
run: |
cd build/linux && printf "y" | ./build.sh release
cd ../..
mv out/filament-release-linux.tgz out/filament-${TAG}-arm-linux.tgz
- uses: actions/github-script@v6
env:
TAG: ${{ steps.git_ref.outputs.tag }}
with:
script: |
const upload = require('./build/common/upload-release-assets');
const { TAG } = process.env;
const globber = await glob.create('out/*.tgz');
await upload({ github, context }, await globber.glob(), TAG);
build-mac:
name: build-mac
runs-on: macos-14-xlarge
@@ -99,7 +132,7 @@ jobs:
build-web:
name: build-web
runs-on: 'ubuntu-24.04-16core'
runs-on: 'arm-ubuntu-24.04-16core'
if: github.event_name == 'release' || github.event.inputs.platform == 'web'
steps:
@@ -188,6 +221,54 @@ jobs:
const globber = await glob.create(['out/*.aar', 'out/*.apk', 'out/*.tgz'].join('\n'));
await upload({ github, context }, await globber.glob(), TAG);
sonatype-publish:
name: sonatype-publish
runs-on: 'ubuntu-24.04-16core'
# Depends on the the Android build for the Android binaries.
# Depends on the Mac, Linux, and Windows builds for host tools.
needs: [build-mac, build-linux, build-windows, build-android]
if: github.event_name == 'release' || github.event.inputs.platform == 'android'
steps:
- name: Decide Git ref
id: git_ref
run: |
REF=${RELEASE_TAG:-${GITHUB_REF}}
TAG=${REF##*/}
echo "ref=${REF}" >> $GITHUB_OUTPUT
echo "tag=${TAG}" >> $GITHUB_OUTPUT
- uses: actions/checkout@v4.1.6
with:
ref: ${{ steps.git_ref.outputs.ref }}
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
- uses: ./.github/actions/linux-prereq
- name: Download Android Release
run: |
gh release download ${TAG} \
--repo ${{ github.repository }} \
--pattern 'filament-*-android-native.tgz'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.git_ref.outputs.tag }}
- name: Unzip Android Release
run: |
mkdir -p out/android-release
tar -xzvf filament-${TAG}-android-native.tgz -C out/android-release/
env:
TAG: ${{ steps.git_ref.outputs.tag }}
- name: Publish To Sonatype
run: |
cd android
./gradlew publishToSonatype closeSonatypeStagingRepository
env:
ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.SONATYPE_USERNAME }}
ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.SONATYPE_PASSWORD }}
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.MAVEN_SIGNING_KEY }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.MAVEN_SIGNING_PASSWORD }}
build-ios:
name: build-ios
runs-on: macos-14-xlarge
+4 -7
View File
@@ -11,14 +11,11 @@ jobs:
name: Verify Release Notes
runs-on: ubuntu-latest
steps:
# We *only* need to check out the .github/ directory.
# The verify RELEASE_NOTES script uses the GitHub API to verify that RELEASE_NOTES was
# modified.
- name: Check out action
uses: Bhacaz/checkout-files@73e17cfbe8d7e0c6b2672b20cb05a718e20d18d4
- uses: actions/checkout@v6
with:
files: .github
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
sparse-checkout: |
.github
- name: Verify release notes
uses: ./.github/actions/verify-release-notes
with:
+10 -3
View File
@@ -326,11 +326,13 @@ endif()
if (FILAMENT_ENABLE_LTO)
include(CheckIPOSupported)
check_ipo_supported(RESULT IPO_SUPPORT)
check_ipo_supported(RESULT IPO_SUPPORT OUTPUT IPO_ERROR)
if (IPO_SUPPORT)
message(STATUS "LTO support is enabled")
message(STATUS "IPO / LTO is enabled")
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
else()
message(WARNING "IPO / LTO is not supported by this architecture: ${IPO_ERROR}")
endif()
endif()
@@ -867,6 +869,12 @@ endfunction()
# Sub-projects
# ==================================================================================================
include(CheckSymbolExists)
check_symbol_exists(getopt_long "getopt.h" HAS_SYSTEM_GETOPT)
if (NOT HAS_SYSTEM_GETOPT)
add_subdirectory(${EXTERNAL}/getopt)
endif()
# Common to all platforms
add_subdirectory(${EXTERNAL}/libgtest/tnt)
add_subdirectory(${LIBRARIES}/camutils)
@@ -902,7 +910,6 @@ add_subdirectory(${EXTERNAL}/cgltf/tnt)
add_subdirectory(${EXTERNAL}/draco/tnt)
add_subdirectory(${EXTERNAL}/jsmn/tnt)
add_subdirectory(${EXTERNAL}/stb/tnt)
add_subdirectory(${EXTERNAL}/getopt)
add_subdirectory(${EXTERNAL}/perfetto/tnt)
add_subdirectory(${EXTERNAL}/basisu/tnt)
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.69.5'
implementation 'com.google.android.filament:filament-android:1.70.0'
}
```
@@ -50,7 +50,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```shell
pod 'Filament', '~> 1.69.5'
pod 'Filament', '~> 1.70.0'
```
## Documentation
+3
View File
@@ -7,6 +7,9 @@ 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.1
## v1.70.0
- engine: fix crash when using variance shadow maps
+8 -2
View File
@@ -40,15 +40,21 @@
// - Build and upload artifacts with ./gradlew publish
// - Close and release staging repo on Nexus with ./gradlew closeAndReleaseStagingRepository
//
// The following is needed in ~/gradle/gradle.properties:
// The following properties need to be set (either in ~/gradle/gradle.properties, on the command
// line, or as environment variables, e.g.: ORG_GRADLE_PROJECT_property=value):
//
// sonatypeUsername=nexus_user
// sonatypePassword=nexus_password
//
// To sign with a key ring file:
// signing.keyId=pgp_key_id
// signing.password=pgp_key_password
// signing.secretKeyRingFile=/Users/user/.gnupg/maven_signing.key
//
// To sign with in-memory keys (useful for CI):,
// signingKey=ASCII armored key (begins with -----BEGIN PGP PRIVATE KEY BLOCK-----)
// signingPassword=key password
//
buildscript {
def path = providers
@@ -194,7 +200,7 @@ subprojects {
google()
}
if (!name.startsWith("sample") && name != "filament-tools") {
if (!name.startsWith("sample") && name != "filament-tools" && name != "gradle-plugin") {
apply plugin: 'com.android.library'
android {
-21
View File
@@ -1,21 +0,0 @@
plugins {
id 'groovy-gradle-plugin'
}
gradlePlugin {
plugins {
create("filament-plugin") {
id = "filament-plugin"
implementationClass = "com.google.android.filament.gradle.FilamentPlugin"
}
}
}
repositories {
mavenCentral()
gradlePluginPortal()
}
dependencies {
implementation "com.google.gradle:osdetector-gradle-plugin:1.7.3"
}
@@ -20,8 +20,6 @@
#include <imagediff/ImageDiff.h>
#include <utils/Log.h>
#include <vector>
using namespace imagediff;
using namespace utils;
@@ -102,30 +100,48 @@ jobject createResult(JNIEnv* env, ImageDiffResult const& result, bool generateDi
if (generateDiff && result.diffImage.getWidth() > 0) {
jclass bitmapClass = env->FindClass("android/graphics/Bitmap");
jmethodID createBitmap = env->GetStaticMethodID(bitmapClass, "createBitmap",
jmethodID createBitmap = env->GetStaticMethodID(bitmapClass, "createBitmap",
"(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
jclass configClass = env->FindClass("android/graphics/Bitmap$Config");
jfieldID argb8888 = env->GetStaticFieldID(configClass, "ARGB_8888", "Landroid/graphics/Bitmap$Config;");
jobject configObj = env->GetStaticObjectField(configClass, argb8888);
uint32_t width = result.diffImage.getWidth();
uint32_t height = result.diffImage.getHeight();
jobject diffBitmap = env->CallStaticObjectMethod(bitmapClass, createBitmap, (jint)width, (jint)height, configObj);
jobject diffBitmap = env->CallStaticObjectMethod(bitmapClass, createBitmap, (jint) width,
(jint) height, configObj);
if (diffBitmap) {
// We need to transport the bit differences accurately to the java side, so set
// premultiplied to false. From the java-side, if the bitmap is used to draw to a
// canvas, then client needs to set premultiplied to true again.
jmethodID setPremultiplied = env->GetMethodID(bitmapClass, "setPremultiplied", "(Z)V");
if (setPremultiplied) {
env->CallVoidMethod(diffBitmap, setPremultiplied, JNI_FALSE);
}
void* diffPixels;
if (AndroidBitmap_lockPixels(env, diffBitmap, &diffPixels) == 0) {
AndroidBitmapInfo info;
AndroidBitmap_getInfo(env, diffBitmap, &info);
float const* src = result.diffImage.getPixelRef();
uint8_t* dst = (uint8_t*) diffPixels;
uint32_t channels = result.diffImage.getChannels(); // usually 4
for (size_t i = 0; i < width * height; ++i) {
for (int c = 0; c < 4; ++c) {
float v = 0.0f;
if (c < channels) v = src[i * channels + c];
if (c == 3 && channels < 4) v = 1.0f; // Alpha 1.0 if missing
dst[i * 4 + c] = (uint8_t) std::min(255.0f, std::max(0.0f, v * 255.0f));
uint32_t const channels = result.diffImage.getChannels(); // usually 4
for (size_t y = 0; y < height; ++y) {
uint8_t* row = dst + y * info.stride;
for (size_t x = 0; x < width; ++x) {
size_t srcIdx = (y * width + x) * channels;
for (int c = 0; c < 4; ++c) {
float v = 0.0f;
if (c < channels) v = src[srcIdx + c];
if (c == 3 && channels < 4) v = 1.0f; // Alpha 1.0 if missing
row[x * 4 + c] = uint8_t(
std::min(255.0f, std::max(0.0f, std::round(v * 255.0f))));
}
}
}
AndroidBitmap_unlockPixels(env, diffBitmap);
@@ -133,7 +149,7 @@ jobject createResult(JNIEnv* env, ImageDiffResult const& result, bool generateDi
}
}
}
return resultObj;
}
@@ -147,7 +163,7 @@ Java_com_google_android_filament_utils_ImageDiff_nCompareBasic(JNIEnv* env, jcla
BitmapLock maskArg(env, maskBitmap);
if (!refArg.isValid() || !candArg.isValid()) {
ImageDiffResult emptyResult;
ImageDiffResult emptyResult;
emptyResult.status = ImageDiffResult::Status::SIZE_MISMATCH; // or ERROR
return createResult(env, emptyResult, false);
}
@@ -175,13 +191,13 @@ Java_com_google_android_filament_utils_ImageDiff_nCompareBasic(JNIEnv* env, jcla
extern "C" JNIEXPORT jobject JNICALL
Java_com_google_android_filament_utils_ImageDiff_nCompareJson(JNIEnv* env, jclass,
jobject refBitmap, jobject candBitmap, jstring jsonConfig, jobject maskBitmap) {
BitmapLock refArg(env, refBitmap);
BitmapLock candArg(env, candBitmap);
BitmapLock maskArg(env, maskBitmap);
if (!refArg.isValid() || !candArg.isValid()) {
ImageDiffResult emptyResult;
ImageDiffResult emptyResult;
emptyResult.status = ImageDiffResult::Status::SIZE_MISMATCH; // or ERROR
return createResult(env, emptyResult, false);
}
@@ -189,7 +205,7 @@ Java_com_google_android_filament_utils_ImageDiff_nCompareJson(JNIEnv* env, jclas
ImageDiffConfig config;
const char* nativeJson = env->GetStringUTFChars(jsonConfig, 0);
size_t length = env->GetStringUTFLength(jsonConfig);
bool parsed = parseConfig(nativeJson, length, &config);
env->ReleaseStringUTFChars(jsonConfig, nativeJson);
@@ -214,4 +230,3 @@ Java_com_google_android_filament_utils_ImageDiff_nCompareJson(JNIEnv* env, jclas
return createResult(env, result, generateDiff);
}
@@ -106,8 +106,8 @@ class ModelViewer(
var skyboxCubemap: Texture? = null
private lateinit var displayHelper: DisplayHelper
private lateinit var cameraManipulator: Manipulator
private lateinit var gestureDetector: GestureDetector
private var cameraManipulator: Manipulator? = null
private var gestureDetector: GestureDetector? = null
private var surfaceView: SurfaceView? = null
private var textureView: TextureView? = null
@@ -157,15 +157,13 @@ class ModelViewer(
surfaceView: SurfaceView,
engine: Engine = Engine.create(),
uiHelper: UiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK),
manipulator: Manipulator? = null
manipulator: Manipulator? = defaultCameraManipulator(surfaceView.width, surfaceView.height)
) : this(engine, uiHelper) {
cameraManipulator = manipulator ?: Manipulator.Builder()
.targetPosition(kDefaultObjectPosition.x, kDefaultObjectPosition.y, kDefaultObjectPosition.z)
.viewport(surfaceView.width, surfaceView.height)
.build(Manipulator.Mode.ORBIT)
this.surfaceView = surfaceView
gestureDetector = GestureDetector(surfaceView, cameraManipulator)
cameraManipulator = manipulator
cameraManipulator?.let { c ->
gestureDetector = GestureDetector(surfaceView, c)
}
displayHelper = DisplayHelper(surfaceView.context)
uiHelper.renderCallback = SurfaceCallback()
uiHelper.attachTo(surfaceView)
@@ -177,15 +175,14 @@ class ModelViewer(
textureView: TextureView,
engine: Engine = Engine.create(),
uiHelper: UiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK),
manipulator: Manipulator? = null
manipulator: Manipulator? = defaultCameraManipulator(textureView.width, textureView.height)
) : this(engine, uiHelper) {
cameraManipulator = manipulator ?: Manipulator.Builder()
.targetPosition(kDefaultObjectPosition.x, kDefaultObjectPosition.y, kDefaultObjectPosition.z)
.viewport(textureView.width, textureView.height)
.build(Manipulator.Mode.ORBIT)
cameraManipulator = manipulator
this.textureView = textureView
gestureDetector = GestureDetector(textureView, cameraManipulator)
cameraManipulator = manipulator
cameraManipulator?.let { c ->
gestureDetector = GestureDetector(textureView, c)
}
displayHelper = DisplayHelper(textureView.context)
uiHelper.renderCallback = SurfaceCallback()
uiHelper.attachTo(textureView)
@@ -302,11 +299,13 @@ class ModelViewer(
asset?.let { populateScene(it) }
// Extract the camera basis from the helper and push it to the Filament camera.
cameraManipulator.getLookAt(eyePos, target, upward)
camera.lookAt(
cameraManipulator?.let { cm ->
cm.getLookAt(eyePos, target, upward)
camera.lookAt(
eyePos[0], eyePos[1], eyePos[2],
target[0], target[1], target[2],
upward[0], upward[1], upward[2])
}
// Render the scene, unless the renderer wants to skip the frame.
if (renderer.beginFrame(swapChain!!, frameTimeNanos)) {
@@ -398,7 +397,7 @@ class ModelViewer(
* Handles a [MotionEvent] to enable one-finger orbit, two-finger pan, and pinch-to-zoom.
*/
fun onTouchEvent(event: MotionEvent) {
gestureDetector.onTouchEvent(event)
gestureDetector?.onTouchEvent(event)
}
@SuppressWarnings("ClickableViewAccessibility")
@@ -451,7 +450,7 @@ class ModelViewer(
override fun onResized(width: Int, height: Int) {
view.viewport = Viewport(0, 0, width, height)
cameraManipulator.setViewport(width, height)
cameraManipulator?.setViewport(width, height)
updateCameraProjection()
synchronizePendingFrames(engine)
}
@@ -468,5 +467,11 @@ class ModelViewer(
companion object {
private val kDefaultObjectPosition = Float3(0.0f, 0.0f, -4.0f)
private fun defaultCameraManipulator(width: Int, height: Int) : Manipulator {
return Manipulator.Builder()
.targetPosition(kDefaultObjectPosition.x, kDefaultObjectPosition.y, kDefaultObjectPosition.z)
.viewport(width, height)
.build(Manipulator.Mode.ORBIT)
}
}
}
@@ -25,7 +25,7 @@ configured, the corresponding task will be disabled.
```groovy
plugins {
id 'filament-plugin'
id 'com.google.android.filament-tools'
}
filament {
+31
View File
@@ -0,0 +1,31 @@
plugins {
id 'groovy-gradle-plugin'
id 'com.gradle.plugin-publish' version '2.1.0'
}
group = "com.google.android.filament"
version = "0.1.0"
gradlePlugin {
website = "https://github.com/google/filament/tree/main/android/gradle-plugin"
vcsUrl = "https://github.com/google/filament/tree/main/android/gradle-plugin"
plugins {
create("filament-tools") {
id = "com.google.android.filament-tools"
displayName = "Filament Tools Gradle Plugin"
description = "A plugin that helps integrate Filament into Android projects"
tags.addAll('android', 'graphics', 'rendering', 'filament', '3d', 'gltf', 'native')
implementationClass = "com.google.android.filament.gradle.FilamentPlugin"
}
}
}
repositories {
mavenCentral()
gradlePluginPortal()
}
dependencies {
implementation "com.google.gradle:osdetector-gradle-plugin:1.7.3"
}
@@ -60,9 +60,10 @@ class ToolsLocator {
def classifier =
project.extensions.getByType(com.google.gradle.osdetector.OsDetector).classifier
// If com.google.android.filament.tools-dir is set, we'll use it as the tool's base path.
// If com.google.android.filament.tools-dir is set to a non-empty string, we'll use it as
// the tool's base path.
def toolsDirProp = project.providers.gradleProperty("com.google.android.filament.tools-dir")
if (toolsDirProp.isPresent()) {
if (toolsDirProp.isPresent() && !toolsDirProp.get().trim().isEmpty()) {
def toolsDir = toolsDirProp.get()
def path = OperatingSystem.current().isWindows() ?
"${toolsDir}/bin/${name}.exe" :
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.69.5
VERSION_NAME=1.70.0
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
+7
View File
@@ -94,6 +94,13 @@ afterEvaluate { project ->
}
signing {
def signingKey = findProperty("signingKey")
def signingPassword = findProperty("signingPassword")
if (signingKey && signingPassword) {
println("Signing with in-memory keys")
useInMemoryPgpKeys(signingKey, signingPassword)
}
publishing.publications.all { publication ->
sign publication
}
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'filament-plugin'
id 'com.google.android.filament-tools'
}
project.ext.isSample = true
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'filament-plugin'
id 'com.google.android.filament-tools'
}
project.ext.isSample = true
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'filament-plugin'
id 'com.google.android.filament-tools'
}
project.ext.isSample = true
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'filament-plugin'
id 'com.google.android.filament-tools'
}
project.ext.isSample = true
+1 -1
View File
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'filament-plugin'
id 'com.google.android.filament-tools'
}
project.ext.isSample = true
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'filament-plugin'
id 'com.google.android.filament-tools'
}
project.ext.isSample = true
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'filament-plugin'
id 'com.google.android.filament-tools'
}
project.ext.isSample = true
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'filament-plugin'
id 'com.google.android.filament-tools'
}
project.ext.isSample = true
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'filament-plugin'
id 'com.google.android.filament-tools'
}
project.ext.isSample = true
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'filament-plugin'
id 'com.google.android.filament-tools'
}
project.ext.isSample = true
@@ -66,10 +66,26 @@ class MainActivity : Activity(), ValidationRunner.Callback {
private lateinit var inputManager: ValidationInputManager
private var currentInput: ValidationInputManager.ValidationInput? = null
private var currentAlphaDiffBitmap: Bitmap? = null
private var globalEnhancementFactor: Float = 1.0f
private data class TestImages(
val testName: String,
val golden: Bitmap?,
val rendered: Bitmap?,
val diff: Bitmap?,
val alphaDiff: Bitmap?
)
private val diffImageViews = mutableListOf<ImageView>()
// UI Elements
private lateinit var runButton: Button
private lateinit var loadButton: Button
private lateinit var optionsButton: Button
private lateinit var enhancementContainer: LinearLayout
private lateinit var enhancementLabel: TextView
private lateinit var enhancementSlider: android.widget.SeekBar
private var resultManager: ValidationResultManager? = null
private var validationRunner: ValidationRunner? = null
@@ -98,6 +114,19 @@ class MainActivity : Activity(), ValidationRunner.Callback {
runButton = findViewById(R.id.run_button)
loadButton = findViewById(R.id.load_button)
optionsButton = findViewById(R.id.options_button)
enhancementContainer = findViewById(R.id.enhancement_container)
enhancementLabel = findViewById(R.id.enhancement_label)
enhancementSlider = findViewById(R.id.enhancement_slider)
enhancementSlider.setOnSeekBarChangeListener(object : android.widget.SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: android.widget.SeekBar?, progress: Int, fromUser: Boolean) {
globalEnhancementFactor = 1.0f + (progress / 100f) * 49.0f
enhancementLabel.text = String.format(Locale.US, "Enhancement: %.1fx", globalEnhancementFactor)
applyGlobalEnhancement()
}
override fun onStartTrackingTouch(seekBar: android.widget.SeekBar?) {}
override fun onStopTrackingTouch(seekBar: android.widget.SeekBar?) {}
})
// Setup Run Button
runButton.setOnClickListener {
@@ -120,6 +149,7 @@ class MainActivity : Activity(), ValidationRunner.Callback {
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) {
@@ -133,6 +163,9 @@ class MainActivity : Activity(), ValidationRunner.Callback {
3 -> exportTestResultsAction()
4 -> showTestAdbInfo()
5 -> showResultAdbInfo()
6 -> {
enhancementContainer.visibility = if (enhancementContainer.visibility == View.VISIBLE) View.GONE else View.VISIBLE
}
}
true
}
@@ -256,6 +289,7 @@ class MainActivity : Activity(), ValidationRunner.Callback {
// Clear existing results UI and state
resultsContainer.removeAllViews()
diffImageViews.clear()
resultManager = null
val newInput = ValidationInputManager.ValidationInput(
@@ -337,6 +371,8 @@ class MainActivity : Activity(), ValidationRunner.Callback {
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}")
@@ -420,7 +456,15 @@ class MainActivity : Activity(), ValidationRunner.Callback {
val imagesRow = LinearLayout(this)
imagesRow.orientation = LinearLayout.HORIZONTAL
fun addImage(label: String, bitmap: Bitmap?) {
val testImages = TestImages(
testName = result.testName,
golden = currentGoldenBitmap,
rendered = currentRenderedBitmap,
diff = currentDiffBitmap,
alphaDiff = currentAlphaDiffBitmap
)
fun addImage(label: String, bitmap: Bitmap?, isDiff: Boolean) {
if (bitmap != null) {
val container = LinearLayout(this)
container.orientation = LinearLayout.VERTICAL
@@ -436,16 +480,29 @@ 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)
addImage("Golden", currentGoldenBitmap)
addImage("Rendered", currentRenderedBitmap, false)
addImage("Golden", currentGoldenBitmap, false)
if (!result.passed) {
addImage("Diff", currentDiffBitmap)
addImage("Diff", currentDiffBitmap, true)
}
if (currentAlphaDiffBitmap != null) {
addImage("Alpha Diff", currentAlphaDiffBitmap, true)
}
resultContainer.addView(imagesRow)
@@ -455,12 +512,14 @@ 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) {
@@ -523,7 +582,165 @@ 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()
}
}
@@ -115,10 +115,6 @@ class ValidationRunner(
}
fun onFrame(frameTimeNanos: Long) {
if (frameCounter % 60 == 0) {
Log.i("ValidationRunner", "onFrame: $currentState (frame: $frameCounter)")
}
when (currentState) {
State.IDLE -> {}
State.WAITING_FOR_RESOURCES -> {
@@ -136,7 +132,6 @@ class ValidationRunner(
}
}
State.RUNNING_TEST -> {
// Log.i("ValidationRunner", "Running test...")
currentEngine?.let { engine ->
val content = AutomationEngine.ViewerContent()
content.view = modelViewer.view
@@ -257,10 +252,54 @@ class ValidationRunner(
passed = (result.status == ImageDiff.Result.Status.PASSED)
diffMetric = result.failingPixelCount.toFloat()
if (!passed) {
if (!passed) {
if (result.diffImage != null) {
callback?.onImageResult("Diff", result.diffImage!!)
resultManager.saveImage("${testFullName}_diff", result.diffImage!!)
val diffImg = result.diffImage!!
val width = diffImg.width
val height = diffImg.height
val pixels = IntArray(width * height)
diffImg.getPixels(pixels, 0, width, 0, 0, width, height)
var hasAlphaDiff = false
val alphaPixels = IntArray(width * height)
for (i in pixels.indices) {
val color = pixels[i]
val a = android.graphics.Color.alpha(color)
val r = android.graphics.Color.red(color)
val g = android.graphics.Color.green(color)
val b = android.graphics.Color.blue(color)
if (a > 0) {
hasAlphaDiff = true
}
// Map alpha diff to grayscale RGB
alphaPixels[i] = android.graphics.Color.argb(255, a, a, a)
// Force main diff image alpha to 255
pixels[i] = android.graphics.Color.argb(255, r, g, b)
}
// Apply updated pixels to diff image
diffImg.setPixels(pixels, 0, width, 0, 0, width, height)
// The C++ ImageDiff code sets isPremultiplied to false so Android
// doesn't erase RGB diff values when Alpha diff is 0. However, Android's
// Canvas will crash if we try to draw a non-premultiplied bitmap.
// Since we just forced all alpha values to 255 (fully opaque) in the
// loop above, we can safely mark it as premultiplied again here.
diffImg.isPremultiplied = true
callback?.onImageResult("Diff", diffImg)
resultManager.saveImage("${testFullName}_diff", diffImg)
if (hasAlphaDiff) {
val alphaDiffImg = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
alphaDiffImg.setPixels(alphaPixels, 0, width, 0, 0, width, height)
callback?.onImageResult("Alpha Diff", alphaDiffImg)
resultManager.saveImage("${testFullName}_alpha_diff", alphaDiffImg)
}
}
}
} else {
@@ -81,6 +81,33 @@
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
@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#FF202020"
android:padding="8dp">
<!-- Header with title and close button -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:id="@+id/dialog_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Test Result"
android:textColor="#FFFFFF"
android:textSize="16sp"
android:textStyle="bold" />
<ImageButton
android:id="@+id/btn_close"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@android:drawable/ic_menu_close_clear_cancel" />
</LinearLayout>
<!-- Subtitle for Image Type -->
<TextView
android:id="@+id/dialog_image_type"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:text="Rendered"
android:textColor="#BBBBBB"
android:textSize="14sp"
android:paddingBottom="8dp" />
<!-- Image Area with Arrows -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintDimensionRatio="1:1">
<ImageView
android:id="@+id/dialog_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="matrix" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- Navigation Arrows -->
<LinearLayout
android:id="@+id/dialog_arrow_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:paddingTop="8dp"
android:paddingBottom="8dp">
<ImageButton
android:id="@+id/btn_prev"
android:layout_width="64dp"
android:layout_height="64dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@android:drawable/ic_media_previous"
app:tint="#FFFFFF" />
<ImageButton
android:id="@+id/btn_reset"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginHorizontal="16dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@android:drawable/ic_menu_revert"
app:tint="#FFFFFF" />
<ImageButton
android:id="@+id/btn_next"
android:layout_width="64dp"
android:layout_height="64dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@android:drawable/ic_media_next"
app:tint="#FFFFFF" />
</LinearLayout>
<!-- Enhancement Controls (only for diff images) -->
<LinearLayout
android:id="@+id/dialog_enhancement_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingTop="12dp"
android:paddingBottom="8dp"
android:visibility="gone">
<TextView
android:id="@+id/dialog_enhancement_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Enhance: 1.0x"
android:textColor="#FFFFFF"
android:textSize="12sp"
android:minWidth="100dp" />
<SeekBar
android:id="@+id/dialog_enhancement_slider"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:max="100"
android:progress="0" />
</LinearLayout>
</LinearLayout>
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'filament-plugin'
id 'com.google.android.filament-tools'
}
project.ext.isSample = true
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'filament-plugin'
id 'com.google.android.filament-tools'
}
project.ext.isSample = true
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'filament-plugin'
id 'com.google.android.filament-tools'
}
project.ext.isSample = true
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'filament-plugin'
id 'com.google.android.filament-tools'
}
project.ext.isSample = true
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'filament-plugin'
id 'com.google.android.filament-tools'
}
project.ext.isSample = true
+5
View File
@@ -1,3 +1,8 @@
// Filament tools plugin
pluginManagement {
includeBuild 'gradle-plugin'
}
// Libraries
include ':filament-android'
include ':filamat-android'
@@ -46,6 +46,7 @@ list(SORT NDK_VERSIONS)
list(GET NDK_VERSIONS -1 NDK_VERSION)
get_filename_component(NDK_VERSION ${NDK_VERSION} NAME)
set(TOOLCHAIN ${ANDROID_HOME_UNIX}/ndk/${NDK_VERSION}/toolchains/llvm/prebuilt/${HOST_NAME_L}-x86_64)
set(CMAKE_ANDROID_NDK_VERSION ${NDK_VERSION})
# specify the cross compiler
set(COMPILER_SUFFIX)
+1
View File
@@ -47,6 +47,7 @@ list(SORT NDK_VERSIONS)
list(GET NDK_VERSIONS -1 NDK_VERSION)
get_filename_component(NDK_VERSION ${NDK_VERSION} NAME)
set(TOOLCHAIN ${ANDROID_HOME_UNIX}/ndk/${NDK_VERSION}/toolchains/llvm/prebuilt/${HOST_NAME_L}-x86_64)
set(CMAKE_ANDROID_NDK_VERSION ${NDK_VERSION})
# specify the cross compiler
set(COMPILER_SUFFIX)
+1
View File
@@ -46,6 +46,7 @@ list(SORT NDK_VERSIONS)
list(GET NDK_VERSIONS -1 NDK_VERSION)
get_filename_component(NDK_VERSION ${NDK_VERSION} NAME)
set(TOOLCHAIN ${ANDROID_HOME_UNIX}/ndk/${NDK_VERSION}/toolchains/llvm/prebuilt/${HOST_NAME_L}-x86_64)
set(CMAKE_ANDROID_NDK_VERSION ${NDK_VERSION})
# specify the cross compiler
set(COMPILER_SUFFIX)
@@ -46,6 +46,7 @@ list(SORT NDK_VERSIONS)
list(GET NDK_VERSIONS -1 NDK_VERSION)
get_filename_component(NDK_VERSION ${NDK_VERSION} NAME)
set(TOOLCHAIN ${ANDROID_HOME_UNIX}/ndk/${NDK_VERSION}/toolchains/llvm/prebuilt/${HOST_NAME_L}-x86_64)
set(CMAKE_ANDROID_NDK_VERSION ${NDK_VERSION})
# specify the cross compiler
set(COMPILER_SUFFIX)
+2 -2
View File
@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.69.4'
implementation 'com.google.android.filament:filament-android:1.70.0'
}
</code></pre>
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
@@ -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', '~&gt; 1.69.4'
<pre><code class="language-shell">pod 'Filament', '~&gt; 1.70.0'
</code></pre>
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
<ul>
+199 -199
View File
@@ -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&nbsp;1</a>, is simple and efficient.
</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>
</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>
<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">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;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&nbsp;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-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>
</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>
<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">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;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&nbsp;4</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">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>
</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>
<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">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;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&nbsp;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 class="hljs-params"></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> {</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-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>
</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>
<span class="line">}</span>
<span class="line"></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-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-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">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;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&nbsp;9</a>.
</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>
</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>
<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"><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">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>
<span class="line"></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-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-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> Fd_Lambert() {</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-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-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 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>
<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 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>
<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> roughness = perceptualRoughness * perceptualRoughness;</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>
<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 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>
<span class="line"> <span class="hljs-comment">// specular BRDF</span></span>
<span class="line"> <span class="hljs-type">vec3</span> Fr = (D * V) * F;</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>
<span class="line"> <span class="hljs-comment">// diffuse BRDF</span></span>
<span class="line"> <span class="hljs-type">vec3</span> Fd = diffuseColor * Fd_Lambert();</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>
<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">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;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&nbsp;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">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">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;12:</b> Computing \(\fNormal\) for dielectric and metallic materials in GLSL</div></center>
</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">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;12:</b> Computing \(\fNormal\) for dielectric and metallic materials in GLSL</div></center>
<a class="target" name="roughnessremappingandclamping">&nbsp;</a><a class="target" name="materialsystem/parameterization/remapping/roughnessremappingandclamping">&nbsp;</a><a class="target" name="toc4.8.3.3">&nbsp;</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&nbsp;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-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>
</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>
<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">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;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">&nbsp;</a><b style="font-style:normal;">Figure&nbsp;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&nbsp;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-keyword">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-type">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 = clamp(clearCoatPerceptualRoughness, <span class="hljs-number">0.089</span>, <span class="hljs-number">1.0</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"> 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-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 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>
<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&nbsp;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">float D_Ashikhmin(float roughness, float NoH) {</span>
</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>
<span class="line"> <span class="hljs-comment">// Ashikhmin 2007, &quot;Distribution-based BRDFs&quot;</span></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 &gt; 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 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 &gt; 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">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_clothbrdf">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;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&nbsp;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-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>
</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>
<span class="line"> <span class="hljs-comment">// Estevez and Kulla 2017, &quot;Production Friendly Microfacet Sheen BRDF&quot;</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 &gt; 0 in fp16</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 &gt; 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">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;18:</b> Implementation of the &ldquo;Charlie&rdquo; NDF in GLSL</div></center>
<a class="target" name="sheencolor">&nbsp;</a><a class="target" name="materialsystem/clothmodel/clothspecularbrdf/sheencolor">&nbsp;</a><a class="target" name="toc4.12.1.1">&nbsp;</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">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;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-keyword">float</span> multiplier;</span>
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">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.isMasked()) {</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-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.getDesiredIntensity() /</span>
<span class="line"> photometricLight.getIntegratedIntensity();</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">} <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.getMultiplier();</span>
<span class="line"> multiplier = photometricLight.<span class="hljs-built_in">getMultiplier</span>();</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-keyword">float</span> lightIntensity = photometricLight.getMaxIntensity() * multiplier;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_photometriclightintensity">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;23:</b> Computing the intensity of a photometric light on the CPU</div></center>
<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">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;23:</b> Computing the intensity of a photometric light on the CPU</div></center>
<p>
<div class="endnote"><a class="target" name="endnote-xarrowintensity">&nbsp;</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">&nbsp;</a><b style="font-style:normal;">Figure&nbsp;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 <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>
</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>
<span class="line">}</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_irradiancesh">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;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-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>
</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>
<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">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;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&#x27;t have its own normal map</span></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-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-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 *= 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">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;31:</b> GLSL implementation of the clear coat specular lobe for image-based lighting</div></center>
<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">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;31:</b> GLSL implementation of the clear coat specular lobe for image-based lighting</div></center>
<a class="target" name="anisotropy">&nbsp;</a><a class="target" name="lighting/imagebasedlights/anisotropy">&nbsp;</a><a class="target" name="toc5.3.6">&nbsp;</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">&nbsp;</a><b style="font-style:normal;">Figure&nbsp;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-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>
</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>
<span class="line"></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) &amp;&amp; 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">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) &amp;&amp; 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"></span>
<span class="line">vec3 ibl = diffuseColor * indirectDiffuse + indirectSpecular * specularColor;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_clothapprox">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;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-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-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-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&#x27;s EV100</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 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>
<span class="line"></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 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>
<span class="line">vec4 color = evaluateLighting();</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">color.rgb *= exposure;</span></code></pre><center><div class="listingcaption tilde"><a class="target" name="listing_fragmentexposure">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;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-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 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>
<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-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 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>
<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>&lt;<span class="hljs-keyword">float</span>&gt; ior; <span class="hljs-comment">// complex IOR, n + ik</span></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&lt;<span class="hljs-type">float</span>&gt; 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-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 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>
<span class="line"> float2 wavelengthBounds = CIE_D65_START + float2{indexBounds} * CIE_D65_INTERVAL;</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 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>
<span class="line"></span>
<span class="line"><span class="hljs-comment">// For std::lower_bound</span></span>
<span class="line"><span class="hljs-keyword">bool</span> <span class="hljs-keyword">operator</span>&lt;(<span class="hljs-keyword">const</span> Sample&amp; lhs, <span class="hljs-keyword">const</span> Sample&amp; rhs) {</span>
<span class="line"><span class="hljs-type">bool</span> <span class="hljs-keyword">operator</span>&lt;(<span class="hljs-type">const</span> Sample&amp; lhs, <span class="hljs-type">const</span> Sample&amp; rhs) {</span>
<span class="line"> <span class="hljs-keyword">return</span> lhs.w &lt; 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-keyword">static</span> <span class="hljs-built_in">std</span>::<span class="hljs-built_in">complex</span>&lt;<span class="hljs-keyword">float</span>&gt; <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>&lt;sample&gt;&amp; 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-function"><span class="hljs-type">static</span> std::complex&lt;<span class="hljs-type">float</span>&gt; <span class="hljs-title">findSample</span><span class="hljs-params">(<span class="hljs-type">const</span> std::vector&lt;sample&gt;&amp; 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-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-keyword">float</span> t = (w - i0-&gt;w) / (i1-&gt;w - i0-&gt;w);</span>
<span class="line"> <span class="hljs-keyword">float</span> n = lerp(i0-&gt;ior.real(), i1-&gt;ior.real(), t);</span>
<span class="line"> <span class="hljs-keyword">float</span> k = lerp(i0-&gt;ior.imag(), i1-&gt;ior.imag(), t);</span>
<span class="line"> <span class="hljs-type">float</span> t = (w - i0-&gt;w) / (i1-&gt;w - i0-&gt;w);</span>
<span class="line"> <span class="hljs-type">float</span> n = <span class="hljs-built_in">lerp</span>(i0-&gt;ior.<span class="hljs-built_in">real</span>(), i1-&gt;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-&gt;ior.<span class="hljs-built_in">imag</span>(), i1-&gt;ior.<span class="hljs-built_in">imag</span>(), 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-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>&lt;<span class="hljs-keyword">float</span>&gt;&amp; 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 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&lt;<span class="hljs-type">float</span>&gt;&amp; 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>
<span class="line"></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&amp; v)</span> </span>{</span>
<span class="line"> <span class="hljs-keyword">const</span> mat3f XYZ_sRGB{</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&amp; v)</span> </span>{</span>
<span class="line"> <span class="hljs-type">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-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>&lt;sample&gt;&amp; samples)</span> </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&lt;sample&gt;&amp; samples)</span> </span>{</span>
<span class="line"> float3 xyz{<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 class="hljs-type">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-keyword">size_t</span> i = <span class="hljs-number">0</span>; i &lt; CIE_XYZ_COUNT; i++) {</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 &lt; CIE_XYZ_COUNT; i++) {</span>
<span class="line"> <span class="hljs-comment">// Current wavelength</span></span>
<span class="line"> <span class="hljs-keyword">float</span> w = CIE_XYZ_START + i;</span>
<span class="line"> <span class="hljs-type">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 = findSample(samples, w);</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-comment">// Compute Fresnel reflectance at normal incidence</span></span>
<span class="line"> <span class="hljs-keyword">float</span> f0 = fresnel(sample);</span>
<span class="line"> <span class="hljs-type">float</span> f0 = <span class="hljs-built_in">fresnel</span>(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-keyword">float</span> d65 = illuminantD65(w);</span>
<span class="line"> <span class="hljs-type">float</span> d65 = <span class="hljs-built_in">illuminantD65</span>(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 = XYZ_to_sRGB(xyz);</span>
<span class="line"> float3 linear = <span class="hljs-built_in">XYZ_to_sRGB</span>(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> (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 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>
<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">&nbsp;</a><b style="font-style:normal;">Listing&nbsp;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 &lt;&lt; <span class="hljs-string">16) | (bits &gt;&gt; 16</span>);</span>
<span class="line"> bits = ((bits &amp; 0x55555555) &lt;&lt; <span class="hljs-string">1) | ((bits &amp; 0xAAAAAAAA) &gt;&gt; 1</span>);</span>
<span class="line"> bits = ((bits &amp; 0x33333333) &lt;&lt; <span class="hljs-string">2) | ((bits &amp; 0xCCCCCCCC) &gt;&gt; 2</span>);</span>
<span class="line"> bits = ((bits &amp; 0x0F0F0F0F) &lt;&lt; <span class="hljs-string">4) | ((bits &amp; 0xF0F0F0F0) &gt;&gt; 4</span>);</span>
<span class="line"> bits = ((bits &amp; 0x00FF00FF) &lt;&lt; <span class="hljs-string">8) | ((bits &amp; 0xFF00FF00) &gt;&gt; 8</span>);</span>
<span class="line"> <span class="hljs-built_in">return</span> vec2f(i / numSamples, bits / exp2(32));</span>
<span class="line"> bits = ((bits &amp; <span class="hljs-number">0</span>x55555555) &lt;&lt; <span class="hljs-number">1</span>) | ((bits &amp; <span class="hljs-number">0</span>xAAAAAAAA) &gt;&gt; <span class="hljs-number">1</span>);</span>
<span class="line"> bits = ((bits &amp; <span class="hljs-number">0</span>x33333333) &lt;&lt; <span class="hljs-number">2</span>) | ((bits &amp; <span class="hljs-number">0</span>xCCCCCCCC) &gt;&gt; <span class="hljs-number">2</span>);</span>
<span class="line"> bits = ((bits &amp; <span class="hljs-number">0</span>x0F0F0F0F) &lt;&lt; <span class="hljs-number">4</span>) | ((bits &amp; <span class="hljs-number">0</span>xF0F0F0F0) &gt;&gt; <span class="hljs-number">4</span>);</span>
<span class="line"> bits = ((bits &amp; <span class="hljs-number">0</span>x00FF00FF) &lt;&lt; <span class="hljs-number">8</span>) | ((bits &amp; <span class="hljs-number">0</span>xFF00FF00) &gt;&gt; <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">}</span></code></pre><center><div class="listingcaption tilde">C++ implementation of a Hammersley sequence generator</div></center>
<a class="target" name="precomputinglforimage-basedlighting">&nbsp;</a><a class="target" name="annex/precomputinglforimage-basedlighting">&nbsp;</a><a class="target" name="toc9.5">&nbsp;</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> 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>
</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>
<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 DFG(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> a) {</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"> float3 V;</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.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.z = NoV;</span>
<span class="line"></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 &lt; 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 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 &lt; 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"></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 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>
<span class="line"> <span class="hljs-keyword">if</span> (NoL &gt; <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"> <span class="hljs-keyword">if</span> (NoL &gt; <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"> 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.0</span>f / sampleCount);</span>
<span class="line"> <span class="hljs-keyword">return</span> r * (<span class="hljs-number">1.0f</span> / 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">&nbsp;</a><a class="target" name="annex/sphericalharmonics">&nbsp;</a><a class="target" name="toc9.6">&nbsp;</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&nbsp;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-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>
</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>
<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-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&amp; s)</span></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&amp; 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-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"> <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"> SHb[<span class="hljs-number">0</span>] = Pml_1;</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 &lt; 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"> <span class="hljs-keyword">for</span> (<span class="hljs-type">ssize_t</span> l = <span class="hljs-number">1</span>; l &lt; 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"> Pml_2 = Pml_1;</span>
<span class="line"> Pml_1 = Pml;</span>
<span class="line"> SHb[SHindex(<span class="hljs-number">0</span>, l)] = 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"> }</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 &lt; numBands ; m++) {</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 &lt; 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-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-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-comment">// l == m</span></span>
<span class="line"> SHb[SHindex(-m, m)] = Pml_2;</span>
<span class="line"> SHb[SHindex( 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[<span class="hljs-built_in">SHindex</span>( m, m)] = Pml_2;</span>
<span class="line"> <span class="hljs-keyword">if</span> (m + <span class="hljs-number">1</span> &lt; numBands) {</span>
<span class="line"> <span class="hljs-comment">// l == m+1</span></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 &lt; 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"> 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 &lt; 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"> / (l - m);</span>
<span class="line"> Pml_2 = Pml_1;</span>
<span class="line"> Pml_1 = Pml;</span>
<span class="line"> SHb[SHindex(-m, l)] = Pml;</span>
<span class="line"> SHb[SHindex( m, l)] = 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"> }</span>
<span class="line"> }</span>
<span class="line"> }</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 &lt;= numBands ; m++) {</span>
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">ssize_t</span> l = m; l &lt; 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 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 &lt;= numBands ; m++) {</span>
<span class="line"> <span class="hljs-keyword">for</span> (<span class="hljs-type">ssize_t</span> l = m; l &lt; 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>
<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"> <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"> 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-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>
</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>
<span class="line"></span>
<span class="line"><span class="hljs-comment">// &lt; cos(theta) &gt; SH coefficients pre-multiplied by 1 / K(0,l)</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-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-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 &amp; <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-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 &amp; <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> &lt;&lt; l));</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 &amp; <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> &lt;&lt; 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-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-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-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 &gt; d) {</span>
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
View File
@@ -93,6 +93,7 @@ set(SRCS
src/MorphTargetBuffer.cpp
src/PostProcessManager.cpp
src/ProgramSpecialization.cpp
src/LocalProgramCache.cpp
src/RenderPass.cpp
src/RenderPrimitive.cpp
src/RenderTarget.cpp
@@ -186,6 +187,7 @@ set(PRIVATE_HDRS
src/MaterialCache.h
src/MaterialDefinition.h
src/MaterialParser.h
src/LocalProgramCache.h
src/MaterialInstanceManager.h
src/PIDController.h
src/PostProcessManager.h
+2 -3
View File
@@ -600,6 +600,7 @@ if (FILAMENT_BUILD_TESTING)
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
@@ -615,7 +616,6 @@ if (FILAMENT_BUILD_TESTING)
set(BACKEND_TEST_LIBS
absl::str_format
backend
getopt
gtest
imageio
filamat
@@ -724,10 +724,9 @@ if (FILAMENT_BUILD_TESTING)
target_link_libraries(metal_utils_test PRIVATE
backend
getopt
gtest
)
set_target_properties(metal_utils_test PROPERTIES FOLDER Tests)
endif()
endif()
endif()
+23 -1
View File
@@ -216,6 +216,18 @@ public:
MULTIVIEW,
};
/**
* Types of device/driver information that can be queried from the platform.
*/
enum class DeviceInfoType {
OPENGL_RENDERER, //!< glGetString(GL_RENDERER)
OPENGL_VENDOR, //!< glGetString(GL_VENDOR)
OPENGL_VERSION, //!< glGetString(GL_VERSION)
VULKAN_DEVICE_NAME, //!< VkPhysicalDeviceProperties::deviceName
VULKAN_DRIVER_NAME, //!< VkPhysicalDeviceDriverProperties::driverName
VULKAN_DRIVER_INFO, //!< VkPhysicalDeviceDriverProperties::driverInfo
};
/**
* This controls the priority level for GPU work scheduling, which helps prioritize the
* submitted GPU work and enables preemption.
@@ -316,7 +328,7 @@ public:
*/
StereoscopicType stereoscopicType = StereoscopicType::NONE;
/*
/**
* The number of eyes to render when stereoscopic rendering is enabled. Supported values are
* between 1 and Engine::getMaxStereoscopicEyes() (inclusive).
*/
@@ -377,6 +389,16 @@ public:
*/
virtual int getOSVersion() const noexcept = 0;
/**
* Queries device/driver information of the graphics API.
* @param infoType the type of information to query.
* @param driver a pointer to the current driver.
* @return a CString containing the requested information.
*/
virtual utils::CString getDeviceInfo(DeviceInfoType infoType,
Driver* UTILS_NULLABLE driver) const noexcept = 0;
/**
* Creates and initializes the low-level API (e.g. an OpenGL context or Vulkan instance),
* then creates the concrete Driver.
@@ -52,6 +52,9 @@ protected:
~OpenGLPlatform() noexcept override;
utils::CString getDeviceInfo(DeviceInfoType infoType,
Driver* UTILS_NULLABLE driver) const noexcept override;
public:
struct ExternalTexture {
unsigned int target; // GLenum target
@@ -70,6 +73,12 @@ public:
*/
static utils::CString getRendererString(Driver const* UTILS_NONNULL driver);
/**
* Return the OpenGL version string of the specified Driver instance.
* @return The GL_VERSION string
*/
static utils::CString getVersionString(Driver const* UTILS_NONNULL driver);
/**
* Called by the driver to destroy the OpenGL context. This should clean up any windows
* or buffers from initialization. This is for instance where `eglDestroyContext` would be
@@ -159,6 +159,12 @@ protected:
EGLConfig getEglConfig() const noexcept { return mEGLConfig; }
EGLConfig getSuitableConfigForSwapChain(uint64_t flags, bool window, bool pbuffer) const;
// Sets the EGLDisplay to be used by this platform. This should only be called by derived
// classes before invoking createDriver. Calling it after that point will result in
// undefined behaviour. This class will take ownership of the display and call eglTerminate
// on it during shutdown.
void setEglDisplay(EGLDisplay display) noexcept;
// supported extensions detected at runtime
struct {
struct {
@@ -38,6 +38,7 @@ public:
Driver* createDriver(void* sharedContext, const Platform::DriverConfig& driverConfig) override;
int getOSVersion() const noexcept override { return 0; }
utils::CString getDeviceInfo(DeviceInfoType, Driver*) const noexcept override { return {}; }
/**
* Optionally initializes the Metal platform by acquiring resources necessary for rendering.
@@ -31,7 +31,6 @@
#include <cstring>
#include <cstddef>
#include <functional>
#include <string>
#include <tuple>
#include <unordered_set>
@@ -69,7 +68,7 @@ public:
struct ExtensionHashFn {
std::size_t operator()(utils::CString const& s) const noexcept {
return std::hash<std::string>{}(s.data());
return std::hash<utils::CString>{}(s.data());
}
};
// Note: utils::CString::operator== has an edge case that breaks for the extension set.
@@ -143,6 +142,8 @@ public:
return 0;
}
utils::CString getDeviceInfo(DeviceInfoType infoType, Driver* driver) const noexcept override;
// ----------------------------------------------------
// ---------- Platform Customization options ----------
struct Customization {
@@ -46,6 +46,9 @@ public:
~WebGPUPlatform() override = default;
[[nodiscard]] int getOSVersion() const noexcept final { return 0; }
[[nodiscard]] utils::CString getDeviceInfo(DeviceInfoType, Driver*) const noexcept override {
return {};
}
[[nodiscard]] wgpu::Instance& getInstance() noexcept { return mInstance; }
+1 -1
View File
@@ -32,7 +32,7 @@
#include <cstddef>
#include <functional>
#include <string>
#include <string_view>
#include <utility>
#ifdef __ANDROID__
+1
View File
@@ -26,6 +26,7 @@ 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,7 +341,44 @@ 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 const params = arg.params;
SamplerParams params = arg.params;
#if defined(__EMSCRIPTEN__)
// From https://registry.khronos.org/OpenGL-Refpages/es2.0/
// GLES 2.0 will draw mipmapped samplers as black if the following conditions are not met:
//
// "...if the width or height of a texture image are not powers of two and either the
// GL_TEXTURE_MIN_FILTER is set to one of the functions that requires mipmaps or the
// GL_TEXTURE_WRAP_S or GL_TEXTURE_WRAP_T is not set to GL_CLAMP_TO_EDGE, then the
// texture image unit will return (R, G, B, A) = (0, 0, 0, 1)."
//
// "If the texture has dimensions w × h , there are [floor(log2(max(w, h))) + 1] mipmap levels.
// Level 0 is the original texture; level [floor(log2(max(w, h)))] is the final 1 × 1 mipmap."
// "Suppose that a texture is accessed from a fragment shader or vertex shader and has set GL_TEXTURE_MIN_FILTER
// to one of the functions that requires mipmaps. If either the dimensions of the texture images currently
// defined (with previous calls to glTexImage2D, glCompressedTexImage2D, or glCopyTexImage2D) do not follow
// the proper sequence for mipmaps (described above), or there are fewer texture images defined than are needed,
// or the set of texture images were defined with different formats or types, then the texture image unit
// will return (R, G, B, A) = (0, 0, 0, 1)."
//
// So we force the sampler to a valid state in those cases.
auto isPowerOfTwo = [](uint32_t x) -> bool { return (x & (x - 1)) == 0 && x > 0; };
bool textureIsPowerOfTwo = isPowerOfTwo(t->width) && isPowerOfTwo(t->height);
uint32_t requiredMipLevels = (std::floor(std::log2(std::max(t->width, std::max(t->height, t->depth)))) + 1);
bool textureHasRequiredMipLevels = t->levels == requiredMipLevels;
bool samplerCanBeInvalid = params.filterMin > SamplerMinFilter::LINEAR;
if (samplerCanBeInvalid && (!textureIsPowerOfTwo || !textureHasRequiredMipLevels)) {
if (params.filterMin == SamplerMinFilter::NEAREST_MIPMAP_NEAREST
|| params.filterMin == SamplerMinFilter::LINEAR_MIPMAP_NEAREST) {
params.filterMin = SamplerMinFilter::NEAREST;
} else {
params.filterMin = SamplerMinFilter::LINEAR;
}
}
#endif
glTexParameteri(t->gl.target, GL_TEXTURE_MIN_FILTER,
(GLint)GLUtils::getTextureFilter(params.filterMin));
glTexParameteri(t->gl.target, GL_TEXTURE_MAG_FILTER,
+5 -1
View File
@@ -716,7 +716,11 @@ Handle<HwSwapChain> OpenGLDriver::createSwapChainHeadlessS() noexcept {
}
Handle<HwTimerQuery> OpenGLDriver::createTimerQueryS() noexcept {
return initHandle<GLTimerQuery>();
Handle<HwTimerQuery> tqh = initHandle<GLTimerQuery>();
// The state must be constructed here, as a synchronous call to getTimerQueryValue might happen
// before createTimerQueryR is executed on the backend thread.
handle_cast<GLTimerQuery*>(tqh)->state = std::make_shared<GLTimerQuery::State>();
return tqh;
}
Handle<HwDescriptorSetLayout> OpenGLDriver::createDescriptorSetLayoutS() noexcept {
@@ -250,6 +250,10 @@ 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,6 +34,7 @@ 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,6 +45,21 @@ 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
@@ -63,6 +78,15 @@ 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);
@@ -88,15 +88,14 @@ TimerQueryFactoryInterface::~TimerQueryFactoryInterface() = default;
// This is a backend synchronous call
TimerQueryResult TimerQueryFactoryInterface::getTimerQueryValue(
GLTimerQuery* tq, uint64_t* elapsedTime) noexcept {
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);
assert_invariant(tq->state);
int64_t const elapsed = tq->state->elapsed.load(std::memory_order_relaxed);
if (elapsed > 0) {
*elapsedTime = elapsed;
return TimerQueryResult::AVAILABLE;
}
return TimerQueryResult::ERROR;
return TimerQueryResult(elapsed);
}
// ------------------------------------------------------------------------------------------------
@@ -110,9 +109,8 @@ 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()
}
@@ -181,8 +179,7 @@ TimerQueryFenceFactory::~TimerQueryFenceFactory() {
}
void TimerQueryFenceFactory::createTimerQuery(GLTimerQuery* tq) {
assert_invariant(!tq->state);
tq->state = std::make_shared<GLTimerQuery::State>();
assert_invariant(tq->state);
}
void TimerQueryFenceFactory::destroyTimerQuery(GLTimerQuery* tq) {
@@ -238,8 +235,7 @@ TimerQueryFallbackFactory::TimerQueryFallbackFactory() = default;
TimerQueryFallbackFactory::~TimerQueryFallbackFactory() = default;
void TimerQueryFallbackFactory::createTimerQuery(GLTimerQuery* tq) {
assert_invariant(!tq->state);
tq->state = std::make_shared<GLTimerQuery::State>();
assert_invariant(tq->state);
}
void TimerQueryFallbackFactory::destroyTimerQuery(GLTimerQuery* tq) {
@@ -117,6 +117,13 @@ 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.
@@ -524,8 +531,15 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) {
}
void ShaderCompilerService::ensureTokenIsReady(program_token_t const& token) {
if (token->gl.program) {
return;// It's ready.
if (mMode == Mode::THREAD_POOL) {
// Check if `token->gl.program` is populated
if (token->isReady()) {
return;
}
} else {
if (token->gl.program) {
return;
}
}
switch (mMode) {
@@ -935,6 +949,7 @@ 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;
}
@@ -34,6 +34,7 @@
#include <utils/Invocable.h>
#include <utils/Logger.h>
#include <utils/Panic.h>
#include <utils/debug.h>
#include <utils/ostream.h>
@@ -119,31 +120,41 @@ 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) {
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
assert_invariant(mEGLDisplay != EGL_NO_DISPLAY);
if (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)
@@ -706,10 +717,11 @@ 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);
GLUtils::unordered_string_set const glExtensions = GLUtils::split(extensions);
ext.gl.OES_EGL_image_external_essl3 = glExtensions.has("GL_OES_EGL_image_external_essl3");
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");
}
}
EGLContext PlatformEGL::getContextForType(ContextType const type) const noexcept {
@@ -106,6 +106,12 @@ void VulkanBufferCache::gc() noexcept {
}
}
size_t totalBuffers = 0;
for (auto& bufferPool : mGpuBufferPools) {
totalBuffers += bufferPool.size();
}
FVK_LOGD << "VulkanBufferCache sizes: buffers=" << totalBuffers;
FVK_SYSTRACE_END();
}
@@ -157,6 +163,10 @@ VulkanGpuBuffer const* VulkanBufferCache::allocate(VulkanBufferBinding binding,
UTILS_UNUSED_IN_RELEASE VkResult result = vmaCreateBuffer(mAllocator, &bufferInfo, &allocInfo,
&gpuBuffer->vkbuffer, &gpuBuffer->vmaAllocation, &gpuBuffer->allocationInfo);
FVK_LOGD << "VulkanBufferCache - allocated a vkBuffer " << gpuBuffer->vkbuffer
<< " of size " << numBytes << " and binding = " << static_cast<int>(binding)
<< " result=" << (int)result;
#if FVK_ENABLED(FVK_DEBUG_VULKAN_BUFFER_CACHE)
if (result != VK_SUCCESS) {
FVK_LOGE << "VulkanBufferCache - failed to allocate a new vkBuffer of size " << numBytes
@@ -173,6 +183,7 @@ VulkanGpuBuffer const* VulkanBufferCache::allocate(VulkanBufferBinding binding,
}
void VulkanBufferCache::destroy(VulkanGpuBuffer const* gpuBuffer) noexcept {
FVK_LOGD << "VulkanBufferCache - Destroying vkBuffer " << gpuBuffer->vkbuffer;
vmaDestroyBuffer(mAllocator, gpuBuffer->vkbuffer, gpuBuffer->vmaAllocation);
delete gpuBuffer;
gpuBuffer = nullptr;
@@ -194,6 +194,7 @@ void VulkanCommandBuffer::begin() noexcept {
}
fvkmemory::resource_ptr<VulkanSemaphore> VulkanCommandBuffer::submit() {
FVK_LOGD << "Submitting VulkanCommandBuffer: " << mBuffer << " (age=" << age() << ")";
while (mMarkerCount > 0) {
popMarker();
}
@@ -137,6 +137,14 @@ 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;
}
@@ -212,11 +220,16 @@ public:
return mGlobalPrioritySupported;
}
inline bool isDriverPropertiesSupported() const noexcept { return mDriverPropertiesSupported; }
private:
VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
};
VkPhysicalDeviceDriverProperties mDriverProperties = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES,
};
VkPhysicalDeviceVulkan11Features mPhysicalDeviceVk11Features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES,
};
@@ -246,6 +259,7 @@ 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,35 +355,8 @@ 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 {
// 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);
});
}
uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture,
VkSampler sampler, VkDescriptorSetLayout externalSamplerLayout) noexcept {
VkDescriptorSet const vkset = set->getVkSet();
VkImageSubresourceRange range = texture->getPrimaryViewRange();
VkImageViewType const expectedType = texture->getViewType();
@@ -432,6 +405,28 @@ 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);
@@ -76,6 +76,11 @@ public:
fvkmemory::resource_ptr<VulkanDescriptorSet> createSet(Handle<HwDescriptorSet> handle,
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
// Create and set as current a new VkDescriptorSet using the `set` currently bound layout and
// copy all the bindings and ignoring the samplers bindings in the `samplerMask`.
void cloneSet(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
fvkutils::SamplerBitmask samplerMask) noexcept;
// This method is meant to be used with external samplers
VkDescriptorSet getVkSet(DescriptorCount const& count, VkDescriptorSetLayout vklayout);
+1 -1
View File
@@ -1989,7 +1989,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
rt->emitBarriersBeginRenderPass(*commandBuffer);
fvkmemory::resource_ptr<VulkanFramebuffer> vkfb =
mFramebufferCache.getFramebuffer(fbkey, &mResourceManager);
mFramebufferCache.getFramebuffer(fbkey, &mResourceManager, rt);
// Assign a label to the framebuffer for debugging purposes.
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS | FVK_DEBUG_DEBUG_UTILS)
@@ -94,6 +94,9 @@ void VulkanExternalImageManager::updateSetAndLayout(
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> const& layout = set->getLayout();
set->boundLayout = mDescriptorSetLayoutCache->getVkLayout(layout->bitmask,
actualExternalSamplers, outSamplers);
mDescriptorSetCache->cloneSet(set, actualExternalSamplers);
// Update the external samplers in the set
for (auto& [binding, sampler, image]: samplerAndBindings) {
// We cannot call updateSamplerForExternalSamplerSet because some samplers are non NULL
+11 -5
View File
@@ -24,7 +24,7 @@
// If any VkRenderPass or VkFramebuffer is unused for more than TIME_BEFORE_EVICTION frames, it
// is evicted from the cache.
static constexpr uint32_t TIME_BEFORE_EVICTION = FVK_MAX_COMMAND_BUFFERS;
static constexpr uint32_t TIME_BEFORE_EVICTION = 3;
using namespace bluevk;
@@ -70,8 +70,9 @@ VulkanFboCache::~VulkanFboCache() {
<< "Please explicitly call terminate() while the VkDevice is still alive.";
}
fvkmemory::resource_ptr<VulkanFramebuffer> VulkanFboCache::getFramebuffer(
FboKey const& config, fvkmemory::ResourceManager* resManager) noexcept {
fvkmemory::resource_ptr<VulkanFramebuffer> VulkanFboCache::getFramebuffer(FboKey const& config,
fvkmemory::ResourceManager* resManager,
fvkmemory::resource_ptr<VulkanRenderTarget> renderTarget) noexcept {
FboMap::iterator iter = mFramebufferCache.find(config);
if (UTILS_LIKELY(iter != mFramebufferCache.end())) {
iter.value().timestamp = mCurrentTime;
@@ -119,9 +120,12 @@ fvkmemory::resource_ptr<VulkanFramebuffer> VulkanFboCache::getFramebuffer(
VkResult error = vkCreateFramebuffer(mDevice, &info, VKALLOC, &framebuffer);
FILAMENT_CHECK_POSTCONDITION(error == VK_SUCCESS) << "Unable to create framebuffer."
<< " error=" << static_cast<int32_t>(error);
FVK_LOGD << "VulkanFboCache - Created VkFramebuffer " << framebuffer << " for renderTarget " << renderTarget.get()
<< " size=" << config.width << "x" << config.height;
fvkmemory::resource_ptr<VulkanFramebuffer> fbh =
fvkmemory::resource_ptr<VulkanFramebuffer>::construct(resManager, mDevice, framebuffer);
mFramebufferCache[config] = {fbh, mCurrentTime};
fvkmemory::resource_ptr<VulkanFramebuffer>::construct(resManager, mDevice, framebuffer,
renderTarget);
mFramebufferCache[config] = { fbh, mCurrentTime };
return fbh;
}
@@ -402,6 +406,8 @@ void VulkanFboCache::gc() noexcept {
}
}
FVK_LOGD << "VulkanFboCache sizes: framebuffers=" << mFramebufferCache.size() << " renderpasses=" << mRenderPassCache.size();
FVK_SYSTRACE_END();
}
+3 -2
View File
@@ -104,8 +104,9 @@ public:
~VulkanFboCache();
// Retrieves or creates a VkFramebuffer handle.
fvkmemory::resource_ptr<VulkanFramebuffer> getFramebuffer(
FboKey const& config, fvkmemory::ResourceManager* resManager) noexcept;
fvkmemory::resource_ptr<VulkanFramebuffer> getFramebuffer(FboKey const& config,
fvkmemory::ResourceManager* resManager,
fvkmemory::resource_ptr<VulkanRenderTarget> renderTarget) noexcept;
// Retrieves or creates a VkRenderPass handle.
fvkmemory::resource_ptr<VulkanRenderPass> getRenderPass(
+17 -3
View File
@@ -403,10 +403,13 @@ VulkanRenderTarget::VulkanRenderTarget()
mOffscreen(false),
mProtected(false),
mInfo(std::make_unique<Auxiliary>()) {
FVK_LOGD << "Creating default VulkanRenderTarget: " << this;
mInfo->rpkey.samples = mInfo->fbkey.samples = 1;
}
VulkanRenderTarget::~VulkanRenderTarget() = default;
VulkanRenderTarget::~VulkanRenderTarget() {
FVK_LOGD << "Destroying VulkanRenderTarget: " << this << " (offscreen=" << mOffscreen << ")";
}
void VulkanRenderTarget::bindSwapChain(fvkmemory::resource_ptr<VulkanSwapChain> swapchain) {
assert_invariant(!mOffscreen);
@@ -443,9 +446,12 @@ void VulkanRenderTarget::bindSwapChain(fvkmemory::resource_ptr<VulkanSwapChain>
fbkey.depth = VK_NULL_HANDLE;
}
mInfo->colors.set(0);
FVK_LOGD << "bindSwapChain: target=" << this << ", swapchain=" << swapchain.get()
<< ", size=" << width << "x" << height;
}
void VulkanRenderTarget::releaseSwapchain() {
FVK_LOGD << "releaseSwapchain: target=" << this;
mInfo->colors = {};
mInfo->attachments.clear();
}
@@ -459,6 +465,7 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica
mOffscreen(true),
mProtected(false),
mInfo(std::make_unique<Auxiliary>()) {
FVK_LOGD << "Creating offscreen VulkanRenderTarget: " << this << ", size=" << width << "x" << height << ", samples=" << (int)samples;
auto& depth = depthStencil[0];
// Constrain the sample count according to both kinds of sample count masks obtained from
@@ -715,10 +722,17 @@ VulkanRenderPrimitive::VulkanRenderPrimitive(PrimitiveType pt,
vertexBuffer(vb),
indexBuffer(ib) {}
VulkanFramebuffer::VulkanFramebuffer(VkDevice device, VkFramebuffer framebuffer)
: mDevice(device), mFramebuffer(framebuffer) {}
VulkanFramebuffer::VulkanFramebuffer(VkDevice device, VkFramebuffer framebuffer,
fvkmemory::resource_ptr<VulkanRenderTarget> renderTarget)
: mDevice(device),
mFramebuffer(framebuffer),
mRenderTarget(renderTarget) {
FVK_LOGD << "Creating VulkanFramebuffer: " << framebuffer
<< ", target=" << renderTarget.get();
}
VulkanFramebuffer::~VulkanFramebuffer() {
FVK_LOGD << "Destroying VulkanFramebuffer: " << mFramebuffer;
vkDestroyFramebuffer(mDevice, mFramebuffer, VKALLOC);
}
+10 -3
View File
@@ -195,7 +195,7 @@ public:
}
// The current layout used by the descriptor set. This one will match the bindings, including
// external samplers data.
// external samplers data.
// This will not necessarilly be the same as `mLayout`.
VkDescriptorSetLayout boundLayout = VK_NULL_HANDLE;
@@ -203,7 +203,7 @@ public:
fvkutils::UniformBufferBitmask const& dynamicUboMask;
uint8_t const uniqueDynamicUboCount;
// Flag to indicate if the current layout needs to be recreated or not.
// This should only set to `true` when a external sampler image is bound to the descriptor set.
bool isLayoutDirty = false;
@@ -586,7 +586,9 @@ struct VulkanRenderPrimitive : public HwRenderPrimitive, fvkmemory::Resource {
};
struct VulkanFramebuffer : public fvkmemory::Resource {
VulkanFramebuffer(VkDevice device, VkFramebuffer framebuffer);
VulkanFramebuffer(VkDevice device,
VkFramebuffer framebuffer,
fvkmemory::resource_ptr<VulkanRenderTarget> renderTarget);
~VulkanFramebuffer();
inline VkFramebuffer getVkFramebuffer() const noexcept {
@@ -596,6 +598,11 @@ struct VulkanFramebuffer : public fvkmemory::Resource {
private:
VkDevice mDevice;
VkFramebuffer mFramebuffer;
// We need to keep a reference to the renderTarget because the key of the framebuffer in the
// cache has references to the image views that are derived from the textures of the render
// target.
fvkmemory::resource_ptr<VulkanRenderTarget> mRenderTarget;
};
struct VulkanRenderPass : public fvkmemory::Resource {
@@ -534,6 +534,8 @@ void VulkanPipelineCache::gc() noexcept {
++iter;
}
}
FVK_LOGD << "VulkanPipelineCache sizes: pipelines=" << mPipelines.size();
}
bool VulkanPipelineCache::PipelineEqual::operator()(const PipelineKey& k1,
@@ -271,6 +271,8 @@ void VulkanStagePool::gc() noexcept {
}
}
FVK_LOGD << "VulkanStagePool sizes: stages=" << mStages.size() << " freeImages=" << mFreeImages.size();
FVK_SYSTRACE_END();
}
@@ -46,6 +46,7 @@ VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const&
mCurrentSwapIndex(0),
mAcquired(false),
mIsFirstRenderPass(true) {
FVK_LOGD << "Creating VulkanSwapChain: " << this << ", window=" << nativeWindow << ", size=" << extent.width << "x" << extent.height;
swapChain = mPlatform->createSwapChain(nativeWindow, flags, extent);
FILAMENT_CHECK_POSTCONDITION(swapChain) << "Unable to create swapchain";
@@ -53,6 +54,7 @@ VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const&
}
VulkanSwapChain::~VulkanSwapChain() {
FVK_LOGD << "Destroying VulkanSwapChain: " << this;
// Must wait for the inflight command buffers to finish since they might contain the images
// we're about to destroy.
mCommands->flush();
@@ -68,6 +70,7 @@ VulkanSwapChain::~VulkanSwapChain() {
}
void VulkanSwapChain::update() {
FVK_LOGD << "VulkanSwapChain::update: " << this;
mColors.clear();
auto const bundle = mPlatform->getSwapChainBundle(swapChain);
@@ -89,6 +92,7 @@ void VulkanSwapChain::update() {
colorUsage |= TextureUsage::PROTECTED;
}
for (auto const color: bundle.colors) {
FVK_LOGD << "VulkanSwapChain::update: Adding color image=" << color << " to swapchain " << this;
auto colorTexture = fvkmemory::resource_ptr<VulkanTexture>::construct(mResourceManager,
mContext, device, mAllocator, mResourceManager, mCommands, color, VK_NULL_HANDLE,
bundle.colorFormat, VK_NULL_HANDLE /*ycrcb */, 1, bundle.extent.width,
@@ -157,6 +161,7 @@ void VulkanSwapChain::acquire(bool& resized) {
// Check if the swapchain should be resized.
if ((resized = mPlatform->hasResized(swapChain))) {
FVK_LOGD << "VulkanSwapChain::acquire: Resizing " << this;
if (mFlushAndWaitOnResize) {
mCommands->flush();
mCommands->wait();
@@ -176,6 +181,7 @@ void VulkanSwapChain::acquire(bool& resized) {
mCurrentSwapIndex = imageSyncData.imageIndex;
assert_invariant(mCurrentSwapIndex < mFinishedDrawing.size());
FVK_LOGD << "VulkanSwapChain::acquire: " << this << ", index=" << mCurrentSwapIndex << ", image=" << mColors[mCurrentSwapIndex]->getVkImage();
mFinishedDrawing[mCurrentSwapIndex] = {};
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR)
<< "Cannot acquire in swapchain. error=" << static_cast<int32_t>(result);
+29 -4
View File
@@ -294,9 +294,19 @@ VulkanTextureState::VulkanTextureState(VulkanStagePool& stagePool, VulkanCommand
mYcbcr{ ycbcrConversion },
mDefaultLayout(getDefaultLayoutImpl(usage)),
mUsage(usage),
mIsProtected(isProtected) {}
mIsProtected(isProtected) {
FVK_LOGD << "Creating VulkanTextureState: image=" << image
<< ", memory=" << deviceMemory
<< ", format=" << format
<< ", viewType=" << viewType
<< ", levels=" << (int)levels
<< ", layers=" << (int)layerCount
<< ", usage=" << usage;
}
VulkanTextureState::~VulkanTextureState() {
FVK_LOGD << "Destroying VulkanTextureState: image=" << mTextureImage
<< ", memory=" << mTextureImageMemory;
clearCachedImageViews();
if (mTextureImageMemory != VK_NULL_HANDLE) {
vkDestroyImage(mDevice, mTextureImage, VKALLOC);
@@ -512,6 +522,12 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t width, uint32_t height,
uint32_t depth, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t miplevel) {
FVK_LOGD << "updateImage: texture=" << mState->mTextureImage
<< ", format=" << (int)data.format
<< ", type=" << (int)data.type
<< ", size=" << width << "x" << height << "x" << depth
<< ", offset=" << xoffset << "," << yoffset << "," << zoffset
<< ", mip=" << miplevel;
assert_invariant(width <= this->width && height <= this->height);
assert_invariant(depth <= this->depth * ((target == SamplerType::SAMPLER_CUBEMAP ||
target == SamplerType::SAMPLER_CUBEMAP_ARRAY) ? 6 : 1));
@@ -616,6 +632,11 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& data, uint32_t width,
uint32_t height, uint32_t depth, uint32_t miplevel) {
FVK_LOGD << "updateImageWithBlit: texture=" << mState->mTextureImage
<< ", format=" << (int)data.format
<< ", type=" << (int)data.type
<< ", size=" << width << "x" << height << "x" << depth
<< ", mip=" << miplevel;
// Otherwise, use vkCmdCopyBufferToImage.
size_t const bpp = PixelBufferDescriptor::computeDataSize(data.format, data.type, 1, 1, 1);
// Note: if bpp = 0, we're dealing with a compressed format; we should fall
@@ -755,10 +776,14 @@ bool VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, VkImageSubresourceR
setLayout(range, newLayout);
if (hasTransitions) {
FVK_LOGD << "transition texture=" << mState->mTextureImage << " (" << range.baseArrayLayer
<< "," << range.baseMipLevel << ")" << " count=(" << range.layerCount << ","
<< range.levelCount << ")" << " from=" << (int)oldLayout << " to=" << (int)newLayout
<< " format=" << mState->mVkFormat;
#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION)
FVK_LOGD << "transition texture=" << mState->mTextureImage << " (" << range.baseArrayLayer
<< "," << range.baseMipLevel << ")" << " count=(" << range.layerCount << ","
<< range.levelCount << ")" << " from=" << oldLayout << " to=" << newLayout
<< range.levelCount << ")" << " from=" << (int)oldLayout << " to=" << (int)newLayout
<< " format=" << mState->mVkFormat << " depth="
<< fvkutils::isVkDepthFormat(mState->mVkFormat)
<< " slice-by-slice=" << transitionSliceBySlice;
@@ -767,7 +792,7 @@ bool VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, VkImageSubresourceR
#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION)
FVK_LOGD << "transition texture=" << mState->mTextureImage << " (" << range.baseArrayLayer
<< "," << range.baseMipLevel << ")" << " count=(" << range.layerCount << ","
<< range.levelCount << ")" << " to=" << newLayout
<< range.levelCount << ")" << " to=" << (int)newLayout
<< " is skipped because of no change in layout";
#endif
}
@@ -878,7 +903,7 @@ void VulkanTexture::print() const {
level >= mPrimaryViewRange.baseMipLevel &&
level < (mPrimaryViewRange.baseMipLevel + mPrimaryViewRange.levelCount);
FVK_LOGD << "[" << mState->mTextureImage << "]: (" << layer << "," << level
<< ")=" << getLayout(layer, level)
<< ")=" << (int)getLayout(layer, level)
<< " primary=" << primary;
}
}
@@ -704,6 +704,30 @@ VulkanPlatform::VulkanPlatform() = default;
VulkanPlatform::~VulkanPlatform() = default;
utils::CString VulkanPlatform::getDeviceInfo(DeviceInfoType infoType,
Driver* driver) const noexcept {
if (mImpl->mPhysicalDevice == VK_NULL_HANDLE) {
return {};
}
auto& context = mImpl->mContext;
switch (infoType) {
case DeviceInfoType::VULKAN_DEVICE_NAME: {
return utils::CString(context.getPhysicalDeviceName());
}
case DeviceInfoType::VULKAN_DRIVER_NAME: {
return context.isDriverPropertiesSupported() ? utils::CString(context.getDriverName())
: utils::CString();
}
case DeviceInfoType::VULKAN_DRIVER_INFO: {
return context.isDriverPropertiesSupported() ? utils::CString(context.getDriverInfo())
: utils::CString();
}
default:
FILAMENT_CHECK_POSTCONDITION(false) << "Unsupported DeviceInfoType for VulkanPlatform";
return {};
}
}
VulkanPlatform::SwapChainBundle VulkanPlatform::getSwapChainBundle(SwapChainPtr handle) {
return static_cast<VulkanPlatformSwapChainBase*>(handle)->getSwapChainBundle();
}
@@ -952,6 +976,11 @@ void VulkanPlatform::queryAndSetDeviceFeatures(Platform::DriverConfig const& dri
chainStruct(&context.mPhysicalDeviceFeatures, &globalPriorityFeatures);
}
if (vkGetPhysicalDeviceProperties2) {
chainStruct(&context.mPhysicalDeviceProperties, &context.mDriverProperties);
context.mDriverPropertiesSupported = true;
}
// Initialize the following fields: physicalDeviceProperties, memoryProperties,
// physicalDeviceFeatures.
vkGetPhysicalDeviceProperties2(mImpl->mPhysicalDevice, &context.mPhysicalDeviceProperties);
+28 -34
View File
@@ -192,8 +192,15 @@ void WebGPUDriver::flush(int) {
}
void WebGPUDriver::finish(int /* dummy */) {
mReadPixelMapsCounter.waitForAllToFinish();
mQueueManager.finish();
// We use polling to advance webgpu's callback counter until all the read backs have been
// processed. Note that blocking with mReadPixelMapsCounter.waitForAllToFinish will only
// deadlock since we could not advance the counter.
while (!mReadPixelMapsCounter.isIdle()) {
mAdapter.GetInstance().ProcessEvents();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
void WebGPUDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
@@ -940,10 +947,6 @@ size_t WebGPUDriver::getUniformBufferOffsetAlignment(){
void WebGPUDriver::updateIndexBuffer(Handle<HwIndexBuffer> indexBufferHandle,
BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset) {
// make sure command elements (draws, etc.) prior to the buffer update are processed before the
// update on the GPU, otherwise the expected data may not be available at the time certain
// draw calls are made.
flush();
handleCast<WebGPUIndexBuffer>(indexBufferHandle)
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
scheduleDestroy(std::move(bufferDescriptor));
@@ -958,10 +961,6 @@ void WebGPUDriver::updateIndexBufferAsyncR(AsyncCallId jobId,
void WebGPUDriver::updateBufferObject(Handle<HwBufferObject> bufferObjectHandle,
BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset) {
// make sure command elements (draws, etc.) prior to the buffer update are processed before the
// update on the GPU, otherwise the expected data may not be available at the time certain
// draw calls are made.
flush();
handleCast<WebGPUBufferObject>(bufferObjectHandle)
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
scheduleDestroy(std::move(bufferDescriptor));
@@ -1008,10 +1007,6 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
const uint32_t xoffset, const uint32_t yoffset, const uint32_t zoffset,
const uint32_t width, const uint32_t height, const uint32_t depth,
PixelBufferDescriptor&& pixelBufferDescriptor) {
// one way or another this function writes texture(s), thus any commands (draw calls etc.) should
// get submitted prior to these updates so that subsequent commands/draws run with the
// image/texture(s) updated as expected.
flush();
PixelBufferDescriptor* inputData{ &pixelBufferDescriptor };
PixelBufferDescriptor reshapedData;
if (reshape(pixelBufferDescriptor, reshapedData)) {
@@ -1183,11 +1178,6 @@ void WebGPUDriver::generateMipmaps(Handle<HwTexture> textureHandle) {
return; // nothing to do
}
// make sure command elements (draws, etc.) prior to the texture update are processed before the
// update on the GPU.
// this ensures subsequent draw calls are run after the mipmaps have been generated as expected
flush();
const auto usage = wgpuTexture.GetUsage();
FILAMENT_CHECK_PRECONDITION(usage & wgpu::TextureUsage::TextureBinding)
<< "Texture for mipmap generation must have TextureBinding usage.";
@@ -1376,6 +1366,9 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
customDepthStencilMsaaSidecarTextureView);
mRenderPassEncoder = commandEncoder.BeginRenderPass(&renderPassDescriptor);
// TODO: there's a bug here because the webgpu viewport has the origin at top-left, whereas
// filament expects it to be bottom left.
mRenderPassEncoder.SetViewport(
static_cast<float>(params.viewport.left),
static_cast<float>(params.viewport.bottom),
@@ -1463,7 +1456,6 @@ void WebGPUDriver::stopCapture(int /* dummy */) {
void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, const uint32_t x,
const uint32_t y, const uint32_t width, const uint32_t height,
PixelBufferDescriptor&& pixelBufferDescriptor) {
flush();
const auto srcTarget{ handleCast<WebGPURenderTarget>(sourceRenderTargetHandle) };
assert_invariant(srcTarget);
@@ -1521,10 +1513,7 @@ void WebGPUDriver::readTextureToBuffer(wgpu::Texture srcTexture, uint32_t level,
return;
}
const wgpu::CommandEncoderDescriptor commandEncoderDescriptor{
.label = "read_texture_to_buffer_command",
};
auto commandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
auto commandEncoder = mQueueManager.getCommandEncoder();
FILAMENT_CHECK_POSTCONDITION(commandEncoder)
<< "Failed to create command encoder for readTextureToBuffer?";
@@ -1646,9 +1635,6 @@ void WebGPUDriver::readTextureToBuffer(wgpu::Texture srcTexture, uint32_t level,
.depthOrArrayLayers = 1,
};
commandEncoder.CopyTextureToBuffer(&source, &destination, &copySize);
wgpu::CommandBuffer commandBuffer = commandEncoder.Finish();
assert_invariant(commandBuffer);
mDevice.GetQueue().Submit(1, &commandBuffer);
// Map the buffer to read the data
struct UserData final {
@@ -1669,6 +1655,9 @@ void WebGPUDriver::readTextureToBuffer(wgpu::Texture srcTexture, uint32_t level,
});
mReadPixelMapsCounter.startTask();
// We need to flush here before we can readback from the staging buffer since the copy op is
// pending to be submitted.
mQueueManager.flush();
userData->buffer.MapAsync(
wgpu::MapMode::Read, 0, bufferSize, wgpu::CallbackMode::AllowSpontaneous,
[](wgpu::MapAsyncStatus status, const char* message, UserData* userdata) {
@@ -1761,7 +1750,6 @@ void WebGPUDriver::blitDEPRECATED(TargetBufferFlags buffers,
const wgpu::Extent2D destinationSize{ static_cast<uint32_t>(destinationViewport.width),
static_cast<uint32_t>(destinationViewport.height) };
mQueueManager.flush();
wgpu::CommandEncoder commandEncoder = mQueueManager.getCommandEncoder();
const WebGPUBlitter::BlitArgs blitArgs{
.source = { .texture = sourceTexture->getTexture(),
@@ -1781,8 +1769,6 @@ void WebGPUDriver::blitDEPRECATED(TargetBufferFlags buffers,
.filter = filter,
};
mBlitter.blit(mDevice.GetQueue(), commandEncoder, blitArgs);
mQueueManager.flush();
}
void WebGPUDriver::resolve(Handle<HwTexture> destinationTextureHandle, const uint8_t sourceLevel,
@@ -1822,7 +1808,6 @@ void WebGPUDriver::blit(Handle<HwTexture> destinationTextureHandle, const uint8_
Handle<HwTexture> sourceTextureHandle, const uint8_t destinationLevel,
const uint8_t destinationLayer, const math::uint2 sourceOrigin, const math::uint2 size) {
FWGPU_SYSTRACE_SCOPE();
mQueueManager.flush();
wgpu::CommandEncoder commandEncoder = mQueueManager.getCommandEncoder();
const auto sourceTexture{ handleCast<WebGPUTexture>(sourceTextureHandle) };
const auto destinationTexture{ handleCast<WebGPUTexture>(destinationTextureHandle) };
@@ -1846,7 +1831,6 @@ void WebGPUDriver::blit(Handle<HwTexture> destinationTextureHandle, const uint8_
.filter = SamplerMagFilter::NEAREST,
};
mBlitter.blit(mDevice.GetQueue(), commandEncoder, blitArgs);
mQueueManager.flush();
}
void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
@@ -1986,9 +1970,19 @@ void WebGPUDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGr
//todo
}
void WebGPUDriver::scissor(
Viewport scissor) {
//todo
void WebGPUDriver::scissor(Viewport scissor) {
assert_invariant(mRenderPassEncoder);
assert_invariant(mCurrentRenderTarget);
// The WebGPU scissor starts from the top-left corner
assert_invariant(scissor.left >= 0 &&
mCurrentRenderTarget->height >= scissor.bottom + scissor.height /*top >= 0*/ &&
scissor.width <= mCurrentRenderTarget->width &&
scissor.height <= mCurrentRenderTarget->height);
mRenderPassEncoder.SetScissorRect(scissor.left,
mCurrentRenderTarget->height - scissor.bottom - scissor.height /*top*/, scissor.width,
scissor.height);
}
void WebGPUDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
@@ -50,6 +50,9 @@ public:
[[nodiscard]] bool isHeadless() const { return mType == SwapChainType::HEADLESS; }
[[nodiscard]] uint32_t getWidth() const { return mConfig.width; }
[[nodiscard]] uint32_t getHeight() const { return mConfig.height; }
void present(DriverBase& driver);
void setFrameScheduledCallback(CallbackHandler* handler, FrameScheduledCallback&& callback) {
@@ -41,5 +41,10 @@ void AsyncTaskCounter::waitForAllToFinish() {
mFinishedCondition.wait(lock, [this] { return mTasksInProgress == 0; });
}
bool AsyncTaskCounter::isIdle() {
std::lock_guard<std::mutex> lock{ mMutex };
return mTasksInProgress == 0;
}
} // namespace filament::backend::webgpuutils
@@ -51,6 +51,11 @@ public:
*/
void waitForAllToFinish();
/**
* Check if all tasks have finished (work counter is 0) (thread-safe)
*/
bool isIdle();
private:
std::mutex mMutex;
std::condition_variable mFinishedCondition;
+8 -8
View File
@@ -16,7 +16,7 @@
#include "PlatformRunner.h"
#include <getopt/getopt.h>
#include <utils/getopt.h>
#include <iostream>
#include <string>
@@ -30,18 +30,18 @@ TestArguments parseArguments(int argc, char* argv[]) {
// The first colon in OPTSTR turns on silent error reporting. This is important, as the
// arguments may also contain gtest parameters we don't know about.
static constexpr const char* OPTSTR = ":a:kc";
static const struct option OPTIONS[] = {
{ "api", required_argument, nullptr, 'a' },
{ "headless_only", no_argument, nullptr, 'k' },
{ "ci", no_argument, nullptr, 'c' },
{ nullptr, 0, nullptr, 0 } // termination of the option list
static const utils::getopt::option OPTIONS[] = {
{ "api", utils::getopt::required_argument, nullptr, 'a' },
{ "headless_only", utils::getopt::no_argument, nullptr, 'k' },
{ "ci", utils::getopt::no_argument, nullptr, 'c' },
{ nullptr, 0, nullptr, 0 } // termination of the utils::getopt::option list
};
int opt;
int optionIndex = 0;
while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &optionIndex)) >= 0) {
std::string arg(optarg ? optarg : "");
while ((opt = utils::getopt::getopt_long(argc, argv, OPTSTR, OPTIONS, &optionIndex)) >= 0) {
std::string arg(utils::getopt::optarg ? utils::getopt::optarg : "");
switch (opt) {
case 'a':
if (arg == "opengl") {
+4 -3
View File
@@ -35,9 +35,10 @@ test::NativeView getNativeView() {
namespace {
std::array<test::Backend, 2> const VALID_BACKENDS{
test::Backend::OPENGL,
test::Backend::VULKAN,
std::array<test::Backend, 3> const VALID_BACKENDS{
test::Backend::OPENGL,
test::Backend::VULKAN,
test::Backend::WEBGPU
};
}// namespace
+50
View File
@@ -0,0 +1,50 @@
/*
* 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 <backend/Platform.h>
#include <gtest/gtest.h>
#include <private/backend/PlatformFactory.h>
namespace filament::backend {
TEST(PlatformTest, GetDeviceInfo) {
Backend backend = Backend::DEFAULT;
Platform* platform = PlatformFactory::create(&backend);
ASSERT_NE(platform, nullptr);
// Test valid queries for the current platform (will be either GL, Vulkan, or Metal depending on
// host)
if (backend == Backend::OPENGL) {
platform->getDeviceInfo(Platform::DeviceInfoType::OPENGL_RENDERER, nullptr);
platform->getDeviceInfo(Platform::DeviceInfoType::OPENGL_VENDOR, nullptr);
// Death tests for Vulkan info on OpenGL platform
EXPECT_DEATH(platform->getDeviceInfo(Platform::DeviceInfoType::VULKAN_DEVICE_NAME, nullptr),
"Unsupported DeviceInfoType");
} else if (backend == Backend::VULKAN) {
platform->getDeviceInfo(Platform::DeviceInfoType::VULKAN_DEVICE_NAME, nullptr);
platform->getDeviceInfo(Platform::DeviceInfoType::VULKAN_DRIVER_NAME, nullptr);
platform->getDeviceInfo(Platform::DeviceInfoType::VULKAN_DRIVER_INFO, nullptr);
// Death tests for OpenGL info on Vulkan platform
EXPECT_DEATH(platform->getDeviceInfo(Platform::DeviceInfoType::OPENGL_RENDERER, nullptr),
"Unsupported DeviceInfoType");
}
PlatformFactory::destroy(&platform);
}
} // namespace filament::backend
-1
View File
@@ -32,7 +32,6 @@ using namespace filament;
using namespace filament::backend;
TEST_F(BackendTest, ScissorViewportRegion) {
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
auto& api = getDriverApi();
constexpr int kSrcTexWidth = 1024;
-1
View File
@@ -227,7 +227,6 @@ bool Froxelizer::prepare(
const mat4f& projection, float const projectionNear, float const projectionFar,
float4 const& clipTransform) noexcept {
assert_invariant(projectionFar > projectionNear);
assert_invariant(projectionNear > 0);
setViewport(viewport);
setProjection(projection, projectionNear, projectionFar);
+265
View File
@@ -0,0 +1,265 @@
/*
* 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 "LocalProgramCache.h"
#include "MaterialParser.h"
#include "details/Engine.h"
#include "details/Material.h"
#include <backend/DriverApiForward.h>
namespace filament {
using namespace backend;
using namespace utils;
LocalProgramCache::LocalProgramCache(LocalProgramCache const& other)
: mMaterial(other.mMaterial),
mCachedPrograms(other.mCachedPrograms.size()),
mSpecializationConstants((other.mMaterial != nullptr)
? other.mMaterial->getEngine()
.getMaterialCache()
.getSpecializationConstantsInternPool()
.acquire(other.mSpecializationConstants)
: SpecializationConstants()) {}
LocalProgramCache& LocalProgramCache::operator=(LocalProgramCache const& other) {
assert_invariant(mMaterial == nullptr);
assert_invariant(mCachedPrograms.empty());
assert_invariant(mSpecializationConstants.empty());
mMaterial = other.mMaterial;
if (mMaterial != nullptr) {
mCachedPrograms = FixedCapacityVector<Handle<HwProgram>>(other.mCachedPrograms.size());
mSpecializationConstants = other.mMaterial->getEngine()
.getMaterialCache()
.getSpecializationConstantsInternPool()
.acquire(other.mSpecializationConstants);
}
return *this;
}
void LocalProgramCache::initializeForMaterial(FEngine& engine, FMaterial const& material,
utils::FixedCapacityVector<backend::Program::SpecializationConstant>
specializationConstants) {
assert_invariant(mMaterial == nullptr);
assert_invariant(mCachedPrograms.empty());
assert_invariant(mSpecializationConstants.empty());
mMaterial = &material;
mSpecializationConstants =
engine.getMaterialCache().getSpecializationConstantsInternPool().acquire(
std::move(specializationConstants));
size_t cachedProgramsSize;
switch (material.getMaterialDomain()) {
case filament::MaterialDomain::SURFACE:
cachedProgramsSize = 1 << VARIANT_BITS;
break;
case filament::MaterialDomain::POST_PROCESS:
cachedProgramsSize = 1 << POST_PROCESS_VARIANT_BITS;
break;
case filament::MaterialDomain::COMPUTE:
cachedProgramsSize = 1;
break;
}
mCachedPrograms = FixedCapacityVector<Handle<HwProgram>>(cachedProgramsSize);
material.getDefinition().acquirePrograms(engine, mCachedPrograms.as_slice(),
material.getMaterialParser(), mSpecializationConstants, material.isDefaultMaterial());
}
void LocalProgramCache::initializeForMaterialInstance(FEngine& engine, FMaterial const& material) {
assert_invariant(mMaterial == nullptr);
assert_invariant(mCachedPrograms.empty());
assert_invariant(mSpecializationConstants.empty());
mMaterial = &material;
LocalProgramCache const& programs = material.getPrograms();
mSpecializationConstants =
engine.getMaterialCache().getSpecializationConstantsInternPool().acquire(
programs.getSpecializationConstants());
mCachedPrograms =
FixedCapacityVector<Handle<HwProgram>>(material.getPrograms().mCachedPrograms.size());
}
Handle<HwProgram> LocalProgramCache::prepareProgramSlow(DriverApi& driver, Variant const variant,
CompilerPriorityQueue const priorityQueue) const noexcept {
assert_invariant(mMaterial != nullptr);
FEngine& engine = mMaterial->getEngine();
if (mMaterial->isSharedVariant(variant)) {
FMaterial const* defaultMaterial = engine.getDefaultMaterial();
assert_invariant(defaultMaterial);
LocalProgramCache const& defaultPrograms = defaultMaterial->getPrograms();
Handle<HwProgram> program = defaultPrograms.mCachedPrograms[variant.key];
if (program) {
return mCachedPrograms[variant.key] = program;
}
return mCachedPrograms[variant.key] =
defaultPrograms.prepareProgram(driver, variant, priorityQueue);
}
return mCachedPrograms[variant.key] = mMaterial->getDefinition().prepareProgram(engine, driver,
mMaterial->getMaterialParser(), getProgramSpecialization(variant), priorityQueue);
}
ProgramSpecialization LocalProgramCache::getProgramSpecialization(Variant variant) const noexcept {
assert_invariant(mMaterial != nullptr);
return ProgramSpecialization {
.materialCrc32 = mMaterial->getMaterialParser().getCrc32(),
.variant = variant,
.specializationConstants = mSpecializationConstants,
};
}
void LocalProgramCache::terminate(FEngine& engine) {
assert_invariant(mMaterial != nullptr);
mMaterial->getDefinition().releasePrograms(engine, mCachedPrograms.as_slice(),
mMaterial->getMaterialParser(), mSpecializationConstants,
mMaterial->isDefaultMaterial());
engine.getMaterialCache().releaseMaterial(engine, mMaterial->getDefinition());
engine.getMaterialCache().getSpecializationConstantsInternPool().release(
mSpecializationConstants);
}
void LocalProgramCache::clear(FEngine& engine) {
assert_invariant(mMaterial != nullptr);
mMaterial->getDefinition().releasePrograms(engine, mCachedPrograms.as_slice(),
mMaterial->getMaterialParser(), mSpecializationConstants,
mMaterial->isDefaultMaterial());
}
Variant LocalProgramCache::filterVariantForGetProgram(Variant variant) const noexcept {
if (UTILS_UNLIKELY(mMaterial->getEngine().features.material.enable_fog_as_postprocess)) {
// if the fog as post-process feature is enabled, we need to proceed "as-if" the material
// didn't have the FOG variant bit.
if (mMaterial->getMaterialDomain() == MaterialDomain::SURFACE) {
BlendingMode const blendingMode = mMaterial->getBlendingMode();
bool const hasScreenSpaceRefraction =
mMaterial->getRefractionMode() == RefractionMode::SCREEN_SPACE;
bool const isBlendingCommand = !hasScreenSpaceRefraction &&
(blendingMode != BlendingMode::OPAQUE && blendingMode != BlendingMode::MASKED);
if (!isBlendingCommand) {
variant.setFog(false);
}
}
}
return variant;
}
Program::SpecializationConstant LocalProgramCache::getConstantImpl(uint32_t id) const noexcept {
return mSpecializationConstants[id];
}
Program::SpecializationConstant LocalProgramCache::getConstantImpl(
std::string_view name) const noexcept {
assert_invariant(mMaterial != nullptr);
MaterialDefinition const& definition = mMaterial->getDefinition();
auto it = definition.specializationConstantsNameToIndex.find(name);
if (it != definition.specializationConstantsNameToIndex.cend()) {
return getConstantImpl(it->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS);
}
CString name_cstring(name);
PANIC_PRECONDITION("No such constant exists: %s", name_cstring.c_str());
return {};
}
void LocalProgramCache::setConstants(
std::initializer_list<std::pair<uint32_t, Program::SpecializationConstant>>
constants) noexcept {
assert_invariant(mMaterial != nullptr);
auto newSpecializationConstants =
FixedCapacityVector<Program::SpecializationConstant>(mSpecializationConstants);
bool hasChanged = false;
for (const auto& [id, value] : constants) {
if (newSpecializationConstants[id] != value) {
newSpecializationConstants[id] = value;
hasChanged = true;
}
}
if (hasChanged) {
setConstantsImpl(std::move(newSpecializationConstants));
}
}
void LocalProgramCache::setConstants(
std::initializer_list<std::pair<std::string_view, Program::SpecializationConstant>>
constants) noexcept {
assert_invariant(mMaterial != nullptr);
auto newSpecializationConstants =
FixedCapacityVector<Program::SpecializationConstant>(mSpecializationConstants);
bool hasChanged = false;
for (const auto& [name, value] : constants) {
MaterialDefinition const& definition = mMaterial->getDefinition();
auto it = definition.specializationConstantsNameToIndex.find(name);
if (it != definition.specializationConstantsNameToIndex.cend()) {
uint32_t id = it->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS;
if (newSpecializationConstants[id] != value) {
newSpecializationConstants[id] = value;
hasChanged = true;
}
}
}
if (hasChanged) {
setConstantsImpl(std::move(newSpecializationConstants));
}
}
void LocalProgramCache::setConstantsImpl(
FixedCapacityVector<Program::SpecializationConstant> constants) noexcept {
FEngine& engine = mMaterial->getEngine();
auto& internPool = engine.getMaterialCache().getSpecializationConstantsInternPool();
MaterialParser const& materialParser = mMaterial->getMaterialParser();
MaterialDefinition const& definition = mMaterial->getDefinition();
const bool isDefaultMaterial = mMaterial->isDefaultMaterial();
// Release old resources...
definition.releasePrograms(engine, mCachedPrograms.as_slice(), materialParser,
mSpecializationConstants, isDefaultMaterial);
internPool.release(mSpecializationConstants);
// Then acquire new ones.
mSpecializationConstants = internPool.acquire(std::move(constants));
definition.acquirePrograms(engine, mCachedPrograms.as_slice(), materialParser,
mSpecializationConstants, isDefaultMaterial);
}
template int32_t LocalProgramCache::getConstant<int32_t>(uint32_t id) const noexcept;
template float LocalProgramCache::getConstant<float>(uint32_t id) const noexcept;
template bool LocalProgramCache::getConstant<bool>(uint32_t id) const noexcept;
template int32_t LocalProgramCache::getConstant<int32_t>(std::string_view name) const noexcept;
template float LocalProgramCache::getConstant<float>(std::string_view name) const noexcept;
template bool LocalProgramCache::getConstant<bool>(std::string_view name) const noexcept;
} // namespace filament

Some files were not shown because too many files have changed in this diff Show More