Compare commits
58
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4fa86fb01 | ||
|
|
7da2a08df6 | ||
|
|
82246d934d | ||
|
|
c60969ef67 | ||
|
|
ce37f216bc | ||
|
|
81c71fbbb9 | ||
|
|
07a7c6003a | ||
|
|
804a74c205 | ||
|
|
dde49a410a | ||
|
|
770ce7f8ec | ||
|
|
11714d3adc | ||
|
|
6aac9071b3 | ||
|
|
da9173e9dc | ||
|
|
cd64d50408 | ||
|
|
a3145cb96f | ||
|
|
cdfb92e14a | ||
|
|
55c16e6e7a | ||
|
|
65e3c3bfb9 | ||
|
|
902f869721 | ||
|
|
ad1bc6f360 | ||
|
|
73c343635e | ||
|
|
432e672022 | ||
|
|
b56b04c5f8 | ||
|
|
99816d67c2 | ||
|
|
d6d4f92922 | ||
|
|
6a59a68622 | ||
|
|
4580f57987 | ||
|
|
38f7e579f1 | ||
|
|
9b1c8a2bf5 | ||
|
|
4504471021 | ||
|
|
37c316fa03 | ||
|
|
14960f7118 | ||
|
|
1deb657442 | ||
|
|
45c0d1b34f | ||
|
|
1ddd10f326 | ||
|
|
308668a705 | ||
|
|
1cd48619e3 | ||
|
|
89c3b3f40b | ||
|
|
e830ec28e4 | ||
|
|
b58ffb87e0 | ||
|
|
385d8969cf | ||
|
|
53bc372876 | ||
|
|
58f6d77e78 | ||
|
|
3769d0a9d3 | ||
|
|
2bc71240cf | ||
|
|
e1fb3f7442 | ||
|
|
e832805faf | ||
|
|
2ce71d6d98 | ||
|
|
26c51e0d9a | ||
|
|
510ae15867 | ||
|
|
d6caa9dc0b | ||
|
|
19209a00e6 | ||
|
|
188113bad6 | ||
|
|
5916837318 | ||
|
|
27aa517c48 | ||
|
|
4622e88a6b | ||
|
|
9bdb6acd63 | ||
|
|
751d213145 |
@@ -0,0 +1,26 @@
|
|||||||
|
name: 'Renderdiff Generate'
|
||||||
|
description: 'Sets up prerequisites and runs the generate script for renderdiff'
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
- uses: ./.github/actions/mac-prereq
|
||||||
|
- uses: ./.github/actions/get-gltf-assets
|
||||||
|
- uses: ./.github/actions/get-mesa
|
||||||
|
- uses: ./.github/actions/get-vulkan-sdk
|
||||||
|
- name: Prerequisites
|
||||||
|
run: |
|
||||||
|
# Must have at least clang-16 for a webgpu/dawn build.
|
||||||
|
sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer
|
||||||
|
shell: bash
|
||||||
|
- name: Generate images
|
||||||
|
run: |
|
||||||
|
TEST_DIR=test/renderdiff
|
||||||
|
source ${TEST_DIR}/src/preamble.sh
|
||||||
|
set -eux
|
||||||
|
bash ${TEST_DIR}/generate.sh
|
||||||
|
set +eux
|
||||||
|
shell: bash
|
||||||
|
- name: Build diffimg tool
|
||||||
|
run: |
|
||||||
|
./build.sh release diffimg
|
||||||
|
shell: bash
|
||||||
@@ -6,18 +6,31 @@ on:
|
|||||||
- main
|
- main
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
# Update the renderdiff goldens in filament-assets. This will add or merge the new goldens from
|
||||||
|
# a branch on filament-assets.
|
||||||
update-renderdiff-goldens:
|
update-renderdiff-goldens:
|
||||||
name: update-renderdiff-goldens
|
name: update-renderdiff-goldens
|
||||||
runs-on: 'ubuntu-24.04-4core'
|
runs-on: 'macos-14-xlarge'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: ./.github/actions/linux-prereq
|
|
||||||
- id: get_commit_msg
|
- id: get_commit_msg
|
||||||
uses: ./.github/actions/get-commit-msg
|
uses: ./.github/actions/get-commit-msg
|
||||||
- name: Prerequisites
|
- name: Check if accepting new goldens
|
||||||
run: pip install tifffile numpy
|
id: check_accept
|
||||||
|
env:
|
||||||
|
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
|
||||||
|
run: |
|
||||||
|
if echo "${COMMIT_MESSAGE}" | python3 test/renderdiff/src/commit_msg.py --mode=accept_new_goldens; then
|
||||||
|
echo "accept=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "accept=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
shell: bash
|
||||||
|
- name: Renderdiff Generate for new goldens
|
||||||
|
if: steps.check_accept.outputs.accept == 'true'
|
||||||
|
uses: ./.github/actions/renderdiff-generate
|
||||||
- name: Run update script
|
- name: Run update script
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.FILAMENTBOT_TOKEN }}
|
GH_TOKEN: ${{ secrets.FILAMENTBOT_TOKEN }}
|
||||||
@@ -25,16 +38,31 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
GOLDEN_BRANCH=$(echo "${COMMIT_MESSAGE}" | python3 test/renderdiff/src/commit_msg.py)
|
GOLDEN_BRANCH=$(echo "${COMMIT_MESSAGE}" | python3 test/renderdiff/src/commit_msg.py)
|
||||||
COMMIT_HASH="${{ steps.get_commit_msg.outputs.hash }}"
|
COMMIT_HASH="${{ steps.get_commit_msg.outputs.hash }}"
|
||||||
|
|
||||||
|
git config --global user.email "filament.bot@gmail.com"
|
||||||
|
git config --global user.name "Filament Bot"
|
||||||
|
git config --global credential.helper cache
|
||||||
|
|
||||||
|
if [[ "${{ steps.check_accept.outputs.accept }}" == "true" ]]; then
|
||||||
|
SHORT_HASH="${COMMIT_HASH:0:8}"
|
||||||
|
GOLDEN_BRANCH="accept-goldens-${SHORT_HASH}"
|
||||||
|
echo "Generating new goldens for branch ${GOLDEN_BRANCH}"
|
||||||
|
python3 test/renderdiff/src/update_golden.py \
|
||||||
|
--branch=${GOLDEN_BRANCH} \
|
||||||
|
--source=$(pwd)/out/renderdiff/renders \
|
||||||
|
--commit-msg="Auto-update goldens from ${COMMIT_HASH}" \
|
||||||
|
--push-to-remote \
|
||||||
|
--golden-repo-token=${GH_TOKEN}
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "${GOLDEN_BRANCH}" != "main" ]]; then
|
if [[ "${GOLDEN_BRANCH}" != "main" ]]; then
|
||||||
git config --global user.email "filament.bot@gmail.com"
|
|
||||||
git config --global user.name "Filament Bot"
|
|
||||||
git config --global credential.helper cache
|
|
||||||
echo "branch==${GOLDEN_BRANCH}"
|
echo "branch==${GOLDEN_BRANCH}"
|
||||||
echo "hash==${COMMIT_HASH}"
|
echo "hash==${COMMIT_HASH}"
|
||||||
python3 test/renderdiff/src/update_golden.py --branch=${GOLDEN_BRANCH} \
|
python3 test/renderdiff/src/update_golden.py --branch=${GOLDEN_BRANCH} \
|
||||||
--merge-to-main --filament-tag=${COMMIT_HASH} --golden-repo-token=${GH_TOKEN}
|
--merge-to-main --filament-tag=${COMMIT_HASH} --golden-repo-token=${GH_TOKEN}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Update the /docs (offiicla github-hosted Filament site) if necessary
|
||||||
update-docs:
|
update-docs:
|
||||||
name: update-docs
|
name: update-docs
|
||||||
runs-on: 'ubuntu-24.04-4core'
|
runs-on: 'ubuntu-24.04-4core'
|
||||||
@@ -57,3 +85,40 @@ jobs:
|
|||||||
git config --global user.name "Filament Bot"
|
git config --global user.name "Filament Bot"
|
||||||
git config --global credential.helper cache
|
git config --global credential.helper cache
|
||||||
bash docs_src/build/postsubmit.sh ${COMMIT_HASH} ${GH_TOKEN}
|
bash docs_src/build/postsubmit.sh ${COMMIT_HASH} ${GH_TOKEN}
|
||||||
|
|
||||||
|
# Produce a json that describes the android artifact sizes, and will push that json to a folder in
|
||||||
|
# filament-assets
|
||||||
|
update-sizeguard:
|
||||||
|
name: update-sizeguard
|
||||||
|
runs-on: 'ubuntu-24.04-16core'
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4.1.6
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: ./.github/actions/linux-prereq
|
||||||
|
- uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: '17'
|
||||||
|
- id: get_commit_msg
|
||||||
|
uses: ./.github/actions/get-commit-msg
|
||||||
|
- name: Build and generate size report
|
||||||
|
run: |
|
||||||
|
cd build/android && printf "y" | ./build.sh release all
|
||||||
|
cd ../..
|
||||||
|
COMMIT_HASH="${{ steps.get_commit_msg.outputs.hash }}"
|
||||||
|
python3 test/sizeguard/dump_artifact_size.py out/*.tgz out/*.aar > "${COMMIT_HASH}.json"
|
||||||
|
- name: Push to filament-assets
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.FILAMENTBOT_TOKEN }}
|
||||||
|
run: |
|
||||||
|
COMMIT_HASH="${{ steps.get_commit_msg.outputs.hash }}"
|
||||||
|
git config --global user.email "filament.bot@gmail.com"
|
||||||
|
git config --global user.name "Filament Bot"
|
||||||
|
git clone https://x-access-token:${GH_TOKEN}@github.com/google/filament-assets.git filament-assets
|
||||||
|
mkdir -p filament-assets/sizeguard
|
||||||
|
mv "${COMMIT_HASH}.json" filament-assets/sizeguard/
|
||||||
|
cd filament-assets
|
||||||
|
git add sizeguard/"${COMMIT_HASH}.json"
|
||||||
|
git commit -m "Update sizeguard for filament@${COMMIT_HASH}" || echo "No changes to commit"
|
||||||
|
git push https://x-access-token:${GH_TOKEN}@github.com/google/filament-assets.git main
|
||||||
|
|||||||
@@ -67,7 +67,16 @@ jobs:
|
|||||||
# Only build 1 64 bit target during presubmit to cut down build times during presubmit
|
# Only build 1 64 bit target during presubmit to cut down build times during presubmit
|
||||||
# Continuous builds will build everything
|
# Continuous builds will build everything
|
||||||
run: |
|
run: |
|
||||||
cd build/android && printf "y" | ./build.sh presubmit arm64-v8a
|
pushd .
|
||||||
|
cd build/android && printf "y" | ./build.sh presubmit-with-archive arm64-v8a
|
||||||
|
popd
|
||||||
|
- name: Check artifact sizes
|
||||||
|
run: |
|
||||||
|
python3 test/sizeguard/dump_artifact_size.py out/*.aar > current_size.json
|
||||||
|
python3 test/sizeguard/check_size.py current_size.json \
|
||||||
|
--target-branch origin/main \
|
||||||
|
--threshold 20480 \
|
||||||
|
--artifacts filament-android-release.aar/jni/arm64-v8a/libfilament-jni.so
|
||||||
|
|
||||||
build-ios:
|
build-ios:
|
||||||
name: build-iOS
|
name: build-iOS
|
||||||
@@ -117,19 +126,24 @@ jobs:
|
|||||||
- uses: actions/checkout@v4.1.6
|
- uses: actions/checkout@v4.1.6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: ./.github/actions/mac-prereq
|
|
||||||
- uses: ./.github/actions/get-gltf-assets
|
|
||||||
- uses: ./.github/actions/get-mesa
|
|
||||||
- uses: ./.github/actions/get-vulkan-sdk
|
|
||||||
- id: get_commit_msg
|
- id: get_commit_msg
|
||||||
uses: ./.github/actions/get-commit-msg
|
uses: ./.github/actions/get-commit-msg
|
||||||
- name: Prerequisites
|
- name: Check if accepting new goldens
|
||||||
|
id: check_accept
|
||||||
|
env:
|
||||||
|
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
|
||||||
run: |
|
run: |
|
||||||
pip install tifffile numpy
|
if echo "${COMMIT_MESSAGE}" | python3 test/renderdiff/src/commit_msg.py --mode=accept_new_goldens; then
|
||||||
# Must have at least clang-16 for a webgpu/dawn build.
|
echo "accept=true" >> "$GITHUB_OUTPUT"
|
||||||
sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer
|
else
|
||||||
|
echo "accept=false" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
shell: bash
|
shell: bash
|
||||||
|
- name: Renderdiff Generate
|
||||||
|
if: steps.check_accept.outputs.accept != 'true'
|
||||||
|
uses: ./.github/actions/renderdiff-generate
|
||||||
- name: Render and compare
|
- name: Render and compare
|
||||||
|
if: steps.check_accept.outputs.accept != 'true'
|
||||||
id: render_compare
|
id: render_compare
|
||||||
env:
|
env:
|
||||||
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
|
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
|
||||||
@@ -138,7 +152,7 @@ jobs:
|
|||||||
source ${TEST_DIR}/src/preamble.sh
|
source ${TEST_DIR}/src/preamble.sh
|
||||||
set -eux
|
set -eux
|
||||||
GOLDEN_BRANCH=$(echo "${COMMIT_MESSAGE}" | python3 ${TEST_DIR}/src/commit_msg.py)
|
GOLDEN_BRANCH=$(echo "${COMMIT_MESSAGE}" | python3 ${TEST_DIR}/src/commit_msg.py)
|
||||||
bash ${TEST_DIR}/generate.sh
|
|
||||||
python3 ${TEST_DIR}/src/golden_manager.py \
|
python3 ${TEST_DIR}/src/golden_manager.py \
|
||||||
--branch=${GOLDEN_BRANCH} \
|
--branch=${GOLDEN_BRANCH} \
|
||||||
--output=${GOLDEN_OUTPUT_DIR}
|
--output=${GOLDEN_OUTPUT_DIR}
|
||||||
@@ -149,7 +163,9 @@ jobs:
|
|||||||
python3 ${TEST_DIR}/src/compare.py \
|
python3 ${TEST_DIR}/src/compare.py \
|
||||||
--src=${GOLDEN_OUTPUT_DIR} \
|
--src=${GOLDEN_OUTPUT_DIR} \
|
||||||
--dest=${RENDER_OUTPUT_DIR} \
|
--dest=${RENDER_OUTPUT_DIR} \
|
||||||
--out=${DIFF_OUTPUT_DIR} 2>&1 | tee compare_output.txt
|
--out=${DIFF_OUTPUT_DIR} \
|
||||||
|
--diffimg="$(pwd)/out/cmake-release/tools/diffimg/diffimg" \
|
||||||
|
--test="${TEST_DIR}/tests/presubmit.json" 2>&1 | tee compare_output.txt
|
||||||
|
|
||||||
if grep "Failed" compare_output.txt > /dev/null; then
|
if grep "Failed" compare_output.txt > /dev/null; then
|
||||||
DELIMITER="EOF_FILE_CONTENT_$(date +%s)" # Using timestamp to make it more unique
|
DELIMITER="EOF_FILE_CONTENT_$(date +%s)" # Using timestamp to make it more unique
|
||||||
@@ -159,10 +175,12 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
shell: bash
|
shell: bash
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
|
if: steps.check_accept.outputs.accept != 'true'
|
||||||
with:
|
with:
|
||||||
name: presubmit-renderdiff-result
|
name: presubmit-renderdiff-result
|
||||||
path: ./out/renderdiff
|
path: ./out/renderdiff
|
||||||
- name: Compare result
|
- name: Compare result
|
||||||
|
if: steps.check_accept.outputs.accept != 'true'
|
||||||
run: |
|
run: |
|
||||||
ERROR_STR="${{ steps.render_compare.outputs.err }}"
|
ERROR_STR="${{ steps.render_compare.outputs.err }}"
|
||||||
if [ -n "${ERROR_STR}" ]; then
|
if [ -n "${ERROR_STR}" ]; then
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ option(FILAMENT_SUPPORTS_WEBP_TEXTURES "Enable webp texture support for Filament
|
|||||||
# On the regular filament build (where size is of less concern), we enable GTAO by default.
|
# On the regular filament build (where size is of less concern), we enable GTAO by default.
|
||||||
option(FILAMENT_DISABLE_GTAO "Disable GTAO" OFF)
|
option(FILAMENT_DISABLE_GTAO "Disable GTAO" OFF)
|
||||||
|
|
||||||
|
option(FILAMENT_BUILD_TESTING "Build tests" ON)
|
||||||
|
|
||||||
set(FILAMENT_NDK_VERSION "" CACHE STRING
|
set(FILAMENT_NDK_VERSION "" CACHE STRING
|
||||||
"Android NDK version or version prefix to be used when building for Android."
|
"Android NDK version or version prefix to be used when building for Android."
|
||||||
)
|
)
|
||||||
@@ -974,6 +976,7 @@ if (IS_HOST_PLATFORM)
|
|||||||
|
|
||||||
add_subdirectory(${TOOLS}/cmgen)
|
add_subdirectory(${TOOLS}/cmgen)
|
||||||
add_subdirectory(${TOOLS}/cso-lut)
|
add_subdirectory(${TOOLS}/cso-lut)
|
||||||
|
add_subdirectory(${TOOLS}/diffimg)
|
||||||
add_subdirectory(${TOOLS}/filamesh)
|
add_subdirectory(${TOOLS}/filamesh)
|
||||||
add_subdirectory(${TOOLS}/glslminifier)
|
add_subdirectory(${TOOLS}/glslminifier)
|
||||||
add_subdirectory(${TOOLS}/matc)
|
add_subdirectory(${TOOLS}/matc)
|
||||||
|
|||||||
@@ -6,3 +6,6 @@
|
|||||||
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||||
|
|
||||||
## Release notes for next branch cut
|
## Release notes for next branch cut
|
||||||
|
|
||||||
|
- engine: fix crash when using variance shadow maps
|
||||||
|
- materials: better shadow normal-bias calculations [⚠️ **New Material Version**]
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.google.android.filament:filament-android:1.69.3'
|
implementation 'com.google.android.filament:filament-android:1.69.4'
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -39,19 +39,18 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
|||||||
|
|
||||||
| Artifact | Description |
|
| Artifact | Description |
|
||||||
| ------------- | ------------- |
|
| ------------- | ------------- |
|
||||||
| [](https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android) | The Filament rendering engine itself. |
|
| [](https://mvnrepository.com/artifact/com.google.android.filament/filament-android) | The Filament rendering engine itself. |
|
||||||
| [](https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android-debug) | Debug version of `filament-android`. |
|
| [](https://mvnrepository.com/artifact/com.google.android.filament/filament-android-debug) | Debug version of `filament-android`. |
|
||||||
| [](https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/gltfio-android) | A glTF 2.0 loader for Filament, depends on `filament-android`. |
|
| [](https://mvnrepository.com/artifact/com.google.android.filament/gltfio-android) | A glTF 2.0 loader for Filament, depends on `filament-android`. |
|
||||||
| [](https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-utils-android) | KTX loading, Kotlin math, and camera utilities, depends on `gltfio-android`. |
|
| [](https://mvnrepository.com/artifact/com.google.android.filament/filament-utils-android) | KTX loading, Kotlin math, and camera utilities, depends on `gltfio-android`. |
|
||||||
| [](https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android) | A runtime material builder/compiler. This library is large but contains a full shader compiler/validator/optimizer and supports both OpenGL and Vulkan. |
|
| [](https://mvnrepository.com/artifact/com.google.android.filament/filamat-android) | A runtime material builder/compiler. This library is large but contains a full shader compiler/validator/optimizer and supports both OpenGL and Vulkan. |
|
||||||
| [](https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android-lite) | A much smaller alternative to `filamat-android` that can only generate OpenGL shaders. It does not provide validation or optimizations. |
|
|
||||||
|
|
||||||
### iOS
|
### iOS
|
||||||
|
|
||||||
iOS projects can use CocoaPods to install the latest release:
|
iOS projects can use CocoaPods to install the latest release:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pod 'Filament', '~> 1.69.3'
|
pod 'Filament', '~> 1.69.4'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
@@ -89,7 +88,8 @@ pod 'Filament', '~> 1.69.3'
|
|||||||
- OpenGL ES 3.0+ for Android and iOS
|
- OpenGL ES 3.0+ for Android and iOS
|
||||||
- Metal for macOS and iOS
|
- Metal for macOS and iOS
|
||||||
- Vulkan 1.0 for Android, Linux, macOS, and Windows
|
- Vulkan 1.0 for Android, Linux, macOS, and Windows
|
||||||
- WebGL 2.0 for all platforms
|
- WebGPU for Android, Linux, macOS, and Windows
|
||||||
|
- WebGL 2.0 for all browsers supporting it
|
||||||
|
|
||||||
### Rendering
|
### Rendering
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ pod 'Filament', '~> 1.69.3'
|
|||||||
|
|
||||||
- HDR bloom
|
- HDR bloom
|
||||||
- Depth of field bokeh
|
- Depth of field bokeh
|
||||||
- Multiple tone mappers: generic (customizable), ACES, filmic, etc.
|
- Multiple tone mappers: PBR Neutral, AgX, generic (customizable), ACES, filmic, etc.
|
||||||
- Color and tone management: luminance scaling, gamut mapping
|
- Color and tone management: luminance scaling, gamut mapping
|
||||||
- Color grading: exposure, night adaptation, white balance, channel mixer,
|
- Color grading: exposure, night adaptation, white balance, channel mixer,
|
||||||
shadows/mid-tones/highlights, ASC CDL, contrast, saturation, etc.
|
shadows/mid-tones/highlights, ASC CDL, contrast, saturation, etc.
|
||||||
@@ -158,15 +158,16 @@ pod 'Filament', '~> 1.69.3'
|
|||||||
- [x] KHR_draco_mesh_compression
|
- [x] KHR_draco_mesh_compression
|
||||||
- [x] KHR_lights_punctual
|
- [x] KHR_lights_punctual
|
||||||
- [x] KHR_materials_clearcoat
|
- [x] KHR_materials_clearcoat
|
||||||
|
- [x] KHR_materials_dispersion
|
||||||
- [x] KHR_materials_emissive_strength
|
- [x] KHR_materials_emissive_strength
|
||||||
- [x] KHR_materials_ior
|
- [x] KHR_materials_ior
|
||||||
- [x] KHR_materials_pbrSpecularGlossiness
|
- [x] KHR_materials_pbrSpecularGlossiness
|
||||||
- [x] KHR_materials_sheen
|
- [x] KHR_materials_sheen
|
||||||
|
- [x] KHR_materials_specular
|
||||||
- [x] KHR_materials_transmission
|
- [x] KHR_materials_transmission
|
||||||
- [x] KHR_materials_unlit
|
- [x] KHR_materials_unlit
|
||||||
- [x] KHR_materials_variants
|
- [x] KHR_materials_variants
|
||||||
- [x] KHR_materials_volume
|
- [x] KHR_materials_volume
|
||||||
- [x] KHR_materials_specular
|
|
||||||
- [x] KHR_mesh_quantization
|
- [x] KHR_mesh_quantization
|
||||||
- [x] KHR_texture_basisu
|
- [x] KHR_texture_basisu
|
||||||
- [x] KHR_texture_transform
|
- [x] KHR_texture_transform
|
||||||
@@ -331,7 +332,7 @@ and tools.
|
|||||||
- `filamesh`: Mesh converter
|
- `filamesh`: Mesh converter
|
||||||
- `glslminifier`: Minifies GLSL source code
|
- `glslminifier`: Minifies GLSL source code
|
||||||
- `matc`: Material compiler
|
- `matc`: Material compiler
|
||||||
- `filament-matp`: Material parser
|
- `matedit`: Material editor for compiled materials
|
||||||
- `matinfo` Displays information about materials compiled with `matc`
|
- `matinfo` Displays information about materials compiled with `matc`
|
||||||
- `mipgen` Generates a series of miplevels from a source image
|
- `mipgen` Generates a series of miplevels from a source image
|
||||||
- `normal-blending`: Tool to blend normal maps
|
- `normal-blending`: Tool to blend normal maps
|
||||||
|
|||||||
@@ -7,6 +7,13 @@ A new header is inserted each time a *tag* is created.
|
|||||||
Instead, if you are authoring a PR for the main branch, add your release note to
|
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||||
|
|
||||||
|
## v1.69.5
|
||||||
|
|
||||||
|
- engine: fix crash when using variance shadow maps
|
||||||
|
|
||||||
|
## v1.69.4
|
||||||
|
|
||||||
|
|
||||||
## v1.69.3
|
## v1.69.3
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
# Filament Tools Gradle Plugin
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
The **Filament Tools Gradle Plugin** helps integrate Filament into your Android project. It
|
||||||
|
automates the use of Filament's command-line tools (`matc`, `cmgen`, and `filamesh`).
|
||||||
|
|
||||||
|
This plugin handles:
|
||||||
|
- **Material Compilation**: Compiles `.mat` material definitions into `.filamat` binaries.
|
||||||
|
- **IBL Generation**: Generates Image-Based Lighting assets from HDR environment maps.
|
||||||
|
- **Mesh Compilation**: Converts models into Filament's efficient `.filamesh` binary format. *Note:
|
||||||
|
This tool is no longer recommended; instead, use glTF and Filament's `gltfio` library for model
|
||||||
|
loading.*
|
||||||
|
|
||||||
|
The plugin hooks directly into the Android build lifecycle (via `preBuild`) and supports incremental
|
||||||
|
builds, so assets are only recompiled when source files change.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Apply the plugin in your module's `build.gradle` file and configure the `filament` block. You can
|
||||||
|
specify inputs and outputs for any combination of materials, IBLs, or meshes. If a path is not
|
||||||
|
configured, the corresponding task will be disabled.
|
||||||
|
|
||||||
|
### Example Configuration
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
plugins {
|
||||||
|
id 'filament-plugin'
|
||||||
|
}
|
||||||
|
|
||||||
|
filament {
|
||||||
|
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
||||||
|
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
||||||
|
|
||||||
|
iblInputFile = project.layout.projectDirectory.file("path/to/environment.hdr")
|
||||||
|
iblOutputDir = project.layout.projectDirectory.dir("src/main/assets/envs")
|
||||||
|
|
||||||
|
meshInputFile = project.layout.projectDirectory.file("path/to/model.obj")
|
||||||
|
meshOutputDir = project.layout.projectDirectory.dir("src/main/assets/models")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration Details
|
||||||
|
|
||||||
|
- **materialInputDir**: The directory containing your source material definitions (`.mat` files).
|
||||||
|
- **materialOutputDir**: The directory where the compiled material files (`.filamat`) will be
|
||||||
|
generated.
|
||||||
|
- **iblInputFile**: The source high-dynamic-range image file (e.g., `.hdr` or `.exr`) used to
|
||||||
|
generate Image Based Lighting assets.
|
||||||
|
- **iblOutputDir**: The directory where the generated IBL assets (typically `.ktx` files) will be
|
||||||
|
placed.
|
||||||
|
- **meshInputFile**: The source mesh file (e.g., `.obj`) to be compiled.
|
||||||
|
- **meshOutputDir**: The directory where the compiled mesh file (`.filamesh`) will be generated.
|
||||||
|
|
||||||
|
Automatically adds tasks to your Android build to compile materials, generate an IBL, and compile a
|
||||||
|
mesh. The plugin hooks into `preBuild` to ensure assets are generated before the application is
|
||||||
|
built.
|
||||||
|
|
||||||
|
### Configuration Flags
|
||||||
|
|
||||||
|
You can control specific compilation options using Gradle properties (e.g., in `gradle.properties`
|
||||||
|
or via command line `-P`).
|
||||||
|
|
||||||
|
- **`com.google.android.filament.exclude-vulkan`** When set to `true`, the Vulkan backend is
|
||||||
|
excluded from the compiled materials. This can be useful to reduce the size of the generated
|
||||||
|
assets if your application does not target Vulkan. *Default: `false` (Vulkan is included)*
|
||||||
|
|
||||||
|
- **`com.google.android.filament.include-webgpu`** When set to `true`, the WebGPU backend is
|
||||||
|
included in the compiled materials. Use this if you intend to use the materials in a context
|
||||||
|
supporting WebGPU. *Default: `false`*
|
||||||
|
|
||||||
|
## Tools Configuration
|
||||||
|
|
||||||
|
The Filament Tools plugin requires some binary tools to be available: `matc`, `cmgen`, and
|
||||||
|
`filamesh`.
|
||||||
|
|
||||||
|
There are three ways to configure Filament tools:
|
||||||
|
|
||||||
|
1. **Point to a local path directly**
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
filament {
|
||||||
|
matc {
|
||||||
|
path = "/path/to/matc"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmgen {
|
||||||
|
path = "/path/to/cmgen"
|
||||||
|
}
|
||||||
|
|
||||||
|
filamesh {
|
||||||
|
path = "/path/to/filamesh"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Point to a Maven artifact**
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
filament {
|
||||||
|
matc {
|
||||||
|
// The minor version (the middle number) must match the Filament dependency's.
|
||||||
|
artifact = 'com.google.android.filament:matc:1.68.5'
|
||||||
|
}
|
||||||
|
|
||||||
|
cmgen {
|
||||||
|
artifact = 'com.google.android.filament:cmgen:1.68.5'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
*Note that the `filamesh` artifact is not hosted on Maven Central, so it must be provided locally or
|
||||||
|
via another mechanism if needed.*
|
||||||
|
|
||||||
|
Gradle will automatically handle downloading the tool appropriate for your machine (MacOS/Linux/Windows) from Maven.
|
||||||
|
|
||||||
|
3. **Set the `com.google.android.filament.tools-dir` Gradle property**
|
||||||
|
|
||||||
|
This will override any other configuration. Gradle will attempt to locate the tools under
|
||||||
|
`<tools-dir>/bin` (e.g., `.../bin/matc`, `.../bin/cmgen`, `.../bin/filamesh`).
|
||||||
@@ -4,13 +4,18 @@ plugins {
|
|||||||
|
|
||||||
gradlePlugin {
|
gradlePlugin {
|
||||||
plugins {
|
plugins {
|
||||||
create("filament-tools-plugin") {
|
create("filament-plugin") {
|
||||||
id = "filament-tools-plugin"
|
id = "filament-plugin"
|
||||||
implementationClass = "FilamentToolsPlugin"
|
implementationClass = "com.google.android.filament.gradle.FilamentPlugin"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "com.google.gradle:osdetector-gradle-plugin:1.7.3"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,359 +0,0 @@
|
|||||||
// This plugin accepts the following parameters:
|
|
||||||
//
|
|
||||||
// com.google.android.filament.tools-dir
|
|
||||||
// Path to the Filament distribution/install directory for desktop.
|
|
||||||
// This directory must contain bin/matc.
|
|
||||||
//
|
|
||||||
// com.google.android.filament.exclude-vulkan
|
|
||||||
// When set, support for Vulkan will be excluded.
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
// ./gradlew -Pcom.google.android.filament.tools-dir=../../dist-release assembleDebug
|
|
||||||
|
|
||||||
import org.gradle.api.DefaultTask
|
|
||||||
import org.gradle.api.GradleException
|
|
||||||
import org.gradle.api.Plugin
|
|
||||||
import org.gradle.api.Project
|
|
||||||
import org.gradle.api.provider.ProviderFactory
|
|
||||||
import org.gradle.api.file.DirectoryProperty
|
|
||||||
import org.gradle.api.file.FileSystemOperations
|
|
||||||
import org.gradle.api.file.FileType
|
|
||||||
import org.gradle.api.file.RegularFileProperty
|
|
||||||
import org.gradle.api.logging.LogLevel
|
|
||||||
import org.gradle.api.logging.Logger
|
|
||||||
import org.gradle.api.provider.Property
|
|
||||||
import org.gradle.api.tasks.Input
|
|
||||||
import org.gradle.api.tasks.InputDirectory
|
|
||||||
import org.gradle.api.tasks.InputFile
|
|
||||||
import org.gradle.api.tasks.Optional
|
|
||||||
import org.gradle.api.tasks.OutputDirectory
|
|
||||||
import org.gradle.api.tasks.TaskAction
|
|
||||||
import org.gradle.api.tasks.incremental.InputFileDetails
|
|
||||||
import org.gradle.api.model.ObjectFactory
|
|
||||||
import org.gradle.internal.os.OperatingSystem
|
|
||||||
import org.gradle.process.ExecOperations
|
|
||||||
import org.gradle.work.ChangeType
|
|
||||||
import org.gradle.work.Incremental
|
|
||||||
import org.gradle.work.InputChanges
|
|
||||||
|
|
||||||
import java.nio.file.Paths
|
|
||||||
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
abstract class TaskWithBinary extends DefaultTask {
|
|
||||||
private final String binaryName
|
|
||||||
private Property<String> binaryPath = null
|
|
||||||
|
|
||||||
TaskWithBinary(String name) {
|
|
||||||
binaryName = name
|
|
||||||
}
|
|
||||||
|
|
||||||
@Inject abstract ObjectFactory getObjects()
|
|
||||||
@Inject abstract ProviderFactory getProviders()
|
|
||||||
|
|
||||||
@Input
|
|
||||||
Property<String> getBinary() {
|
|
||||||
if (binaryPath == null) {
|
|
||||||
def tool = ["/bin/${binaryName}.exe", "/bin/${binaryName}"]
|
|
||||||
def fullPath = tool.collect { path ->
|
|
||||||
def filamentToolsPath = providers
|
|
||||||
.gradleProperty("com.google.android.filament.tools-dir")
|
|
||||||
.forUseAtConfigurationTime().get()
|
|
||||||
def directory = objects.fileProperty()
|
|
||||||
.fileValue(new File(filamentToolsPath)).getAsFile().get()
|
|
||||||
Paths.get(directory.absolutePath, path).toFile()
|
|
||||||
}
|
|
||||||
|
|
||||||
binaryPath = objects.property(String.class)
|
|
||||||
binaryPath.set(
|
|
||||||
(OperatingSystem.current().isWindows() ? fullPath[0] : fullPath[1]).toString())
|
|
||||||
}
|
|
||||||
return binaryPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LogOutputStream extends ByteArrayOutputStream {
|
|
||||||
private final Logger logger
|
|
||||||
private final LogLevel level
|
|
||||||
|
|
||||||
LogOutputStream(Logger logger, LogLevel level) {
|
|
||||||
this.logger = logger
|
|
||||||
this.level = level
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void flush() {
|
|
||||||
logger.log(level, toString())
|
|
||||||
reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Custom task to compile material files using matc
|
|
||||||
// This task handles incremental builds
|
|
||||||
abstract class MaterialCompiler extends TaskWithBinary {
|
|
||||||
@Incremental
|
|
||||||
@InputDirectory
|
|
||||||
abstract DirectoryProperty getInputDir()
|
|
||||||
|
|
||||||
@OutputDirectory
|
|
||||||
abstract DirectoryProperty getOutputDir()
|
|
||||||
|
|
||||||
@Inject abstract FileSystemOperations getFs()
|
|
||||||
@Inject abstract ExecOperations getExec()
|
|
||||||
@Inject abstract ObjectFactory getObjects()
|
|
||||||
@Inject abstract ProviderFactory getProviders()
|
|
||||||
|
|
||||||
MaterialCompiler() {
|
|
||||||
super("matc")
|
|
||||||
}
|
|
||||||
|
|
||||||
@TaskAction
|
|
||||||
void execute(InputChanges inputs) {
|
|
||||||
if (!inputs.incremental) {
|
|
||||||
fs.delete({
|
|
||||||
delete(objects.fileTree().from(outputDir).matching { include '*.filamat' })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
inputs.getFileChanges(inputDir).each { InputFileDetails change ->
|
|
||||||
if (change.fileType == FileType.DIRECTORY) return
|
|
||||||
|
|
||||||
def file = change.file
|
|
||||||
|
|
||||||
if (change.changeType == ChangeType.REMOVED) {
|
|
||||||
getOutputFile(file).delete()
|
|
||||||
} else {
|
|
||||||
def out = new LogOutputStream(logger, LogLevel.LIFECYCLE)
|
|
||||||
def err = new LogOutputStream(logger, LogLevel.ERROR)
|
|
||||||
|
|
||||||
def header = ("Compiling material " + file + "\n").getBytes()
|
|
||||||
out.write(header)
|
|
||||||
out.flush()
|
|
||||||
|
|
||||||
if (!new File(binary.get()).exists()) {
|
|
||||||
throw new GradleException("Could not find ${binary.get()}." +
|
|
||||||
" Ensure Filament has been built/installed before building this app.")
|
|
||||||
}
|
|
||||||
|
|
||||||
def matcArgs = []
|
|
||||||
def exclude_vulkan = providers
|
|
||||||
.gradleProperty("com.google.android.filament.exclude-vulkan")
|
|
||||||
.forUseAtConfigurationTime().present
|
|
||||||
if (!exclude_vulkan) {
|
|
||||||
matcArgs += ['-a', 'vulkan']
|
|
||||||
}
|
|
||||||
def include_webgpu = providers
|
|
||||||
.gradleProperty("com.google.android.filament.include-webgpu")
|
|
||||||
.forUseAtConfigurationTime().present
|
|
||||||
if (include_webgpu) {
|
|
||||||
matcArgs += ['-a', 'webgpu', '--variant-filter=stereo']
|
|
||||||
}
|
|
||||||
|
|
||||||
def mat_no_opt = providers
|
|
||||||
.gradleProperty("com.google.android.filament.matnopt")
|
|
||||||
.forUseAtConfigurationTime().present
|
|
||||||
if (mat_no_opt) {
|
|
||||||
matcArgs += ['-g']
|
|
||||||
}
|
|
||||||
|
|
||||||
matcArgs += ['-a', 'opengl', '-p', 'mobile', '-o', getOutputFile(file), file]
|
|
||||||
|
|
||||||
exec.exec {
|
|
||||||
standardOutput out
|
|
||||||
errorOutput err
|
|
||||||
executable "${binary.get()}"
|
|
||||||
args matcArgs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
File getOutputFile(final File file) {
|
|
||||||
return outputDir.file(file.name[0..file.name.lastIndexOf('.')] + 'filamat').get().asFile
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Custom task to process IBLs using cmgen
|
|
||||||
// This task handles incremental builds
|
|
||||||
abstract class IblGenerator extends TaskWithBinary {
|
|
||||||
@Input
|
|
||||||
@Optional
|
|
||||||
abstract Property<String> getCmgenArgs()
|
|
||||||
|
|
||||||
@Incremental
|
|
||||||
@InputFile
|
|
||||||
abstract RegularFileProperty getInputFile()
|
|
||||||
|
|
||||||
@OutputDirectory
|
|
||||||
abstract DirectoryProperty getOutputDir()
|
|
||||||
|
|
||||||
@Inject abstract FileSystemOperations getFs()
|
|
||||||
@Inject abstract ExecOperations getExec()
|
|
||||||
@Inject abstract ObjectFactory getObjects()
|
|
||||||
|
|
||||||
IblGenerator() {
|
|
||||||
super("cmgen")
|
|
||||||
}
|
|
||||||
|
|
||||||
@TaskAction
|
|
||||||
void execute(InputChanges inputs) {
|
|
||||||
if (!inputs.incremental) {
|
|
||||||
fs.delete({
|
|
||||||
delete(objects.fileTree().from(outputDir).matching { include '*' })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
inputs.getFileChanges(inputFile).each { InputFileDetails change ->
|
|
||||||
if (change.fileType == FileType.DIRECTORY) return
|
|
||||||
|
|
||||||
def file = change.file
|
|
||||||
|
|
||||||
if (change.changeType == ChangeType.REMOVED) {
|
|
||||||
getOutputFile(file).delete()
|
|
||||||
} else {
|
|
||||||
def out = new LogOutputStream(logger, LogLevel.LIFECYCLE)
|
|
||||||
def err = new LogOutputStream(logger, LogLevel.ERROR)
|
|
||||||
|
|
||||||
def header = ("Generating IBL " + file + "\n").getBytes()
|
|
||||||
out.write(header)
|
|
||||||
out.flush()
|
|
||||||
|
|
||||||
if (!new File(binary.get()).exists()) {
|
|
||||||
throw new GradleException("Could not find ${binary.get()}." +
|
|
||||||
" Ensure Filament has been built/installed before building this app.")
|
|
||||||
}
|
|
||||||
|
|
||||||
def outputPath = outputDir.get().asFile
|
|
||||||
def commandArgs = cmgenArgs.getOrNull()
|
|
||||||
if (commandArgs == null) {
|
|
||||||
commandArgs =
|
|
||||||
'-q -x ' + outputPath + ' --format=rgb32f ' +
|
|
||||||
'--extract-blur=0.08 --extract=' + outputPath.absolutePath
|
|
||||||
}
|
|
||||||
commandArgs = commandArgs + " " + file
|
|
||||||
|
|
||||||
exec.exec {
|
|
||||||
standardOutput out
|
|
||||||
errorOutput err
|
|
||||||
executable "${binary.get()}"
|
|
||||||
args(commandArgs.split())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
File getOutputFile(final File file) {
|
|
||||||
return outputDir.file(file.name[0..file.name.lastIndexOf('.') - 1]).get().asFile
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Custom task to compile mesh files using filamesh
|
|
||||||
// This task handles incremental builds
|
|
||||||
abstract class MeshCompiler extends TaskWithBinary {
|
|
||||||
@Incremental
|
|
||||||
@InputFile
|
|
||||||
abstract RegularFileProperty getInputFile()
|
|
||||||
|
|
||||||
@OutputDirectory
|
|
||||||
abstract DirectoryProperty getOutputDir()
|
|
||||||
|
|
||||||
@Inject abstract FileSystemOperations getFs()
|
|
||||||
@Inject abstract ExecOperations getExec()
|
|
||||||
|
|
||||||
MeshCompiler() {
|
|
||||||
super("filamesh")
|
|
||||||
}
|
|
||||||
|
|
||||||
@TaskAction
|
|
||||||
void execute(InputChanges inputs) {
|
|
||||||
if (!inputs.incremental) {
|
|
||||||
fs.delete({
|
|
||||||
delete(objects.fileTree().from(outputDir).matching { include '*.filamesh' })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
inputs.getFileChanges(inputFile).each { InputFileDetails change ->
|
|
||||||
if (change.fileType == FileType.DIRECTORY) return
|
|
||||||
|
|
||||||
def file = change.file
|
|
||||||
|
|
||||||
if (change.changeType == ChangeType.REMOVED) {
|
|
||||||
getOutputFile(file).delete()
|
|
||||||
} else {
|
|
||||||
def out = new LogOutputStream(logger, LogLevel.LIFECYCLE)
|
|
||||||
def err = new LogOutputStream(logger, LogLevel.ERROR)
|
|
||||||
|
|
||||||
def header = ("Compiling mesh " + file + "\n").getBytes()
|
|
||||||
out.write(header)
|
|
||||||
out.flush()
|
|
||||||
|
|
||||||
if (!new File(binary.get()).exists()) {
|
|
||||||
throw new GradleException("Could not find ${binary.get()}." +
|
|
||||||
" Ensure Filament has been built/installed before building this app.")
|
|
||||||
}
|
|
||||||
|
|
||||||
exec.exec {
|
|
||||||
standardOutput out
|
|
||||||
errorOutput err
|
|
||||||
executable "${binary.get()}"
|
|
||||||
args(file, getOutputFile(file))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
File getOutputFile(final File file) {
|
|
||||||
return outputDir.file(file.name[0..file.name.lastIndexOf('.')] + 'filamesh').get().asFile
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FilamentToolsPluginExtension {
|
|
||||||
DirectoryProperty materialInputDir
|
|
||||||
DirectoryProperty materialOutputDir
|
|
||||||
|
|
||||||
String cmgenArgs
|
|
||||||
RegularFileProperty iblInputFile
|
|
||||||
DirectoryProperty iblOutputDir
|
|
||||||
|
|
||||||
RegularFileProperty meshInputFile
|
|
||||||
DirectoryProperty meshOutputDir
|
|
||||||
}
|
|
||||||
|
|
||||||
class FilamentToolsPlugin implements Plugin<Project> {
|
|
||||||
void apply(Project project) {
|
|
||||||
def extension = project.extensions.create('filamentTools', FilamentToolsPluginExtension)
|
|
||||||
extension.materialInputDir = project.objects.directoryProperty()
|
|
||||||
extension.materialOutputDir = project.objects.directoryProperty()
|
|
||||||
extension.iblInputFile = project.objects.fileProperty()
|
|
||||||
extension.iblOutputDir = project.objects.directoryProperty()
|
|
||||||
extension.meshInputFile = project.objects.fileProperty()
|
|
||||||
extension.meshOutputDir = project.objects.directoryProperty()
|
|
||||||
|
|
||||||
project.tasks.register("filamentCompileMaterials", MaterialCompiler) {
|
|
||||||
enabled =
|
|
||||||
extension.materialInputDir.isPresent() &&
|
|
||||||
extension.materialOutputDir.isPresent()
|
|
||||||
inputDir.set(extension.materialInputDir.getOrNull())
|
|
||||||
outputDir.set(extension.materialOutputDir.getOrNull())
|
|
||||||
}
|
|
||||||
|
|
||||||
project.preBuild.dependsOn "filamentCompileMaterials"
|
|
||||||
|
|
||||||
project.tasks.register("filamentGenerateIbl", IblGenerator) {
|
|
||||||
enabled = extension.iblInputFile.isPresent() && extension.iblOutputDir.isPresent()
|
|
||||||
cmgenArgs = extension.cmgenArgs
|
|
||||||
inputFile = extension.iblInputFile.getOrNull()
|
|
||||||
outputDir = extension.iblOutputDir.getOrNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
project.preBuild.dependsOn "filamentGenerateIbl"
|
|
||||||
|
|
||||||
project.tasks.register("filamentCompileMesh", MeshCompiler) {
|
|
||||||
enabled = extension.meshInputFile.isPresent() && extension.meshOutputDir.isPresent()
|
|
||||||
inputFile = extension.meshInputFile.getOrNull()
|
|
||||||
outputDir = extension.meshOutputDir.getOrNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
project.preBuild.dependsOn "filamentCompileMesh"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.filament.gradle
|
||||||
|
|
||||||
|
import org.gradle.api.Action
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.file.DirectoryProperty
|
||||||
|
import org.gradle.api.file.RegularFileProperty
|
||||||
|
import org.gradle.api.provider.Property
|
||||||
|
|
||||||
|
class FilamentExtension {
|
||||||
|
final ToolsLocator tools
|
||||||
|
final DirectoryProperty materialInputDir
|
||||||
|
final DirectoryProperty materialOutputDir
|
||||||
|
final Property<String> cmgenArgs
|
||||||
|
final RegularFileProperty iblInputFile
|
||||||
|
final DirectoryProperty iblOutputDir
|
||||||
|
final RegularFileProperty meshInputFile
|
||||||
|
final DirectoryProperty meshOutputDir
|
||||||
|
|
||||||
|
FilamentExtension(Project project) {
|
||||||
|
this.tools = new ToolsLocator(project)
|
||||||
|
this.materialInputDir = project.objects.directoryProperty()
|
||||||
|
this.materialOutputDir = project.objects.directoryProperty()
|
||||||
|
this.cmgenArgs = project.objects.property(String)
|
||||||
|
this.iblInputFile = project.objects.fileProperty()
|
||||||
|
this.iblOutputDir = project.objects.directoryProperty()
|
||||||
|
this.meshInputFile = project.objects.fileProperty()
|
||||||
|
this.meshOutputDir = project.objects.directoryProperty()
|
||||||
|
}
|
||||||
|
|
||||||
|
void matc(Action<ToolsLocator.ToolConfig> action) {
|
||||||
|
action.execute(tools.matc)
|
||||||
|
}
|
||||||
|
|
||||||
|
void cmgen(Action<ToolsLocator.ToolConfig> action) {
|
||||||
|
action.execute(tools.cmgen)
|
||||||
|
}
|
||||||
|
|
||||||
|
void filamesh(Action<ToolsLocator.ToolConfig> action) {
|
||||||
|
action.execute(tools.filamesh)
|
||||||
|
}
|
||||||
|
}
|
||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.filament.gradle
|
||||||
|
|
||||||
|
import org.gradle.api.Plugin
|
||||||
|
import org.gradle.api.Project
|
||||||
|
|
||||||
|
class FilamentPlugin implements Plugin<Project> {
|
||||||
|
@Override
|
||||||
|
void apply(Project project) {
|
||||||
|
project.pluginManager.apply("com.google.osdetector")
|
||||||
|
|
||||||
|
FilamentExtension extension = project.extensions.create("filament", FilamentExtension, project)
|
||||||
|
|
||||||
|
project.afterEvaluate {
|
||||||
|
extension.tools.resolve(project)
|
||||||
|
|
||||||
|
project.tasks.register("filamentCompileMaterials", MaterialCompileTask) {
|
||||||
|
enabled = extension.materialInputDir.isPresent() && extension.materialOutputDir.isPresent()
|
||||||
|
inputDir.set(extension.materialInputDir.getOrNull())
|
||||||
|
outputDir.set(extension.materialOutputDir.getOrNull())
|
||||||
|
matcTool.from(extension.tools.matcToolFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
project.tasks.register("filamentGenerateIbl", IblGenerateTask) {
|
||||||
|
enabled = extension.iblInputFile.isPresent() && extension.iblOutputDir.isPresent()
|
||||||
|
cmgenArgs = extension.cmgenArgs
|
||||||
|
inputFile.set(extension.iblInputFile.getOrNull())
|
||||||
|
outputDir.set(extension.iblOutputDir.getOrNull())
|
||||||
|
cmgenTool.from(extension.tools.cmgenToolFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
project.tasks.register("filamentCompileMesh", MeshCompileTask) {
|
||||||
|
enabled = extension.meshInputFile.isPresent() && extension.meshOutputDir.isPresent()
|
||||||
|
inputFile = extension.meshInputFile.getOrNull()
|
||||||
|
outputDir = extension.meshOutputDir.getOrNull()
|
||||||
|
filameshTool.from(extension.tools.filameshToolFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
project.preBuild.dependsOn "filamentCompileMaterials"
|
||||||
|
project.preBuild.dependsOn "filamentGenerateIbl"
|
||||||
|
project.preBuild.dependsOn "filamentCompileMesh"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+126
@@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.filament.gradle
|
||||||
|
|
||||||
|
import org.gradle.api.DefaultTask
|
||||||
|
import org.gradle.api.GradleException
|
||||||
|
import org.gradle.api.file.ConfigurableFileCollection
|
||||||
|
import org.gradle.api.file.DirectoryProperty
|
||||||
|
import org.gradle.api.file.FileSystemOperations
|
||||||
|
import org.gradle.api.file.FileType
|
||||||
|
import org.gradle.api.file.RegularFileProperty
|
||||||
|
import org.gradle.api.model.ObjectFactory
|
||||||
|
import org.gradle.api.provider.Property
|
||||||
|
import org.gradle.api.tasks.Input
|
||||||
|
import org.gradle.api.tasks.InputFile
|
||||||
|
import org.gradle.api.tasks.InputFiles
|
||||||
|
import org.gradle.api.tasks.Optional
|
||||||
|
import org.gradle.api.tasks.OutputDirectory
|
||||||
|
import org.gradle.api.tasks.TaskAction
|
||||||
|
import org.gradle.process.ExecOperations
|
||||||
|
import org.gradle.work.ChangeType
|
||||||
|
import org.gradle.work.Incremental
|
||||||
|
import org.gradle.work.InputChanges
|
||||||
|
import org.gradle.api.tasks.incremental.InputFileDetails
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
abstract class IblGenerateTask extends DefaultTask {
|
||||||
|
|
||||||
|
@Input
|
||||||
|
@Optional
|
||||||
|
abstract Property<String> getCmgenArgs()
|
||||||
|
|
||||||
|
@Incremental
|
||||||
|
@InputFile
|
||||||
|
abstract RegularFileProperty getInputFile()
|
||||||
|
|
||||||
|
@OutputDirectory
|
||||||
|
abstract DirectoryProperty getOutputDir()
|
||||||
|
|
||||||
|
@InputFiles
|
||||||
|
abstract ConfigurableFileCollection getCmgenTool()
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
abstract FileSystemOperations getFileSystemOperations()
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
abstract ExecOperations getExecOperations()
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
abstract ObjectFactory getObjectFactory()
|
||||||
|
|
||||||
|
@TaskAction
|
||||||
|
void execute(InputChanges inputs) {
|
||||||
|
if (cmgenTool.empty) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"cmgen executable not configured. Please configure the 'cmgen' block in the " +
|
||||||
|
"'filament' extension or set the 'com.google.android.filament.tools-dir' " +
|
||||||
|
"property."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
File cmgen = getCmgenTool().singleFile
|
||||||
|
if (!cmgen.exists()) {
|
||||||
|
throw new IllegalStateException("cmgen executable does not exist: ${cmgen.absolutePath}")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cmgen.canExecute()) {
|
||||||
|
cmgen.setExecutable(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inputs.incremental) {
|
||||||
|
getFileSystemOperations().delete {
|
||||||
|
delete(getObjectFactory().fileTree().from(getOutputDir()).matching { include '*' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs.getFileChanges(getInputFile()).each { InputFileDetails change ->
|
||||||
|
if (change.fileType == FileType.DIRECTORY) return
|
||||||
|
|
||||||
|
def file = change.file
|
||||||
|
|
||||||
|
if (change.changeType == ChangeType.REMOVED) {
|
||||||
|
computeOutputFile(file).delete()
|
||||||
|
} else {
|
||||||
|
println "Generating IBL: ${file.name}"
|
||||||
|
|
||||||
|
def outputPath = getOutputDir().get().asFile
|
||||||
|
def commandArgs = getCmgenArgs().getOrNull()
|
||||||
|
if (commandArgs == null) {
|
||||||
|
// Default args if not provided
|
||||||
|
commandArgs = '-q -x ' + outputPath + ' --format=rgb32f ' +
|
||||||
|
'--extract-blur=0.08 --extract=' + outputPath.absolutePath
|
||||||
|
}
|
||||||
|
|
||||||
|
def argsList = commandArgs.split(' ').toList()
|
||||||
|
argsList.add(file.absolutePath)
|
||||||
|
|
||||||
|
getExecOperations().exec { spec ->
|
||||||
|
spec.executable(cmgen)
|
||||||
|
spec.args(argsList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File computeOutputFile(final File file) {
|
||||||
|
String name = file.name
|
||||||
|
int dotIndex = name.lastIndexOf('.')
|
||||||
|
String baseName = dotIndex > 0 ? name.substring(0, dotIndex) : name
|
||||||
|
return getOutputDir().file(baseName).get().asFile
|
||||||
|
}
|
||||||
|
}
|
||||||
+147
@@ -0,0 +1,147 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.filament.gradle
|
||||||
|
|
||||||
|
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
|
||||||
|
import org.gradle.api.DefaultTask
|
||||||
|
import org.gradle.api.file.ConfigurableFileCollection
|
||||||
|
import org.gradle.api.file.DirectoryProperty
|
||||||
|
import org.gradle.api.file.FileSystemOperations
|
||||||
|
import org.gradle.api.file.FileType
|
||||||
|
import org.gradle.api.model.ObjectFactory
|
||||||
|
import org.gradle.api.provider.ProviderFactory
|
||||||
|
import org.gradle.api.tasks.InputDirectory
|
||||||
|
import org.gradle.api.tasks.InputFiles
|
||||||
|
import org.gradle.api.tasks.OutputDirectory
|
||||||
|
import org.gradle.api.tasks.PathSensitive
|
||||||
|
import org.gradle.api.tasks.PathSensitivity
|
||||||
|
import org.gradle.api.tasks.SkipWhenEmpty
|
||||||
|
import org.gradle.api.tasks.TaskAction
|
||||||
|
import org.gradle.process.ExecOperations
|
||||||
|
import org.gradle.work.ChangeType
|
||||||
|
import org.gradle.work.Incremental
|
||||||
|
import org.gradle.work.InputChanges
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
abstract class MaterialCompileTask extends DefaultTask {
|
||||||
|
|
||||||
|
@Incremental
|
||||||
|
@InputDirectory
|
||||||
|
@PathSensitive(PathSensitivity.RELATIVE)
|
||||||
|
abstract DirectoryProperty getInputDir()
|
||||||
|
|
||||||
|
@OutputDirectory
|
||||||
|
abstract DirectoryProperty getOutputDir()
|
||||||
|
|
||||||
|
@InputFiles
|
||||||
|
@PathSensitive(PathSensitivity.NONE)
|
||||||
|
abstract ConfigurableFileCollection getMatcTool()
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
abstract ExecOperations getExecOperations()
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
abstract FileSystemOperations getFileSystemOperations()
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
abstract ObjectFactory getObjectFactory()
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
abstract ProviderFactory getProviderFactory()
|
||||||
|
|
||||||
|
@TaskAction
|
||||||
|
void compile(InputChanges inputs) {
|
||||||
|
if (matcTool.empty) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"matc executable not configured. Please configure the 'matc' block in the " +
|
||||||
|
"'filament' extension or set the 'com.google.android.filament.tools-dir' " +
|
||||||
|
"property."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
File matc = matcTool.singleFile
|
||||||
|
if (!matc.exists()) {
|
||||||
|
throw new IllegalStateException("matc executable does not exist: ${matc.absolutePath}")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!matc.canExecute()) {
|
||||||
|
matc.setExecutable(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inputs.incremental) {
|
||||||
|
getFileSystemOperations().delete {
|
||||||
|
delete(getObjectFactory().fileTree().from(getOutputDir()).matching {
|
||||||
|
include '*.filamat'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def pf = getProviderFactory()
|
||||||
|
def excludeVulkanProperty = pf.gradleProperty("com.google.android.filament.exclude-vulkan")
|
||||||
|
def includeWebGpuProperty = pf.gradleProperty("com.google.android.filament.include-webgpu")
|
||||||
|
def matNoOptProperty = pf.gradleProperty("com.google.android.filament.matnopt")
|
||||||
|
def excludeVulkan = excludeVulkanProperty.orNull == "true"
|
||||||
|
def includeWebGpu = includeWebGpuProperty.orNull == "true"
|
||||||
|
def matNoOpt = matNoOptProperty.orNull == "true"
|
||||||
|
|
||||||
|
inputs.getFileChanges(getInputDir()).each { change ->
|
||||||
|
if (change.fileType == FileType.DIRECTORY) return
|
||||||
|
|
||||||
|
File file = change.file
|
||||||
|
File outputFile = computeOutputFile(file)
|
||||||
|
|
||||||
|
if (change.changeType == ChangeType.REMOVED) {
|
||||||
|
outputFile.delete()
|
||||||
|
} else {
|
||||||
|
println "Compiling material: ${file.name}"
|
||||||
|
|
||||||
|
def args = []
|
||||||
|
if (!excludeVulkan) {
|
||||||
|
args += ['-a', 'vulkan']
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeWebGpu) {
|
||||||
|
args += ['-a', 'webgpu', '--variant-filter=stereo']
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matNoOpt) {
|
||||||
|
args += ['-g']
|
||||||
|
}
|
||||||
|
|
||||||
|
args += [
|
||||||
|
'-a', 'opengl', '-p', 'mobile',
|
||||||
|
'-o', outputFile.absolutePath,
|
||||||
|
file.absolutePath
|
||||||
|
]
|
||||||
|
|
||||||
|
getExecOperations().exec { spec ->
|
||||||
|
spec.executable(matc)
|
||||||
|
spec.args(args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File computeOutputFile(File inputFile) {
|
||||||
|
String baseName = inputFile.name
|
||||||
|
int dotIndex = baseName.lastIndexOf('.')
|
||||||
|
if (dotIndex > 0) {
|
||||||
|
baseName = baseName.substring(0, dotIndex)
|
||||||
|
}
|
||||||
|
return getOutputDir().file("${baseName}.filamat").get().asFile
|
||||||
|
}
|
||||||
|
}
|
||||||
+115
@@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.filament.gradle
|
||||||
|
|
||||||
|
import org.gradle.api.DefaultTask
|
||||||
|
import org.gradle.api.file.ConfigurableFileCollection
|
||||||
|
import org.gradle.api.file.DirectoryProperty
|
||||||
|
import org.gradle.api.file.FileSystemOperations
|
||||||
|
import org.gradle.api.file.FileType
|
||||||
|
import org.gradle.api.file.RegularFileProperty
|
||||||
|
import org.gradle.api.model.ObjectFactory
|
||||||
|
import org.gradle.api.tasks.InputFile
|
||||||
|
import org.gradle.api.tasks.InputFiles
|
||||||
|
import org.gradle.api.tasks.OutputDirectory
|
||||||
|
import org.gradle.api.tasks.PathSensitive
|
||||||
|
import org.gradle.api.tasks.PathSensitivity
|
||||||
|
import org.gradle.api.tasks.TaskAction
|
||||||
|
import org.gradle.process.ExecOperations
|
||||||
|
import org.gradle.work.ChangeType
|
||||||
|
import org.gradle.work.Incremental
|
||||||
|
import org.gradle.work.InputChanges
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
abstract class MeshCompileTask extends DefaultTask {
|
||||||
|
|
||||||
|
@Incremental
|
||||||
|
@InputFile
|
||||||
|
@PathSensitive(PathSensitivity.RELATIVE)
|
||||||
|
abstract RegularFileProperty getInputFile()
|
||||||
|
|
||||||
|
@OutputDirectory
|
||||||
|
abstract DirectoryProperty getOutputDir()
|
||||||
|
|
||||||
|
@InputFiles
|
||||||
|
@PathSensitive(PathSensitivity.NONE)
|
||||||
|
abstract ConfigurableFileCollection getFilameshTool()
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
abstract ExecOperations getExecOperations()
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
abstract FileSystemOperations getFileSystemOperations()
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
abstract ObjectFactory getObjectFactory()
|
||||||
|
|
||||||
|
@TaskAction
|
||||||
|
void compile(InputChanges inputs) {
|
||||||
|
if (filameshTool.empty) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"filamesh executable not configured. Please configure the 'filamesh' block in the " +
|
||||||
|
"'filament' extension or set the 'com.google.android.filament.tools-dir' " +
|
||||||
|
"property."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
File filamesh = filameshTool.singleFile
|
||||||
|
if (!filamesh.exists()) {
|
||||||
|
throw new IllegalStateException("filamesh executable does not exist: ${filamesh.absolutePath}")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!filamesh.canExecute()) {
|
||||||
|
filamesh.setExecutable(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inputs.incremental) {
|
||||||
|
getFileSystemOperations().delete {
|
||||||
|
delete(getObjectFactory().fileTree().from(getOutputDir()).matching {
|
||||||
|
include '*.filamesh'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs.getFileChanges(inputFile).each { change ->
|
||||||
|
if (change.fileType == FileType.DIRECTORY) return
|
||||||
|
|
||||||
|
File file = change.file
|
||||||
|
File outputFile = computeOutputFile(file)
|
||||||
|
|
||||||
|
if (change.changeType == ChangeType.REMOVED) {
|
||||||
|
outputFile.delete()
|
||||||
|
} else {
|
||||||
|
println "Compiling mesh: ${file.name}"
|
||||||
|
|
||||||
|
getExecOperations().exec { spec ->
|
||||||
|
spec.executable(filamesh)
|
||||||
|
spec.args(file.absolutePath, outputFile.absolutePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File computeOutputFile(File inputFile) {
|
||||||
|
String baseName = inputFile.name
|
||||||
|
int dotIndex = baseName.lastIndexOf('.')
|
||||||
|
if (dotIndex > 0) {
|
||||||
|
baseName = baseName.substring(0, dotIndex)
|
||||||
|
}
|
||||||
|
return getOutputDir().file("${baseName}.filamesh").get().asFile
|
||||||
|
}
|
||||||
|
}
|
||||||
+113
@@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.filament.gradle
|
||||||
|
|
||||||
|
import org.gradle.api.Action
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.artifacts.Configuration
|
||||||
|
import org.gradle.api.file.FileCollection
|
||||||
|
import org.gradle.internal.os.OperatingSystem
|
||||||
|
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
|
class ToolsLocator {
|
||||||
|
static class ToolConfig {
|
||||||
|
String artifact
|
||||||
|
String path
|
||||||
|
FileCollection files
|
||||||
|
}
|
||||||
|
|
||||||
|
final ToolConfig matc = new ToolConfig()
|
||||||
|
final ToolConfig cmgen = new ToolConfig()
|
||||||
|
final ToolConfig filamesh = new ToolConfig()
|
||||||
|
private final Project project
|
||||||
|
|
||||||
|
ToolsLocator(Project project) {
|
||||||
|
this.project = project
|
||||||
|
}
|
||||||
|
|
||||||
|
void resolve(Project project) {
|
||||||
|
resolveTool(matc, "matc")
|
||||||
|
resolveTool(cmgen, "cmgen")
|
||||||
|
resolveTool(filamesh, "filamesh")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a specific tool by its name and sets the {@link ToolConfig#files} property of the
|
||||||
|
* provided {@link ToolConfig} object. It first attempts to locate the tool based on a Gradle
|
||||||
|
* property `com.google.android.filament.tools-dir` if present, otherwise it resolves the tool
|
||||||
|
* through a Gradle configuration.
|
||||||
|
*
|
||||||
|
* @param tool The {@link ToolConfig} object whose {@code files} property will be set.
|
||||||
|
* @param name The name of the tool (e.g., "matc", "cmgen").
|
||||||
|
*/
|
||||||
|
private void resolveTool(ToolConfig tool, String name) {
|
||||||
|
// Find the OS classifier, e.g. 'osx-aarch_64'.
|
||||||
|
def classifier =
|
||||||
|
project.extensions.getByType(com.google.gradle.osdetector.OsDetector).classifier
|
||||||
|
|
||||||
|
// If com.google.android.filament.tools-dir is set, we'll use it as the tool's base path.
|
||||||
|
def toolsDirProp = project.providers.gradleProperty("com.google.android.filament.tools-dir")
|
||||||
|
if (toolsDirProp.isPresent()) {
|
||||||
|
def toolsDir = toolsDirProp.get()
|
||||||
|
def path = OperatingSystem.current().isWindows() ?
|
||||||
|
"${toolsDir}/bin/${name}.exe" :
|
||||||
|
"${toolsDir}/bin/${name}"
|
||||||
|
tool.files = project.files(path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If an explicit path for the tool is provided in ToolConfig
|
||||||
|
// (e.g. matc { path = 'path/to/tool' }), use it directly.
|
||||||
|
if (tool.path) {
|
||||||
|
tool.files = project.files(tool.path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, if an artifact is provided
|
||||||
|
// (e.g. matc { artifact = 'com.google.android.filament:matc:1.68.5' }), resolve it.
|
||||||
|
if (tool.artifact) {
|
||||||
|
String depString = tool.artifact
|
||||||
|
|
||||||
|
// In Gradle, a configuration is a named, manageable group of dependencies.
|
||||||
|
// Resolve the tool artifact using a detached configuration. A detached configuration
|
||||||
|
// is a temporary, isolated configuration that is not part of the project's regular
|
||||||
|
// configuration hierarchy.
|
||||||
|
Configuration config = project.configurations.detachedConfiguration()
|
||||||
|
config.setTransitive(false) // We only want the tool itself, not its dependencies
|
||||||
|
|
||||||
|
def dep = project.dependencies.create("${depString}:${classifier}@exe")
|
||||||
|
config.dependencies.add(dep)
|
||||||
|
|
||||||
|
// A Gradle Configuration implements FileCollection. When treated as a FileCollection,
|
||||||
|
// it represents the resolved files of its dependencies.
|
||||||
|
tool.files = config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileCollection getMatcToolFiles() {
|
||||||
|
return matc.files ?: project.files()
|
||||||
|
}
|
||||||
|
|
||||||
|
FileCollection getCmgenToolFiles() {
|
||||||
|
return cmgen.files ?: project.files()
|
||||||
|
}
|
||||||
|
|
||||||
|
FileCollection getFilameshToolFiles() {
|
||||||
|
return filamesh.files ?: project.files()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -71,8 +71,10 @@ Java_com_google_android_filament_utils_AutomationEngine_nStartBatchMode(JNIEnv*
|
|||||||
extern "C" JNIEXPORT void JNICALL
|
extern "C" JNIEXPORT void JNICALL
|
||||||
Java_com_google_android_filament_utils_AutomationEngine_nTick(JNIEnv* env, jclass klass,
|
Java_com_google_android_filament_utils_AutomationEngine_nTick(JNIEnv* env, jclass klass,
|
||||||
jlong nativeAutomation, jlong nativeEngine,
|
jlong nativeAutomation, jlong nativeEngine,
|
||||||
jlong view, jlongArray materials, jlong renderer, jfloat deltaTime) {
|
jlong view, jlongArray materials, jlong renderer, jlong nativeIbl, jint sunlightEntity,
|
||||||
|
jintArray assetLights, jlong nativeLm, jlong scene, jfloat deltaTime) {
|
||||||
using MaterialPointer = MaterialInstance*;
|
using MaterialPointer = MaterialInstance*;
|
||||||
|
|
||||||
jsize materialCount = 0;
|
jsize materialCount = 0;
|
||||||
jlong* longMaterials = nullptr;
|
jlong* longMaterials = nullptr;
|
||||||
MaterialPointer* ptrMaterials = nullptr;
|
MaterialPointer* ptrMaterials = nullptr;
|
||||||
@@ -84,12 +86,28 @@ Java_com_google_android_filament_utils_AutomationEngine_nTick(JNIEnv* env, jclas
|
|||||||
ptrMaterials[i] = (MaterialPointer) longMaterials[i];
|
ptrMaterials[i] = (MaterialPointer) longMaterials[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jsize lightCount = 0;
|
||||||
|
jint* intLights = nullptr;
|
||||||
|
if (assetLights) {
|
||||||
|
lightCount = env->GetArrayLength(assetLights);
|
||||||
|
intLights = env->GetIntArrayElements(assetLights, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assert(sizeof(jint) == sizeof(Entity));
|
||||||
|
|
||||||
AutomationEngine* automation = (AutomationEngine*) nativeAutomation;
|
AutomationEngine* automation = (AutomationEngine*) nativeAutomation;
|
||||||
AutomationEngine::ViewerContent content = {
|
AutomationEngine::ViewerContent content = {
|
||||||
.view = (View*) view,
|
.view = (View*) view,
|
||||||
.renderer = (Renderer*) renderer,
|
.renderer = (Renderer*) renderer,
|
||||||
.materials = ptrMaterials,
|
.materials = ptrMaterials,
|
||||||
.materialCount = (size_t) materialCount,
|
.materialCount = (size_t) materialCount,
|
||||||
|
.lightManager = (LightManager*) nativeLm,
|
||||||
|
.scene = (Scene*) scene,
|
||||||
|
.indirectLight = (IndirectLight*) nativeIbl,
|
||||||
|
.sunlight = (Entity&) sunlightEntity,
|
||||||
|
.assetLights = (Entity*) intLights,
|
||||||
|
.assetLightCount = (size_t) lightCount,
|
||||||
};
|
};
|
||||||
Engine* engine = (Engine*)nativeEngine;
|
Engine* engine = (Engine*)nativeEngine;
|
||||||
automation->tick(engine, content, deltaTime);
|
automation->tick(engine, content, deltaTime);
|
||||||
@@ -97,6 +115,9 @@ Java_com_google_android_filament_utils_AutomationEngine_nTick(JNIEnv* env, jclas
|
|||||||
env->ReleaseLongArrayElements(materials, longMaterials, 0);
|
env->ReleaseLongArrayElements(materials, longMaterials, 0);
|
||||||
delete[] ptrMaterials;
|
delete[] ptrMaterials;
|
||||||
}
|
}
|
||||||
|
if (intLights) {
|
||||||
|
env->ReleaseIntArrayElements(assetLights, intLights, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
extern "C" JNIEXPORT void JNICALL
|
||||||
|
|||||||
+8
-2
@@ -178,7 +178,12 @@ public class AutomationEngine {
|
|||||||
}
|
}
|
||||||
long nativeView = content.view.getNativeObject();
|
long nativeView = content.view.getNativeObject();
|
||||||
long nativeRenderer = content.renderer.getNativeObject();
|
long nativeRenderer = content.renderer.getNativeObject();
|
||||||
nTick(mNativeObject, engine.getNativeObject(), nativeView, nativeMaterialInstances, nativeRenderer, deltaTime);
|
long nativeIbl = content.indirectLight == null ? 0 : content.indirectLight.getNativeObject();
|
||||||
|
long nativeLm = content.lightManager == null ? 0 : content.lightManager.getNativeObject();
|
||||||
|
long nativeScene = content.scene == null ? 0 : content.scene.getNativeObject();
|
||||||
|
nTick(mNativeObject, engine.getNativeObject(), nativeView, nativeMaterialInstances,
|
||||||
|
nativeRenderer, nativeIbl, content.sunlight, content.assetLights, nativeLm,
|
||||||
|
nativeScene, deltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -287,7 +292,8 @@ public class AutomationEngine {
|
|||||||
private static native void nStartRunning(long nativeObject);
|
private static native void nStartRunning(long nativeObject);
|
||||||
private static native void nStartBatchMode(long nativeObject);
|
private static native void nStartBatchMode(long nativeObject);
|
||||||
private static native void nTick(long nativeObject, long nativeEngine,
|
private static native void nTick(long nativeObject, long nativeEngine,
|
||||||
long view, long[] materials, long renderer, float deltaTime);
|
long view, long[] materials, long renderer, long ibl, int sunlight, int[] assetLights,
|
||||||
|
long lightManager, long scene, float deltaTime);
|
||||||
private static native void nApplySettings(long nativeObject, long nativeEngine,
|
private static native void nApplySettings(long nativeObject, long nativeEngine,
|
||||||
String jsonSettings, long view,
|
String jsonSettings, long view,
|
||||||
long[] materials, long ibl, int sunlight, int[] assetLights, long lightManager,
|
long[] materials, long ibl, int sunlight, int[] assetLights, long lightManager,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
GROUP=com.google.android.filament
|
GROUP=com.google.android.filament
|
||||||
VERSION_NAME=1.69.3
|
VERSION_NAME=1.69.4
|
||||||
|
|
||||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-tools-plugin'
|
id 'filament-plugin'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
@@ -10,7 +10,7 @@ kotlin {
|
|||||||
jvmToolchain(versions.jdk)
|
jvmToolchain(versions.jdk)
|
||||||
}
|
}
|
||||||
|
|
||||||
filamentTools {
|
filament {
|
||||||
cmgenArgs = "-q --format=ktx --size=256 --extract-blur=0.1 --deploy=src/main/assets/envs/default_env"
|
cmgenArgs = "-q --format=ktx --size=256 --extract-blur=0.1 --deploy=src/main/assets/envs/default_env"
|
||||||
iblInputFile = project.layout.projectDirectory.file("../../../third_party/environments/lightroom_14b.hdr")
|
iblInputFile = project.layout.projectDirectory.file("../../../third_party/environments/lightroom_14b.hdr")
|
||||||
iblOutputDir = project.layout.projectDirectory.dir("src/main/assets/envs")
|
iblOutputDir = project.layout.projectDirectory.dir("src/main/assets/envs")
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-tools-plugin'
|
id 'filament-plugin'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
@@ -10,7 +10,7 @@ kotlin {
|
|||||||
jvmToolchain(versions.jdk)
|
jvmToolchain(versions.jdk)
|
||||||
}
|
}
|
||||||
|
|
||||||
filamentTools {
|
filament {
|
||||||
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
||||||
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-tools-plugin'
|
id 'filament-plugin'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
@@ -10,7 +10,7 @@ kotlin {
|
|||||||
jvmToolchain(versions.jdk)
|
jvmToolchain(versions.jdk)
|
||||||
}
|
}
|
||||||
|
|
||||||
filamentTools {
|
filament {
|
||||||
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
||||||
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-tools-plugin'
|
id 'filament-plugin'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
@@ -10,7 +10,7 @@ kotlin {
|
|||||||
jvmToolchain(versions.jdk)
|
jvmToolchain(versions.jdk)
|
||||||
}
|
}
|
||||||
|
|
||||||
filamentTools {
|
filament {
|
||||||
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
||||||
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-tools-plugin'
|
id 'filament-plugin'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
@@ -10,7 +10,7 @@ kotlin {
|
|||||||
jvmToolchain(versions.jdk)
|
jvmToolchain(versions.jdk)
|
||||||
}
|
}
|
||||||
|
|
||||||
filamentTools {
|
filament {
|
||||||
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
||||||
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-tools-plugin'
|
id 'filament-plugin'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
@@ -10,7 +10,7 @@ kotlin {
|
|||||||
jvmToolchain(versions.jdk)
|
jvmToolchain(versions.jdk)
|
||||||
}
|
}
|
||||||
|
|
||||||
filamentTools {
|
filament {
|
||||||
meshInputFile = project.layout.projectDirectory.file("../../../third_party/models/shader_ball/shader_ball.obj")
|
meshInputFile = project.layout.projectDirectory.file("../../../third_party/models/shader_ball/shader_ball.obj")
|
||||||
meshOutputDir = project.layout.projectDirectory.dir("src/main/assets/models")
|
meshOutputDir = project.layout.projectDirectory.dir("src/main/assets/models")
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-tools-plugin'
|
id 'filament-plugin'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
@@ -10,7 +10,7 @@ kotlin {
|
|||||||
jvmToolchain(versions.jdk)
|
jvmToolchain(versions.jdk)
|
||||||
}
|
}
|
||||||
|
|
||||||
filamentTools {
|
filament {
|
||||||
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
||||||
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-tools-plugin'
|
id 'filament-plugin'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
@@ -10,7 +10,7 @@ kotlin {
|
|||||||
jvmToolchain(versions.jdk)
|
jvmToolchain(versions.jdk)
|
||||||
}
|
}
|
||||||
|
|
||||||
filamentTools {
|
filament {
|
||||||
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
||||||
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-tools-plugin'
|
id 'filament-plugin'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
@@ -10,7 +10,7 @@ kotlin {
|
|||||||
jvmToolchain(versions.jdk)
|
jvmToolchain(versions.jdk)
|
||||||
}
|
}
|
||||||
|
|
||||||
filamentTools {
|
filament {
|
||||||
iblInputFile = project.layout.projectDirectory.file("../../../third_party/environments/studio_small_02_2k.hdr")
|
iblInputFile = project.layout.projectDirectory.file("../../../third_party/environments/studio_small_02_2k.hdr")
|
||||||
iblOutputDir = project.layout.projectDirectory.dir("src/main/assets/envs")
|
iblOutputDir = project.layout.projectDirectory.dir("src/main/assets/envs")
|
||||||
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
|
id 'filament-plugin'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
@@ -9,6 +10,25 @@ kotlin {
|
|||||||
jvmToolchain(versions.jdk)
|
jvmToolchain(versions.jdk)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filament {
|
||||||
|
cmgenArgs = "-q --format=ktx --size=256 --extract-blur=0.1 --deploy=src/main/assets/envs/default_env"
|
||||||
|
iblInputFile = project.layout.projectDirectory.file("../../../third_party/environments/lightroom_14b.hdr")
|
||||||
|
iblOutputDir = project.layout.projectDirectory.dir("src/main/assets/envs")
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't forget to update MainACtivity.kt when/if changing this.
|
||||||
|
tasks.register('copyDamagedHelmetGltf', Copy) {
|
||||||
|
from file("../../../third_party/models/DamagedHelmet/DamagedHelmet.glb")
|
||||||
|
into file("src/main/assets/models")
|
||||||
|
rename {String fileName -> "helmet.glb"}
|
||||||
|
}
|
||||||
|
|
||||||
|
preBuild.dependsOn copyDamagedHelmetGltf
|
||||||
|
|
||||||
|
clean.doFirst {
|
||||||
|
delete "src/main/assets"
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace 'com.google.android.filament.validation'
|
namespace 'com.google.android.filament.validation'
|
||||||
|
|
||||||
@@ -27,9 +47,9 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation deps.kotlin
|
implementation deps.kotlin
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
implementation deps.coroutines.core
|
implementation deps.coroutines.core
|
||||||
implementation project(':filament-android')
|
implementation project(':filament-android')
|
||||||
implementation project(':gltfio-android')
|
implementation project(':gltfio-android')
|
||||||
implementation project(':filament-utils-android')
|
implementation project(':filament-utils-android')
|
||||||
implementation project(':filament-utils-android')
|
|
||||||
}
|
}
|
||||||
|
|||||||
+242
-46
@@ -17,16 +17,36 @@
|
|||||||
package com.google.android.filament.validation
|
package com.google.android.filament.validation
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.graphics.Bitmap
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Choreographer
|
import android.view.Choreographer
|
||||||
import android.view.SurfaceView
|
import android.view.SurfaceView
|
||||||
|
import android.view.View
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.ScrollView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
|
||||||
import com.google.android.filament.utils.ModelViewer
|
import com.google.android.filament.utils.ModelViewer
|
||||||
import com.google.android.filament.utils.Utils
|
import com.google.android.filament.utils.Utils
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import com.google.android.filament.utils.KTX1Loader
|
||||||
|
import com.google.android.filament.IndirectLight
|
||||||
|
import com.google.android.filament.Skybox
|
||||||
|
import android.graphics.Color
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.URL
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.Spinner
|
||||||
|
import android.widget.AdapterView
|
||||||
|
|
||||||
class MainActivity : Activity(), ValidationRunner.Callback {
|
class MainActivity : Activity(), ValidationRunner.Callback {
|
||||||
|
|
||||||
@@ -42,8 +62,15 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
|||||||
private lateinit var choreographer: Choreographer
|
private lateinit var choreographer: Choreographer
|
||||||
private lateinit var modelViewer: ModelViewer
|
private lateinit var modelViewer: ModelViewer
|
||||||
private lateinit var statusTextView: TextView
|
private lateinit var statusTextView: TextView
|
||||||
|
private lateinit var resultsContainer: LinearLayout
|
||||||
|
private lateinit var inputManager: ValidationInputManager
|
||||||
|
private var currentInput: ValidationInputManager.ValidationInput? = null
|
||||||
|
private lateinit var modeSpinner: Spinner
|
||||||
|
private lateinit var runButton: Button
|
||||||
|
private var resultManager: ValidationResultManager? = null
|
||||||
|
|
||||||
private var validationRunner: ValidationRunner? = null
|
private var validationRunner: ValidationRunner? = null
|
||||||
|
|
||||||
// Frame callback
|
// Frame callback
|
||||||
private val frameScheduler = object : Choreographer.FrameCallback {
|
private val frameScheduler = object : Choreographer.FrameCallback {
|
||||||
override fun doFrame(frameTimeNanos: Long) {
|
override fun doFrame(frameTimeNanos: Long) {
|
||||||
@@ -55,58 +82,109 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_main)
|
||||||
// Simple layout
|
|
||||||
val layout = android.widget.FrameLayout(this)
|
// SurfaceView container
|
||||||
surfaceView = SurfaceView(this)
|
surfaceView = findViewById(R.id.surface_view)
|
||||||
layout.addView(surfaceView)
|
surfaceView.holder.setFixedSize(512, 512)
|
||||||
|
|
||||||
statusTextView = TextView(this)
|
statusTextView = findViewById(R.id.status_text)
|
||||||
statusTextView.setTextColor(0xFFFFFFFF.toInt())
|
modeSpinner = findViewById(R.id.mode_spinner)
|
||||||
statusTextView.textSize = 16f
|
runButton = findViewById(R.id.run_button)
|
||||||
statusTextView.setPadding(20, 20, 20, 20)
|
resultsContainer = findViewById(R.id.results_container)
|
||||||
statusTextView.text = "Initializing..."
|
|
||||||
layout.addView(statusTextView)
|
// Setup Spinner
|
||||||
|
val modes = arrayOf("Run Validation", "Generate Goldens")
|
||||||
setContentView(layout)
|
val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, modes)
|
||||||
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
|
modeSpinner.adapter = adapter
|
||||||
|
|
||||||
|
// Setup Run Button
|
||||||
|
runButton.setOnClickListener {
|
||||||
|
currentInput?.let { input ->
|
||||||
|
val generateGoldens = modeSpinner.selectedItemPosition == 1
|
||||||
|
val newInput = input.copy(generateGoldens = generateGoldens)
|
||||||
|
startValidation(newInput)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
|
||||||
choreographer = Choreographer.getInstance()
|
choreographer = Choreographer.getInstance()
|
||||||
modelViewer = ModelViewer(surfaceView)
|
modelViewer = ModelViewer(surfaceView)
|
||||||
|
inputManager = ValidationInputManager(this)
|
||||||
// Check permissions?
|
|
||||||
// We assume 'adb install -g' or permissions granted.
|
// Initialize IBL
|
||||||
// But for scoped storage we might not need PERMISSION if reading from app-specific dirs,
|
createIndirectLight()
|
||||||
// but user mentioned /sdcard/ so we need MANAGE_EXTERNAL_STORAGE or READ_EXTERNAL_STORAGE.
|
|
||||||
// For waiting/simplicity, we just try.
|
|
||||||
|
|
||||||
handleIntent()
|
handleIntent()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleIntent() {
|
private fun createIndirectLight() {
|
||||||
val intent = intent
|
try {
|
||||||
val testConfigPath = intent.getStringExtra("test_config")
|
val engine = modelViewer.engine
|
||||||
|
val scene = modelViewer.scene
|
||||||
if (testConfigPath != null) {
|
val iblName = "default_env"
|
||||||
startValidation(testConfigPath)
|
|
||||||
} else {
|
fun readAsset(path: String): ByteBuffer {
|
||||||
statusTextView.text = "No test_config provided via Intent.\nUse -e test_config <path>"
|
val input = assets.open(path)
|
||||||
Log.w(TAG, "No test config provided")
|
val bytes = input.readBytes()
|
||||||
|
return ByteBuffer.wrap(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
readAsset("envs/$iblName/${iblName}_ibl.ktx").let {
|
||||||
|
val bundle = KTX1Loader.createIndirectLight(engine, it)
|
||||||
|
scene.indirectLight = bundle.indirectLight
|
||||||
|
modelViewer.indirectLightCubemap = bundle.cubemap
|
||||||
|
scene.indirectLight!!.intensity = 30_000.0f
|
||||||
|
}
|
||||||
|
|
||||||
|
readAsset("envs/$iblName/${iblName}_skybox.ktx").let {
|
||||||
|
val bundle = KTX1Loader.createSkybox(engine, it)
|
||||||
|
scene.skybox = bundle.skybox
|
||||||
|
modelViewer.skyboxCubemap = bundle.cubemap
|
||||||
|
}
|
||||||
|
Log.i(TAG, "IBL loaded successfully")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Failed to load IBL", e)
|
||||||
|
statusTextView.text = "Warning: Failed to load IBL"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startValidation(configPath: String) {
|
private fun handleIntent() {
|
||||||
|
statusTextView.text = "Resolving configuration..."
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
try {
|
||||||
|
val input = inputManager.resolveConfig(intent)
|
||||||
|
currentInput = input
|
||||||
|
|
||||||
|
// Sync spinner with intent
|
||||||
|
modeSpinner.setSelection(if (input.generateGoldens) 1 else 0)
|
||||||
|
|
||||||
|
startValidation(input)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Failed to resolve config", e)
|
||||||
|
statusTextView.text = "Error: ${e.message}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startValidation(input: ValidationInputManager.ValidationInput) {
|
||||||
try {
|
try {
|
||||||
Log.i(TAG, "Parsing config from $configPath")
|
resultsContainer.removeAllViews()
|
||||||
val config = ConfigParser.parseFromPath(configPath)
|
Log.i(TAG, "Starting validation with config: ${input.config.name}")
|
||||||
|
Log.i(TAG, "Output dir: ${input.outputDir.absolutePath}")
|
||||||
val outputDir = File(getExternalFilesDir(null), "validation_results")
|
|
||||||
Log.i(TAG, "Output dir: ${outputDir.absolutePath}")
|
resultManager = ValidationResultManager(input.outputDir)
|
||||||
|
|
||||||
validationRunner = ValidationRunner(this, modelViewer, config, outputDir)
|
validationRunner = ValidationRunner(this, modelViewer, input.config, resultManager!!)
|
||||||
validationRunner?.callback = this
|
validationRunner?.callback = this
|
||||||
|
validationRunner?.generateGoldens = input.generateGoldens
|
||||||
validationRunner?.start()
|
validationRunner?.start()
|
||||||
|
|
||||||
|
// Sync spinner in case it was called programmatically or changed implicitly
|
||||||
|
modeSpinner.setSelection(if (input.generateGoldens) 1 else 0)
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to start validation", e)
|
Log.e(TAG, "Failed to start validation", e)
|
||||||
statusTextView.text = "Error: ${e.message}"
|
statusTextView.text = "Error: ${e.message}"
|
||||||
@@ -128,11 +206,68 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
|||||||
choreographer.removeFrameCallback(frameScheduler)
|
choreographer.removeFrameCallback(frameScheduler)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTestFinished(result: ValidationRunner.TestResult) {
|
private var currentRenderedBitmap: Bitmap? = null
|
||||||
|
private var currentGoldenBitmap: Bitmap? = null
|
||||||
|
private var currentDiffBitmap: Bitmap? = null
|
||||||
|
|
||||||
|
override fun onTestFinished(result: ValidationResult) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
val status = "Test ${result.name} finished: ${if(result.passed) "PASS" else "FAIL"}"
|
val status = "Test ${result.testName} finished: ${if(result.passed) "PASS" else "FAIL"}"
|
||||||
statusTextView.text = status
|
statusTextView.text = status
|
||||||
Log.i(TAG, status)
|
Log.i(TAG, status)
|
||||||
|
|
||||||
|
// Container for this result
|
||||||
|
val resultContainer = LinearLayout(this)
|
||||||
|
resultContainer.orientation = LinearLayout.VERTICAL
|
||||||
|
resultContainer.setPadding(0, 10, 0, 20)
|
||||||
|
|
||||||
|
// Header
|
||||||
|
val resultView = TextView(this)
|
||||||
|
resultView.text = "${result.testName}: ${if(result.passed) "PASS" else "FAIL"} (Diff: ${result.diffMetric})"
|
||||||
|
resultView.setTextColor(if(result.passed) Color.GREEN else Color.RED)
|
||||||
|
resultView.textSize = 16f
|
||||||
|
resultView.setTypeface(null, android.graphics.Typeface.BOLD)
|
||||||
|
resultContainer.addView(resultView)
|
||||||
|
|
||||||
|
// Images Row
|
||||||
|
val imagesRow = LinearLayout(this)
|
||||||
|
imagesRow.orientation = LinearLayout.HORIZONTAL
|
||||||
|
|
||||||
|
fun addImage(label: String, bitmap: Bitmap?) {
|
||||||
|
if (bitmap != null) {
|
||||||
|
val container = LinearLayout(this)
|
||||||
|
container.orientation = LinearLayout.VERTICAL
|
||||||
|
container.setPadding(0, 0, 10, 0)
|
||||||
|
|
||||||
|
val labelView = TextView(this)
|
||||||
|
labelView.text = label
|
||||||
|
labelView.textSize = 12f
|
||||||
|
container.addView(labelView)
|
||||||
|
|
||||||
|
val iv = ImageView(this)
|
||||||
|
iv.setImageBitmap(bitmap) // Use the same bitmap (or copy if needed, but same is usually fine for UI)
|
||||||
|
iv.layoutParams = LinearLayout.LayoutParams(250, 250) // Smaller thumbnails
|
||||||
|
iv.scaleType = ImageView.ScaleType.FIT_CENTER
|
||||||
|
iv.setBackgroundColor(0xFF404040.toInt())
|
||||||
|
container.addView(iv)
|
||||||
|
|
||||||
|
imagesRow.addView(container)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addImage("Rendered", currentRenderedBitmap)
|
||||||
|
addImage("Golden", currentGoldenBitmap)
|
||||||
|
if (!result.passed) {
|
||||||
|
addImage("Diff", currentDiffBitmap)
|
||||||
|
}
|
||||||
|
|
||||||
|
resultContainer.addView(imagesRow)
|
||||||
|
resultsContainer.addView(resultContainer)
|
||||||
|
|
||||||
|
// Clear current images for next test
|
||||||
|
currentRenderedBitmap = null
|
||||||
|
currentGoldenBitmap = null
|
||||||
|
currentDiffBitmap = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,8 +275,6 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
|||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
statusTextView.text = "All tests finished!"
|
statusTextView.text = "All tests finished!"
|
||||||
Log.i(TAG, "All tests finished")
|
Log.i(TAG, "All tests finished")
|
||||||
// Optional: Auto-close activity?
|
|
||||||
// finish()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,5 +283,68 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
|||||||
statusTextView.text = status
|
statusTextView.text = status
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onImageResult(type: String, bitmap: Bitmap) {
|
||||||
|
runOnUiThread {
|
||||||
|
// Update the "live" views
|
||||||
|
when (type) {
|
||||||
|
"Rendered" -> {
|
||||||
|
currentRenderedBitmap = bitmap
|
||||||
|
}
|
||||||
|
"Golden" -> {
|
||||||
|
currentGoldenBitmap = bitmap
|
||||||
|
}
|
||||||
|
"Diff" -> {
|
||||||
|
currentDiffBitmap = bitmap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Scripts for reference:
|
||||||
|
*
|
||||||
|
* generate_goldens.sh:
|
||||||
|
* --------------------
|
||||||
|
* #!/bin/bash
|
||||||
|
* set -e
|
||||||
|
*
|
||||||
|
* # Config path (on device)
|
||||||
|
* CONFIG_PATH=$1
|
||||||
|
* if [ -z "$CONFIG_PATH" ]; then
|
||||||
|
* echo "Usage: $0 <device_config_path>"
|
||||||
|
* echo "Example: $0 /sdcard/Android/data/com.google.android.filament.validation/files/default_test.json"
|
||||||
|
* exit 1
|
||||||
|
* fi
|
||||||
|
*
|
||||||
|
* echo "Starting Golden Generation for $CONFIG_PATH..."
|
||||||
|
* adb shell am force-stop com.google.android.filament.validation
|
||||||
|
* adb shell am start -n com.google.android.filament.validation/.MainActivity \
|
||||||
|
* -e test_config "$CONFIG_PATH" \
|
||||||
|
* --ez generate_goldens true
|
||||||
|
*
|
||||||
|
* echo "Check device or logcat for progress."
|
||||||
|
* echo "adb logcat -s FilamentValidation:I ValidationRunner:I"
|
||||||
|
* echo "To pull results: ./samples/sample-render-validation/pull_goldens.sh"
|
||||||
|
*
|
||||||
|
* pull_goldens.sh:
|
||||||
|
* ----------------
|
||||||
|
* #!/bin/bash
|
||||||
|
* set -e
|
||||||
|
*
|
||||||
|
* # Default destination is local golden directory relative to script
|
||||||
|
* SCRIPT_DIR=$(cd $(dirname $0); pwd)
|
||||||
|
* DEST_DIR=${1:-"$SCRIPT_DIR/golden"}
|
||||||
|
*
|
||||||
|
* echo "Pulling goldens to $DEST_DIR..."
|
||||||
|
* mkdir -p "$DEST_DIR"
|
||||||
|
*
|
||||||
|
* # Path on device
|
||||||
|
* DEVICE_GOLDEN_DIR="/storage/emulated/0/Android/data/com.google.android.filament.validation/files/golden/."
|
||||||
|
*
|
||||||
|
* adb pull "$DEVICE_GOLDEN_DIR" "$DEST_DIR"
|
||||||
|
*
|
||||||
|
* echo "Done."
|
||||||
|
* ls -l "$DEST_DIR"
|
||||||
|
*/
|
||||||
|
|||||||
+20
@@ -70,6 +70,26 @@ class ConfigParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Explicit models map override
|
||||||
|
val modelsJson = json.optJSONObject("models")
|
||||||
|
if (modelsJson != null) {
|
||||||
|
val keys = modelsJson.keys()
|
||||||
|
while (keys.hasNext()) {
|
||||||
|
val name = keys.next()
|
||||||
|
val path = modelsJson.getString(name)
|
||||||
|
// Resolve path relative to baseDir if not absolute
|
||||||
|
val file = File(path)
|
||||||
|
if (file.isAbsolute) {
|
||||||
|
models[name] = path
|
||||||
|
} else if (baseDir != null) {
|
||||||
|
models[name] = File(baseDir, path).absolutePath
|
||||||
|
} else {
|
||||||
|
models[name] = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
val presetsJson = json.optJSONArray("presets")
|
val presetsJson = json.optJSONArray("presets")
|
||||||
val presets = mutableMapOf<String, PresetConfig>()
|
val presets = mutableMapOf<String, PresetConfig>()
|
||||||
if (presetsJson != null) {
|
if (presetsJson != null) {
|
||||||
|
|||||||
+170
@@ -0,0 +1,170 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.filament.validation
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the retrieval and preparation of test configuration and assets.
|
||||||
|
* Supports loading from:
|
||||||
|
* 1. Intent extras (local path or URL)
|
||||||
|
* 2. Default embedded assets (fallback)
|
||||||
|
*/
|
||||||
|
class ValidationInputManager(private val context: Context) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "ValidationInputManager"
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ValidationInput(
|
||||||
|
val config: RenderTestConfig,
|
||||||
|
val outputDir: File,
|
||||||
|
val generateGoldens: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the test configuration based on the provided intent extras.
|
||||||
|
* This may involve extracting assets or downloading files.
|
||||||
|
*/
|
||||||
|
suspend fun resolveConfig(intent: Intent): ValidationInput = withContext(Dispatchers.IO) {
|
||||||
|
val testConfigPath = intent.getStringExtra("test_config")
|
||||||
|
val urlConfig = intent.getStringExtra("url_config")
|
||||||
|
val urlModelsBase = intent.getStringExtra("url_models_base")
|
||||||
|
val generateGoldens = intent.getBooleanExtra("generate_goldens", false)
|
||||||
|
val outputPath = intent.getStringExtra("output_path")
|
||||||
|
|
||||||
|
val outputDir = if (outputPath != null) {
|
||||||
|
File(outputPath).apply { mkdirs() }
|
||||||
|
} else {
|
||||||
|
File(context.getExternalFilesDir(null), "validation_results").apply { mkdirs() }
|
||||||
|
}
|
||||||
|
|
||||||
|
val config = when {
|
||||||
|
urlConfig != null -> downloadConfig(urlConfig, urlModelsBase)
|
||||||
|
testConfigPath != null -> ConfigParser.parseFromPath(testConfigPath)
|
||||||
|
else -> extractDefaultAssets()
|
||||||
|
}
|
||||||
|
|
||||||
|
return@withContext ValidationInput(config, outputDir, generateGoldens)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun extractDefaultAssets(): RenderTestConfig {
|
||||||
|
Log.i(TAG, "Extracting default assets...")
|
||||||
|
val filesDir = context.getExternalFilesDir(null) ?: context.filesDir
|
||||||
|
val assetManager = context.assets
|
||||||
|
|
||||||
|
// Copy default_test.json
|
||||||
|
val configDir = File(filesDir, "config")
|
||||||
|
configDir.mkdirs()
|
||||||
|
val configOut = File(configDir, "default_test.json")
|
||||||
|
|
||||||
|
assetManager.open("default_test.json").use { input ->
|
||||||
|
FileOutputStream(configOut).use { output ->
|
||||||
|
input.copyTo(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy DamagedHelmet.glb
|
||||||
|
val modelsDir = File(filesDir, "models")
|
||||||
|
modelsDir.mkdirs()
|
||||||
|
val modelOut = File(modelsDir, "DamagedHelmet.glb")
|
||||||
|
|
||||||
|
assetManager.open("DamagedHelmet.glb").use { input ->
|
||||||
|
FileOutputStream(modelOut).use { output ->
|
||||||
|
input.copyTo(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update config to point to relative path (standardizing on relative for portability where possible)
|
||||||
|
// or absolute. Here we use relative as per previous logic.
|
||||||
|
val configJson = JSONObject(configOut.readText())
|
||||||
|
val models = configJson.getJSONObject("models")
|
||||||
|
|
||||||
|
// Ensure the default model points to the extracted file
|
||||||
|
// We can use absolute path to be safe since we know where it is now.
|
||||||
|
models.put("DamagedHelmet", modelOut.absolutePath)
|
||||||
|
|
||||||
|
configOut.writeText(configJson.toString(2))
|
||||||
|
|
||||||
|
return ConfigParser.parseFromPath(configOut.absolutePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun downloadConfig(urlConfig: String, urlModelsBase: String?): RenderTestConfig {
|
||||||
|
Log.i(TAG, "Downloading config from $urlConfig")
|
||||||
|
val filesDir = context.getExternalFilesDir(null) ?: context.filesDir
|
||||||
|
val configDir = File(filesDir, "config")
|
||||||
|
configDir.mkdirs()
|
||||||
|
|
||||||
|
val modelsDir = File(filesDir, "models")
|
||||||
|
modelsDir.mkdirs()
|
||||||
|
|
||||||
|
val configName = "downloaded_config.json"
|
||||||
|
val configFile = File(configDir, configName)
|
||||||
|
|
||||||
|
downloadFile(urlConfig, configFile)
|
||||||
|
|
||||||
|
if (urlModelsBase != null) {
|
||||||
|
val configJson = JSONObject(configFile.readText())
|
||||||
|
val models = configJson.optJSONObject("models")
|
||||||
|
if (models != null) {
|
||||||
|
val keys = models.keys()
|
||||||
|
while (keys.hasNext()) {
|
||||||
|
val key = keys.next()
|
||||||
|
val modelPath = models.getString(key)
|
||||||
|
val fileName = File(modelPath).name
|
||||||
|
val modelFile = File(modelsDir, fileName)
|
||||||
|
val modelUrl = "$urlModelsBase/$fileName"
|
||||||
|
|
||||||
|
Log.i(TAG, "Downloading model: $fileName from $modelUrl")
|
||||||
|
downloadFile(modelUrl, modelFile)
|
||||||
|
|
||||||
|
// Update config to point to absolute path
|
||||||
|
models.put(key, modelFile.absolutePath)
|
||||||
|
}
|
||||||
|
configFile.writeText(configJson.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConfigParser.parseFromPath(configFile.absolutePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun downloadFile(urlStr: String, destFile: File) {
|
||||||
|
val url = URL(urlStr)
|
||||||
|
val connection = url.openConnection() as HttpURLConnection
|
||||||
|
connection.connect()
|
||||||
|
|
||||||
|
if (connection.responseCode != HttpURLConnection.HTTP_OK) {
|
||||||
|
throw Exception("Server returned HTTP ${connection.responseCode} for $urlStr")
|
||||||
|
}
|
||||||
|
|
||||||
|
destFile.parentFile?.mkdirs()
|
||||||
|
connection.inputStream.use { input ->
|
||||||
|
FileOutputStream(destFile).use { output ->
|
||||||
|
input.copyTo(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+109
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.android.filament.validation
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.util.Log
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.util.zip.ZipEntry
|
||||||
|
import java.util.zip.ZipOutputStream
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
data class ValidationResult(
|
||||||
|
val testName: String,
|
||||||
|
val passed: Boolean,
|
||||||
|
val diffMetric: Float = 0f
|
||||||
|
)
|
||||||
|
|
||||||
|
class ValidationResultManager(private val outputDir: File) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "ValidationResultManager"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val results = mutableListOf<ValidationResult>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (!outputDir.exists()) {
|
||||||
|
outputDir.mkdirs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addResult(result: ValidationResult) {
|
||||||
|
results.add(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveImage(name: String, bitmap: Bitmap) {
|
||||||
|
val file = File(outputDir, "$name.png")
|
||||||
|
try {
|
||||||
|
FileOutputStream(file).use { out ->
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Failed to save image $name", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getOutputDir(): File {
|
||||||
|
return outputDir
|
||||||
|
}
|
||||||
|
|
||||||
|
fun finalizeResults(): File? {
|
||||||
|
// Write results JSON
|
||||||
|
writeResultsJson()
|
||||||
|
|
||||||
|
// Zip results
|
||||||
|
val zipFile = File(outputDir, "results.zip")
|
||||||
|
try {
|
||||||
|
ZipOutputStream(FileOutputStream(zipFile)).use { zos ->
|
||||||
|
outputDir.walkTopDown().filter { it.isFile && it.name != "results.zip" }.forEach { file ->
|
||||||
|
val entryName = file.relativeTo(outputDir).path
|
||||||
|
zos.putNextEntry(ZipEntry(entryName))
|
||||||
|
file.inputStream().use { it.copyTo(zos) }
|
||||||
|
zos.closeEntry()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.i(TAG, "Zipped results to ${zipFile.absolutePath}")
|
||||||
|
return zipFile
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Failed to zip results", e)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeResultsJson() {
|
||||||
|
val jsonArray = JSONArray()
|
||||||
|
for (result in results) {
|
||||||
|
val jsonObject = JSONObject()
|
||||||
|
jsonObject.put("test_name", result.testName)
|
||||||
|
jsonObject.put("passed", result.passed)
|
||||||
|
jsonObject.put("diff_metric", result.diffMetric)
|
||||||
|
jsonArray.put(jsonObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
val jsonFile = File(outputDir, "results.json")
|
||||||
|
try {
|
||||||
|
FileOutputStream(jsonFile).use { out ->
|
||||||
|
out.write(jsonArray.toString(4).toByteArray())
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Failed to write results.json", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+114
-122
@@ -19,25 +19,19 @@ package com.google.android.filament.validation
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.google.android.filament.Engine
|
|
||||||
import com.google.android.filament.Renderer
|
|
||||||
import com.google.android.filament.View
|
|
||||||
import com.google.android.filament.utils.AutomationEngine
|
import com.google.android.filament.utils.AutomationEngine
|
||||||
import com.google.android.filament.utils.ImageDiff
|
import com.google.android.filament.utils.ImageDiff
|
||||||
import com.google.android.filament.utils.ModelViewer
|
import com.google.android.filament.utils.ModelViewer
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.ByteOrder
|
|
||||||
|
|
||||||
class ValidationRunner(
|
class ValidationRunner(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val modelViewer: ModelViewer,
|
private val modelViewer: ModelViewer,
|
||||||
private val config: RenderTestConfig,
|
private val config: RenderTestConfig,
|
||||||
private val outputDir: File
|
private val resultManager: ValidationResultManager
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private var currentState = State.IDLE
|
private var currentState = State.IDLE
|
||||||
@@ -46,25 +40,30 @@ class ValidationRunner(
|
|||||||
private var currentEngine: AutomationEngine? = null
|
private var currentEngine: AutomationEngine? = null
|
||||||
private var currentTestConfig: TestConfig? = null
|
private var currentTestConfig: TestConfig? = null
|
||||||
private var currentModelName: String? = null
|
private var currentModelName: String? = null
|
||||||
|
|
||||||
private var loadStartFence: com.google.android.filament.Fence? = null
|
private var loadStartFence: com.google.android.filament.Fence? = null
|
||||||
private var loadStartTime = 0L
|
private var loadStartTime = 0L
|
||||||
|
private var frameCounter = 0
|
||||||
|
|
||||||
enum class State {
|
enum class State {
|
||||||
IDLE,
|
IDLE,
|
||||||
LOADING_MODEL,
|
LOADING_MODEL,
|
||||||
WAITING_FOR_FENCE,
|
WAITING_FOR_FENCE,
|
||||||
|
WAITING_FOR_RESOURCES,
|
||||||
|
WARMUP,
|
||||||
RUNNING_TEST,
|
RUNNING_TEST,
|
||||||
COMPARING
|
COMPARING
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Callback {
|
interface Callback {
|
||||||
fun onTestFinished(result: TestResult)
|
fun onTestFinished(result: ValidationResult)
|
||||||
fun onAllTestsFinished()
|
fun onAllTestsFinished()
|
||||||
fun onStatusChanged(status: String)
|
fun onStatusChanged(status: String)
|
||||||
|
fun onImageResult(type: String, bitmap: Bitmap)
|
||||||
}
|
}
|
||||||
|
|
||||||
var callback: Callback? = null
|
var callback: Callback? = null
|
||||||
|
var generateGoldens: Boolean = false
|
||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
if (config.tests.isEmpty()) {
|
if (config.tests.isEmpty()) {
|
||||||
@@ -94,12 +93,11 @@ class ValidationRunner(
|
|||||||
nextModel()
|
nextModel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
currentState = State.LOADING_MODEL
|
currentState = State.LOADING_MODEL
|
||||||
callback?.onStatusChanged("Loading $modelName for ${currentTestConfig?.name}")
|
callback?.onStatusChanged("Loading $modelName for ${currentTestConfig?.name}")
|
||||||
|
|
||||||
// Load model on main thread (required by ModelViewer)
|
// Load model on main thread (required by ModelViewer)
|
||||||
// We assume this is called from main thread or we dispatch
|
|
||||||
loadModel(modelPath)
|
loadModel(modelPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,13 +105,18 @@ class ValidationRunner(
|
|||||||
// Assume called on Main Thread
|
// Assume called on Main Thread
|
||||||
modelViewer.destroyModel()
|
modelViewer.destroyModel()
|
||||||
try {
|
try {
|
||||||
|
Log.i("ValidationRunner", "Reading model file: $path")
|
||||||
val bytes = File(path).readBytes()
|
val bytes = File(path).readBytes()
|
||||||
|
Log.i("ValidationRunner", "Loading GLB buffer... (${bytes.size} bytes)")
|
||||||
val buffer = ByteBuffer.wrap(bytes)
|
val buffer = ByteBuffer.wrap(bytes)
|
||||||
modelViewer.loadModelGlb(buffer)
|
modelViewer.loadModelGlb(buffer)
|
||||||
|
Log.i("ValidationRunner", "Model loaded. initializing fence.")
|
||||||
modelViewer.transformToUnitCube()
|
modelViewer.transformToUnitCube()
|
||||||
loadStartFence = modelViewer.engine.createFence()
|
loadStartFence = modelViewer.engine.createFence()
|
||||||
loadStartTime = System.nanoTime()
|
loadStartTime = System.nanoTime()
|
||||||
currentState = State.WAITING_FOR_FENCE
|
currentState = State.WAITING_FOR_FENCE
|
||||||
|
frameCounter = 0 // Reset for fence timeout tracking
|
||||||
|
Log.i("ValidationRunner", "State set to WAITING_FOR_FENCE")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("ValidationRunner", "Failed to load $path", e)
|
Log.e("ValidationRunner", "Failed to load $path", e)
|
||||||
nextModel()
|
nextModel()
|
||||||
@@ -121,37 +124,67 @@ class ValidationRunner(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onFrame(frameTimeNanos: Long) {
|
fun onFrame(frameTimeNanos: Long) {
|
||||||
|
if (frameCounter % 60 == 0) {
|
||||||
|
Log.i("ValidationRunner", "onFrame: $currentState (frame: $frameCounter)")
|
||||||
|
}
|
||||||
|
|
||||||
when (currentState) {
|
when (currentState) {
|
||||||
State.IDLE -> {}
|
State.IDLE -> {}
|
||||||
State.WAITING_FOR_FENCE -> {
|
State.WAITING_FOR_FENCE -> {
|
||||||
|
frameCounter++
|
||||||
|
if (frameCounter > 600) {
|
||||||
|
Log.w("ValidationRunner", "Fence timed out after 600 frames! Forcing proceed.")
|
||||||
|
modelViewer.engine.destroyFence(loadStartFence!!)
|
||||||
|
loadStartFence = null
|
||||||
|
currentState = State.WAITING_FOR_RESOURCES
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
loadStartFence?.let { fence ->
|
loadStartFence?.let { fence ->
|
||||||
if (fence.wait(com.google.android.filament.Fence.Mode.FLUSH, 0) == com.google.android.filament.Fence.FenceStatus.CONDITION_SATISFIED) {
|
if (fence.wait(com.google.android.filament.Fence.Mode.FLUSH, 0) ==
|
||||||
|
com.google.android.filament.Fence.FenceStatus.CONDITION_SATISFIED) {
|
||||||
modelViewer.engine.destroyFence(fence)
|
modelViewer.engine.destroyFence(fence)
|
||||||
loadStartFence = null
|
loadStartFence = null
|
||||||
|
|
||||||
// Compile materials (simplified)
|
// Compile materials (simplified)
|
||||||
modelViewer.scene.forEach { entity ->
|
modelViewer.scene.forEach { entity ->
|
||||||
// ... existing material compilation logic ...
|
// ... existing material compilation logic ...
|
||||||
}
|
}
|
||||||
|
|
||||||
startAutomation()
|
currentState = State.WAITING_FOR_RESOURCES
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
State.WAITING_FOR_RESOURCES -> {
|
||||||
|
val progress = modelViewer.progress
|
||||||
|
if (progress >= 1.0f) {
|
||||||
|
Log.i("ValidationRunner", "Resources loaded. Starting warmup.")
|
||||||
|
frameCounter = 0
|
||||||
|
currentState = State.WARMUP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
State.WARMUP -> {
|
||||||
|
frameCounter++
|
||||||
|
if (frameCounter > 5) { // 5 frames warmup
|
||||||
|
startAutomation()
|
||||||
|
}
|
||||||
|
}
|
||||||
State.RUNNING_TEST -> {
|
State.RUNNING_TEST -> {
|
||||||
currentEngine?.let { engine ->
|
// Log.i("ValidationRunner", "Running test...")
|
||||||
|
currentEngine?.let { engine ->
|
||||||
val content = AutomationEngine.ViewerContent()
|
val content = AutomationEngine.ViewerContent()
|
||||||
content.view = modelViewer.view
|
content.view = modelViewer.view
|
||||||
content.renderer = modelViewer.renderer
|
content.renderer = modelViewer.renderer
|
||||||
content.scene = modelViewer.scene
|
content.scene = modelViewer.scene
|
||||||
content.lightManager = modelViewer.engine.lightManager
|
content.lightManager = modelViewer.engine.lightManager
|
||||||
|
|
||||||
// Tick
|
// Tick
|
||||||
// Delta time?
|
val deltaTime = 1.0f / 60.0f
|
||||||
val deltaTime = 1.0f / 60.0f // Fixed step for consistency?
|
|
||||||
engine.tick(modelViewer.engine, content, deltaTime)
|
engine.tick(modelViewer.engine, content, deltaTime)
|
||||||
|
|
||||||
if (!engine.isRunning) {
|
frameCounter++
|
||||||
|
if (engine.shouldClose()) {
|
||||||
|
Log.i("ValidationRunner", "Finishing test (frames: $frameCounter)")
|
||||||
// Test finished (for this spec)
|
// Test finished (for this spec)
|
||||||
currentState = State.COMPARING
|
currentState = State.COMPARING
|
||||||
captureAndCompare()
|
captureAndCompare()
|
||||||
@@ -175,104 +208,83 @@ class ValidationRunner(
|
|||||||
options.sleepDuration = 0.0f // Minimal sleep, let frames drive it
|
options.sleepDuration = 0.0f // Minimal sleep, let frames drive it
|
||||||
options.minFrameCount = 5 // Ensure some frames pass
|
options.minFrameCount = 5 // Ensure some frames pass
|
||||||
currentEngine?.setOptions(options)
|
currentEngine?.setOptions(options)
|
||||||
currentEngine?.startRunning()
|
|
||||||
|
// Use batch mode to ensure shouldClose() works reliably
|
||||||
|
currentEngine?.startBatchMode()
|
||||||
|
currentEngine?.signalBatchMode() // Start immediately
|
||||||
|
|
||||||
|
frameCounter = 0
|
||||||
currentState = State.RUNNING_TEST
|
currentState = State.RUNNING_TEST
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun captureAndCompare() {
|
private fun captureAndCompare() {
|
||||||
callback?.onStatusChanged("Comparing ${currentTestConfig?.name}...")
|
callback?.onStatusChanged("Comparing ${currentTestConfig?.name}...")
|
||||||
val view = modelViewer.view
|
modelViewer.debugGetNextFrameCallback { bitmap ->
|
||||||
val renderer = modelViewer.renderer
|
compareCapturedImage(bitmap)
|
||||||
val width = view.viewport.width
|
|
||||||
val height = view.viewport.height
|
|
||||||
|
|
||||||
val buffer = ByteBuffer.allocateDirect(width * height * 4)
|
|
||||||
|
|
||||||
val pbd = com.google.android.filament.Texture.PixelBufferDescriptor(
|
|
||||||
buffer,
|
|
||||||
com.google.android.filament.Texture.Format.RGBA,
|
|
||||||
com.google.android.filament.Texture.Type.UBYTE,
|
|
||||||
1, 0, 0, 0, 0, // alignment, left, top, stride (0=default)
|
|
||||||
null // handler (null = current thread? no, handler is for callback)
|
|
||||||
) {
|
|
||||||
// Callback when readPixels is done
|
|
||||||
// Dispatch to background thread for comparison to avoid blocking UI?
|
|
||||||
// "it" is undefined here? The callback interface is Runnable?
|
|
||||||
// Kotlin lambda for Runnable.
|
|
||||||
compareCapturedImage(buffer, width, height)
|
|
||||||
}
|
}
|
||||||
renderer.readPixels(0, 0, width, height, pbd)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun compareCapturedImage(buffer: java.nio.Buffer, width: Int, height: Int) {
|
private fun compareCapturedImage(bitmap: Bitmap) {
|
||||||
// This runs on... which thread? Filament driver thread possibly.
|
|
||||||
// We should use a helper to process.
|
|
||||||
|
|
||||||
val testName = currentTestConfig!!.name
|
val testName = currentTestConfig!!.name
|
||||||
val modelName = currentModelName!!
|
val modelName = currentModelName!!
|
||||||
val backend = "opengl" // Hardcoded for now, or get from View/Engine?
|
val backend = currentTestConfig?.backends?.firstOrNull() ?: "opengl"
|
||||||
val testFullName = "${testName}.${backend}.${modelName}"
|
val testFullName = "${testName}.${backend}.${modelName}"
|
||||||
|
|
||||||
// Golden path
|
// Golden path
|
||||||
// We expect a golden directory.
|
val modelFile = File(config.models.get(modelName)!!)
|
||||||
val goldenFile = File(config.models.get(modelName)!!).parentFile.parentFile.resolve("golden/${testFullName}.png")
|
val goldenFile = modelFile.parentFile!!.parentFile!!.resolve("golden/${testFullName}.png")
|
||||||
// Strategy: models are in .../models/model.glb
|
|
||||||
// Goldens are in .../golden/
|
|
||||||
|
|
||||||
Thread {
|
Thread {
|
||||||
try {
|
try {
|
||||||
// Convert buffer to Bitmap
|
val flipped = bitmap
|
||||||
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
|
||||||
bitmap.copyPixelsFromBuffer(buffer)
|
callback?.onImageResult("Rendered", flipped)
|
||||||
|
|
||||||
// Flip Y? ReadPixels is typically bottom-up?
|
|
||||||
// Filament readPixels is bottom-left? YES.
|
|
||||||
// Bitmap is top-left.
|
|
||||||
// We need to flip.
|
|
||||||
val matrix = android.graphics.Matrix()
|
|
||||||
matrix.postScale(1f, -1f)
|
|
||||||
val flipped = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true)
|
|
||||||
|
|
||||||
var passed = false
|
var passed = false
|
||||||
if (goldenFile.exists()) {
|
var diffMetric = 0f
|
||||||
val golden = android.graphics.BitmapFactory.decodeFile(goldenFile.absolutePath)
|
|
||||||
if (golden != null) {
|
if (generateGoldens) {
|
||||||
// Populate tolerance from config
|
goldenFile.parentFile?.mkdirs()
|
||||||
val tol = currentTestConfig?.tolerance ?: org.json.JSONObject()
|
FileOutputStream(goldenFile).use { out ->
|
||||||
val tolJson = tol.toString()
|
flipped.compress(Bitmap.CompressFormat.PNG, 100, out)
|
||||||
|
|
||||||
val result = ImageDiff.compare(golden, flipped, tolJson, null)
|
|
||||||
passed = (result.status == ImageDiff.Result.Status.PASSED)
|
|
||||||
|
|
||||||
// Save diff if failed?
|
|
||||||
if (!passed) {
|
|
||||||
val diffFile = File(outputDir, "${testFullName}_diff.png")
|
|
||||||
if (result.diffImage != null) {
|
|
||||||
FileOutputStream(diffFile).use { out ->
|
|
||||||
result.diffImage.compress(Bitmap.CompressFormat.PNG, 100, out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.e("ValidationRunner", "Failed to load golden: ${goldenFile.absolutePath}")
|
|
||||||
}
|
}
|
||||||
|
passed = true // Generating goldens always passes if successful
|
||||||
|
callback?.onStatusChanged("Golden generated")
|
||||||
} else {
|
} else {
|
||||||
Log.w("ValidationRunner", "Golden not found: ${goldenFile.absolutePath}")
|
if (goldenFile.exists()) {
|
||||||
}
|
val golden = android.graphics.BitmapFactory.decodeFile(goldenFile.absolutePath)
|
||||||
|
if (golden != null) {
|
||||||
// Save output
|
callback?.onImageResult("Golden", golden)
|
||||||
val outFile = File(outputDir, "${testFullName}.png")
|
|
||||||
FileOutputStream(outFile).use { out ->
|
val tol = currentTestConfig?.tolerance ?: org.json.JSONObject()
|
||||||
flipped.compress(Bitmap.CompressFormat.PNG, 100, out)
|
val tolJson = tol.toString()
|
||||||
|
val result = ImageDiff.compare(golden, flipped, tolJson, null)
|
||||||
|
passed = (result.status == ImageDiff.Result.Status.PASSED)
|
||||||
|
diffMetric = result.failingPixelCount.toFloat()
|
||||||
|
|
||||||
|
if (!passed) {
|
||||||
|
if (result.diffImage != null) {
|
||||||
|
callback?.onImageResult("Diff", result.diffImage!!)
|
||||||
|
resultManager.saveImage("${testFullName}_diff", result.diffImage!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
callback?.onStatusChanged("Failed to load golden")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.w("ValidationRunner", "Golden not found: ${goldenFile.absolutePath}")
|
||||||
|
callback?.onStatusChanged("Golden not found")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
callback?.onTestFinished(TestResult(testFullName, passed))
|
// Save output
|
||||||
|
resultManager.saveImage(testFullName, flipped)
|
||||||
// Schedule next model on main thread
|
|
||||||
// Use Handler or View.post
|
val result = ValidationResult(testFullName, passed, diffMetric)
|
||||||
modelViewer.view.viewport
|
resultManager.addResult(result)
|
||||||
// dispatch nextModel()
|
callback?.onTestFinished(result)
|
||||||
|
|
||||||
android.os.Handler(android.os.Looper.getMainLooper()).post {
|
android.os.Handler(android.os.Looper.getMainLooper()).post {
|
||||||
nextModel()
|
nextModel()
|
||||||
}
|
}
|
||||||
@@ -300,28 +312,8 @@ class ValidationRunner(
|
|||||||
startTest(config.tests[currentTestIndex])
|
startTest(config.tests[currentTestIndex])
|
||||||
} else {
|
} else {
|
||||||
currentState = State.IDLE
|
currentState = State.IDLE
|
||||||
zipResults()
|
resultManager.finalizeResults()
|
||||||
callback?.onAllTestsFinished()
|
callback?.onAllTestsFinished()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private fun zipResults() {
|
|
||||||
callback?.onStatusChanged("Zipping results...")
|
|
||||||
val zipFile = File(outputDir, "results.zip")
|
|
||||||
try {
|
|
||||||
java.util.zip.ZipOutputStream(java.io.FileOutputStream(zipFile)).use { zos ->
|
|
||||||
outputDir.walkTopDown().filter { it.isFile && it.name != "results.zip" }.forEach { file ->
|
|
||||||
val entryName = file.relativeTo(outputDir).path
|
|
||||||
zos.putNextEntry(java.util.zip.ZipEntry(entryName))
|
|
||||||
file.inputStream().use { it.copyTo(zos) }
|
|
||||||
zos.closeEntry()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Log.i("ValidationRunner", "Zipped results to ${zipFile.absolutePath}")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("ValidationRunner", "Failed to zip results", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class TestResult(val name: String, val passed: Boolean)
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/surface_container"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintDimensionRatio="1:1"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent">
|
||||||
|
|
||||||
|
<SurfaceView
|
||||||
|
android:id="@+id/surface_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/surface_container"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="20dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/status_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Initializing..."
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:paddingBottom="10dp">
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/mode_spinner"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/run_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Run" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Test Results"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:paddingTop="20dp"
|
||||||
|
android:paddingBottom="10dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/results_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical" />
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="5dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/image_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Label" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/image_view"
|
||||||
|
android:layout_width="300px"
|
||||||
|
android:layout_height="300px"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:background="#404040" />
|
||||||
|
</LinearLayout>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-tools-plugin'
|
id 'filament-plugin'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
@@ -10,7 +10,7 @@ kotlin {
|
|||||||
jvmToolchain(versions.jdk)
|
jvmToolchain(versions.jdk)
|
||||||
}
|
}
|
||||||
|
|
||||||
filamentTools {
|
filament {
|
||||||
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
||||||
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-tools-plugin'
|
id 'filament-plugin'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
@@ -10,7 +10,7 @@ kotlin {
|
|||||||
jvmToolchain(versions.jdk)
|
jvmToolchain(versions.jdk)
|
||||||
}
|
}
|
||||||
|
|
||||||
filamentTools {
|
filament {
|
||||||
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
||||||
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-tools-plugin'
|
id 'filament-plugin'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
@@ -10,7 +10,7 @@ kotlin {
|
|||||||
jvmToolchain(versions.jdk)
|
jvmToolchain(versions.jdk)
|
||||||
}
|
}
|
||||||
|
|
||||||
filamentTools {
|
filament {
|
||||||
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
||||||
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-tools-plugin'
|
id 'filament-plugin'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
@@ -10,7 +10,7 @@ kotlin {
|
|||||||
jvmToolchain(versions.jdk)
|
jvmToolchain(versions.jdk)
|
||||||
}
|
}
|
||||||
|
|
||||||
filamentTools {
|
filament {
|
||||||
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
||||||
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'kotlin-android'
|
id 'kotlin-android'
|
||||||
id 'filament-tools-plugin'
|
id 'filament-plugin'
|
||||||
}
|
}
|
||||||
|
|
||||||
project.ext.isSample = true
|
project.ext.isSample = true
|
||||||
@@ -10,7 +10,7 @@ kotlin {
|
|||||||
jvmToolchain(versions.jdk)
|
jvmToolchain(versions.jdk)
|
||||||
}
|
}
|
||||||
|
|
||||||
filamentTools {
|
filament {
|
||||||
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
||||||
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ if [[ "$TARGET" == "presubmit-with-test" ]]; then
|
|||||||
RUN_TESTS=-u
|
RUN_TESTS=-u
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ "$TARGET" == "presubmit-with-archive" ]]; then
|
||||||
|
BUILD_RELEASE=release
|
||||||
|
GENERATE_ARCHIVES=-a
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "$TARGET" == "debug" ]]; then
|
if [[ "$TARGET" == "debug" ]]; then
|
||||||
BUILD_DEBUG=debug
|
BUILD_DEBUG=debug
|
||||||
GENERATE_ARCHIVES=-a
|
GENERATE_ARCHIVES=-a
|
||||||
|
|||||||
+6
-6
@@ -235,9 +235,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz",
|
||||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
"integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
},
|
},
|
||||||
@@ -429,9 +429,9 @@
|
|||||||
"integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q=="
|
"integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q=="
|
||||||
},
|
},
|
||||||
"minimatch": {
|
"minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz",
|
||||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
"integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-13
@@ -181,22 +181,21 @@ important for <code>matc</code> (material compiler).</p>
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.google.android.filament:filament-android:1.69.1'
|
implementation 'com.google.android.filament:filament-android:1.69.4'
|
||||||
}
|
}
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
|
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
|
||||||
<div class="table-wrapper"><table><thead><tr><th>Artifact</th><th>Description</th></tr></thead><tbody>
|
<div class="table-wrapper"><table><thead><tr><th>Artifact</th><th>Description</th></tr></thead><tbody>
|
||||||
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android/badge.svg?subject=filament-android" alt="filament-android" /></a></td><td>The Filament rendering engine itself.</td></tr>
|
<tr><td><a href="https://mvnrepository.com/artifact/com.google.android.filament/filament-android"><img src="https://img.shields.io/maven-central/v/com.google.android.filament/filament-android?label=filament-android&color=green" alt="filament-android" /></a></td><td>The Filament rendering engine itself.</td></tr>
|
||||||
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android-debug"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android-debug/badge.svg?subject=filament-android-debug" alt="filament-android-debug" /></a></td><td>Debug version of <code>filament-android</code>.</td></tr>
|
<tr><td><a href="https://mvnrepository.com/artifact/com.google.android.filament/filament-android-debug"><img src="https://img.shields.io/maven-central/v/com.google.android.filament/filament-android-debug?label=filament-android-debug&color=green" alt="filament-android-debug" /></a></td><td>Debug version of <code>filament-android</code>.</td></tr>
|
||||||
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/gltfio-android"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/gltfio-android/badge.svg?subject=gltfio-android" alt="gltfio-android" /></a></td><td>A glTF 2.0 loader for Filament, depends on <code>filament-android</code>.</td></tr>
|
<tr><td><a href="https://mvnrepository.com/artifact/com.google.android.filament/gltfio-android"><img src="https://img.shields.io/maven-central/v/com.google.android.filament/gltfio-android?label=gltfio-android&color=green" alt="gltfio-android" /></a></td><td>A glTF 2.0 loader for Filament, depends on <code>filament-android</code>.</td></tr>
|
||||||
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-utils-android"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-utils-android/badge.svg?subject=filament-utils-android" alt="filament-utils-android" /></a></td><td>KTX loading, Kotlin math, and camera utilities, depends on <code>gltfio-android</code>.</td></tr>
|
<tr><td><a href="https://mvnrepository.com/artifact/com.google.android.filament/filament-utils-android"><img src="https://img.shields.io/maven-central/v/com.google.android.filament/filament-utils-android?label=filament-utils-android&color=green" alt="filament-utils-android" /></a></td><td>KTX loading, Kotlin math, and camera utilities, depends on <code>gltfio-android</code>.</td></tr>
|
||||||
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android/badge.svg?subject=filamat-android" alt="filamat-android" /></a></td><td>A runtime material builder/compiler. This library is large but contains a full shader compiler/validator/optimizer and supports both OpenGL and Vulkan.</td></tr>
|
<tr><td><a href="https://mvnrepository.com/artifact/com.google.android.filament/filamat-android"><img src="https://img.shields.io/maven-central/v/com.google.android.filament/filamat-android?label=filamat-android&color=green" alt="filamat-android" /></a></td><td>A runtime material builder/compiler. This library is large but contains a full shader compiler/validator/optimizer and supports both OpenGL and Vulkan.</td></tr>
|
||||||
<tr><td><a href="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android-lite"><img src="https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android-lite/badge.svg?subject=filamat-android-lite" alt="filamat-android-lite" /></a></td><td>A much smaller alternative to <code>filamat-android</code> that can only generate OpenGL shaders. It does not provide validation or optimizations.</td></tr>
|
|
||||||
</tbody></table>
|
</tbody></table>
|
||||||
</div>
|
</div>
|
||||||
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
|
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
|
||||||
<p>iOS projects can use CocoaPods to install the latest release:</p>
|
<p>iOS projects can use CocoaPods to install the latest release:</p>
|
||||||
<pre><code class="language-shell">pod 'Filament', '~> 1.69.1'
|
<pre><code class="language-shell">pod 'Filament', '~> 1.69.4'
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
|
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -230,7 +229,8 @@ sheet for the standard material model.</li>
|
|||||||
<li>OpenGL ES 3.0+ for Android and iOS</li>
|
<li>OpenGL ES 3.0+ for Android and iOS</li>
|
||||||
<li>Metal for macOS and iOS</li>
|
<li>Metal for macOS and iOS</li>
|
||||||
<li>Vulkan 1.0 for Android, Linux, macOS, and Windows</li>
|
<li>Vulkan 1.0 for Android, Linux, macOS, and Windows</li>
|
||||||
<li>WebGL 2.0 for all platforms</li>
|
<li>WebGPU for Android, Linux, macOS, and Windows</li>
|
||||||
|
<li>WebGL 2.0 for all browsers supporting it</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h3 id="rendering"><a class="header" href="#rendering">Rendering</a></h3>
|
<h3 id="rendering"><a class="header" href="#rendering">Rendering</a></h3>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -265,7 +265,7 @@ sheet for the standard material model.</li>
|
|||||||
<ul>
|
<ul>
|
||||||
<li>HDR bloom</li>
|
<li>HDR bloom</li>
|
||||||
<li>Depth of field bokeh</li>
|
<li>Depth of field bokeh</li>
|
||||||
<li>Multiple tone mappers: generic (customizable), ACES, filmic, etc.</li>
|
<li>Multiple tone mappers: PBR Neutral, AgX, generic (customizable), ACES, filmic, etc.</li>
|
||||||
<li>Color and tone management: luminance scaling, gamut mapping</li>
|
<li>Color and tone management: luminance scaling, gamut mapping</li>
|
||||||
<li>Color grading: exposure, night adaptation, white balance, channel mixer,
|
<li>Color grading: exposure, night adaptation, white balance, channel mixer,
|
||||||
shadows/mid-tones/highlights, ASC CDL, contrast, saturation, etc.</li>
|
shadows/mid-tones/highlights, ASC CDL, contrast, saturation, etc.</li>
|
||||||
@@ -332,6 +332,8 @@ KHR_lights_punctual</li>
|
|||||||
<li><input disabled="" type="checkbox" checked=""/>
|
<li><input disabled="" type="checkbox" checked=""/>
|
||||||
KHR_materials_clearcoat</li>
|
KHR_materials_clearcoat</li>
|
||||||
<li><input disabled="" type="checkbox" checked=""/>
|
<li><input disabled="" type="checkbox" checked=""/>
|
||||||
|
KHR_materials_dispersion</li>
|
||||||
|
<li><input disabled="" type="checkbox" checked=""/>
|
||||||
KHR_materials_emissive_strength</li>
|
KHR_materials_emissive_strength</li>
|
||||||
<li><input disabled="" type="checkbox" checked=""/>
|
<li><input disabled="" type="checkbox" checked=""/>
|
||||||
KHR_materials_ior</li>
|
KHR_materials_ior</li>
|
||||||
@@ -340,6 +342,8 @@ KHR_materials_pbrSpecularGlossiness</li>
|
|||||||
<li><input disabled="" type="checkbox" checked=""/>
|
<li><input disabled="" type="checkbox" checked=""/>
|
||||||
KHR_materials_sheen</li>
|
KHR_materials_sheen</li>
|
||||||
<li><input disabled="" type="checkbox" checked=""/>
|
<li><input disabled="" type="checkbox" checked=""/>
|
||||||
|
KHR_materials_specular</li>
|
||||||
|
<li><input disabled="" type="checkbox" checked=""/>
|
||||||
KHR_materials_transmission</li>
|
KHR_materials_transmission</li>
|
||||||
<li><input disabled="" type="checkbox" checked=""/>
|
<li><input disabled="" type="checkbox" checked=""/>
|
||||||
KHR_materials_unlit</li>
|
KHR_materials_unlit</li>
|
||||||
@@ -348,8 +352,6 @@ KHR_materials_variants</li>
|
|||||||
<li><input disabled="" type="checkbox" checked=""/>
|
<li><input disabled="" type="checkbox" checked=""/>
|
||||||
KHR_materials_volume</li>
|
KHR_materials_volume</li>
|
||||||
<li><input disabled="" type="checkbox" checked=""/>
|
<li><input disabled="" type="checkbox" checked=""/>
|
||||||
KHR_materials_specular</li>
|
|
||||||
<li><input disabled="" type="checkbox" checked=""/>
|
|
||||||
KHR_mesh_quantization</li>
|
KHR_mesh_quantization</li>
|
||||||
<li><input disabled="" type="checkbox" checked=""/>
|
<li><input disabled="" type="checkbox" checked=""/>
|
||||||
KHR_texture_basisu</li>
|
KHR_texture_basisu</li>
|
||||||
@@ -501,7 +503,7 @@ and tools.</p>
|
|||||||
<li><code>filamesh</code>: Mesh converter</li>
|
<li><code>filamesh</code>: Mesh converter</li>
|
||||||
<li><code>glslminifier</code>: Minifies GLSL source code</li>
|
<li><code>glslminifier</code>: Minifies GLSL source code</li>
|
||||||
<li><code>matc</code>: Material compiler</li>
|
<li><code>matc</code>: Material compiler</li>
|
||||||
<li><code>filament-matp</code>: Material parser</li>
|
<li><code>matedit</code>: Material editor for compiled materials</li>
|
||||||
<li><code>matinfo</code> Displays information about materials compiled with <code>matc</code></li>
|
<li><code>matinfo</code> Displays information about materials compiled with <code>matc</code></li>
|
||||||
<li><code>mipgen</code> Generates a series of miplevels from a source image</li>
|
<li><code>mipgen</code> Generates a series of miplevels from a source image</li>
|
||||||
<li><code>normal-blending</code>: Tool to blend normal maps</li>
|
<li><code>normal-blending</code>: Tool to blend normal maps</li>
|
||||||
|
|||||||
@@ -228,6 +228,7 @@ branch called <code>my-pr-branch</code>, to <code>filament</code>. This PR requi
|
|||||||
it in the following fashion</p>
|
it in the following fashion</p>
|
||||||
<h3 id="using-a-script-to-update-the-golden-repo"><a class="header" href="#using-a-script-to-update-the-golden-repo">Using a script to update the golden repo</a></h3>
|
<h3 id="using-a-script-to-update-the-golden-repo"><a class="header" href="#using-a-script-to-update-the-golden-repo">Using a script to update the golden repo</a></h3>
|
||||||
<ul>
|
<ul>
|
||||||
|
<li>Make sure you've completed the steps in 'Setting up python'</li>
|
||||||
<li>Run interactive mode in the <code>update_golden.py</code> script.
|
<li>Run interactive mode in the <code>update_golden.py</code> script.
|
||||||
<pre><code>python3 test/renderdiff/src/update_golden.py
|
<pre><code>python3 test/renderdiff/src/update_golden.py
|
||||||
</code></pre>
|
</code></pre>
|
||||||
@@ -263,6 +264,27 @@ branch of the golden repo (i.e. <code>my-pr-branch-golden</code>).</li>
|
|||||||
<li>If the PR is merged, then there is another workflow that will merge <code>my-pr-branch-golden</code>
|
<li>If the PR is merged, then there is another workflow that will merge <code>my-pr-branch-golden</code>
|
||||||
to the <code>main</code> branch of the golden repo.</li>
|
to the <code>main</code> branch of the golden repo.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<h3 id="automated-update-via-commit-message"><a class="header" href="#automated-update-via-commit-message">Automated update via commit message</a></h3>
|
||||||
|
<p>Alternatively, if you are confident in your changes and want the CI to handle the update
|
||||||
|
for you, you can use the following tag in your commit message:</p>
|
||||||
|
<ul>
|
||||||
|
<li>In the commit message of your working branch on <code>filament</code>, add the following line:
|
||||||
|
<pre><code>RDIFF_ACCEPT_NEW_GOLDENS
|
||||||
|
</code></pre>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>This has the following effects:</p>
|
||||||
|
<ul>
|
||||||
|
<li>The presubmit test <code>test-renderdiff</code> will be bypassed (it will not perform rendering or
|
||||||
|
comparison).</li>
|
||||||
|
<li>When the PR is merged, the postsubmit CI will automatically:
|
||||||
|
<ol>
|
||||||
|
<li>Build Filament and generate the new images.</li>
|
||||||
|
<li>Upload them to a temporary branch in <code>filament-assets</code>.</li>
|
||||||
|
<li>Merge that branch into <code>main</code>.</li>
|
||||||
|
</ol>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
<h2 id="viewing-test-results"><a class="header" href="#viewing-test-results">Viewing test results</a></h2>
|
<h2 id="viewing-test-results"><a class="header" href="#viewing-test-results">Viewing test results</a></h2>
|
||||||
<p>We provide a viewer for looking at the result of a test run. The viewer is a webapp that can
|
<p>We provide a viewer for looking at the result of a test run. The viewer is a webapp that can
|
||||||
be used by pointing your browser to a localhost port. If you input the viewer with a PR or a
|
be used by pointing your browser to a localhost port. If you input the viewer with a PR or a
|
||||||
|
|||||||
@@ -195,6 +195,30 @@ for inspection.</li>
|
|||||||
<p><strong>Tip:</strong> To view all defined subspecs, grep the podspec file:
|
<p><strong>Tip:</strong> To view all defined subspecs, grep the podspec file:
|
||||||
<code>grep "spec.subspec" ios/CocoaPods/Filament.podspec</code></p>
|
<code>grep "spec.subspec" ios/CocoaPods/Filament.podspec</code></p>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
|
<h2 id="testing-a-podspec-locally"><a class="header" href="#testing-a-podspec-locally">Testing a Podspec Locally</a></h2>
|
||||||
|
<p>Before pushing a new version to the CocoaPods trunk, you should verify the podspec in a sample project.</p>
|
||||||
|
<h3 id="using-the-sample-project"><a class="header" href="#using-the-sample-project">Using the Sample Project</a></h3>
|
||||||
|
<p>Filament includes a dedicated sample project located in <code>ios/samples/HelloCocoaPods</code>. To test your
|
||||||
|
local changes:</p>
|
||||||
|
<ol>
|
||||||
|
<li><strong>Modify the Podfile</strong>: Add a <code>:podspec</code> directive pointing to your local <code>.podspec</code> file.</li>
|
||||||
|
</ol>
|
||||||
|
<pre><code class="language-ruby">platform :ios, '13.0'
|
||||||
|
|
||||||
|
target 'HelloCocoaPods' do
|
||||||
|
# Use the local development podspec
|
||||||
|
pod 'Filament', :podspec => '../../CocoaPods/Filament.podspec'
|
||||||
|
end
|
||||||
|
</code></pre>
|
||||||
|
<ol start="2">
|
||||||
|
<li><strong>Sync Dependencies</strong>: Run the installation command from the sample directory:</li>
|
||||||
|
</ol>
|
||||||
|
<pre><code class="language-bash">pod install
|
||||||
|
</code></pre>
|
||||||
|
<ol start="3">
|
||||||
|
<li><strong>Verify the Build</strong>: Open the generated <code>HelloCocoaPods.xcworkspace</code> in Xcode. Build and run the
|
||||||
|
target to ensure the headers are found and the binaries link correctly.</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|||||||
+11
-7
@@ -199,17 +199,21 @@ Sonatype's <a href="https://central.sonatype.org/">Central Publisher Portal</a>.
|
|||||||
<p>The new Central Publisher Portal does not officially support Gradle. However, Sonatype provides a
|
<p>The new Central Publisher Portal does not officially support Gradle. However, Sonatype provides a
|
||||||
staging API compatibility service, which works with Filament's Gradle setup.</p>
|
staging API compatibility service, which works with Filament's Gradle setup.</p>
|
||||||
<hr />
|
<hr />
|
||||||
<h3 id="1-upload-to-the-staging-api-compatibility-service"><a class="header" href="#1-upload-to-the-staging-api-compatibility-service">1. Upload to the Staging API Compatibility Service</a></h3>
|
<h3 id="1-upload-to-the-central-publisher-portal"><a class="header" href="#1-upload-to-the-central-publisher-portal">1. Upload to the Central Publisher Portal</a></h3>
|
||||||
|
<p>To upload the artifacts, it is important to run both of these Gradle tasks together in a single
|
||||||
|
command. This ensures the staging repository is created and closed automatically.</p>
|
||||||
<pre><code class="language-bash">cd android
|
<pre><code class="language-bash">cd android
|
||||||
./gradlew publishToSonatype
|
./gradlew publishToSonatype closeSonatypeStagingRepository
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<h3 id="2-move-the-repository-to-the-central-publisher-portal"><a class="header" href="#2-move-the-repository-to-the-central-publisher-portal">2. Move the Repository to the Central Publisher Portal</a></h3>
|
<h4 id="troubleshooting-manual-staging"><a class="header" href="#troubleshooting-manual-staging">Troubleshooting: Manual Staging</a></h4>
|
||||||
<p>We have a script to automate this. It reads the <code>sonatypeUsername</code> and <code>sonatypePassword</code> from your
|
<p>If you ran <code>publishToSonatype</code> by itself, the repository will remain open and won't appear in the
|
||||||
<code>~/.gradle/gradle.properties</code> file.</p>
|
portal correctly. You can fix this by running our automation script, which uses the
|
||||||
|
<code>sonatypeUsername</code> and <code>sonatypePassword</code> from your ~/.gradle/gradle.properties file:</p>
|
||||||
<pre><code class="language-bash">python3 build/common/close-sonatype-staging-repository.py
|
<pre><code class="language-bash">python3 build/common/close-sonatype-staging-repository.py
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<h3 id="3-publish-the-release-on-sonatype"><a class="header" href="#3-publish-the-release-on-sonatype">3. Publish the Release on Sonatype</a></h3>
|
<h3 id="2-publish-the-release-on-sonatype"><a class="header" href="#2-publish-the-release-on-sonatype">2. Publish the Release on Sonatype</a></h3>
|
||||||
<p>Navigate to <a href="https://central.sonatype.com/publishing/deployments">Maven Central Repository Deployments</a>.</p>
|
<p>Once the upload is successful, you must manually trigger the final release. Navigate to <a href="https://central.sonatype.com/publishing/deployments">Maven
|
||||||
|
Central Repository Deployments</a>.</p>
|
||||||
<p>Here, you should see a new deployment with a <strong>Validated</strong> status and all your artifacts listed. Click
|
<p>Here, you should see a new deployment with a <strong>Validated</strong> status and all your artifacts listed. Click
|
||||||
the <strong>Publish</strong> button to publish the artifacts. It typically takes around 5 minutes after clicking
|
the <strong>Publish</strong> button to publish the artifacts. It typically takes around 5 minutes after clicking
|
||||||
<strong>Publish</strong> for the artifacts to go live.</p>
|
<strong>Publish</strong> for the artifacts to go live.</p>
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -16,12 +16,13 @@ class SimulatedSkybox {
|
|||||||
this.ozone = 0.0;
|
this.ozone = 0.0;
|
||||||
this.msFactors = [0.1, 0.5, 0.0];
|
this.msFactors = [0.1, 0.5, 0.0];
|
||||||
this.contrast = 1.0;
|
this.contrast = 1.0;
|
||||||
this.nightColor = [0.0, 0.0003, 0.00075];
|
this.nightColor = [0.0, 3.0e-9, 7.5e-9];
|
||||||
this.shimmerControl = [0.0, 20.0, 0.1];
|
this.shimmerControl = [0.0, 20.0, 0.1];
|
||||||
this.cloudControl = [0.0, 0.1, 8000.0, 0.0];
|
this.cloudControl = [0.0, 0.1, 8000.0, 0.0];
|
||||||
this.cloudControl2 = [0.0, 0.0, 0.0, 0.0];
|
this.cloudControl2 = [0.0, 0.0, 0.0, 0.0];
|
||||||
this.waterControl = [50.0, 1.0, 1.0, 4.0]; // x=Strength, y=Speed, z=DerivativeTrick, w=Octaves
|
this.waterControl = [50.0, 1.0, 1.0, 4.0]; // x=Strength, y=Speed, z=DerivativeTrick, w=Octaves
|
||||||
this.starControl = [0.001, 1.0, 350.0, 0.01]; // x=Density, y=Enabled, z=Frequency, w=PixelScale
|
this.starControl = [0.001, 1.0, 350.0, 0.01]; // x=Density, y=Enabled, z=Frequency, w=PixelScale
|
||||||
|
this.starIntensity = 1.0;
|
||||||
this.focalLength = 24.0;
|
this.focalLength = 24.0;
|
||||||
this.height = 1000.0;
|
this.height = 1000.0;
|
||||||
this.planetRadius = 6360.0;
|
this.planetRadius = 6360.0;
|
||||||
@@ -43,7 +44,7 @@ class SimulatedSkybox {
|
|||||||
|
|
||||||
// Milky Way Parameters
|
// Milky Way Parameters
|
||||||
// x=Intensity, y=Saturation, z=Unused
|
// x=Intensity, y=Saturation, z=Unused
|
||||||
this.milkyWayControl = [1.0, 1.0, 0.07];
|
this.milkyWayControl = [1.0, 1.2, 0.05];
|
||||||
this.milkyWayEnabled = true;
|
this.milkyWayEnabled = true;
|
||||||
this.milkyWayRotation = [1, 0, 0, 0, 1, 0, 0, 0, 1]; // Identity by default
|
this.milkyWayRotation = [1, 0, 0, 0, 1, 0, 0, 0, 1]; // Identity by default
|
||||||
this.initEntity();
|
this.initEntity();
|
||||||
@@ -453,6 +454,11 @@ class SimulatedSkybox {
|
|||||||
this.updateCoefficients();
|
this.updateCoefficients();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setStarIntensity(intensity) {
|
||||||
|
this.starIntensity = Math.max(0.0, intensity);
|
||||||
|
this.updateCoefficients();
|
||||||
|
}
|
||||||
|
|
||||||
setMoonPosition(direction) {
|
setMoonPosition(direction) {
|
||||||
// normalize
|
// normalize
|
||||||
const len = Math.hypot(direction[0], direction[1], direction[2]);
|
const len = Math.hypot(direction[0], direction[1], direction[2]);
|
||||||
@@ -500,6 +506,13 @@ class SimulatedSkybox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
setExposure(exposure) {
|
||||||
|
this.exposure = exposure;
|
||||||
|
this.updateCoefficients();
|
||||||
|
}
|
||||||
|
|
||||||
updateCoefficients() {
|
updateCoefficients() {
|
||||||
if (!this.materialInstance) {
|
if (!this.materialInstance) {
|
||||||
console.warn("updateCoefficients called before material loaded");
|
console.warn("updateCoefficients called before material loaded");
|
||||||
@@ -585,6 +598,7 @@ class SimulatedSkybox {
|
|||||||
this.materialInstance.setFloat4Parameter('cloudControl2', new Float32Array(this.cloudControl2));
|
this.materialInstance.setFloat4Parameter('cloudControl2', new Float32Array(this.cloudControl2));
|
||||||
this.materialInstance.setFloat4Parameter('waterControl', new Float32Array(this.waterControl));
|
this.materialInstance.setFloat4Parameter('waterControl', new Float32Array(this.waterControl));
|
||||||
this.materialInstance.setFloat4Parameter('starControl', new Float32Array(this.starControl));
|
this.materialInstance.setFloat4Parameter('starControl', new Float32Array(this.starControl));
|
||||||
|
this.materialInstance.setFloatParameter('starIntensity', this.starIntensity);
|
||||||
|
|
||||||
this.materialInstance.setFloatParameter('sunIntensity', physicalSunIntensity);
|
this.materialInstance.setFloatParameter('sunIntensity', physicalSunIntensity);
|
||||||
|
|
||||||
@@ -615,12 +629,13 @@ class SimulatedSkybox {
|
|||||||
|
|
||||||
this.materialInstance.setFloatParameter('sunIntensity2', finalMoonIntensity);
|
this.materialInstance.setFloatParameter('sunIntensity2', finalMoonIntensity);
|
||||||
|
|
||||||
// Scale Milky Way by Sun Intensity (Pre-exposed) to match dynamic range
|
// Scale Milky Way by Sun Intensity
|
||||||
// Calibration: 1.0 User Intensity ~ 0.025 Lux Relative (approx 2.5% of Sun Pixel Value at Day)
|
// Calibration: 1.0 User Intensity = 1.5e-3 cd/m^2 (Nits) approx.
|
||||||
// At Sunny 16 (Sun ~ 2.6), this gives ~0.065 pixel brightness, which is visible but dim.
|
// Target: 1.5e-3 Nits. SunIntensity = 100,000.
|
||||||
|
// Scale = 1.5e-3 / 1.0e5 = 1.5e-8.
|
||||||
const mwIntensity = this.milkyWayEnabled ? this.milkyWayControl[0] : 0.0;
|
const mwIntensity = this.milkyWayEnabled ? this.milkyWayControl[0] : 0.0;
|
||||||
const mwUniform = [
|
const mwUniform = [
|
||||||
mwIntensity * this.sunIntensity * 0.025,
|
mwIntensity * this.sunIntensity * 1.5e-8,
|
||||||
this.milkyWayControl[1],
|
this.milkyWayControl[1],
|
||||||
this.milkyWayControl[2]
|
this.milkyWayControl[2]
|
||||||
];
|
];
|
||||||
@@ -634,6 +649,7 @@ class SimulatedSkybox {
|
|||||||
const moonHaloUpload = [...this.moonHalo];
|
const moonHaloUpload = [...this.moonHalo];
|
||||||
moonHaloUpload[2] *= moonRadConv;
|
moonHaloUpload[2] *= moonRadConv;
|
||||||
this.materialInstance.setFloat4Parameter('sunHalo2', new Float32Array(moonHaloUpload));
|
this.materialInstance.setFloat4Parameter('sunHalo2', new Float32Array(moonHaloUpload));
|
||||||
|
this.materialInstance.setFloatParameter('exposure', this.exposure !== undefined ? this.exposure : 1.0);
|
||||||
|
|
||||||
// Solar Eclipse (CPU Calculation)
|
// Solar Eclipse (CPU Calculation)
|
||||||
const sunRadius = Math.acos(this.sunHalo[0]);
|
const sunRadius = Math.acos(this.sunHalo[0]);
|
||||||
|
|||||||
Binary file not shown.
+26
-5
@@ -218,6 +218,7 @@ class App {
|
|||||||
const exposure = this.getExposure();
|
const exposure = this.getExposure();
|
||||||
const preExposedIntensity = this.params.sunIntensity * exposure;
|
const preExposedIntensity = this.params.sunIntensity * exposure;
|
||||||
this.skybox.setSunIntensity(preExposedIntensity);
|
this.skybox.setSunIntensity(preExposedIntensity);
|
||||||
|
this.skybox.setExposure(exposure); // Update Skybox Exposure Uniform
|
||||||
|
|
||||||
// Moon Exposure
|
// Moon Exposure
|
||||||
if (this.mParams) {
|
if (this.mParams) {
|
||||||
@@ -389,7 +390,7 @@ class App {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mwFolder.add(this.mwParams, 'enabled').name('Enabled').onChange(updateMW);
|
mwFolder.add(this.mwParams, 'enabled').name('Enabled').onChange(updateMW);
|
||||||
mwFolder.add(this.mwParams, 'intensity', 0.0, 5.0).onChange(updateMW);
|
mwFolder.add(this.mwParams, 'intensity', 0.0, 100.0).onChange(updateMW);
|
||||||
mwFolder.add(this.mwParams, 'saturation', 0.0, 2.0).onChange(updateMW);
|
mwFolder.add(this.mwParams, 'saturation', 0.0, 2.0).onChange(updateMW);
|
||||||
mwFolder.add(this.mwParams, 'blackPoint', 0.0, 0.5).name('Black Point').onChange(updateMW);
|
mwFolder.add(this.mwParams, 'blackPoint', 0.0, 0.5).name('Black Point').onChange(updateMW);
|
||||||
mwFolder.add(this.mwParams, 'siderealTime', 0.0, 24.0).name('Sidereal Time').listen().onChange(updateMW);
|
mwFolder.add(this.mwParams, 'siderealTime', 0.0, 24.0).name('Sidereal Time').listen().onChange(updateMW);
|
||||||
@@ -464,18 +465,23 @@ class App {
|
|||||||
const starFolder = gui.addFolder('Stars');
|
const starFolder = gui.addFolder('Stars');
|
||||||
this.sParams = {
|
this.sParams = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
density: 0.001
|
density: 0.001,
|
||||||
|
intensity: 0.0 // 2^0 = 1.0
|
||||||
};
|
};
|
||||||
// Initialize defaults (Density 0.001, Enabled True)
|
// Initialize defaults (Density 0.001, Enabled True)
|
||||||
sky.setStarControl(0.001, true);
|
sky.setStarControl(0.001, true);
|
||||||
|
sky.setStarIntensity(Math.pow(2.0, 0.0));
|
||||||
|
|
||||||
const updateStars = () => {
|
const updateStars = () => {
|
||||||
sky.setStarControl(this.sParams.density, this.sParams.enabled);
|
sky.setStarControl(this.sParams.density, this.sParams.enabled);
|
||||||
|
sky.setStarIntensity(Math.pow(2.0, this.sParams.intensity));
|
||||||
};
|
};
|
||||||
|
|
||||||
starFolder.add(this.sParams, 'enabled').name('Enabled').onChange(updateStars);
|
starFolder.add(this.sParams, 'enabled').name('Enabled').onChange(updateStars);
|
||||||
starFolder.add(this.sParams, 'density', 0.0, 0.01, 0.0001).name('Density').onChange(updateStars);
|
starFolder.add(this.sParams, 'density', 0.0, 0.01, 0.0001).name('Density').onChange(updateStars);
|
||||||
|
starFolder.add(this.sParams, 'intensity', 0.0, 24.0).name('Intensity (Exp)').onChange(updateStars);
|
||||||
starFolder.close();
|
starFolder.close();
|
||||||
|
this.updateStars = updateStars;
|
||||||
|
|
||||||
const artFolder = gui.addFolder('Artistic');
|
const artFolder = gui.addFolder('Artistic');
|
||||||
// Set Horizon Glow default to 0.0
|
// Set Horizon Glow default to 0.0
|
||||||
@@ -489,7 +495,7 @@ class App {
|
|||||||
artFolder.add(sky.msFactors, 2, 0.0, 1.0).name('Horizon Glow').onChange(v => sky.setHorizonGlow(v));
|
artFolder.add(sky.msFactors, 2, 0.0, 1.0).name('Horizon Glow').onChange(v => sky.setHorizonGlow(v));
|
||||||
artFolder.add(sky, 'contrast', 0.1, 2.0).onChange(v => sky.setContrast(v));
|
artFolder.add(sky, 'contrast', 0.1, 2.0).onChange(v => sky.setContrast(v));
|
||||||
|
|
||||||
artFolder.addColor(sky, 'nightColor').onChange(v => sky.setNightColor(v));
|
|
||||||
|
|
||||||
const shmFolder = artFolder.addFolder('Shimmer');
|
const shmFolder = artFolder.addFolder('Shimmer');
|
||||||
// Set Shimmer Strength default to 0.0
|
// Set Shimmer Strength default to 0.0
|
||||||
@@ -502,7 +508,7 @@ class App {
|
|||||||
const camFolder = gui.addFolder('Camera');
|
const camFolder = gui.addFolder('Camera');
|
||||||
camFolder.add(this.params, 'focalLength', 8.0, 300.0).name('Focal Length').onChange(() => this.updateCameraProjection());
|
camFolder.add(this.params, 'focalLength', 8.0, 300.0).name('Focal Length').onChange(() => this.updateCameraProjection());
|
||||||
camFolder.add(this.params, 'aperture', 1.4, 32.0).onChange(() => this.updateCameraExposure());
|
camFolder.add(this.params, 'aperture', 1.4, 32.0).onChange(() => this.updateCameraExposure());
|
||||||
camFolder.add(this.params, 'shutterSpeed', 1.0, 1000.0).onChange(() => this.updateCameraExposure());
|
camFolder.add(this.params, 'shutterSpeed', 0.05, 1000.0).onChange(() => this.updateCameraExposure());
|
||||||
camFolder.add(this.params, 'iso', 50.0, 3200.0).onChange(() => this.updateCameraExposure());
|
camFolder.add(this.params, 'iso', 50.0, 3200.0).onChange(() => this.updateCameraExposure());
|
||||||
|
|
||||||
const bloomFolder = camFolder.addFolder('Bloom');
|
const bloomFolder = camFolder.addFolder('Bloom');
|
||||||
@@ -692,13 +698,15 @@ class App {
|
|||||||
const s = this.sParams;
|
const s = this.sParams;
|
||||||
const b = this.bParams;
|
const b = this.bParams;
|
||||||
const m = this.mParams;
|
const m = this.mParams;
|
||||||
|
const mw = this.mwParams;
|
||||||
const sk = this.skybox;
|
const sk = this.skybox;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
p: { a: p.aperture, ss: p.shutterSpeed, i: p.iso, st: p.sunTheta, sp: p.sunPhi, fl: p.focalLength, si: p.sunIntensity },
|
p: { a: p.aperture, ss: p.shutterSpeed, i: p.iso, st: p.sunTheta, sp: p.sunPhi, fl: p.focalLength, si: p.sunIntensity },
|
||||||
c: { v: c.volumetrics, co: c.coverage, d: c.density, h: c.height, s: c.speed, e: c.evolution },
|
c: { v: c.volumetrics, co: c.coverage, d: c.density, h: c.height, s: c.speed, e: c.evolution },
|
||||||
w: { dt: w.derivativeTrick, st: w.strength, s: w.speed, o: w.octaves },
|
w: { dt: w.derivativeTrick, st: w.strength, s: w.speed, o: w.octaves },
|
||||||
s: { e: s.enabled, d: s.density },
|
s: { e: s.enabled, d: s.density, i: s.intensity },
|
||||||
|
mw: { e: mw.enabled, i: mw.intensity, s: mw.saturation, bp: mw.blackPoint, st: mw.siderealTime, l: mw.latitude },
|
||||||
b: { e: b.enabled, lf: b.lensFlare },
|
b: { e: b.enabled, lf: b.lensFlare },
|
||||||
m: { e: m.enabled, az: m.azimuth, h: m.height, r: m.radius, i: m.intensity },
|
m: { e: m.enabled, az: m.azimuth, h: m.height, r: m.radius, i: m.intensity },
|
||||||
cm: { t: this.camState.theta, p: this.camState.phi },
|
cm: { t: this.camState.theta, p: this.camState.phi },
|
||||||
@@ -723,6 +731,7 @@ class App {
|
|||||||
const c = state.c;
|
const c = state.c;
|
||||||
const w = state.w;
|
const w = state.w;
|
||||||
const s = state.s;
|
const s = state.s;
|
||||||
|
const mw = state.mw;
|
||||||
const b = state.b;
|
const b = state.b;
|
||||||
const m = state.m;
|
const m = state.m;
|
||||||
const k = state.k;
|
const k = state.k;
|
||||||
@@ -757,6 +766,18 @@ class App {
|
|||||||
if (s) {
|
if (s) {
|
||||||
if (s.e !== undefined) this.sParams.enabled = s.e;
|
if (s.e !== undefined) this.sParams.enabled = s.e;
|
||||||
if (s.d !== undefined) this.sParams.density = s.d;
|
if (s.d !== undefined) this.sParams.density = s.d;
|
||||||
|
if (s.i !== undefined) this.sParams.intensity = s.i;
|
||||||
|
if (this.updateStars) this.updateStars();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mw) {
|
||||||
|
if (mw.e !== undefined) this.mwParams.enabled = mw.e;
|
||||||
|
if (mw.i !== undefined) this.mwParams.intensity = mw.i;
|
||||||
|
if (mw.s !== undefined) this.mwParams.saturation = mw.s;
|
||||||
|
if (mw.bp !== undefined) this.mwParams.blackPoint = mw.bp;
|
||||||
|
if (mw.st !== undefined) this.mwParams.siderealTime = mw.st;
|
||||||
|
if (mw.l !== undefined) this.mwParams.latitude = mw.l;
|
||||||
|
if (this.updateMW) this.updateMW();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (b) {
|
if (b) {
|
||||||
|
|||||||
@@ -96,10 +96,20 @@ material {
|
|||||||
name : starControl, // x=Density, y=Enabled, z=Frequency, w=PixelScale
|
name : starControl, // x=Density, y=Enabled, z=Frequency, w=PixelScale
|
||||||
precision : high
|
precision : high
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type : float,
|
||||||
|
name : starIntensity,
|
||||||
|
precision : high
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type : sampler2d,
|
type : sampler2d,
|
||||||
name : moonTexture
|
name : moonTexture
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type : float,
|
||||||
|
name : exposure,
|
||||||
|
precision : high
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type : sampler2d,
|
type : sampler2d,
|
||||||
name : moonNormal
|
name : moonNormal
|
||||||
@@ -162,9 +172,9 @@ fragment {
|
|||||||
// --- CONFIGURATION ---
|
// --- CONFIGURATION ---
|
||||||
|
|
||||||
// Stars
|
// Stars
|
||||||
#define STAR_GLOBAL_INTENSITY 150.0 // Master brightness multiplier [0.0 - 500.0]
|
#define STAR_GLOBAL_INTENSITY 100.0 // Master brightness multiplier [0.0 - 500.0]
|
||||||
#define STAR_BRIGHTNESS_BASE 0.5 // Minimum random brightness [0.0 - 1.0]
|
#define STAR_BRIGHTNESS_BASE 0.01 // Minimum random brightness [0.0 - 1.0]
|
||||||
#define STAR_BRIGHTNESS_VAR 4.0 // Random brightness variance range [0.0 - 10.0]
|
#define STAR_BRIGHTNESS_VAR 15.0 // Random brightness variance range [0.0 - 10.0]
|
||||||
#define STAR_FADE_SUN_ELV_HIGH 0.10 // Sun elevation (sin) where stars are fully hidden [0.0 - 0.5]
|
#define STAR_FADE_SUN_ELV_HIGH 0.10 // Sun elevation (sin) where stars are fully hidden [0.0 - 0.5]
|
||||||
#define STAR_FADE_SUN_ELV_LOW -0.20 // Sun elevation (sin) where stars are fully visible [-0.5 - 0.0]
|
#define STAR_FADE_SUN_ELV_LOW -0.20 // Sun elevation (sin) where stars are fully visible [-0.5 - 0.0]
|
||||||
#define STAR_CLOUD_OCCLUSION 0.1 // Visibility when covered by clouds [0.0 - 1.0]
|
#define STAR_CLOUD_OCCLUSION 0.1 // Visibility when covered by clouds [0.0 - 1.0]
|
||||||
@@ -961,7 +971,11 @@ fragment {
|
|||||||
highp vec4 sunHalo, highp vec4 cloudControl, highp vec4 cloudControl2,
|
highp vec4 sunHalo, highp vec4 cloudControl, highp vec4 cloudControl2,
|
||||||
highp vec4 shimmerControl, highp vec4 waterControl,
|
highp vec4 shimmerControl, highp vec4 waterControl,
|
||||||
highp vec3 L2, highp float sunIntensity2, highp vec4 sunHalo2,
|
highp vec3 L2, highp float sunIntensity2, highp vec4 sunHalo2,
|
||||||
sampler2D moonTex, sampler2D moonNormal) {
|
sampler2D moonTex, sampler2D moonNormal,
|
||||||
|
highp vec3 nightColor,
|
||||||
|
highp mat3 milkyWayRotation,
|
||||||
|
highp vec3 milkyWayControl,
|
||||||
|
sampler2D milkyWayTexture) {
|
||||||
|
|
||||||
// Project to plane y=0
|
// Project to plane y=0
|
||||||
highp float t = WATER_PLANE_HEIGHT / min(V.y, -0.0002); // Reduced clamp to minimize "wall" artifact
|
highp float t = WATER_PLANE_HEIGHT / min(V.y, -0.0002); // Reduced clamp to minimize "wall" artifact
|
||||||
@@ -1057,7 +1071,7 @@ fragment {
|
|||||||
highp float rHorizonMask = 1.0 - smoothstep(0.0, REFLECTION_HORIZON_FADE, R.y);
|
highp float rHorizonMask = 1.0 - smoothstep(0.0, REFLECTION_HORIZON_FADE, R.y);
|
||||||
|
|
||||||
if (rHorizonMask > 0.0) {
|
if (rHorizonMask > 0.0) {
|
||||||
reflection += getStarLayer(R, L, reflCloudDensity, outTransmittance, materialParams.starControl) * rHorizonMask;
|
reflection += getStarLayer(R, L, reflCloudDensity, outTransmittance, materialParams.starControl) * rHorizonMask * materialParams.exposure;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sun Disk Reflection
|
// Sun Disk Reflection
|
||||||
@@ -1088,6 +1102,29 @@ fragment {
|
|||||||
// Apply clouds to reflection
|
// Apply clouds to reflection
|
||||||
reflection = mix(reflection, reflCloudLayer, reflCloudDensity);
|
reflection = mix(reflection, reflCloudLayer, reflCloudDensity);
|
||||||
|
|
||||||
|
// Add Milky Way to Reflection
|
||||||
|
// Calculate Fade based on Sun Elevation
|
||||||
|
highp float sunElvSin = L.y;
|
||||||
|
highp float mwFade = smoothstep(STAR_FADE_SUN_ELV_HIGH, STAR_FADE_SUN_ELV_LOW, sunElvSin);
|
||||||
|
|
||||||
|
if (mwFade > 0.0) {
|
||||||
|
highp vec3 mwColor = getMilkyWay(R, milkyWayRotation, milkyWayTexture, milkyWayControl);
|
||||||
|
|
||||||
|
// Apply Atmosphere Transmittance (approximate)
|
||||||
|
mwColor *= outTransmittance;
|
||||||
|
|
||||||
|
// Apply Fades
|
||||||
|
mwColor *= mwFade;
|
||||||
|
mwColor *= (1.0 - reflMoonOcclusion); // Occlude by Moon
|
||||||
|
mwColor *= (1.0 - reflCloudDensity); // Occlude by Clouds
|
||||||
|
|
||||||
|
reflection += mwColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Add Night Color to Reflection
|
||||||
|
reflection += nightColor;
|
||||||
|
|
||||||
// Fresnel
|
// Fresnel
|
||||||
highp float F0 = WATER_FRESNEL_F0; // Water
|
highp float F0 = WATER_FRESNEL_F0; // Water
|
||||||
highp float cosTheta = clamp(dot(-V, N_water), 0.0, 1.0);
|
highp float cosTheta = clamp(dot(-V, N_water), 0.0, 1.0);
|
||||||
@@ -1187,7 +1224,7 @@ fragment {
|
|||||||
|
|
||||||
// 7. Stars
|
// 7. Stars
|
||||||
// Add stars before clouds (clouds cover stars)
|
// Add stars before clouds (clouds cover stars)
|
||||||
highp vec3 starColor = getStarLayer(V, L, cloudDensityVal, transmittance, materialParams.starControl);
|
highp vec3 starColor = getStarLayer(V, L, cloudDensityVal, transmittance, materialParams.starControl) * materialParams.exposure * materialParams.starIntensity;
|
||||||
|
|
||||||
// 7b. Milky Way
|
// 7b. Milky Way
|
||||||
// Add Milky Way behind stars (conceptually) but handled similarly
|
// Add Milky Way behind stars (conceptually) but handled similarly
|
||||||
@@ -1232,7 +1269,11 @@ fragment {
|
|||||||
materialParams.shimmerControl,
|
materialParams.shimmerControl,
|
||||||
materialParams.waterControl,
|
materialParams.waterControl,
|
||||||
L2, materialParams.sunIntensity2, materialParams.sunHalo2,
|
L2, materialParams.sunIntensity2, materialParams.sunHalo2,
|
||||||
materialParams_moonTexture, materialParams_moonNormal);
|
materialParams_moonTexture, materialParams_moonNormal,
|
||||||
|
materialParams.nightColor,
|
||||||
|
materialParams.milkyWayRotation,
|
||||||
|
materialParams.milkyWayControl,
|
||||||
|
materialParams_milkyWayTexture);
|
||||||
color = applyDynamicToneMapping(color, L, materialParams.contrast);
|
color = applyDynamicToneMapping(color, L, materialParams.contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,25 +64,30 @@ staging API compatibility service, which works with Filament's Gradle setup.
|
|||||||
|
|
||||||
-----
|
-----
|
||||||
|
|
||||||
### 1\. Upload to the Staging API Compatibility Service
|
### 1\. Upload to the Central Publisher Portal
|
||||||
|
|
||||||
|
To upload the artifacts, it is important to run both of these Gradle tasks together in a single
|
||||||
|
command. This ensures the staging repository is created and closed automatically.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd android
|
cd android
|
||||||
./gradlew publishToSonatype
|
./gradlew publishToSonatype closeSonatypeStagingRepository
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2\. Move the Repository to the Central Publisher Portal
|
#### Troubleshooting: Manual Staging
|
||||||
|
|
||||||
We have a script to automate this. It reads the `sonatypeUsername` and `sonatypePassword` from your
|
If you ran `publishToSonatype` by itself, the repository will remain open and won't appear in the
|
||||||
`~/.gradle/gradle.properties` file.
|
portal correctly. You can fix this by running our automation script, which uses the
|
||||||
|
`sonatypeUsername` and `sonatypePassword` from your ~/.gradle/gradle.properties file:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 build/common/close-sonatype-staging-repository.py
|
python3 build/common/close-sonatype-staging-repository.py
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3\. Publish the Release on Sonatype
|
### 2\. Publish the Release on Sonatype
|
||||||
|
|
||||||
Navigate to [Maven Central Repository Deployments](https://central.sonatype.com/publishing/deployments).
|
Once the upload is successful, you must manually trigger the final release. Navigate to [Maven
|
||||||
|
Central Repository Deployments](https://central.sonatype.com/publishing/deployments).
|
||||||
|
|
||||||
Here, you should see a new deployment with a **Validated** status and all your artifacts listed. Click
|
Here, you should see a new deployment with a **Validated** status and all your artifacts listed. Click
|
||||||
the **Publish** button to publish the artifacts. It typically takes around 5 minutes after clicking
|
the **Publish** button to publish the artifacts. It typically takes around 5 minutes after clicking
|
||||||
|
|||||||
@@ -16,12 +16,13 @@ class SimulatedSkybox {
|
|||||||
this.ozone = 0.0;
|
this.ozone = 0.0;
|
||||||
this.msFactors = [0.1, 0.5, 0.0];
|
this.msFactors = [0.1, 0.5, 0.0];
|
||||||
this.contrast = 1.0;
|
this.contrast = 1.0;
|
||||||
this.nightColor = [0.0, 0.0003, 0.00075];
|
this.nightColor = [0.0, 3.0e-9, 7.5e-9];
|
||||||
this.shimmerControl = [0.0, 20.0, 0.1];
|
this.shimmerControl = [0.0, 20.0, 0.1];
|
||||||
this.cloudControl = [0.0, 0.1, 8000.0, 0.0];
|
this.cloudControl = [0.0, 0.1, 8000.0, 0.0];
|
||||||
this.cloudControl2 = [0.0, 0.0, 0.0, 0.0];
|
this.cloudControl2 = [0.0, 0.0, 0.0, 0.0];
|
||||||
this.waterControl = [50.0, 1.0, 1.0, 4.0]; // x=Strength, y=Speed, z=DerivativeTrick, w=Octaves
|
this.waterControl = [50.0, 1.0, 1.0, 4.0]; // x=Strength, y=Speed, z=DerivativeTrick, w=Octaves
|
||||||
this.starControl = [0.001, 1.0, 350.0, 0.01]; // x=Density, y=Enabled, z=Frequency, w=PixelScale
|
this.starControl = [0.001, 1.0, 350.0, 0.01]; // x=Density, y=Enabled, z=Frequency, w=PixelScale
|
||||||
|
this.starIntensity = 1.0;
|
||||||
this.focalLength = 24.0;
|
this.focalLength = 24.0;
|
||||||
this.height = 1000.0;
|
this.height = 1000.0;
|
||||||
this.planetRadius = 6360.0;
|
this.planetRadius = 6360.0;
|
||||||
@@ -43,7 +44,7 @@ class SimulatedSkybox {
|
|||||||
|
|
||||||
// Milky Way Parameters
|
// Milky Way Parameters
|
||||||
// x=Intensity, y=Saturation, z=Unused
|
// x=Intensity, y=Saturation, z=Unused
|
||||||
this.milkyWayControl = [1.0, 1.0, 0.07];
|
this.milkyWayControl = [1.0, 1.2, 0.05];
|
||||||
this.milkyWayEnabled = true;
|
this.milkyWayEnabled = true;
|
||||||
this.milkyWayRotation = [1, 0, 0, 0, 1, 0, 0, 0, 1]; // Identity by default
|
this.milkyWayRotation = [1, 0, 0, 0, 1, 0, 0, 0, 1]; // Identity by default
|
||||||
this.initEntity();
|
this.initEntity();
|
||||||
@@ -453,6 +454,11 @@ class SimulatedSkybox {
|
|||||||
this.updateCoefficients();
|
this.updateCoefficients();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setStarIntensity(intensity) {
|
||||||
|
this.starIntensity = Math.max(0.0, intensity);
|
||||||
|
this.updateCoefficients();
|
||||||
|
}
|
||||||
|
|
||||||
setMoonPosition(direction) {
|
setMoonPosition(direction) {
|
||||||
// normalize
|
// normalize
|
||||||
const len = Math.hypot(direction[0], direction[1], direction[2]);
|
const len = Math.hypot(direction[0], direction[1], direction[2]);
|
||||||
@@ -500,6 +506,13 @@ class SimulatedSkybox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
setExposure(exposure) {
|
||||||
|
this.exposure = exposure;
|
||||||
|
this.updateCoefficients();
|
||||||
|
}
|
||||||
|
|
||||||
updateCoefficients() {
|
updateCoefficients() {
|
||||||
if (!this.materialInstance) {
|
if (!this.materialInstance) {
|
||||||
console.warn("updateCoefficients called before material loaded");
|
console.warn("updateCoefficients called before material loaded");
|
||||||
@@ -585,6 +598,7 @@ class SimulatedSkybox {
|
|||||||
this.materialInstance.setFloat4Parameter('cloudControl2', new Float32Array(this.cloudControl2));
|
this.materialInstance.setFloat4Parameter('cloudControl2', new Float32Array(this.cloudControl2));
|
||||||
this.materialInstance.setFloat4Parameter('waterControl', new Float32Array(this.waterControl));
|
this.materialInstance.setFloat4Parameter('waterControl', new Float32Array(this.waterControl));
|
||||||
this.materialInstance.setFloat4Parameter('starControl', new Float32Array(this.starControl));
|
this.materialInstance.setFloat4Parameter('starControl', new Float32Array(this.starControl));
|
||||||
|
this.materialInstance.setFloatParameter('starIntensity', this.starIntensity);
|
||||||
|
|
||||||
this.materialInstance.setFloatParameter('sunIntensity', physicalSunIntensity);
|
this.materialInstance.setFloatParameter('sunIntensity', physicalSunIntensity);
|
||||||
|
|
||||||
@@ -615,12 +629,13 @@ class SimulatedSkybox {
|
|||||||
|
|
||||||
this.materialInstance.setFloatParameter('sunIntensity2', finalMoonIntensity);
|
this.materialInstance.setFloatParameter('sunIntensity2', finalMoonIntensity);
|
||||||
|
|
||||||
// Scale Milky Way by Sun Intensity (Pre-exposed) to match dynamic range
|
// Scale Milky Way by Sun Intensity
|
||||||
// Calibration: 1.0 User Intensity ~ 0.025 Lux Relative (approx 2.5% of Sun Pixel Value at Day)
|
// Calibration: 1.0 User Intensity = 1.5e-3 cd/m^2 (Nits) approx.
|
||||||
// At Sunny 16 (Sun ~ 2.6), this gives ~0.065 pixel brightness, which is visible but dim.
|
// Target: 1.5e-3 Nits. SunIntensity = 100,000.
|
||||||
|
// Scale = 1.5e-3 / 1.0e5 = 1.5e-8.
|
||||||
const mwIntensity = this.milkyWayEnabled ? this.milkyWayControl[0] : 0.0;
|
const mwIntensity = this.milkyWayEnabled ? this.milkyWayControl[0] : 0.0;
|
||||||
const mwUniform = [
|
const mwUniform = [
|
||||||
mwIntensity * this.sunIntensity * 0.025,
|
mwIntensity * this.sunIntensity * 1.5e-8,
|
||||||
this.milkyWayControl[1],
|
this.milkyWayControl[1],
|
||||||
this.milkyWayControl[2]
|
this.milkyWayControl[2]
|
||||||
];
|
];
|
||||||
@@ -634,6 +649,7 @@ class SimulatedSkybox {
|
|||||||
const moonHaloUpload = [...this.moonHalo];
|
const moonHaloUpload = [...this.moonHalo];
|
||||||
moonHaloUpload[2] *= moonRadConv;
|
moonHaloUpload[2] *= moonRadConv;
|
||||||
this.materialInstance.setFloat4Parameter('sunHalo2', new Float32Array(moonHaloUpload));
|
this.materialInstance.setFloat4Parameter('sunHalo2', new Float32Array(moonHaloUpload));
|
||||||
|
this.materialInstance.setFloatParameter('exposure', this.exposure !== undefined ? this.exposure : 1.0);
|
||||||
|
|
||||||
// Solar Eclipse (CPU Calculation)
|
// Solar Eclipse (CPU Calculation)
|
||||||
const sunRadius = Math.acos(this.sunHalo[0]);
|
const sunRadius = Math.acos(this.sunHalo[0]);
|
||||||
|
|||||||
Binary file not shown.
@@ -218,6 +218,7 @@ class App {
|
|||||||
const exposure = this.getExposure();
|
const exposure = this.getExposure();
|
||||||
const preExposedIntensity = this.params.sunIntensity * exposure;
|
const preExposedIntensity = this.params.sunIntensity * exposure;
|
||||||
this.skybox.setSunIntensity(preExposedIntensity);
|
this.skybox.setSunIntensity(preExposedIntensity);
|
||||||
|
this.skybox.setExposure(exposure); // Update Skybox Exposure Uniform
|
||||||
|
|
||||||
// Moon Exposure
|
// Moon Exposure
|
||||||
if (this.mParams) {
|
if (this.mParams) {
|
||||||
@@ -389,7 +390,7 @@ class App {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mwFolder.add(this.mwParams, 'enabled').name('Enabled').onChange(updateMW);
|
mwFolder.add(this.mwParams, 'enabled').name('Enabled').onChange(updateMW);
|
||||||
mwFolder.add(this.mwParams, 'intensity', 0.0, 5.0).onChange(updateMW);
|
mwFolder.add(this.mwParams, 'intensity', 0.0, 100.0).onChange(updateMW);
|
||||||
mwFolder.add(this.mwParams, 'saturation', 0.0, 2.0).onChange(updateMW);
|
mwFolder.add(this.mwParams, 'saturation', 0.0, 2.0).onChange(updateMW);
|
||||||
mwFolder.add(this.mwParams, 'blackPoint', 0.0, 0.5).name('Black Point').onChange(updateMW);
|
mwFolder.add(this.mwParams, 'blackPoint', 0.0, 0.5).name('Black Point').onChange(updateMW);
|
||||||
mwFolder.add(this.mwParams, 'siderealTime', 0.0, 24.0).name('Sidereal Time').listen().onChange(updateMW);
|
mwFolder.add(this.mwParams, 'siderealTime', 0.0, 24.0).name('Sidereal Time').listen().onChange(updateMW);
|
||||||
@@ -464,18 +465,23 @@ class App {
|
|||||||
const starFolder = gui.addFolder('Stars');
|
const starFolder = gui.addFolder('Stars');
|
||||||
this.sParams = {
|
this.sParams = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
density: 0.001
|
density: 0.001,
|
||||||
|
intensity: 0.0 // 2^0 = 1.0
|
||||||
};
|
};
|
||||||
// Initialize defaults (Density 0.001, Enabled True)
|
// Initialize defaults (Density 0.001, Enabled True)
|
||||||
sky.setStarControl(0.001, true);
|
sky.setStarControl(0.001, true);
|
||||||
|
sky.setStarIntensity(Math.pow(2.0, 0.0));
|
||||||
|
|
||||||
const updateStars = () => {
|
const updateStars = () => {
|
||||||
sky.setStarControl(this.sParams.density, this.sParams.enabled);
|
sky.setStarControl(this.sParams.density, this.sParams.enabled);
|
||||||
|
sky.setStarIntensity(Math.pow(2.0, this.sParams.intensity));
|
||||||
};
|
};
|
||||||
|
|
||||||
starFolder.add(this.sParams, 'enabled').name('Enabled').onChange(updateStars);
|
starFolder.add(this.sParams, 'enabled').name('Enabled').onChange(updateStars);
|
||||||
starFolder.add(this.sParams, 'density', 0.0, 0.01, 0.0001).name('Density').onChange(updateStars);
|
starFolder.add(this.sParams, 'density', 0.0, 0.01, 0.0001).name('Density').onChange(updateStars);
|
||||||
|
starFolder.add(this.sParams, 'intensity', 0.0, 24.0).name('Intensity (Exp)').onChange(updateStars);
|
||||||
starFolder.close();
|
starFolder.close();
|
||||||
|
this.updateStars = updateStars;
|
||||||
|
|
||||||
const artFolder = gui.addFolder('Artistic');
|
const artFolder = gui.addFolder('Artistic');
|
||||||
// Set Horizon Glow default to 0.0
|
// Set Horizon Glow default to 0.0
|
||||||
@@ -489,7 +495,7 @@ class App {
|
|||||||
artFolder.add(sky.msFactors, 2, 0.0, 1.0).name('Horizon Glow').onChange(v => sky.setHorizonGlow(v));
|
artFolder.add(sky.msFactors, 2, 0.0, 1.0).name('Horizon Glow').onChange(v => sky.setHorizonGlow(v));
|
||||||
artFolder.add(sky, 'contrast', 0.1, 2.0).onChange(v => sky.setContrast(v));
|
artFolder.add(sky, 'contrast', 0.1, 2.0).onChange(v => sky.setContrast(v));
|
||||||
|
|
||||||
artFolder.addColor(sky, 'nightColor').onChange(v => sky.setNightColor(v));
|
|
||||||
|
|
||||||
const shmFolder = artFolder.addFolder('Shimmer');
|
const shmFolder = artFolder.addFolder('Shimmer');
|
||||||
// Set Shimmer Strength default to 0.0
|
// Set Shimmer Strength default to 0.0
|
||||||
@@ -502,7 +508,7 @@ class App {
|
|||||||
const camFolder = gui.addFolder('Camera');
|
const camFolder = gui.addFolder('Camera');
|
||||||
camFolder.add(this.params, 'focalLength', 8.0, 300.0).name('Focal Length').onChange(() => this.updateCameraProjection());
|
camFolder.add(this.params, 'focalLength', 8.0, 300.0).name('Focal Length').onChange(() => this.updateCameraProjection());
|
||||||
camFolder.add(this.params, 'aperture', 1.4, 32.0).onChange(() => this.updateCameraExposure());
|
camFolder.add(this.params, 'aperture', 1.4, 32.0).onChange(() => this.updateCameraExposure());
|
||||||
camFolder.add(this.params, 'shutterSpeed', 1.0, 1000.0).onChange(() => this.updateCameraExposure());
|
camFolder.add(this.params, 'shutterSpeed', 0.05, 1000.0).onChange(() => this.updateCameraExposure());
|
||||||
camFolder.add(this.params, 'iso', 50.0, 3200.0).onChange(() => this.updateCameraExposure());
|
camFolder.add(this.params, 'iso', 50.0, 3200.0).onChange(() => this.updateCameraExposure());
|
||||||
|
|
||||||
const bloomFolder = camFolder.addFolder('Bloom');
|
const bloomFolder = camFolder.addFolder('Bloom');
|
||||||
@@ -692,13 +698,15 @@ class App {
|
|||||||
const s = this.sParams;
|
const s = this.sParams;
|
||||||
const b = this.bParams;
|
const b = this.bParams;
|
||||||
const m = this.mParams;
|
const m = this.mParams;
|
||||||
|
const mw = this.mwParams;
|
||||||
const sk = this.skybox;
|
const sk = this.skybox;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
p: { a: p.aperture, ss: p.shutterSpeed, i: p.iso, st: p.sunTheta, sp: p.sunPhi, fl: p.focalLength, si: p.sunIntensity },
|
p: { a: p.aperture, ss: p.shutterSpeed, i: p.iso, st: p.sunTheta, sp: p.sunPhi, fl: p.focalLength, si: p.sunIntensity },
|
||||||
c: { v: c.volumetrics, co: c.coverage, d: c.density, h: c.height, s: c.speed, e: c.evolution },
|
c: { v: c.volumetrics, co: c.coverage, d: c.density, h: c.height, s: c.speed, e: c.evolution },
|
||||||
w: { dt: w.derivativeTrick, st: w.strength, s: w.speed, o: w.octaves },
|
w: { dt: w.derivativeTrick, st: w.strength, s: w.speed, o: w.octaves },
|
||||||
s: { e: s.enabled, d: s.density },
|
s: { e: s.enabled, d: s.density, i: s.intensity },
|
||||||
|
mw: { e: mw.enabled, i: mw.intensity, s: mw.saturation, bp: mw.blackPoint, st: mw.siderealTime, l: mw.latitude },
|
||||||
b: { e: b.enabled, lf: b.lensFlare },
|
b: { e: b.enabled, lf: b.lensFlare },
|
||||||
m: { e: m.enabled, az: m.azimuth, h: m.height, r: m.radius, i: m.intensity },
|
m: { e: m.enabled, az: m.azimuth, h: m.height, r: m.radius, i: m.intensity },
|
||||||
cm: { t: this.camState.theta, p: this.camState.phi },
|
cm: { t: this.camState.theta, p: this.camState.phi },
|
||||||
@@ -723,6 +731,7 @@ class App {
|
|||||||
const c = state.c;
|
const c = state.c;
|
||||||
const w = state.w;
|
const w = state.w;
|
||||||
const s = state.s;
|
const s = state.s;
|
||||||
|
const mw = state.mw;
|
||||||
const b = state.b;
|
const b = state.b;
|
||||||
const m = state.m;
|
const m = state.m;
|
||||||
const k = state.k;
|
const k = state.k;
|
||||||
@@ -757,6 +766,18 @@ class App {
|
|||||||
if (s) {
|
if (s) {
|
||||||
if (s.e !== undefined) this.sParams.enabled = s.e;
|
if (s.e !== undefined) this.sParams.enabled = s.e;
|
||||||
if (s.d !== undefined) this.sParams.density = s.d;
|
if (s.d !== undefined) this.sParams.density = s.d;
|
||||||
|
if (s.i !== undefined) this.sParams.intensity = s.i;
|
||||||
|
if (this.updateStars) this.updateStars();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mw) {
|
||||||
|
if (mw.e !== undefined) this.mwParams.enabled = mw.e;
|
||||||
|
if (mw.i !== undefined) this.mwParams.intensity = mw.i;
|
||||||
|
if (mw.s !== undefined) this.mwParams.saturation = mw.s;
|
||||||
|
if (mw.bp !== undefined) this.mwParams.blackPoint = mw.bp;
|
||||||
|
if (mw.st !== undefined) this.mwParams.siderealTime = mw.st;
|
||||||
|
if (mw.l !== undefined) this.mwParams.latitude = mw.l;
|
||||||
|
if (this.updateMW) this.updateMW();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (b) {
|
if (b) {
|
||||||
|
|||||||
@@ -96,10 +96,20 @@ material {
|
|||||||
name : starControl, // x=Density, y=Enabled, z=Frequency, w=PixelScale
|
name : starControl, // x=Density, y=Enabled, z=Frequency, w=PixelScale
|
||||||
precision : high
|
precision : high
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type : float,
|
||||||
|
name : starIntensity,
|
||||||
|
precision : high
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type : sampler2d,
|
type : sampler2d,
|
||||||
name : moonTexture
|
name : moonTexture
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type : float,
|
||||||
|
name : exposure,
|
||||||
|
precision : high
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type : sampler2d,
|
type : sampler2d,
|
||||||
name : moonNormal
|
name : moonNormal
|
||||||
@@ -162,9 +172,9 @@ fragment {
|
|||||||
// --- CONFIGURATION ---
|
// --- CONFIGURATION ---
|
||||||
|
|
||||||
// Stars
|
// Stars
|
||||||
#define STAR_GLOBAL_INTENSITY 150.0 // Master brightness multiplier [0.0 - 500.0]
|
#define STAR_GLOBAL_INTENSITY 100.0 // Master brightness multiplier [0.0 - 500.0]
|
||||||
#define STAR_BRIGHTNESS_BASE 0.5 // Minimum random brightness [0.0 - 1.0]
|
#define STAR_BRIGHTNESS_BASE 0.01 // Minimum random brightness [0.0 - 1.0]
|
||||||
#define STAR_BRIGHTNESS_VAR 4.0 // Random brightness variance range [0.0 - 10.0]
|
#define STAR_BRIGHTNESS_VAR 15.0 // Random brightness variance range [0.0 - 10.0]
|
||||||
#define STAR_FADE_SUN_ELV_HIGH 0.10 // Sun elevation (sin) where stars are fully hidden [0.0 - 0.5]
|
#define STAR_FADE_SUN_ELV_HIGH 0.10 // Sun elevation (sin) where stars are fully hidden [0.0 - 0.5]
|
||||||
#define STAR_FADE_SUN_ELV_LOW -0.20 // Sun elevation (sin) where stars are fully visible [-0.5 - 0.0]
|
#define STAR_FADE_SUN_ELV_LOW -0.20 // Sun elevation (sin) where stars are fully visible [-0.5 - 0.0]
|
||||||
#define STAR_CLOUD_OCCLUSION 0.1 // Visibility when covered by clouds [0.0 - 1.0]
|
#define STAR_CLOUD_OCCLUSION 0.1 // Visibility when covered by clouds [0.0 - 1.0]
|
||||||
@@ -961,7 +971,11 @@ fragment {
|
|||||||
highp vec4 sunHalo, highp vec4 cloudControl, highp vec4 cloudControl2,
|
highp vec4 sunHalo, highp vec4 cloudControl, highp vec4 cloudControl2,
|
||||||
highp vec4 shimmerControl, highp vec4 waterControl,
|
highp vec4 shimmerControl, highp vec4 waterControl,
|
||||||
highp vec3 L2, highp float sunIntensity2, highp vec4 sunHalo2,
|
highp vec3 L2, highp float sunIntensity2, highp vec4 sunHalo2,
|
||||||
sampler2D moonTex, sampler2D moonNormal) {
|
sampler2D moonTex, sampler2D moonNormal,
|
||||||
|
highp vec3 nightColor,
|
||||||
|
highp mat3 milkyWayRotation,
|
||||||
|
highp vec3 milkyWayControl,
|
||||||
|
sampler2D milkyWayTexture) {
|
||||||
|
|
||||||
// Project to plane y=0
|
// Project to plane y=0
|
||||||
highp float t = WATER_PLANE_HEIGHT / min(V.y, -0.0002); // Reduced clamp to minimize "wall" artifact
|
highp float t = WATER_PLANE_HEIGHT / min(V.y, -0.0002); // Reduced clamp to minimize "wall" artifact
|
||||||
@@ -1057,7 +1071,7 @@ fragment {
|
|||||||
highp float rHorizonMask = 1.0 - smoothstep(0.0, REFLECTION_HORIZON_FADE, R.y);
|
highp float rHorizonMask = 1.0 - smoothstep(0.0, REFLECTION_HORIZON_FADE, R.y);
|
||||||
|
|
||||||
if (rHorizonMask > 0.0) {
|
if (rHorizonMask > 0.0) {
|
||||||
reflection += getStarLayer(R, L, reflCloudDensity, outTransmittance, materialParams.starControl) * rHorizonMask;
|
reflection += getStarLayer(R, L, reflCloudDensity, outTransmittance, materialParams.starControl) * rHorizonMask * materialParams.exposure;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sun Disk Reflection
|
// Sun Disk Reflection
|
||||||
@@ -1088,6 +1102,29 @@ fragment {
|
|||||||
// Apply clouds to reflection
|
// Apply clouds to reflection
|
||||||
reflection = mix(reflection, reflCloudLayer, reflCloudDensity);
|
reflection = mix(reflection, reflCloudLayer, reflCloudDensity);
|
||||||
|
|
||||||
|
// Add Milky Way to Reflection
|
||||||
|
// Calculate Fade based on Sun Elevation
|
||||||
|
highp float sunElvSin = L.y;
|
||||||
|
highp float mwFade = smoothstep(STAR_FADE_SUN_ELV_HIGH, STAR_FADE_SUN_ELV_LOW, sunElvSin);
|
||||||
|
|
||||||
|
if (mwFade > 0.0) {
|
||||||
|
highp vec3 mwColor = getMilkyWay(R, milkyWayRotation, milkyWayTexture, milkyWayControl);
|
||||||
|
|
||||||
|
// Apply Atmosphere Transmittance (approximate)
|
||||||
|
mwColor *= outTransmittance;
|
||||||
|
|
||||||
|
// Apply Fades
|
||||||
|
mwColor *= mwFade;
|
||||||
|
mwColor *= (1.0 - reflMoonOcclusion); // Occlude by Moon
|
||||||
|
mwColor *= (1.0 - reflCloudDensity); // Occlude by Clouds
|
||||||
|
|
||||||
|
reflection += mwColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Add Night Color to Reflection
|
||||||
|
reflection += nightColor;
|
||||||
|
|
||||||
// Fresnel
|
// Fresnel
|
||||||
highp float F0 = WATER_FRESNEL_F0; // Water
|
highp float F0 = WATER_FRESNEL_F0; // Water
|
||||||
highp float cosTheta = clamp(dot(-V, N_water), 0.0, 1.0);
|
highp float cosTheta = clamp(dot(-V, N_water), 0.0, 1.0);
|
||||||
@@ -1187,7 +1224,7 @@ fragment {
|
|||||||
|
|
||||||
// 7. Stars
|
// 7. Stars
|
||||||
// Add stars before clouds (clouds cover stars)
|
// Add stars before clouds (clouds cover stars)
|
||||||
highp vec3 starColor = getStarLayer(V, L, cloudDensityVal, transmittance, materialParams.starControl);
|
highp vec3 starColor = getStarLayer(V, L, cloudDensityVal, transmittance, materialParams.starControl) * materialParams.exposure * materialParams.starIntensity;
|
||||||
|
|
||||||
// 7b. Milky Way
|
// 7b. Milky Way
|
||||||
// Add Milky Way behind stars (conceptually) but handled similarly
|
// Add Milky Way behind stars (conceptually) but handled similarly
|
||||||
@@ -1232,7 +1269,11 @@ fragment {
|
|||||||
materialParams.shimmerControl,
|
materialParams.shimmerControl,
|
||||||
materialParams.waterControl,
|
materialParams.waterControl,
|
||||||
L2, materialParams.sunIntensity2, materialParams.sunHalo2,
|
L2, materialParams.sunIntensity2, materialParams.sunHalo2,
|
||||||
materialParams_moonTexture, materialParams_moonNormal);
|
materialParams_moonTexture, materialParams_moonNormal,
|
||||||
|
materialParams.nightColor,
|
||||||
|
materialParams.milkyWayRotation,
|
||||||
|
materialParams.milkyWayControl,
|
||||||
|
materialParams_milkyWayTexture);
|
||||||
color = applyDynamicToneMapping(color, L, materialParams.contrast);
|
color = applyDynamicToneMapping(color, L, materialParams.contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -578,6 +578,7 @@ if (APPLE OR LINUX)
|
|||||||
test/Shader.cpp
|
test/Shader.cpp
|
||||||
test/SharedShaders.cpp
|
test/SharedShaders.cpp
|
||||||
test/Skip.cpp
|
test/Skip.cpp
|
||||||
|
test/test_Autoresolve.cpp
|
||||||
test/test_FeedbackLoops.cpp
|
test/test_FeedbackLoops.cpp
|
||||||
test/test_Blit.cpp
|
test/test_Blit.cpp
|
||||||
test/test_MissingRequiredAttributes.cpp
|
test/test_MissingRequiredAttributes.cpp
|
||||||
|
|||||||
@@ -561,16 +561,18 @@ public:
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the callback function that the backend can use to update backend-specific statistics
|
* Sets the callback function that the backend can use to update backend-specific statistics
|
||||||
* to aid with debugging. This callback is guaranteed to be called on the Filament driver
|
* to aid with debugging. This callback can be called on either the Filament main thread or
|
||||||
* thread.
|
* the Filament driver thread.
|
||||||
*
|
*
|
||||||
* The callback signature is (key, intValue, stringValue). Note that for any given call,
|
* The callback signature is (key, intValue, stringValue). Note that for any given call,
|
||||||
* only one of the value parameters (intValue or stringValue) will be meaningful, depending on
|
* only one of the value parameters (intValue or stringValue) will be meaningful, depending on
|
||||||
* the specific key.
|
* the specific key.
|
||||||
*
|
*
|
||||||
* IMPORTANT_NOTE: because the callback is called on the driver thread, only quick, non-blocking
|
* IMPORTANT_NOTE: because the callback can be called on the driver thread, only quick,
|
||||||
* work should be done inside it. Furthermore, no graphics API calls (such as GL calls) should
|
* non-blocking work should be done inside it. Furthermore, no graphics API calls (such as GL
|
||||||
* be made, which could interfere with Filament's driver state.
|
* calls) should be made, which could interfere with Filament's driver state. Lastly, the
|
||||||
|
* callback implementation must be synchronized (thread-safe) since it can be called from
|
||||||
|
* either thread.
|
||||||
*
|
*
|
||||||
* @param debugUpdateStat an Invocable that updates debug statistics
|
* @param debugUpdateStat an Invocable that updates debug statistics
|
||||||
*/
|
*/
|
||||||
@@ -587,8 +589,7 @@ public:
|
|||||||
* with a given key. It is possible for this function to be called multiple times with the
|
* with a given key. It is possible for this function to be called multiple times with the
|
||||||
* same key, in which case newer values should overwrite older values.
|
* same key, in which case newer values should overwrite older values.
|
||||||
*
|
*
|
||||||
* This function is guaranteed to be called only on a single thread, the Filament driver
|
* This function can be called on either the Filament main thread or the Filament driver thread.
|
||||||
* thread.
|
|
||||||
*
|
*
|
||||||
* @param key a null-terminated C-string with the key of the debug statistic
|
* @param key a null-terminated C-string with the key of the debug statistic
|
||||||
* @param intValue the updated integer value of key (the string value passed to the
|
* @param intValue the updated integer value of key (the string value passed to the
|
||||||
@@ -602,8 +603,7 @@ public:
|
|||||||
* with a given key. It is possible for this function to be called multiple times with the
|
* with a given key. It is possible for this function to be called multiple times with the
|
||||||
* same key, in which case newer values should overwrite older values.
|
* same key, in which case newer values should overwrite older values.
|
||||||
*
|
*
|
||||||
* This function is guaranteed to be called only on a single thread, the Filament driver
|
* This function can be called on either the Filament main thread or the Filament driver thread.
|
||||||
* thread.
|
|
||||||
*
|
*
|
||||||
* @param key a null-terminated C-string with the key of the debug statistic
|
* @param key a null-terminated C-string with the key of the debug statistic
|
||||||
* @param stringValue the updated string value of key (the integer value passed to the
|
* @param stringValue the updated string value of key (the integer value passed to the
|
||||||
|
|||||||
@@ -19,21 +19,169 @@
|
|||||||
|
|
||||||
#include <backend/PixelBufferDescriptor.h>
|
#include <backend/PixelBufferDescriptor.h>
|
||||||
|
|
||||||
|
#include <math/scalar.h>
|
||||||
|
#include <math/half.h>
|
||||||
|
|
||||||
|
#include <utils/debug.h>
|
||||||
|
#include <utils/Logger.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include <math/scalar.h>
|
|
||||||
|
|
||||||
#include <utils/debug.h>
|
|
||||||
|
|
||||||
namespace filament {
|
namespace filament {
|
||||||
namespace backend {
|
namespace backend {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
// Provides an alpha value when expanding 3-channel images to 4-channel.
|
// Provides an alpha value when expanding 3-channel images to 4-channel.
|
||||||
// Also used as a normalization scale when converting between numeric types.
|
// Also used as a normalization scale when converting between numeric types.
|
||||||
template<typename componentType> inline componentType getMaxValue();
|
template<typename componentType> inline componentType getMaxValue();
|
||||||
|
|
||||||
|
template<> inline constexpr float getMaxValue() { return 1.0f; }
|
||||||
|
template<> inline constexpr int32_t getMaxValue() { return 0x7fffffff; }
|
||||||
|
template<> inline constexpr uint32_t getMaxValue() { return 0xffffffff; }
|
||||||
|
template<> inline constexpr uint16_t getMaxValue() { return 0x3c00; } // 0x3c00 is 1.0 in half-float.
|
||||||
|
template<> inline constexpr uint8_t getMaxValue() { return 0xff; }
|
||||||
|
template<> inline math::half getMaxValue() { return math::half(1.0f); }
|
||||||
|
|
||||||
|
// We use template below to reduce code duplication across the different input/output
|
||||||
|
// type/channle-count permutations. Morever, templates help us reduce the number of conditionals
|
||||||
|
// in the inner-loop of the reshape operation. However, this needs to be a carefully considered
|
||||||
|
// because too many templated params will cause a large binary size increase.
|
||||||
|
|
||||||
|
// Note that we intentionally do not want to expand the template params to include the channel count
|
||||||
|
// because of the size increase.
|
||||||
|
template<typename dstComponentType, bool hasAlpha>
|
||||||
|
void grayscaleFill(dstComponentType* dst, uint8_t, uint8_t) {
|
||||||
|
for (size_t channel = 1; channel < 3; ++channel) {
|
||||||
|
dst[channel] = dst[0];
|
||||||
|
}
|
||||||
|
if constexpr (hasAlpha) {
|
||||||
|
dst[3] = getMaxValue<dstComponentType>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that we intentionally do not want to expand the template params to include the channel count
|
||||||
|
// because of the size increase.
|
||||||
|
template<typename dstComponentType>
|
||||||
|
inline void maxValFill(dstComponentType* dst, uint8_t srcChannelCount, uint8_t dstChannelCount) {
|
||||||
|
dstComponentType dstMaxValue = getMaxValue<dstComponentType>();
|
||||||
|
for (size_t channel = srcChannelCount; channel < dstChannelCount; ++channel) {
|
||||||
|
dst[channel] = dstMaxValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts a n-channel image of UBYTE, INT, UINT, HALF, or FLOAT to a different type.
|
||||||
|
template<typename dstComponentType, typename srcComponentType>
|
||||||
|
void reshapeImageImpl(uint8_t* UTILS_RESTRICT dest, const uint8_t* UTILS_RESTRICT src,
|
||||||
|
size_t srcBytesPerRow, size_t srcChannelCount, size_t dstRowOffset, size_t dstColumnOffset,
|
||||||
|
size_t dstBytesPerRow, size_t dstChannelCount, size_t width, size_t height, bool swizzle) {
|
||||||
|
|
||||||
|
static_assert(!std::is_same_v<dstComponentType, math::half>);
|
||||||
|
const size_t minChannelCount = math::min(srcChannelCount, dstChannelCount);
|
||||||
|
const dstComponentType dstMaxValue = getMaxValue<dstComponentType>();
|
||||||
|
const srcComponentType srcMaxValue = getMaxValue<srcComponentType>();
|
||||||
|
double const mFactor = dstMaxValue / ((double) srcMaxValue);
|
||||||
|
assert_invariant(minChannelCount <= 4);
|
||||||
|
UTILS_ASSUME(minChannelCount <= 4);
|
||||||
|
dest += (dstRowOffset * dstBytesPerRow);
|
||||||
|
|
||||||
|
void (*fill)(dstComponentType*, uint8_t, uint8_t);
|
||||||
|
if (srcChannelCount == 1 && dstChannelCount == 3) {
|
||||||
|
fill = grayscaleFill<dstComponentType, false>;
|
||||||
|
} else if (srcChannelCount == 1 && dstChannelCount == 4) {
|
||||||
|
fill = grayscaleFill<dstComponentType, true>;
|
||||||
|
} else {
|
||||||
|
fill = maxValFill<dstComponentType>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int inds[4] = { swizzle ? 2 : 0, 1, swizzle ? 0 : 2, 3 };
|
||||||
|
for (size_t row = 0; row < height; ++row) {
|
||||||
|
const srcComponentType* in = (const srcComponentType*) src;
|
||||||
|
dstComponentType* out = (dstComponentType*) dest + (dstColumnOffset * dstChannelCount);
|
||||||
|
for (size_t column = 0; column < width; ++column) {
|
||||||
|
for (uint8_t channel = 0; channel < minChannelCount; ++channel) {
|
||||||
|
if constexpr (std::is_same_v<dstComponentType, srcComponentType>) {
|
||||||
|
out[channel] = in[inds[channel]];
|
||||||
|
} else {
|
||||||
|
// convert to double then clamp and cast to dst type.
|
||||||
|
out[channel] = static_cast<dstComponentType>(std::clamp(
|
||||||
|
in[inds[channel]] * mFactor, 0.0,
|
||||||
|
static_cast<double>(std::numeric_limits<dstComponentType>::max())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will fill in all the channels that are not copied.
|
||||||
|
fill(out, srcChannelCount, dstChannelCount);
|
||||||
|
in += srcChannelCount;
|
||||||
|
out += dstChannelCount;
|
||||||
|
}
|
||||||
|
src += srcBytesPerRow;
|
||||||
|
dest += dstBytesPerRow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UnpackerR11G11B10 {
|
||||||
|
static void unpack(const uint8_t* src, float* out) {
|
||||||
|
uint32_t p;
|
||||||
|
std::memcpy(&p, src, 4);
|
||||||
|
|
||||||
|
using R11 = math::fp<0, 5, 6>;
|
||||||
|
using G11 = math::fp<0, 5, 6>;
|
||||||
|
using B10 = math::fp<0, 5, 5>;
|
||||||
|
|
||||||
|
out[0] = R11::tof(R11(uint16_t((p >> 21) & 0x7FF)));
|
||||||
|
out[1] = G11::tof(G11(uint16_t((p >> 10) & 0x7FF)));
|
||||||
|
out[2] = B10::tof(B10(uint16_t(p & 0x3FF)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename dstComponentType, typename Unpacker, bool Swizzle>
|
||||||
|
static void reshapeImagePacked(uint8_t* UTILS_RESTRICT dest, const uint8_t* UTILS_RESTRICT src,
|
||||||
|
size_t srcBytesPerRow, size_t srcChannelCount, size_t dstRowOffset, size_t dstColumnOffset,
|
||||||
|
size_t dstBytesPerRow, size_t dstChannelCount, size_t width, size_t height, bool /*swizzle*/) {
|
||||||
|
|
||||||
|
dest += (dstRowOffset * dstBytesPerRow);
|
||||||
|
const dstComponentType dstMaxValue = getMaxValue<dstComponentType>();
|
||||||
|
|
||||||
|
for (size_t row = 0; row < height; ++row) {
|
||||||
|
const uint8_t* inPtr = src;
|
||||||
|
dstComponentType* out = (dstComponentType*) dest + (dstColumnOffset * dstChannelCount);
|
||||||
|
|
||||||
|
for (size_t column = 0; column < width; ++column) {
|
||||||
|
float rgba[4] = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||||
|
Unpacker::unpack(inPtr, rgba);
|
||||||
|
|
||||||
|
if constexpr (Swizzle) {
|
||||||
|
std::swap(rgba[0], rgba[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t c = 0; c < dstChannelCount; ++c) {
|
||||||
|
if constexpr (std::is_same_v<dstComponentType, float>) {
|
||||||
|
out[c] = rgba[c];
|
||||||
|
} else if constexpr (std::is_same_v<dstComponentType, math::half>) {
|
||||||
|
out[c] = math::half(rgba[c]);
|
||||||
|
} else {
|
||||||
|
out[c] = static_cast<dstComponentType>(std::clamp(
|
||||||
|
static_cast<double>(rgba[c]) * static_cast<double>(dstMaxValue),
|
||||||
|
0.0,
|
||||||
|
static_cast<double>(std::numeric_limits<dstComponentType>::max())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inPtr += 4;
|
||||||
|
out += dstChannelCount;
|
||||||
|
}
|
||||||
|
src += srcBytesPerRow;
|
||||||
|
dest += dstBytesPerRow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
class DataReshaper {
|
class DataReshaper {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
@@ -76,51 +224,9 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converts a n-channel image of UBYTE, INT, UINT, or FLOAT to a different type.
|
|
||||||
template<typename dstComponentType, typename srcComponentType>
|
|
||||||
static void reshapeImage(uint8_t* UTILS_RESTRICT dest, const uint8_t* UTILS_RESTRICT src,
|
|
||||||
size_t srcBytesPerRow,
|
|
||||||
size_t srcChannelCount,
|
|
||||||
size_t dstRowOffset, size_t dstColumnOffset,
|
|
||||||
size_t dstBytesPerRow, size_t dstChannelCount,
|
|
||||||
size_t width, size_t height, bool swizzle) {
|
|
||||||
// TODO: there's a fast-path where memcpy will work but currently not being taken advantage
|
|
||||||
// of.
|
|
||||||
|
|
||||||
const dstComponentType dstMaxValue = getMaxValue<dstComponentType>();
|
|
||||||
const srcComponentType srcMaxValue = getMaxValue<srcComponentType>();
|
|
||||||
const size_t minChannelCount = math::min(srcChannelCount, dstChannelCount);
|
|
||||||
assert_invariant(minChannelCount <= 4);
|
|
||||||
UTILS_ASSUME(minChannelCount <= 4);
|
|
||||||
dest += (dstRowOffset * dstBytesPerRow);
|
|
||||||
const int inds[4] = { swizzle ? 2 : 0, 1, swizzle ? 0 : 2, 3 };
|
|
||||||
for (size_t row = 0; row < height; ++row) {
|
|
||||||
const srcComponentType* in = (const srcComponentType*) src;
|
|
||||||
dstComponentType* out = (dstComponentType*)dest + (dstColumnOffset * dstChannelCount);
|
|
||||||
for (size_t column = 0; column < width; ++column) {
|
|
||||||
for (size_t channel = 0; channel < minChannelCount; ++channel) {
|
|
||||||
if constexpr (std::is_same_v<dstComponentType, srcComponentType>) {
|
|
||||||
out[channel] = in[inds[channel]];
|
|
||||||
} else {
|
|
||||||
// FIXME: beware of overflows in the multiply
|
|
||||||
// FIXME: probably not correct for _INTEGER src/dst
|
|
||||||
out[channel] = in[inds[channel]] * dstMaxValue / srcMaxValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (size_t channel = srcChannelCount; channel < dstChannelCount; ++channel) {
|
|
||||||
out[channel] = dstMaxValue;
|
|
||||||
}
|
|
||||||
in += srcChannelCount;
|
|
||||||
out += dstChannelCount;
|
|
||||||
}
|
|
||||||
src += srcBytesPerRow;
|
|
||||||
dest += dstBytesPerRow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Converts a n-channel image of UBYTE, INT, UINT, or FLOAT to a different type.
|
// Converts a n-channel image of UBYTE, INT, UINT, or FLOAT to a different type.
|
||||||
static bool reshapeImage(PixelBufferDescriptor* UTILS_RESTRICT dst, PixelDataType srcType,
|
static bool reshapeImage(PixelBufferDescriptor* UTILS_RESTRICT dst, PixelDataType srcType,
|
||||||
uint32_t srcChannelCount, const uint8_t* UTILS_RESTRICT srcBytes, int srcBytesPerRow,
|
uint32_t srcChannelCount, const uint8_t* UTILS_RESTRICT srcBytes, int srcBytesPerRow,
|
||||||
int width, int height, bool swizzle) {
|
int width, int height, bool swizzle) {
|
||||||
size_t dstChannelCount;
|
size_t dstChannelCount;
|
||||||
switch (dst->format) {
|
switch (dst->format) {
|
||||||
@@ -132,87 +238,123 @@ public:
|
|||||||
case PixelDataFormat::RG: dstChannelCount = 2; break;
|
case PixelDataFormat::RG: dstChannelCount = 2; break;
|
||||||
case PixelDataFormat::RGB: dstChannelCount = 3; break;
|
case PixelDataFormat::RGB: dstChannelCount = 3; break;
|
||||||
case PixelDataFormat::RGBA: dstChannelCount = 4; break;
|
case PixelDataFormat::RGBA: dstChannelCount = 4; break;
|
||||||
default: return false;
|
default:
|
||||||
|
LOG(ERROR) << "DataReshaper: unsupported dst->format: " << (int) dst->format;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
void (*reshaper)(uint8_t* dest, const uint8_t* src, size_t srcBytesPerRow,
|
void (*reshaper)(uint8_t* dest, const uint8_t* src, size_t srcBytesPerRow,
|
||||||
size_t srcChannelCount,
|
size_t srcChannelCount, size_t srcRowOffset, size_t srcColumnOffset,
|
||||||
size_t srcRowOffset, size_t srcColumnOffset,
|
size_t dstBytesPerRow, size_t dstChannelCount, size_t width, size_t height,
|
||||||
size_t dstBytesPerRow, size_t dstChannelCount,
|
bool swizzle) = nullptr;
|
||||||
size_t width, size_t height, bool swizzle) = nullptr;
|
|
||||||
constexpr auto UBYTE = PixelDataType::UBYTE;
|
constexpr auto UBYTE = PixelDataType::UBYTE;
|
||||||
constexpr auto FLOAT = PixelDataType::FLOAT;
|
constexpr auto FLOAT = PixelDataType::FLOAT;
|
||||||
constexpr auto UINT = PixelDataType::UINT;
|
constexpr auto UINT = PixelDataType::UINT;
|
||||||
constexpr auto INT = PixelDataType::INT;
|
constexpr auto INT = PixelDataType::INT;
|
||||||
constexpr auto HALF = PixelDataType::HALF;
|
constexpr auto HALF = PixelDataType::HALF;
|
||||||
|
constexpr auto UINT_10F_11F_11F_REV = PixelDataType::UINT_10F_11F_11F_REV;
|
||||||
|
|
||||||
switch (dst->type) {
|
switch (dst->type) {
|
||||||
case UBYTE:
|
case UBYTE:
|
||||||
switch (srcType) {
|
switch (srcType) {
|
||||||
case UBYTE:
|
case UBYTE:
|
||||||
reshaper = reshapeImage<uint8_t, uint8_t>;
|
reshaper = reshapeImageImpl<uint8_t, uint8_t>;
|
||||||
if (dst->format == PixelDataFormat::RGBA &&
|
if (dst->format == PixelDataFormat::RGBA &&
|
||||||
dstChannelCount == srcChannelCount && !swizzle && dst->top == 0 &&
|
dstChannelCount == srcChannelCount && !swizzle && dst->top == 0 &&
|
||||||
dst->left == 0) {
|
dst->left == 0) {
|
||||||
reshaper = copyImage;
|
reshaper = copyImage;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case FLOAT: reshaper = reshapeImage<uint8_t, float>; break;
|
case FLOAT: reshaper = reshapeImageImpl<uint8_t, float>; break;
|
||||||
case INT: reshaper = reshapeImage<uint8_t, int32_t>; break;
|
case INT: reshaper = reshapeImageImpl<uint8_t, int32_t>; break;
|
||||||
case UINT: reshaper = reshapeImage<uint8_t, uint32_t>; break;
|
case UINT: reshaper = reshapeImageImpl<uint8_t, uint32_t>; break;
|
||||||
default: return false;
|
case HALF: reshaper = reshapeImageImpl<uint8_t, math::half>; break;
|
||||||
|
case UINT_10F_11F_11F_REV:
|
||||||
|
if (swizzle) reshaper = reshapeImagePacked<uint8_t, UnpackerR11G11B10, true>;
|
||||||
|
else reshaper = reshapeImagePacked<uint8_t, UnpackerR11G11B10, false>;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG(ERROR) << "DataReshaper: UBYTE dst, unsupported srcType: "
|
||||||
|
<< (int) srcType;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case FLOAT:
|
case FLOAT:
|
||||||
switch (srcType) {
|
switch (srcType) {
|
||||||
case UBYTE: reshaper = reshapeImage<float, uint8_t>; break;
|
case UBYTE: reshaper = reshapeImageImpl<float, uint8_t>; break;
|
||||||
case FLOAT: reshaper = reshapeImage<float, float>; break;
|
case FLOAT: reshaper = reshapeImageImpl<float, float>; break;
|
||||||
case INT: reshaper = reshapeImage<float, int32_t>; break;
|
case INT: reshaper = reshapeImageImpl<float, int32_t>; break;
|
||||||
case UINT: reshaper = reshapeImage<float, uint32_t>; break;
|
case UINT: reshaper = reshapeImageImpl<float, uint32_t>; break;
|
||||||
default: return false;
|
case UINT_10F_11F_11F_REV:
|
||||||
|
if (swizzle) reshaper = reshapeImagePacked<float, UnpackerR11G11B10, true>;
|
||||||
|
else reshaper = reshapeImagePacked<float, UnpackerR11G11B10, false>;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG(ERROR) << "DataReshaper: FLOAT dst, unsupported srcType: "
|
||||||
|
<< (int) srcType;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case INT:
|
case INT:
|
||||||
switch (srcType) {
|
switch (srcType) {
|
||||||
case UBYTE: reshaper = reshapeImage<int32_t, uint8_t>; break;
|
case UBYTE: reshaper = reshapeImageImpl<int32_t, uint8_t>; break;
|
||||||
case FLOAT: reshaper = reshapeImage<int32_t, float>; break;
|
case FLOAT: reshaper = reshapeImageImpl<int32_t, float>; break;
|
||||||
case INT: reshaper = reshapeImage<int32_t, int32_t>; break;
|
case INT: reshaper = reshapeImageImpl<int32_t, int32_t>; break;
|
||||||
case UINT: reshaper = reshapeImage<int32_t, uint32_t>; break;
|
case UINT: reshaper = reshapeImageImpl<int32_t, uint32_t>; break;
|
||||||
default: return false;
|
case UINT_10F_11F_11F_REV:
|
||||||
|
if (swizzle) reshaper = reshapeImagePacked<int32_t, UnpackerR11G11B10, true>;
|
||||||
|
else reshaper = reshapeImagePacked<int32_t, UnpackerR11G11B10, false>;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG(ERROR)
|
||||||
|
<< "DataReshaper: INT dst, unsupported srcType: " << (int) srcType;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case UINT:
|
case UINT:
|
||||||
switch (srcType) {
|
switch (srcType) {
|
||||||
case UBYTE: reshaper = reshapeImage<uint32_t, uint8_t>; break;
|
case UBYTE: reshaper = reshapeImageImpl<uint32_t, uint8_t>; break;
|
||||||
case FLOAT: reshaper = reshapeImage<uint32_t, float>; break;
|
case FLOAT: reshaper = reshapeImageImpl<uint32_t, float>; break;
|
||||||
case INT: reshaper = reshapeImage<uint32_t, int32_t>; break;
|
case INT: reshaper = reshapeImageImpl<uint32_t, int32_t>; break;
|
||||||
case UINT: reshaper = reshapeImage<uint32_t, uint32_t>; break;
|
case UINT: reshaper = reshapeImageImpl<uint32_t, uint32_t>; break;
|
||||||
default: return false;
|
case UINT_10F_11F_11F_REV:
|
||||||
|
if (swizzle) reshaper = reshapeImagePacked<uint32_t, UnpackerR11G11B10, true>;
|
||||||
|
else reshaper = reshapeImagePacked<uint32_t, UnpackerR11G11B10, false>;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG(ERROR)
|
||||||
|
<< "DataReshaper: UINT dst, unsupported srcType: " << (int) srcType;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case HALF:
|
case HALF:
|
||||||
switch (srcType) {
|
switch (srcType) {
|
||||||
case HALF: reshaper = copyImage; break;
|
case HALF:
|
||||||
default: return false;
|
reshaper = copyImage;
|
||||||
|
break;
|
||||||
|
case UINT_10F_11F_11F_REV:
|
||||||
|
if (swizzle) reshaper = reshapeImagePacked<math::half, UnpackerR11G11B10, true>;
|
||||||
|
else reshaper = reshapeImagePacked<math::half, UnpackerR11G11B10, false>;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG(ERROR)
|
||||||
|
<< "DataReshaper: HALF dst, unsupported srcType: " << (int) srcType;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
LOG(ERROR) << "DataReshaper: unsupported dst->type: " << (int) dst->type;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
uint8_t* dstBytes = (uint8_t*) dst->buffer;
|
uint8_t* dstBytes = (uint8_t*) dst->buffer;
|
||||||
const int dstBytesPerRow = PixelBufferDescriptor::computeDataSize(dst->format, dst->type,
|
const int dstBytesPerRow = PixelBufferDescriptor::computeDataSize(dst->format, dst->type,
|
||||||
dst->stride ? dst->stride : width, 1, dst->alignment);
|
dst->stride ? dst->stride : width, 1, dst->alignment);
|
||||||
reshaper(dstBytes, srcBytes, srcBytesPerRow, srcChannelCount,
|
reshaper(dstBytes, srcBytes, srcBytesPerRow, srcChannelCount, dst->top, dst->left,
|
||||||
dst->top, dst->left, dstBytesPerRow,
|
dstBytesPerRow, dstChannelCount, width, height, swizzle);
|
||||||
dstChannelCount, width, height, swizzle);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template<> inline float getMaxValue() { return 1.0f; }
|
|
||||||
template<> inline int32_t getMaxValue() { return 0x7fffffff; }
|
|
||||||
template<> inline uint32_t getMaxValue() { return 0xffffffff; }
|
|
||||||
template<> inline uint16_t getMaxValue() { return 0x3c00; } // 0x3c00 is 1.0 in half-float.
|
|
||||||
template<> inline uint8_t getMaxValue() { return 0xff; }
|
|
||||||
|
|
||||||
} // namespace backend
|
} // namespace backend
|
||||||
} // namespace filament
|
} // namespace filament
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,8 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
|
||||||
|
@protocol MTLTexture;
|
||||||
|
|
||||||
namespace filament {
|
namespace filament {
|
||||||
namespace backend {
|
namespace backend {
|
||||||
|
|
||||||
@@ -160,6 +162,9 @@ private:
|
|||||||
void enumerateBoundBuffers(BufferObjectBinding bindingType,
|
void enumerateBoundBuffers(BufferObjectBinding bindingType,
|
||||||
const std::function<void(const BufferState&, MetalBuffer*, uint32_t)>& f);
|
const std::function<void(const BufferState&, MetalBuffer*, uint32_t)>& f);
|
||||||
|
|
||||||
|
void readTextureCommon(id<MTLTexture> srcTexture, uint8_t level, uint16_t layer, uint32_t x,
|
||||||
|
uint32_t y, uint32_t width, uint32_t height, PixelBufferDescriptor&& data);
|
||||||
|
|
||||||
backend::StereoscopicType const mStereoscopicType;
|
backend::StereoscopicType const mStereoscopicType;
|
||||||
backend::AsynchronousMode const mAsynchronousMode;
|
backend::AsynchronousMode const mAsynchronousMode;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1751,12 +1751,34 @@ void MetalDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y,
|
|||||||
MetalAttachment color = srcTarget->getDrawColorAttachment(0);
|
MetalAttachment color = srcTarget->getDrawColorAttachment(0);
|
||||||
id<MTLTexture> srcTexture = color.getTexture();
|
id<MTLTexture> srcTexture = color.getTexture();
|
||||||
size_t miplevel = color.getLevel();
|
size_t miplevel = color.getLevel();
|
||||||
|
size_t layer = color.getLayer();
|
||||||
|
|
||||||
// Clamp height and width to actual texture's height and width
|
// Clamp height and width to actual texture's height and width
|
||||||
MTLSize srcTextureSize = MTLSizeMake(srcTexture.width >> miplevel, srcTexture.height >> miplevel, 1);
|
MTLSize srcTextureSize = MTLSizeMake(srcTexture.width >> miplevel, srcTexture.height >> miplevel, 1);
|
||||||
height = std::min(static_cast<uint32_t>(srcTextureSize.height), height);
|
height = std::min(static_cast<uint32_t>(srcTextureSize.height), height);
|
||||||
width = std::min(static_cast<uint32_t>(srcTextureSize.width), width);
|
width = std::min(static_cast<uint32_t>(srcTextureSize.width), width);
|
||||||
|
|
||||||
|
readTextureCommon(srcTexture, miplevel, layer, x, y, width, height, std::move(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MetalDriver::readTexture(Handle<HwTexture> src, uint8_t level, uint16_t layer, uint32_t x,
|
||||||
|
uint32_t y, uint32_t width, uint32_t height, PixelBufferDescriptor&& p) {
|
||||||
|
FILAMENT_CHECK_PRECONDITION(!isInRenderPass(mContext))
|
||||||
|
<< "readTexture must be called outside of a render pass.";
|
||||||
|
|
||||||
|
auto srcTexture = handle_cast<MetalTexture>(src);
|
||||||
|
id<MTLTexture> texture = srcTexture->getMtlTextureForWrite();
|
||||||
|
|
||||||
|
// Clamp height and width to actual texture's height and width
|
||||||
|
MTLSize srcTextureSize = MTLSizeMake(texture.width >> level, texture.height >> level, 1);
|
||||||
|
height = std::min(static_cast<uint32_t>(srcTextureSize.height), height);
|
||||||
|
width = std::min(static_cast<uint32_t>(srcTextureSize.width), width);
|
||||||
|
|
||||||
|
readTextureCommon(texture, level, layer, x, y, width, height, std::move(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MetalDriver::readTextureCommon(id<MTLTexture> srcTexture, uint8_t level, uint16_t layer,
|
||||||
|
uint32_t x, uint32_t y, uint32_t width, uint32_t height, PixelBufferDescriptor&& data) {
|
||||||
const MTLPixelFormat format = getMetalFormat(data.format, data.type);
|
const MTLPixelFormat format = getMetalFormat(data.format, data.type);
|
||||||
FILAMENT_CHECK_PRECONDITION(format != MTLPixelFormatInvalid)
|
FILAMENT_CHECK_PRECONDITION(format != MTLPixelFormatInvalid)
|
||||||
<< "The chosen combination of PixelDataFormat (" << (int)data.format
|
<< "The chosen combination of PixelDataFormat (" << (int)data.format
|
||||||
@@ -1764,10 +1786,14 @@ void MetalDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y,
|
|||||||
<< ") is not supported for "
|
<< ") is not supported for "
|
||||||
"readPixels.";
|
"readPixels.";
|
||||||
|
|
||||||
|
// Clamp height and width to actual texture's height and width
|
||||||
|
MTLSize levelSize = MTLSizeMake(std::max(1lu, srcTexture.width >> level),
|
||||||
|
std::max(1lu, srcTexture.height >> level), 1);
|
||||||
|
|
||||||
MTLTextureDescriptor* textureDescriptor =
|
MTLTextureDescriptor* textureDescriptor =
|
||||||
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:format
|
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:format
|
||||||
width:srcTextureSize.width
|
width:levelSize.width
|
||||||
height:srcTextureSize.height
|
height:levelSize.height
|
||||||
mipmapped:NO];
|
mipmapped:NO];
|
||||||
#if defined(FILAMENT_IOS)
|
#if defined(FILAMENT_IOS)
|
||||||
textureDescriptor.storageMode = MTLStorageModeShared;
|
textureDescriptor.storageMode = MTLStorageModeShared;
|
||||||
@@ -1779,14 +1805,16 @@ void MetalDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y,
|
|||||||
|
|
||||||
MetalBlitter::BlitArgs args{};
|
MetalBlitter::BlitArgs args{};
|
||||||
args.filter = SamplerMagFilter::NEAREST;
|
args.filter = SamplerMagFilter::NEAREST;
|
||||||
args.source.level = miplevel;
|
args.source.level = level;
|
||||||
args.source.region = MTLRegionMake2D(0, 0, srcTexture.width >> miplevel, srcTexture.height >> miplevel);
|
args.source.slice = layer;
|
||||||
|
args.source.region = MTLRegionMake2D(0, 0, levelSize.width, levelSize.height);
|
||||||
args.source.texture = srcTexture;
|
args.source.texture = srcTexture;
|
||||||
args.destination.level = 0;
|
args.destination.level = 0;
|
||||||
|
args.destination.slice = 0;
|
||||||
args.destination.region = MTLRegionMake2D(0, 0, readPixelsTexture.width, readPixelsTexture.height);
|
args.destination.region = MTLRegionMake2D(0, 0, readPixelsTexture.width, readPixelsTexture.height);
|
||||||
args.destination.texture = readPixelsTexture;
|
args.destination.texture = readPixelsTexture;
|
||||||
|
|
||||||
mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "readPixels blit");
|
mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "readTexture blit");
|
||||||
|
|
||||||
#if !defined(FILAMENT_IOS)
|
#if !defined(FILAMENT_IOS)
|
||||||
// Managed textures on macOS require explicit synchronization between GPU / CPU.
|
// Managed textures on macOS require explicit synchronization between GPU / CPU.
|
||||||
@@ -1814,12 +1842,6 @@ void MetalDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y,
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
void MetalDriver::readTexture(Handle<HwTexture> src, uint8_t level, uint16_t layer, uint32_t x,
|
|
||||||
uint32_t y, uint32_t width, uint32_t height, PixelBufferDescriptor&& p) {
|
|
||||||
// TODO: implement readTexture
|
|
||||||
scheduleDestroy(std::move(p));
|
|
||||||
}
|
|
||||||
|
|
||||||
void MetalDriver::readBufferSubData(backend::BufferObjectHandle boh,
|
void MetalDriver::readBufferSubData(backend::BufferObjectHandle boh,
|
||||||
uint32_t offset, uint32_t size, backend::BufferDescriptor&& p) {
|
uint32_t offset, uint32_t size, backend::BufferDescriptor&& p) {
|
||||||
// TODO: implement readBufferSubData
|
// TODO: implement readBufferSubData
|
||||||
|
|||||||
@@ -1092,7 +1092,7 @@ MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint
|
|||||||
// a multisampled sidecar texture and do a resolve automatically.
|
// a multisampled sidecar texture and do a resolve automatically.
|
||||||
if (samples > 1 && texture->samples == 1) {
|
if (samples > 1 && texture->samples == 1) {
|
||||||
auto& sidecar = texture->msaaSidecar;
|
auto& sidecar = texture->msaaSidecar;
|
||||||
if (!sidecar) {
|
if (!sidecar || sidecar.sampleCount != samples) {
|
||||||
sidecar = createMultisampledTexture(mtlTexture.pixelFormat, texture->width,
|
sidecar = createMultisampledTexture(mtlTexture.pixelFormat, texture->width,
|
||||||
texture->height, samples);
|
texture->height, samples);
|
||||||
}
|
}
|
||||||
@@ -1123,7 +1123,7 @@ MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint
|
|||||||
// a multisampled sidecar texture and do a resolve automatically.
|
// a multisampled sidecar texture and do a resolve automatically.
|
||||||
if (samples > 1 && texture->samples == 1) {
|
if (samples > 1 && texture->samples == 1) {
|
||||||
auto& sidecar = texture->msaaSidecar;
|
auto& sidecar = texture->msaaSidecar;
|
||||||
if (!sidecar) {
|
if (!sidecar || sidecar.sampleCount != samples) {
|
||||||
sidecar = createMultisampledTexture(mtlTexture.pixelFormat, texture->width,
|
sidecar = createMultisampledTexture(mtlTexture.pixelFormat, texture->width,
|
||||||
texture->height, samples);
|
texture->height, samples);
|
||||||
}
|
}
|
||||||
@@ -1155,7 +1155,7 @@ MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint
|
|||||||
// a multisampled sidecar texture and do a resolve automatically.
|
// a multisampled sidecar texture and do a resolve automatically.
|
||||||
if (samples > 1 && texture->samples == 1) {
|
if (samples > 1 && texture->samples == 1) {
|
||||||
auto& sidecar = texture->msaaSidecar;
|
auto& sidecar = texture->msaaSidecar;
|
||||||
if (!sidecar) {
|
if (!sidecar || sidecar.sampleCount != samples) {
|
||||||
sidecar = createMultisampledTexture(mtlTexture.pixelFormat, texture->width,
|
sidecar = createMultisampledTexture(mtlTexture.pixelFormat, texture->width,
|
||||||
texture->height, samples);
|
texture->height, samples);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ public:
|
|||||||
GLenum getIndicesType() const noexcept {
|
GLenum getIndicesType() const noexcept {
|
||||||
return indicesType;
|
return indicesType;
|
||||||
}
|
}
|
||||||
} gl;
|
};
|
||||||
|
|
||||||
static bool queryOpenGLVersion(GLint* major, GLint* minor) noexcept;
|
static bool queryOpenGLVersion(GLint* major, GLint* minor) noexcept;
|
||||||
|
|
||||||
|
|||||||
@@ -3623,6 +3623,8 @@ void OpenGLDriver::detachStream(GLTexture* t) noexcept {
|
|||||||
case StreamType::NATIVE:
|
case StreamType::NATIVE:
|
||||||
mPlatform.detach(t->hwStream->stream);
|
mPlatform.detach(t->hwStream->stream);
|
||||||
// ^ this deletes the texture id
|
// ^ this deletes the texture id
|
||||||
|
// We still need to call unbind to update the bookkeeping.
|
||||||
|
gl.unbindTexture(t->gl.target, t->gl.id);
|
||||||
break;
|
break;
|
||||||
case StreamType::ACQUIRED:
|
case StreamType::ACQUIRED:
|
||||||
gl.unbindTexture(t->gl.target, t->gl.id);
|
gl.unbindTexture(t->gl.target, t->gl.id);
|
||||||
|
|||||||
@@ -120,33 +120,27 @@ bool PlatformEGL::isOpenGL() const noexcept {
|
|||||||
PlatformEGL::ExternalImageEGL::~ExternalImageEGL() = default;
|
PlatformEGL::ExternalImageEGL::~ExternalImageEGL() = default;
|
||||||
|
|
||||||
Driver* PlatformEGL::createDriver(void* sharedContext, const DriverConfig& driverConfig) {
|
Driver* PlatformEGL::createDriver(void* sharedContext, const DriverConfig& driverConfig) {
|
||||||
static constexpr int kMaxNumEGLDevices = 32;
|
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||||
|
assert_invariant(mEGLDisplay != EGL_NO_DISPLAY);
|
||||||
|
|
||||||
EGLint major, minor;
|
EGLint major, minor;
|
||||||
EGLBoolean initialized = false;
|
EGLBoolean initialized = eglInitialize(mEGLDisplay, &major, &minor);
|
||||||
|
|
||||||
PFNEGLQUERYDEVICESEXTPROC const eglQueryDevicesEXT =
|
if (!initialized) {
|
||||||
PFNEGLQUERYDEVICESEXTPROC(eglGetProcAddress("eglQueryDevicesEXT"));
|
EGLDeviceEXT eglDevice;
|
||||||
PFNEGLGETPLATFORMDISPLAYEXTPROC const getPlatformDisplay =
|
EGLint numDevices;
|
||||||
PFNEGLGETPLATFORMDISPLAYEXTPROC(eglGetProcAddress("eglGetPlatformDisplay"));
|
PFNEGLQUERYDEVICESEXTPROC const eglQueryDevicesEXT =
|
||||||
|
PFNEGLQUERYDEVICESEXTPROC(eglGetProcAddress("eglQueryDevicesEXT"));
|
||||||
if (eglQueryDevicesEXT != nullptr && getPlatformDisplay != nullptr) {
|
if (eglQueryDevicesEXT != nullptr) {
|
||||||
EGLint numDevices = 0;
|
eglQueryDevicesEXT(1, &eglDevice, &numDevices);
|
||||||
EGLDeviceEXT eglDevices[kMaxNumEGLDevices];
|
if(auto* getPlatformDisplay = reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
|
||||||
if (eglQueryDevicesEXT(kMaxNumEGLDevices, eglDevices, &numDevices)) {
|
eglGetProcAddress("eglGetPlatformDisplay"))) {
|
||||||
for (int i = 0; i < numDevices && !initialized; ++i) {
|
mEGLDisplay = getPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, eglDevice, nullptr);
|
||||||
mEGLDisplay = getPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, eglDevices[i], nullptr);
|
|
||||||
initialized = eglInitialize(mEGLDisplay, &major, &minor);
|
initialized = eglInitialize(mEGLDisplay, &major, &minor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!initialized) {
|
|
||||||
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
|
||||||
assert_invariant(mEGLDisplay != EGL_NO_DISPLAY);
|
|
||||||
initialized = eglInitialize(mEGLDisplay, &major, &minor);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (UTILS_UNLIKELY(!initialized)) {
|
if (UTILS_UNLIKELY(!initialized)) {
|
||||||
LOG(ERROR) << "eglInitialize failed";
|
LOG(ERROR) << "eglInitialize failed";
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ VK_DEFINE_HANDLE(VmaPool)
|
|||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
struct VulkanCommandBuffer;
|
struct VulkanCommandBuffer;
|
||||||
|
struct VulkanRenderPass;
|
||||||
struct VulkanRenderTarget;
|
struct VulkanRenderTarget;
|
||||||
struct VulkanSwapChain;
|
struct VulkanSwapChain;
|
||||||
struct VulkanTexture;
|
struct VulkanTexture;
|
||||||
@@ -58,11 +59,11 @@ struct VulkanAttachment {
|
|||||||
VkImageSubresourceRange getSubresourceRange() const;
|
VkImageSubresourceRange getSubresourceRange() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VulkanRenderPass {
|
struct VulkanRenderPassContext {
|
||||||
// Between the begin and end command render pass we cache the command buffer
|
// Between the begin and end command render pass we cache the command buffer
|
||||||
VulkanCommandBuffer* commandBuffer;
|
VulkanCommandBuffer* commandBuffer;
|
||||||
fvkmemory::resource_ptr<VulkanRenderTarget> renderTarget;
|
fvkmemory::resource_ptr<VulkanRenderTarget> renderTarget;
|
||||||
VkRenderPass renderPass;
|
fvkmemory::resource_ptr<VulkanRenderPass> renderPass;
|
||||||
RenderPassParams params;
|
RenderPassParams params;
|
||||||
int currentSubpass;
|
int currentSubpass;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -289,8 +289,7 @@ void VulkanDescriptorSetCache::unbind(uint8_t setIndex) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands,
|
void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands,
|
||||||
VkPipelineLayout pipelineLayout, fvkutils::DescriptorSetMask const& useExternalSamplers,
|
VkPipelineLayout pipelineLayout, fvkutils::DescriptorSetMask const& setMask) {
|
||||||
fvkutils::DescriptorSetMask const& setMask) {
|
|
||||||
// setMask indicates the set of descriptor sets the driver wants to bind, curMask is the
|
// setMask indicates the set of descriptor sets the driver wants to bind, curMask is the
|
||||||
// actual set of sets that *needs* to be bound.
|
// actual set of sets that *needs* to be bound.
|
||||||
fvkutils::DescriptorSetMask curMask = setMask;
|
fvkutils::DescriptorSetMask curMask = setMask;
|
||||||
@@ -306,8 +305,7 @@ void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands,
|
|||||||
auto& lastBoundSets = mLastBoundInfo.boundSets;
|
auto& lastBoundSets = mLastBoundInfo.boundSets;
|
||||||
curMask.forEachSetBit([&](size_t index) {
|
curMask.forEachSetBit([&](size_t index) {
|
||||||
auto& set = updateSets[index];
|
auto& set = updateSets[index];
|
||||||
if (set == lastBoundSets[index] && !useExternalSamplers[index] &&
|
if (set == lastBoundSets[index] && set->uniqueDynamicUboCount == 0) {
|
||||||
set->uniqueDynamicUboCount == 0) {
|
|
||||||
curMask.unset(index);
|
curMask.unset(index);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -357,17 +355,18 @@ void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescri
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||||
uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture,
|
uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler,
|
||||||
VkSampler sampler, VkDescriptorSetLayout externalSamplerLayout) noexcept {
|
VkDescriptorSetLayout externalSamplerLayout) noexcept {
|
||||||
|
|
||||||
// We have to update a bound set for two use cases
|
// We have to update a bound set for two use cases
|
||||||
// - streaming API (a changing feed of AHardwareBuffer)
|
// - streaming API (a changing feed of AHardwareBuffer)
|
||||||
// - external samplers - potential changing of dataspace per-frame
|
// - external samplers - potential changing of dataspace per-frame
|
||||||
if (UTILS_UNLIKELY(set->isBound())) {
|
|
||||||
|
// TODO: Fix the stream flow case base on the above comment!!
|
||||||
|
if (set->isAnExternalSamplerBound) {
|
||||||
auto layout = set->getLayout();
|
auto layout = set->getLayout();
|
||||||
// Build a new descriptor set from the new layout
|
// Build a new descriptor set from the new layout
|
||||||
auto genLayout = externalSamplerLayout != VK_NULL_HANDLE ?
|
VkDescriptorSetLayout const genLayout = set->boundLayout;
|
||||||
externalSamplerLayout : layout->getVkLayout();
|
|
||||||
VkDescriptorSet const newSet = getVkSet(layout->count, genLayout);
|
VkDescriptorSet const newSet = getVkSet(layout->count, genLayout);
|
||||||
Bitmask const ubo = layout->bitmask.ubo | layout->bitmask.dynamicUbo;
|
Bitmask const ubo = layout->bitmask.ubo | layout->bitmask.dynamicUbo;
|
||||||
Bitmask samplers = layout->bitmask.sampler;
|
Bitmask samplers = layout->bitmask.sampler;
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ public:
|
|||||||
void unbind(uint8_t setIndex);
|
void unbind(uint8_t setIndex);
|
||||||
|
|
||||||
void commit(VulkanCommandBuffer* commands, VkPipelineLayout pipelineLayout,
|
void commit(VulkanCommandBuffer* commands, VkPipelineLayout pipelineLayout,
|
||||||
fvkutils::DescriptorSetMask const& useExternalSamplerMask,
|
|
||||||
fvkutils::DescriptorSetMask const& setMask);
|
fvkutils::DescriptorSetMask const& setMask);
|
||||||
|
|
||||||
fvkmemory::resource_ptr<VulkanDescriptorSet> createSet(Handle<HwDescriptorSet> handle,
|
fvkmemory::resource_ptr<VulkanDescriptorSet> createSet(Handle<HwDescriptorSet> handle,
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext& context,
|
|||||||
mDescriptorSetLayoutCache(mPlatform->getDevice(), &mResourceManager),
|
mDescriptorSetLayoutCache(mPlatform->getDevice(), &mResourceManager),
|
||||||
mDescriptorSetCache(mPlatform->getDevice(), &mResourceManager),
|
mDescriptorSetCache(mPlatform->getDevice(), &mResourceManager),
|
||||||
mQueryManager(mPlatform->getDevice()),
|
mQueryManager(mPlatform->getDevice()),
|
||||||
mExternalImageManager(platform, &mSamplerCache, &mYcbcrConversionCache, &mDescriptorSetCache,
|
mExternalImageManager(&mSamplerCache, &mYcbcrConversionCache, &mDescriptorSetCache,
|
||||||
&mDescriptorSetLayoutCache),
|
&mDescriptorSetLayoutCache),
|
||||||
mStreamedImageManager(&mExternalImageManager),
|
mStreamedImageManager(&mExternalImageManager),
|
||||||
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported),
|
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported),
|
||||||
@@ -465,10 +465,6 @@ void VulkanDriver::beginFrame(int64_t monotonic_clock_ns,
|
|||||||
//
|
//
|
||||||
// This will let us check if any VulkanBuffer is currently in flight or not.
|
// This will let us check if any VulkanBuffer is currently in flight or not.
|
||||||
mCommands.gc();
|
mCommands.gc();
|
||||||
|
|
||||||
if (mAppState.hasExternalSamplers()) {
|
|
||||||
mExternalImageManager.onBeginFrame();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch, CallbackHandler* handler,
|
void VulkanDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch, CallbackHandler* handler,
|
||||||
@@ -514,8 +510,12 @@ void VulkanDriver::updateDescriptorSetTexture(
|
|||||||
if (UTILS_UNLIKELY(mExternalImageManager.isExternallySampledTexture(texture))) {
|
if (UTILS_UNLIKELY(mExternalImageManager.isExternallySampledTexture(texture))) {
|
||||||
mExternalImageManager.bindExternallySampledTexture(set, binding, texture, params);
|
mExternalImageManager.bindExternallySampledTexture(set, binding, texture, params);
|
||||||
mAppState.hasBoundExternalImages = true;
|
mAppState.hasBoundExternalImages = true;
|
||||||
|
set->isAnExternalSamplerBound = true;
|
||||||
|
set->isLayoutDirty = true;
|
||||||
} else if (bool(texture->getStream())) {
|
} else if (bool(texture->getStream())) {
|
||||||
mStreamedImageManager.bindStreamedTexture(set, binding, texture, params);
|
mStreamedImageManager.bindStreamedTexture(set, binding, texture, params);
|
||||||
|
// TODO: Fix the stream flow!! In this case the binded image doesnt have to be one
|
||||||
|
// with an external sampler.
|
||||||
mAppState.hasBoundExternalImages = true;
|
mAppState.hasBoundExternalImages = true;
|
||||||
} else {
|
} else {
|
||||||
VulkanSamplerCache::Params cacheParams = {
|
VulkanSamplerCache::Params cacheParams = {
|
||||||
@@ -768,7 +768,7 @@ void VulkanDriver::createTextureExternalImage2R(Handle<HwTexture> th, backend::S
|
|||||||
texture->transitionLayout(&commands, texture->getPrimaryViewRange(), VulkanLayout::FRAG_READ);
|
texture->transitionLayout(&commands, texture->getPrimaryViewRange(), VulkanLayout::FRAG_READ);
|
||||||
|
|
||||||
if (imgData.external.valid()) {
|
if (imgData.external.valid()) {
|
||||||
mExternalImageManager.addExternallySampledTexture(texture, externalImage);
|
mExternalImageManager.addExternallySampledTexture(texture, conversion);
|
||||||
}
|
}
|
||||||
|
|
||||||
texture.inc();
|
texture.inc();
|
||||||
@@ -1334,7 +1334,7 @@ void VulkanDriver::destroyDescriptorSet(Handle<HwDescriptorSet> dsh) {
|
|||||||
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
|
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
|
||||||
set.dec();
|
set.dec();
|
||||||
|
|
||||||
if (mAppState.hasExternalSamplers()) {
|
if (set->isAnExternalSamplerBound) {
|
||||||
mExternalImageManager.removeDescriptorSet(set);
|
mExternalImageManager.removeDescriptorSet(set);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1430,7 +1430,7 @@ void VulkanDriver::updateStreams(CommandStream* driver) {
|
|||||||
|
|
||||||
if (imgData.external.valid()) {
|
if (imgData.external.valid()) {
|
||||||
mExternalImageManager.addExternallySampledTexture(newTexture,
|
mExternalImageManager.addExternallySampledTexture(newTexture,
|
||||||
externalImage);
|
conversion);
|
||||||
// Cache the AHB backed image. Acquires the image here.
|
// Cache the AHB backed image. Acquires the image here.
|
||||||
s->pushImage(image, newTexture);
|
s->pushImage(image, newTexture);
|
||||||
}
|
}
|
||||||
@@ -1975,17 +1975,19 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
|||||||
rpkey.initialDepthLayout = currentDepthLayout;
|
rpkey.initialDepthLayout = currentDepthLayout;
|
||||||
rpkey.subpassMask = uint8_t(params.subpassMask);
|
rpkey.subpassMask = uint8_t(params.subpassMask);
|
||||||
|
|
||||||
VkRenderPass renderPass = mFramebufferCache.getRenderPass(rpkey);
|
fvkmemory::resource_ptr<VulkanRenderPass> renderPass =
|
||||||
|
mFramebufferCache.getRenderPass(rpkey, &mResourceManager);
|
||||||
mPipelineCache.bindRenderPass(renderPass, 0);
|
mPipelineCache.bindRenderPass(renderPass, 0);
|
||||||
|
|
||||||
// Create the VkFramebuffer or fetch it from cache.
|
// Create the VkFramebuffer or fetch it from cache.
|
||||||
VulkanFboCache::FboKey fbkey = rt->getFboKey();
|
VulkanFboCache::FboKey fbkey = rt->getFboKey();
|
||||||
fbkey.renderPass = renderPass;
|
fbkey.renderPass = renderPass->getVkRenderPass();
|
||||||
fbkey.layers = 1;
|
fbkey.layers = 1;
|
||||||
|
|
||||||
rt->emitBarriersBeginRenderPass(*commandBuffer);
|
rt->emitBarriersBeginRenderPass(*commandBuffer);
|
||||||
|
|
||||||
VkFramebuffer vkfb = mFramebufferCache.getFramebuffer(fbkey);
|
fvkmemory::resource_ptr<VulkanFramebuffer> vkfb =
|
||||||
|
mFramebufferCache.getFramebuffer(fbkey, &mResourceManager);
|
||||||
|
|
||||||
// Assign a label to the framebuffer for debugging purposes.
|
// Assign a label to the framebuffer for debugging purposes.
|
||||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS | FVK_DEBUG_DEBUG_UTILS)
|
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS | FVK_DEBUG_DEBUG_UTILS)
|
||||||
@@ -1998,12 +2000,14 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
|||||||
|
|
||||||
// The current command buffer now has references to the render target and its attachments.
|
// The current command buffer now has references to the render target and its attachments.
|
||||||
commandBuffer->acquire(rt);
|
commandBuffer->acquire(rt);
|
||||||
|
commandBuffer->acquire(renderPass);
|
||||||
|
commandBuffer->acquire(vkfb);
|
||||||
|
|
||||||
// Populate the structures required for vkCmdBeginRenderPass.
|
// Populate the structures required for vkCmdBeginRenderPass.
|
||||||
VkRenderPassBeginInfo renderPassInfo {
|
VkRenderPassBeginInfo renderPassInfo {
|
||||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||||
.renderPass = renderPass,
|
.renderPass = renderPass->getVkRenderPass(),
|
||||||
.framebuffer = vkfb,
|
.framebuffer = vkfb->getVkFramebuffer(),
|
||||||
|
|
||||||
// The renderArea field constrains the LoadOp, but scissoring does not.
|
// The renderArea field constrains the LoadOp, but scissoring does not.
|
||||||
// Therefore, we do not set the scissor rect here, we only need it in draw().
|
// Therefore, we do not set the scissor rect here, we only need it in draw().
|
||||||
@@ -2058,7 +2062,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
|||||||
mCurrentRenderPass = {
|
mCurrentRenderPass = {
|
||||||
.commandBuffer = commandBuffer,
|
.commandBuffer = commandBuffer,
|
||||||
.renderTarget = rt,
|
.renderTarget = rt,
|
||||||
.renderPass = renderPassInfo.renderPass,
|
.renderPass = renderPass,
|
||||||
.params = params,
|
.params = params,
|
||||||
.currentSubpass = 0,
|
.currentSubpass = 0,
|
||||||
};
|
};
|
||||||
@@ -2078,8 +2082,7 @@ void VulkanDriver::endRenderPass(int) {
|
|||||||
rt->emitBarriersEndRenderPass(*mCurrentRenderPass.commandBuffer);
|
rt->emitBarriersEndRenderPass(*mCurrentRenderPass.commandBuffer);
|
||||||
|
|
||||||
mCurrentRenderPass.renderTarget = {};
|
mCurrentRenderPass.renderTarget = {};
|
||||||
mCurrentRenderPass.renderPass = VK_NULL_HANDLE;
|
mCurrentRenderPass.renderPass = {};
|
||||||
|
|
||||||
mCurrentRenderPass.commandBuffer = nullptr;
|
mCurrentRenderPass.commandBuffer = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2244,7 +2247,7 @@ void VulkanDriver::resolve(
|
|||||||
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer) {
|
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer) {
|
||||||
FVK_SYSTRACE_SCOPE();
|
FVK_SYSTRACE_SCOPE();
|
||||||
|
|
||||||
FILAMENT_CHECK_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE)
|
FILAMENT_CHECK_PRECONDITION(!mCurrentRenderPass.renderPass)
|
||||||
<< "resolve() cannot be invoked inside a render pass.";
|
<< "resolve() cannot be invoked inside a render pass.";
|
||||||
|
|
||||||
auto srcTexture = resource_ptr<VulkanTexture>::cast(&mResourceManager, src);
|
auto srcTexture = resource_ptr<VulkanTexture>::cast(&mResourceManager, src);
|
||||||
@@ -2287,7 +2290,7 @@ void VulkanDriver::blit(
|
|||||||
math::uint2 size) {
|
math::uint2 size) {
|
||||||
FVK_SYSTRACE_SCOPE();
|
FVK_SYSTRACE_SCOPE();
|
||||||
|
|
||||||
FILAMENT_CHECK_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE)
|
FILAMENT_CHECK_PRECONDITION(!mCurrentRenderPass.renderPass)
|
||||||
<< "blit() cannot be invoked inside a render pass.";
|
<< "blit() cannot be invoked inside a render pass.";
|
||||||
|
|
||||||
auto srcTexture = resource_ptr<VulkanTexture>::cast(&mResourceManager, src);
|
auto srcTexture = resource_ptr<VulkanTexture>::cast(&mResourceManager, src);
|
||||||
@@ -2329,7 +2332,7 @@ void VulkanDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
|||||||
|
|
||||||
// Note: blitDEPRECATED is only used for Renderer::copyFrame()
|
// Note: blitDEPRECATED is only used for Renderer::copyFrame()
|
||||||
|
|
||||||
FILAMENT_CHECK_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE)
|
FILAMENT_CHECK_PRECONDITION(!mCurrentRenderPass.renderPass)
|
||||||
<< "blitDEPRECATED() cannot be invoked inside a render pass.";
|
<< "blitDEPRECATED() cannot be invoked inside a render pass.";
|
||||||
|
|
||||||
FILAMENT_CHECK_PRECONDITION(buffers == TargetBufferFlags::COLOR0)
|
FILAMENT_CHECK_PRECONDITION(buffers == TargetBufferFlags::COLOR0)
|
||||||
@@ -2481,6 +2484,7 @@ void VulkanDriver::bindPipelineImpl(PipelineState const& pipelineState,
|
|||||||
// Push state changes to the VulkanPipelineCache instance. This is fast and does not make VK calls.
|
// Push state changes to the VulkanPipelineCache instance. This is fast and does not make VK calls.
|
||||||
mPipelineCache.bindProgram(program);
|
mPipelineCache.bindProgram(program);
|
||||||
mPipelineCache.bindRasterState(vulkanRasterState);
|
mPipelineCache.bindRasterState(vulkanRasterState);
|
||||||
|
mPipelineCache.bindStencilState(pipelineState.stencilState);
|
||||||
mPipelineCache.bindPrimitiveTopology(topology);
|
mPipelineCache.bindPrimitiveTopology(topology);
|
||||||
mPipelineCache.bindVertexArray(attribDesc, bufferDesc, vbi->getAttributeCount());
|
mPipelineCache.bindVertexArray(attribDesc, bufferDesc, vbi->getAttributeCount());
|
||||||
|
|
||||||
@@ -2524,16 +2528,24 @@ void VulkanDriver::bindDescriptorSet(
|
|||||||
backend::DescriptorSetOffsetArray&& offsets) {
|
backend::DescriptorSetOffsetArray&& offsets) {
|
||||||
if (dsh) {
|
if (dsh) {
|
||||||
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
|
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
|
||||||
|
|
||||||
|
// If the set has binded texture that requires an immutable sampler,
|
||||||
|
// a new DescriptorSetLayout must be created otherwise use the default layout.
|
||||||
|
if (set->isLayoutDirty) {
|
||||||
|
mExternalImageManager.updateSetAndLayout(set);
|
||||||
|
set->isLayoutDirty = false;
|
||||||
|
}
|
||||||
mDescriptorSetCache.bind(setIndex, set, std::move(offsets));
|
mDescriptorSetCache.bind(setIndex, set, std::move(offsets));
|
||||||
|
|
||||||
if (mAppState.hasExternalSamplers()) {
|
if (set->isAnExternalSamplerBound) {
|
||||||
auto const& bindInDrawBundle = mPipelineState.bindInDraw.second;
|
auto const& bindInDrawBundle = mPipelineState.bindInDraw.second;
|
||||||
// The set index being bound has already been bound or will be bound. If it's already
|
// The set index being bound has already been bound or will be bound. If it's already
|
||||||
// been bound and this set has external samplers, we do the doBindindraw block in
|
// been bound and this set has external samplers, we do the doBindindraw block in
|
||||||
// draw2() again. Because this set might potentially cause a new pipelineLayout
|
// draw2() again. Because this set might potentially cause a new pipelineLayout
|
||||||
// (therefore pipeline) to be bound.
|
// (therefore pipeline) to be bound.
|
||||||
if (bindInDrawBundle.descriptorSetMask[setIndex] &&
|
if (bindInDrawBundle.descriptorSetMask[setIndex]) {
|
||||||
mExternalImageManager.hasExternalSampler(set)) {
|
// TODO: Only do bindInDraw if the previous bounded layout at `setIndex` is the
|
||||||
|
// different.
|
||||||
mPipelineState.bindInDraw.first = true;
|
mPipelineState.bindInDraw.first = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2549,21 +2561,20 @@ void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins
|
|||||||
|
|
||||||
fvkutils::DescriptorSetMask setsWithExternalSamplers = {};
|
fvkutils::DescriptorSetMask setsWithExternalSamplers = {};
|
||||||
if (doBindInDraw) {
|
if (doBindInDraw) {
|
||||||
auto& layoutHandles = bundle.dsLayoutHandles;
|
// Create the new pipeline layout from the current bounded descriptor sets.
|
||||||
setsWithExternalSamplers = mExternalImageManager.prepareBindSets(layoutHandles,
|
// The layout of the descriptor sets at this point should have the final one taking into account
|
||||||
mDescriptorSetCache.getBoundSets());
|
// the external samplers.
|
||||||
|
//
|
||||||
|
// The final layouts of the descriptor sets are regenerated as needed at `bindDescriptorSet`.
|
||||||
|
VulkanDescriptorSetCache::DescriptorSetArray const& boundSets = mDescriptorSetCache.getBoundSets();
|
||||||
VulkanDescriptorSetLayout::DescriptorSetLayoutArray vklayouts;
|
VulkanDescriptorSetLayout::DescriptorSetLayoutArray vklayouts;
|
||||||
for (size_t i = 0; i < layoutHandles.size(); i++) {
|
for (size_t i = 0; i < boundSets.size(); i++) {
|
||||||
if (!layoutHandles[i]) {
|
if (!boundSets[i]) {
|
||||||
vklayouts[i] = VK_NULL_HANDLE;
|
vklayouts[i] = VK_NULL_HANDLE;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (setsWithExternalSamplers[i]) {
|
|
||||||
vklayouts[i] = layoutHandles[i]->getExternalSamplerVkLayout();
|
vklayouts[i] = boundSets[i]->boundLayout;
|
||||||
} else {
|
|
||||||
vklayouts[i] = layoutHandles[i]->getVkLayout();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
auto program =
|
auto program =
|
||||||
resource_ptr<VulkanProgram>::cast(&mResourceManager, bundle.pipelineState.program);
|
resource_ptr<VulkanProgram>::cast(&mResourceManager, bundle.pipelineState.program);
|
||||||
@@ -2579,7 +2590,7 @@ void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins
|
|||||||
mPipelineState.bindInDraw.first = false;
|
mPipelineState.bindInDraw.first = false;
|
||||||
}
|
}
|
||||||
mDescriptorSetCache.commit(mCurrentRenderPass.commandBuffer, mPipelineState.pipelineLayout,
|
mDescriptorSetCache.commit(mCurrentRenderPass.commandBuffer, mPipelineState.pipelineLayout,
|
||||||
setsWithExternalSamplers, mPipelineState.descriptorSetMask);
|
mPipelineState.descriptorSetMask);
|
||||||
|
|
||||||
// Finally, make the actual draw call. TODO: support subranges
|
// Finally, make the actual draw call. TODO: support subranges
|
||||||
uint32_t const firstIndex = indexOffset;
|
uint32_t const firstIndex = indexOffset;
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ private:
|
|||||||
|
|
||||||
resource_ptr<VulkanSwapChain> mCurrentSwapChain;
|
resource_ptr<VulkanSwapChain> mCurrentSwapChain;
|
||||||
resource_ptr<VulkanRenderTarget> mDefaultRenderTarget;
|
resource_ptr<VulkanRenderTarget> mDefaultRenderTarget;
|
||||||
VulkanRenderPass mCurrentRenderPass = {};
|
VulkanRenderPassContext mCurrentRenderPass = {};
|
||||||
VmaAllocator mAllocator = VK_NULL_HANDLE;
|
VmaAllocator mAllocator = VK_NULL_HANDLE;
|
||||||
VkDebugReportCallbackEXT mDebugCallback = VK_NULL_HANDLE;
|
VkDebugReportCallbackEXT mDebugCallback = VK_NULL_HANDLE;
|
||||||
|
|
||||||
|
|||||||
@@ -49,15 +49,13 @@ ImageData& findImage(std::vector<ImageData>& images,
|
|||||||
|
|
||||||
}// namespace
|
}// namespace
|
||||||
|
|
||||||
VulkanExternalImageManager::VulkanExternalImageManager(VulkanPlatform* platform,
|
VulkanExternalImageManager::VulkanExternalImageManager(VulkanSamplerCache* samplerCache,
|
||||||
VulkanSamplerCache* samplerCache, VulkanYcbcrConversionCache* ycbcrConversionCache,
|
VulkanYcbcrConversionCache* ycbcrConversionCache, VulkanDescriptorSetCache* setCache,
|
||||||
VulkanDescriptorSetCache* setCache, VulkanDescriptorSetLayoutCache* layoutCache)
|
VulkanDescriptorSetLayoutCache* layoutCache)
|
||||||
: mPlatform(platform),
|
: mSamplerCache(samplerCache),
|
||||||
mSamplerCache(samplerCache),
|
mYcbcrConversionCache(ycbcrConversionCache),
|
||||||
mYcbcrConversionCache(ycbcrConversionCache),
|
mDescriptorSetCache(setCache),
|
||||||
mDescriptorSetCache(setCache),
|
mDescriptorSetLayoutCache(layoutCache) {}
|
||||||
mDescriptorSetLayoutCache(layoutCache) {
|
|
||||||
}
|
|
||||||
|
|
||||||
VulkanExternalImageManager::~VulkanExternalImageManager() = default;
|
VulkanExternalImageManager::~VulkanExternalImageManager() = default;
|
||||||
|
|
||||||
@@ -66,75 +64,21 @@ void VulkanExternalImageManager::terminate() {
|
|||||||
mImages.clear();
|
mImages.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanExternalImageManager::onBeginFrame() {
|
|
||||||
std::for_each(mImages.begin(), mImages.end(), [](ImageData& image) {
|
|
||||||
image.hasBeenValidated = false;
|
|
||||||
});
|
|
||||||
std::for_each(mSetBindings.begin(), mSetBindings.end(), [](SetBindingInfo& info) {
|
|
||||||
info.bound = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fvkutils::DescriptorSetMask VulkanExternalImageManager::prepareBindSets(LayoutArray const& layouts,
|
|
||||||
SetArray const& sets) {
|
|
||||||
fvkutils::DescriptorSetMask shouldUseExternalSampler{};
|
|
||||||
for (uint8_t i = 0; i < sets.size(); i++) {
|
|
||||||
auto set = sets[i];
|
|
||||||
auto layout = layouts[i];
|
|
||||||
if (!set || !layout) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (hasExternalSampler(set)) {
|
|
||||||
updateSetAndLayout(set, layout);
|
|
||||||
shouldUseExternalSampler.set(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return shouldUseExternalSampler;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VulkanExternalImageManager::hasExternalSampler(
|
|
||||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set) const {
|
|
||||||
auto itr = std::find_if(mSetBindings.begin(), mSetBindings.end(),
|
|
||||||
[&](SetBindingInfo const& info) { return info.set == set; });
|
|
||||||
return itr != mSetBindings.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
void VulkanExternalImageManager::updateSetAndLayout(
|
void VulkanExternalImageManager::updateSetAndLayout(
|
||||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
fvkmemory::resource_ptr<VulkanDescriptorSet> set) {
|
||||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
|
|
||||||
utils::FixedCapacityVector<
|
utils::FixedCapacityVector<
|
||||||
std::tuple<uint8_t, VkSampler, fvkmemory::resource_ptr<VulkanTexture>>>
|
std::tuple<uint8_t, VkSampler, fvkmemory::resource_ptr<VulkanTexture>>>
|
||||||
samplerAndBindings;
|
samplerAndBindings;
|
||||||
samplerAndBindings.reserve(MAX_SAMPLER_COUNT);
|
samplerAndBindings.reserve(MAX_SAMPLER_COUNT);
|
||||||
|
|
||||||
fvkutils::SamplerBitmask actualExternalSamplers;
|
fvkutils::SamplerBitmask actualExternalSamplers;
|
||||||
for (auto& bindingInfo : mSetBindings) {
|
for (auto& bindingInfo: mSetBindings) {
|
||||||
if (bindingInfo.set != set || bindingInfo.bound) {
|
if (bindingInfo.set != set) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto& imageData = findImage(mImages, bindingInfo.image);
|
|
||||||
// For non YUV images (some ext images are NOT ext FMT)
|
|
||||||
// getVkSamplerYcbcrConversion(metadata) will return NULL, and image->conversion will be
|
|
||||||
// null
|
|
||||||
updateImage(&imageData);
|
|
||||||
|
|
||||||
auto samplerParams = bindingInfo.samplerParams;
|
|
||||||
// according to spec, these must match chromaFilter
|
|
||||||
// https://registry.khronos.org/vulkan/specs/latest/man/html/VkSamplerCreateInfo.html#VUID-VkSamplerCreateInfo-minFilter-01645
|
|
||||||
samplerParams.filterMag = SamplerMagFilter::NEAREST;
|
|
||||||
samplerParams.filterMin = SamplerMinFilter::NEAREST;
|
|
||||||
|
|
||||||
auto sampler = mSamplerCache->getSampler({
|
|
||||||
.sampler = samplerParams,
|
|
||||||
.conversion = imageData.conversion,
|
|
||||||
});
|
|
||||||
actualExternalSamplers.set(bindingInfo.binding);
|
actualExternalSamplers.set(bindingInfo.binding);
|
||||||
samplerAndBindings.push_back({ bindingInfo.binding, sampler, bindingInfo.image });
|
samplerAndBindings.push_back(
|
||||||
bindingInfo.bound = true;
|
{ bindingInfo.binding, bindingInfo.sampler, bindingInfo.image });
|
||||||
}
|
|
||||||
|
|
||||||
if (samplerAndBindings.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by binding number
|
// Sort by binding number
|
||||||
@@ -147,14 +91,14 @@ void VulkanExternalImageManager::updateSetAndLayout(
|
|||||||
std::for_each(samplerAndBindings.begin(), samplerAndBindings.end(),
|
std::for_each(samplerAndBindings.begin(), samplerAndBindings.end(),
|
||||||
[&](auto const& b) { outSamplers.push_back({ static_cast<uint64_t>(std::get<0>(b)), std::get<1>(b) }); });
|
[&](auto const& b) { outSamplers.push_back({ static_cast<uint64_t>(std::get<0>(b)), std::get<1>(b) }); });
|
||||||
|
|
||||||
VkDescriptorSetLayout const newLayout = mDescriptorSetLayoutCache->getVkLayout(layout->bitmask,
|
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> const& layout = set->getLayout();
|
||||||
|
set->boundLayout = mDescriptorSetLayoutCache->getVkLayout(layout->bitmask,
|
||||||
actualExternalSamplers, outSamplers);
|
actualExternalSamplers, outSamplers);
|
||||||
layout->setExternalSamplerVkLayout(newLayout);
|
|
||||||
// Update the external samplers in the set
|
// Update the external samplers in the set
|
||||||
for (auto& [binding, sampler, image]: samplerAndBindings) {
|
for (auto& [binding, sampler, image]: samplerAndBindings) {
|
||||||
// We cannot call updateSamplerForExternalSamplerSet because some samplers are non NULL
|
// We cannot call updateSamplerForExternalSamplerSet because some samplers are non NULL
|
||||||
// (RGB) and we cannot do a combined update with a NULL sampler.
|
// (RGB) and we cannot do a combined update with a NULL sampler.
|
||||||
mDescriptorSetCache->updateSampler(set, binding, image, sampler, newLayout);
|
mDescriptorSetCache->updateSampler(set, binding, image, sampler, set->boundLayout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,23 +128,6 @@ VkSamplerYcbcrConversion VulkanExternalImageManager::getVkSamplerYcbcrConversion
|
|||||||
return mYcbcrConversionCache->getConversion(ycbcrParams);
|
return mYcbcrConversionCache->getConversion(ycbcrParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanExternalImageManager::updateImage(ImageData* image) {
|
|
||||||
if (image->hasBeenValidated) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
image->hasBeenValidated = true;
|
|
||||||
|
|
||||||
auto metadata = mPlatform->extractExternalImageMetadata(image->platformHandle);
|
|
||||||
auto vkYcbcr = getVkSamplerYcbcrConversion(metadata);
|
|
||||||
if (vkYcbcr == image->conversion) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
image->image->setYcbcrConversion(vkYcbcr);
|
|
||||||
image->conversion = vkYcbcr;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VulkanExternalImageManager::removeDescriptorSet(
|
void VulkanExternalImageManager::removeDescriptorSet(
|
||||||
fvkmemory::resource_ptr<VulkanDescriptorSet> inSet) {
|
fvkmemory::resource_ptr<VulkanDescriptorSet> inSet) {
|
||||||
erasep<SetBindingInfo>(mSetBindings,
|
erasep<SetBindingInfo>(mSetBindings,
|
||||||
@@ -212,25 +139,32 @@ void VulkanExternalImageManager::bindExternallySampledTexture(
|
|||||||
fvkmemory::resource_ptr<VulkanTexture> image, SamplerParams samplerParams) {
|
fvkmemory::resource_ptr<VulkanTexture> image, SamplerParams samplerParams) {
|
||||||
// Should we do duplicate validation here?
|
// Should we do duplicate validation here?
|
||||||
auto& imageData = findImage(mImages, image);
|
auto& imageData = findImage(mImages, image);
|
||||||
auto itr = std::find_if(mSetBindings.begin(), mSetBindings.end(),
|
// according to spec, these must match chromaFilter
|
||||||
[&](SetBindingInfo const& binding) {
|
// https://registry.khronos.org/vulkan/specs/latest/man/html/VkSamplerCreateInfo.html#VUID-VkSamplerCreateInfo-minFilter-01645
|
||||||
return (binding.set == set && binding.binding == bindingPoint);
|
samplerParams.filterMag = SamplerMagFilter::NEAREST;
|
||||||
});
|
samplerParams.filterMin = SamplerMinFilter::NEAREST;
|
||||||
if (itr == mSetBindings.end()) {
|
// If the sampler has a ycbcrConversion then anisotropic must be disabled and addressModeU,
|
||||||
mSetBindings.push_back({ bindingPoint, imageData.image, set, samplerParams });
|
// addressModeV and addressModeW must be VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE.
|
||||||
} else {
|
// https://docs.vulkan.org/spec/latest/chapters/samplers.html#VUID-VkSamplerCreateInfo-addressModeU-01646
|
||||||
// override the image data in the binding point
|
samplerParams.wrapS = SamplerWrapMode::CLAMP_TO_EDGE;
|
||||||
itr->image = image;
|
samplerParams.wrapT = SamplerWrapMode::CLAMP_TO_EDGE;
|
||||||
itr->samplerParams = samplerParams;
|
samplerParams.wrapR = SamplerWrapMode::CLAMP_TO_EDGE;
|
||||||
}
|
samplerParams.anisotropyLog2 = 0;
|
||||||
|
|
||||||
|
VkSampler const sampler = mSamplerCache->getSampler({
|
||||||
|
.sampler = samplerParams,
|
||||||
|
.conversion = imageData.conversion,
|
||||||
|
});
|
||||||
|
|
||||||
|
mSetBindings.push_back({ bindingPoint, imageData.image, set, sampler });
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanExternalImageManager::addExternallySampledTexture(
|
void VulkanExternalImageManager::addExternallySampledTexture(
|
||||||
fvkmemory::resource_ptr<VulkanTexture> image,
|
fvkmemory::resource_ptr<VulkanTexture> image, VkSamplerYcbcrConversion const conversion) {
|
||||||
Platform::ExternalImageHandleRef platformHandleRef) {
|
mImages.push_back({
|
||||||
// By passing VK_NULL_HANDLE which is already there by default.
|
.image = image,
|
||||||
// We make it clear that all default images do NOT have a chroma conversion.
|
.conversion = conversion,
|
||||||
mImages.push_back({ image, platformHandleRef, false, VK_NULL_HANDLE });
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanExternalImageManager::removeExternallySampledTexture(
|
void VulkanExternalImageManager::removeExternallySampledTexture(
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ class VulkanExternalImageManager {
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
VulkanExternalImageManager(
|
VulkanExternalImageManager(
|
||||||
VulkanPlatform* platform,
|
|
||||||
VulkanSamplerCache* samplerCache,
|
VulkanSamplerCache* samplerCache,
|
||||||
VulkanYcbcrConversionCache* ycbcrConversionCache,
|
VulkanYcbcrConversionCache* ycbcrConversionCache,
|
||||||
VulkanDescriptorSetCache* setCache,
|
VulkanDescriptorSetCache* setCache,
|
||||||
@@ -46,8 +45,6 @@ public:
|
|||||||
|
|
||||||
void terminate();
|
void terminate();
|
||||||
|
|
||||||
void onBeginFrame();
|
|
||||||
|
|
||||||
using SetArray = std::array<fvkmemory::resource_ptr<VulkanDescriptorSet>,
|
using SetArray = std::array<fvkmemory::resource_ptr<VulkanDescriptorSet>,
|
||||||
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
|
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||||
|
|
||||||
@@ -56,10 +53,6 @@ public:
|
|||||||
|
|
||||||
using VkLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
|
using VkLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
|
||||||
|
|
||||||
// Returns bitmask to indicate whether or not to use the external sampler version of each
|
|
||||||
// descriptor set.
|
|
||||||
fvkutils::DescriptorSetMask prepareBindSets(LayoutArray const& layouts, SetArray const& sets);
|
|
||||||
|
|
||||||
void removeDescriptorSet(fvkmemory::resource_ptr<VulkanDescriptorSet> set);
|
void removeDescriptorSet(fvkmemory::resource_ptr<VulkanDescriptorSet> set);
|
||||||
|
|
||||||
void bindExternallySampledTexture(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
void bindExternallySampledTexture(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||||
@@ -70,7 +63,7 @@ public:
|
|||||||
uint8_t bindingPoint);
|
uint8_t bindingPoint);
|
||||||
|
|
||||||
void addExternallySampledTexture(fvkmemory::resource_ptr<VulkanTexture> external,
|
void addExternallySampledTexture(fvkmemory::resource_ptr<VulkanTexture> external,
|
||||||
Platform::ExternalImageHandleRef platformHandleRef);
|
VkSamplerYcbcrConversion const conversion);
|
||||||
|
|
||||||
void removeExternallySampledTexture(fvkmemory::resource_ptr<VulkanTexture> image);
|
void removeExternallySampledTexture(fvkmemory::resource_ptr<VulkanTexture> image);
|
||||||
|
|
||||||
@@ -81,20 +74,19 @@ public:
|
|||||||
|
|
||||||
struct ImageData {
|
struct ImageData {
|
||||||
fvkmemory::resource_ptr<VulkanTexture> image;
|
fvkmemory::resource_ptr<VulkanTexture> image;
|
||||||
Platform::ExternalImageHandle platformHandle;
|
|
||||||
bool hasBeenValidated = false; // indicates whether the image has been validated *this frame*
|
|
||||||
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE;
|
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool hasExternalSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set) const;
|
// - Update the descriptor set layout with the external samplers. This layout will be assign the
|
||||||
|
// respective immutable samplers to the layout.
|
||||||
|
// - Switch from binding the base layout to the new one.
|
||||||
|
// - Create a new descriptor set based on the new layout and copy the old descriptor set
|
||||||
|
// bindings that don't use external samplers.
|
||||||
|
// - Update the bindings that use external samplers.
|
||||||
|
void updateSetAndLayout(fvkmemory::resource_ptr<VulkanDescriptorSet> set);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateSetAndLayout(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
|
||||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
|
||||||
|
|
||||||
void updateImage(ImageData* imageData);
|
|
||||||
|
|
||||||
VulkanPlatform* mPlatform;
|
|
||||||
VulkanSamplerCache* mSamplerCache;
|
VulkanSamplerCache* mSamplerCache;
|
||||||
VulkanYcbcrConversionCache* mYcbcrConversionCache;
|
VulkanYcbcrConversionCache* mYcbcrConversionCache;
|
||||||
VulkanDescriptorSetCache* mDescriptorSetCache;
|
VulkanDescriptorSetCache* mDescriptorSetCache;
|
||||||
@@ -107,8 +99,7 @@ private:
|
|||||||
uint8_t binding = 0;
|
uint8_t binding = 0;
|
||||||
fvkmemory::resource_ptr<VulkanTexture> image;
|
fvkmemory::resource_ptr<VulkanTexture> image;
|
||||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set;
|
fvkmemory::resource_ptr<VulkanDescriptorSet> set;
|
||||||
SamplerParams samplerParams;
|
VkSampler sampler = VK_NULL_HANDLE;
|
||||||
bool bound = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Use vectors instead of hash maps because we only expect small number of entries.
|
// Use vectors instead of hash maps because we only expect small number of entries.
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#include "VulkanFboCache.h"
|
#include "VulkanFboCache.h"
|
||||||
|
|
||||||
#include "VulkanConstants.h"
|
#include "VulkanConstants.h"
|
||||||
|
#include "VulkanHandles.h"
|
||||||
#include "vulkan/utils/Image.h"
|
#include "vulkan/utils/Image.h"
|
||||||
|
|
||||||
#include <utils/Panic.h>
|
#include <utils/Panic.h>
|
||||||
@@ -69,9 +70,10 @@ VulkanFboCache::~VulkanFboCache() {
|
|||||||
<< "Please explicitly call terminate() while the VkDevice is still alive.";
|
<< "Please explicitly call terminate() while the VkDevice is still alive.";
|
||||||
}
|
}
|
||||||
|
|
||||||
VkFramebuffer VulkanFboCache::getFramebuffer(FboKey const& config) noexcept {
|
fvkmemory::resource_ptr<VulkanFramebuffer> VulkanFboCache::getFramebuffer(
|
||||||
|
FboKey const& config, fvkmemory::ResourceManager* resManager) noexcept {
|
||||||
FboMap::iterator iter = mFramebufferCache.find(config);
|
FboMap::iterator iter = mFramebufferCache.find(config);
|
||||||
if (UTILS_LIKELY(iter != mFramebufferCache.end() && iter->second.handle != VK_NULL_HANDLE)) {
|
if (UTILS_LIKELY(iter != mFramebufferCache.end())) {
|
||||||
iter.value().timestamp = mCurrentTime;
|
iter.value().timestamp = mCurrentTime;
|
||||||
return iter->second.handle;
|
return iter->second.handle;
|
||||||
}
|
}
|
||||||
@@ -117,13 +119,16 @@ VkFramebuffer VulkanFboCache::getFramebuffer(FboKey const& config) noexcept {
|
|||||||
VkResult error = vkCreateFramebuffer(mDevice, &info, VKALLOC, &framebuffer);
|
VkResult error = vkCreateFramebuffer(mDevice, &info, VKALLOC, &framebuffer);
|
||||||
FILAMENT_CHECK_POSTCONDITION(error == VK_SUCCESS) << "Unable to create framebuffer."
|
FILAMENT_CHECK_POSTCONDITION(error == VK_SUCCESS) << "Unable to create framebuffer."
|
||||||
<< " error=" << static_cast<int32_t>(error);
|
<< " error=" << static_cast<int32_t>(error);
|
||||||
mFramebufferCache[config] = {framebuffer, mCurrentTime};
|
fvkmemory::resource_ptr<VulkanFramebuffer> fbh =
|
||||||
return framebuffer;
|
fvkmemory::resource_ptr<VulkanFramebuffer>::construct(resManager, mDevice, framebuffer);
|
||||||
|
mFramebufferCache[config] = {fbh, mCurrentTime};
|
||||||
|
return fbh;
|
||||||
}
|
}
|
||||||
|
|
||||||
VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey const& config) noexcept {
|
fvkmemory::resource_ptr<VulkanRenderPass> VulkanFboCache::getRenderPass(
|
||||||
|
RenderPassKey const& config, fvkmemory::ResourceManager* resManager) noexcept {
|
||||||
auto iter = mRenderPassCache.find(config);
|
auto iter = mRenderPassCache.find(config);
|
||||||
if (UTILS_LIKELY(iter != mRenderPassCache.end() && iter->second.handle != VK_NULL_HANDLE)) {
|
if (UTILS_LIKELY(iter != mRenderPassCache.end())) {
|
||||||
iter.value().timestamp = mCurrentTime;
|
iter.value().timestamp = mCurrentTime;
|
||||||
return iter->second.handle;
|
return iter->second.handle;
|
||||||
}
|
}
|
||||||
@@ -326,7 +331,9 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey const& config) noexcept
|
|||||||
VkResult error = vkCreateRenderPass(mDevice, &renderPassInfo, VKALLOC, &renderPass);
|
VkResult error = vkCreateRenderPass(mDevice, &renderPassInfo, VKALLOC, &renderPass);
|
||||||
FILAMENT_CHECK_POSTCONDITION(error == VK_SUCCESS) << "Unable to create render pass."
|
FILAMENT_CHECK_POSTCONDITION(error == VK_SUCCESS) << "Unable to create render pass."
|
||||||
<< " error=" << error;
|
<< " error=" << error;
|
||||||
mRenderPassCache[config] = {renderPass, mCurrentTime};
|
fvkmemory::resource_ptr<VulkanRenderPass> rph =
|
||||||
|
fvkmemory::resource_ptr<VulkanRenderPass>::construct(resManager, mDevice, renderPass);
|
||||||
|
mRenderPassCache[config] = {rph, mCurrentTime};
|
||||||
|
|
||||||
#if FVK_ENABLED(FVK_DEBUG_FBO_CACHE)
|
#if FVK_ENABLED(FVK_DEBUG_FBO_CACHE)
|
||||||
FVK_LOGD << "Created render pass " << renderPass << " with ";
|
FVK_LOGD << "Created render pass " << renderPass << " with ";
|
||||||
@@ -343,13 +350,12 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey const& config) noexcept
|
|||||||
<< "colorAttachmentCount[0] = " << subpasses[0].colorAttachmentCount;
|
<< "colorAttachmentCount[0] = " << subpasses[0].colorAttachmentCount;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return renderPass;
|
return rph;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanFboCache::resetFramebuffers() noexcept {
|
void VulkanFboCache::resetFramebuffers() noexcept {
|
||||||
for (const auto& pair: mFramebufferCache) {
|
for (const auto& pair: mFramebufferCache) {
|
||||||
mRenderPassRefCount[pair.first.renderPass]--;
|
mRenderPassRefCount[pair.first.renderPass]--;
|
||||||
vkDestroyFramebuffer(mDevice, pair.second.handle, VKALLOC);
|
|
||||||
}
|
}
|
||||||
mFramebufferCache.clear();
|
mFramebufferCache.clear();
|
||||||
}
|
}
|
||||||
@@ -357,9 +363,6 @@ void VulkanFboCache::resetFramebuffers() noexcept {
|
|||||||
void VulkanFboCache::terminate() noexcept {
|
void VulkanFboCache::terminate() noexcept {
|
||||||
resetFramebuffers();
|
resetFramebuffers();
|
||||||
|
|
||||||
for (const auto& pair: mRenderPassCache) {
|
|
||||||
vkDestroyRenderPass(mDevice, pair.second.handle, VKALLOC);
|
|
||||||
}
|
|
||||||
mRenderPassRefCount.clear();
|
mRenderPassRefCount.clear();
|
||||||
mRenderPassCache.clear();
|
mRenderPassCache.clear();
|
||||||
}
|
}
|
||||||
@@ -380,8 +383,6 @@ void VulkanFboCache::gc() noexcept {
|
|||||||
const FboVal fbo = iter->second;
|
const FboVal fbo = iter->second;
|
||||||
if (fbo.timestamp < evictTime && fbo.handle) {
|
if (fbo.timestamp < evictTime && fbo.handle) {
|
||||||
mRenderPassRefCount[iter->first.renderPass]--;
|
mRenderPassRefCount[iter->first.renderPass]--;
|
||||||
vkDestroyFramebuffer(mDevice, fbo.handle, VKALLOC);
|
|
||||||
iter.value().handle = VK_NULL_HANDLE;
|
|
||||||
|
|
||||||
// erase(iterator) returns the iterator to the next element.
|
// erase(iterator) returns the iterator to the next element.
|
||||||
iter = mFramebufferCache.erase(iter);
|
iter = mFramebufferCache.erase(iter);
|
||||||
@@ -391,11 +392,8 @@ void VulkanFboCache::gc() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (RenderPassMap::iterator iter = mRenderPassCache.begin(); iter != mRenderPassCache.end(); ) {
|
for (RenderPassMap::iterator iter = mRenderPassCache.begin(); iter != mRenderPassCache.end(); ) {
|
||||||
const VkRenderPass handle = iter->second.handle;
|
const VkRenderPass handle = iter->second.handle->getVkRenderPass();
|
||||||
if (iter->second.timestamp < evictTime && handle && mRenderPassRefCount[handle] == 0) {
|
if (iter->second.timestamp < evictTime && handle && mRenderPassRefCount[handle] == 0) {
|
||||||
vkDestroyRenderPass(mDevice, handle, VKALLOC);
|
|
||||||
iter.value().handle = VK_NULL_HANDLE;
|
|
||||||
|
|
||||||
// erase(iterator) returns the iterator to the next element.
|
// erase(iterator) returns the iterator to the next element.
|
||||||
iter = mRenderPassCache.erase(iter);
|
iter = mRenderPassCache.erase(iter);
|
||||||
mRenderPassRefCount.erase(handle);
|
mRenderPassRefCount.erase(handle);
|
||||||
|
|||||||
@@ -18,6 +18,9 @@
|
|||||||
#define TNT_FILAMENT_BACKEND_VULKANFBOCACHE_H
|
#define TNT_FILAMENT_BACKEND_VULKANFBOCACHE_H
|
||||||
|
|
||||||
#include "VulkanContext.h"
|
#include "VulkanContext.h"
|
||||||
|
#include "vulkan/memory/Resource.h"
|
||||||
|
#include "vulkan/memory/ResourceManager.h"
|
||||||
|
#include "vulkan/memory/ResourcePointer.h"
|
||||||
|
|
||||||
#include <utils/Hash.h>
|
#include <utils/Hash.h>
|
||||||
|
|
||||||
@@ -27,6 +30,9 @@
|
|||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
|
struct VulkanFramebuffer;
|
||||||
|
struct VulkanRenderPass;
|
||||||
|
|
||||||
// Simple manager for VkFramebuffer and VkRenderPass objects.
|
// Simple manager for VkFramebuffer and VkRenderPass objects.
|
||||||
//
|
//
|
||||||
// Note that a VkFramebuffer is just a binding between a render pass and a set of image views. So,
|
// Note that a VkFramebuffer is just a binding between a render pass and a set of image views. So,
|
||||||
@@ -57,7 +63,7 @@ public:
|
|||||||
uint8_t padding[2];
|
uint8_t padding[2];
|
||||||
};
|
};
|
||||||
struct RenderPassVal {
|
struct RenderPassVal {
|
||||||
VkRenderPass handle;
|
fvkmemory::resource_ptr<VulkanRenderPass> handle;
|
||||||
uint32_t timestamp;
|
uint32_t timestamp;
|
||||||
};
|
};
|
||||||
static_assert(0 == MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT % 8);
|
static_assert(0 == MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT % 8);
|
||||||
@@ -83,7 +89,7 @@ public:
|
|||||||
VkImageView depth; // 8 bytes
|
VkImageView depth; // 8 bytes
|
||||||
};
|
};
|
||||||
struct FboVal {
|
struct FboVal {
|
||||||
VkFramebuffer handle;
|
fvkmemory::resource_ptr<VulkanFramebuffer> handle;
|
||||||
uint32_t timestamp;
|
uint32_t timestamp;
|
||||||
};
|
};
|
||||||
static_assert(sizeof(VkRenderPass) == 8, "VkRenderPass has unexpected size.");
|
static_assert(sizeof(VkRenderPass) == 8, "VkRenderPass has unexpected size.");
|
||||||
@@ -98,10 +104,12 @@ public:
|
|||||||
~VulkanFboCache();
|
~VulkanFboCache();
|
||||||
|
|
||||||
// Retrieves or creates a VkFramebuffer handle.
|
// Retrieves or creates a VkFramebuffer handle.
|
||||||
VkFramebuffer getFramebuffer(FboKey const& config) noexcept;
|
fvkmemory::resource_ptr<VulkanFramebuffer> getFramebuffer(
|
||||||
|
FboKey const& config, fvkmemory::ResourceManager* resManager) noexcept;
|
||||||
|
|
||||||
// Retrieves or creates a VkRenderPass handle.
|
// Retrieves or creates a VkRenderPass handle.
|
||||||
VkRenderPass getRenderPass(RenderPassKey const& config) noexcept;
|
fvkmemory::resource_ptr<VulkanRenderPass> getRenderPass(
|
||||||
|
RenderPassKey const& config, fvkmemory::ResourceManager* resManager) noexcept;
|
||||||
|
|
||||||
// Evicts old unused Vulkan objects. Call this once per frame.
|
// Evicts old unused Vulkan objects. Call this once per frame.
|
||||||
void gc() noexcept;
|
void gc() noexcept;
|
||||||
|
|||||||
@@ -207,7 +207,8 @@ VulkanDescriptorSet::~VulkanDescriptorSet() {
|
|||||||
|
|
||||||
VulkanDescriptorSet::VulkanDescriptorSet(fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout,
|
VulkanDescriptorSet::VulkanDescriptorSet(fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout,
|
||||||
OnRecycle&& onRecycleFn, VkDescriptorSet vkSet)
|
OnRecycle&& onRecycleFn, VkDescriptorSet vkSet)
|
||||||
: dynamicUboMask(layout->bitmask.dynamicUbo),
|
: boundLayout(layout->getVkLayout()),
|
||||||
|
dynamicUboMask(layout->bitmask.dynamicUbo),
|
||||||
uniqueDynamicUboCount(layout->count.dynamicUbo),
|
uniqueDynamicUboCount(layout->count.dynamicUbo),
|
||||||
mLayout(layout),
|
mLayout(layout),
|
||||||
mCurrentSetIndex(0) {
|
mCurrentSetIndex(0) {
|
||||||
@@ -564,7 +565,7 @@ void VulkanRenderTarget::transformViewportToPlatform(VkViewport* bounds) const {
|
|||||||
flipVertically(bounds, getExtent().height);
|
flipVertically(bounds, getExtent().height);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t VulkanRenderTarget::getColorTargetCount(const VulkanRenderPass& pass) const {
|
uint8_t VulkanRenderTarget::getColorTargetCount(const VulkanRenderPassContext& pass) const {
|
||||||
if (!mOffscreen) {
|
if (!mOffscreen) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -714,4 +715,18 @@ VulkanRenderPrimitive::VulkanRenderPrimitive(PrimitiveType pt,
|
|||||||
vertexBuffer(vb),
|
vertexBuffer(vb),
|
||||||
indexBuffer(ib) {}
|
indexBuffer(ib) {}
|
||||||
|
|
||||||
|
VulkanFramebuffer::VulkanFramebuffer(VkDevice device, VkFramebuffer framebuffer)
|
||||||
|
: mDevice(device), mFramebuffer(framebuffer) {}
|
||||||
|
|
||||||
|
VulkanFramebuffer::~VulkanFramebuffer() {
|
||||||
|
vkDestroyFramebuffer(mDevice, mFramebuffer, VKALLOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
VulkanRenderPass::VulkanRenderPass(VkDevice device, VkRenderPass renderPass)
|
||||||
|
: mDevice(device), mRenderPass(renderPass) {}
|
||||||
|
|
||||||
|
VulkanRenderPass::~VulkanRenderPass() {
|
||||||
|
vkDestroyRenderPass(mDevice, mRenderPass, VKALLOC);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace filament::backend
|
} // namespace filament::backend
|
||||||
|
|||||||
@@ -194,10 +194,20 @@ public:
|
|||||||
return bool(mSets[mCurrentSetIndex].fenceStatus);
|
return bool(mSets[mCurrentSetIndex].fenceStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The current layout used by the descriptor set. This one will match the bindings, including
|
||||||
|
// external samplers data.
|
||||||
|
// This will not necessarilly be the same as `mLayout`.
|
||||||
|
VkDescriptorSetLayout boundLayout = VK_NULL_HANDLE;
|
||||||
|
|
||||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> getLayout() const { return mLayout; }
|
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> getLayout() const { return mLayout; }
|
||||||
|
|
||||||
fvkutils::UniformBufferBitmask const& dynamicUboMask;
|
fvkutils::UniformBufferBitmask const& dynamicUboMask;
|
||||||
uint8_t const uniqueDynamicUboCount;
|
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;
|
||||||
|
bool isAnExternalSamplerBound = false;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class VulkanDescriptorSetCache;
|
friend class VulkanDescriptorSetCache;
|
||||||
@@ -409,7 +419,7 @@ struct VulkanRenderTarget : private HwRenderTarget, fvkmemory::Resource {
|
|||||||
return mInfo->fbkey.samples;
|
return mInfo->fbkey.samples;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t getColorTargetCount(VulkanRenderPass const& pass) const;
|
uint8_t getColorTargetCount(VulkanRenderPassContext const& pass) const;
|
||||||
|
|
||||||
inline bool hasDepth() const { return mInfo->depthIndex != Auxiliary::UNDEFINED_INDEX; }
|
inline bool hasDepth() const { return mInfo->depthIndex != Auxiliary::UNDEFINED_INDEX; }
|
||||||
|
|
||||||
@@ -575,6 +585,32 @@ struct VulkanRenderPrimitive : public HwRenderPrimitive, fvkmemory::Resource {
|
|||||||
fvkmemory::resource_ptr<VulkanIndexBuffer> indexBuffer;
|
fvkmemory::resource_ptr<VulkanIndexBuffer> indexBuffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct VulkanFramebuffer : public fvkmemory::Resource {
|
||||||
|
VulkanFramebuffer(VkDevice device, VkFramebuffer framebuffer);
|
||||||
|
~VulkanFramebuffer();
|
||||||
|
|
||||||
|
inline VkFramebuffer getVkFramebuffer() const noexcept {
|
||||||
|
return mFramebuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
VkDevice mDevice;
|
||||||
|
VkFramebuffer mFramebuffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VulkanRenderPass : public fvkmemory::Resource {
|
||||||
|
VulkanRenderPass(VkDevice device, VkRenderPass renderPass);
|
||||||
|
~VulkanRenderPass();
|
||||||
|
|
||||||
|
inline VkRenderPass getVkRenderPass() const noexcept {
|
||||||
|
return mRenderPass;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
VkDevice mDevice;
|
||||||
|
VkRenderPass mRenderPass;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace filament::backend
|
} // namespace filament::backend
|
||||||
|
|
||||||
#endif // TNT_FILAMENT_BACKEND_VULKANHANDLES_H
|
#endif // TNT_FILAMENT_BACKEND_VULKANHANDLES_H
|
||||||
|
|||||||
@@ -166,6 +166,7 @@ void VulkanPipelineCache::asyncPrewarmCache(
|
|||||||
.depthBiasConstantFactor = 0.f,
|
.depthBiasConstantFactor = 0.f,
|
||||||
.depthBiasSlopeFactor = 0.f,
|
.depthBiasSlopeFactor = 0.f,
|
||||||
},
|
},
|
||||||
|
.stencilState = {},
|
||||||
.layout = layout,
|
.layout = layout,
|
||||||
};
|
};
|
||||||
PipelineDynamicOptions dynamicOptions {
|
PipelineDynamicOptions dynamicOptions {
|
||||||
@@ -297,24 +298,45 @@ VkPipeline VulkanPipelineCache::createPipeline(
|
|||||||
bool const enableDepthTest =
|
bool const enableDepthTest =
|
||||||
raster.depthCompareOp != SamplerCompareFunc::A ||
|
raster.depthCompareOp != SamplerCompareFunc::A ||
|
||||||
raster.depthWriteEnable;
|
raster.depthWriteEnable;
|
||||||
|
// Stencil must be enabled if we're testing OR writing to the stencil buffer.
|
||||||
|
auto const& stencil = key.stencilState;
|
||||||
|
bool const enableStencilTest =
|
||||||
|
stencil.front.stencilFunc != StencilState::StencilFunction::A ||
|
||||||
|
stencil.back.stencilFunc != StencilState::StencilFunction::A ||
|
||||||
|
stencil.front.stencilOpDepthFail != StencilOperation::KEEP ||
|
||||||
|
stencil.back.stencilOpDepthFail != StencilOperation::KEEP ||
|
||||||
|
stencil.front.stencilOpStencilFail != StencilOperation::KEEP ||
|
||||||
|
stencil.back.stencilOpStencilFail != StencilOperation::KEEP ||
|
||||||
|
stencil.front.stencilOpDepthStencilPass != StencilOperation::KEEP ||
|
||||||
|
stencil.back.stencilOpDepthStencilPass != StencilOperation::KEEP ||
|
||||||
|
stencil.stencilWrite;
|
||||||
VkPipelineDepthStencilStateCreateInfo vkDs = {
|
VkPipelineDepthStencilStateCreateInfo vkDs = {
|
||||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
|
||||||
.depthTestEnable = enableDepthTest ? VK_TRUE : VK_FALSE,
|
.depthTestEnable = enableDepthTest ? VK_TRUE : VK_FALSE,
|
||||||
.depthWriteEnable = raster.depthWriteEnable,
|
.depthWriteEnable = raster.depthWriteEnable,
|
||||||
.depthCompareOp = fvkutils::getCompareOp(raster.depthCompareOp),
|
.depthCompareOp = fvkutils::getCompareOp(raster.depthCompareOp),
|
||||||
.depthBoundsTestEnable = VK_FALSE,
|
.depthBoundsTestEnable = VK_FALSE,
|
||||||
.stencilTestEnable = VK_FALSE,
|
.stencilTestEnable = enableStencilTest ? VK_TRUE : VK_FALSE,
|
||||||
.minDepthBounds = 0.0f,
|
.minDepthBounds = 0.0f,
|
||||||
.maxDepthBounds = 0.0f,
|
.maxDepthBounds = 0.0f,
|
||||||
};
|
};
|
||||||
vkDs.front = vkDs.back = {
|
vkDs.front = {
|
||||||
.failOp = VK_STENCIL_OP_KEEP,
|
.failOp = fvkutils::getStencilOp(stencil.front.stencilOpStencilFail),
|
||||||
.passOp = VK_STENCIL_OP_KEEP,
|
.passOp = fvkutils::getStencilOp(stencil.front.stencilOpDepthStencilPass),
|
||||||
.depthFailOp = VK_STENCIL_OP_KEEP,
|
.depthFailOp = fvkutils::getStencilOp(stencil.front.stencilOpDepthFail),
|
||||||
.compareOp = VK_COMPARE_OP_ALWAYS,
|
.compareOp = fvkutils::getCompareOp(stencil.front.stencilFunc),
|
||||||
.compareMask = 0u,
|
.compareMask = stencil.front.readMask,
|
||||||
.writeMask = 0u,
|
.writeMask = (uint32_t) (stencil.stencilWrite ? stencil.front.writeMask : 0u),
|
||||||
.reference = 0u,
|
.reference = (uint32_t) stencil.front.ref,
|
||||||
|
};
|
||||||
|
vkDs.back = {
|
||||||
|
.failOp = fvkutils::getStencilOp(stencil.back.stencilOpStencilFail),
|
||||||
|
.passOp = fvkutils::getStencilOp(stencil.back.stencilOpDepthStencilPass),
|
||||||
|
.depthFailOp = fvkutils::getStencilOp(stencil.back.stencilOpDepthFail),
|
||||||
|
.compareOp = fvkutils::getCompareOp(stencil.back.stencilFunc),
|
||||||
|
.compareMask = stencil.back.readMask,
|
||||||
|
.writeMask = (uint32_t) (stencil.stencilWrite ? stencil.back.writeMask : 0u),
|
||||||
|
.reference = (uint32_t) stencil.back.ref,
|
||||||
};
|
};
|
||||||
|
|
||||||
VkGraphicsPipelineCreateInfo pipelineCreateInfo = {
|
VkGraphicsPipelineCreateInfo pipelineCreateInfo = {
|
||||||
@@ -433,8 +455,14 @@ void VulkanPipelineCache::bindRasterState(RasterState const& rasterState) noexce
|
|||||||
mPipelineRequirements.rasterState = rasterState;
|
mPipelineRequirements.rasterState = rasterState;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanPipelineCache::bindRenderPass(VkRenderPass renderPass, int subpassIndex) noexcept {
|
void VulkanPipelineCache::bindStencilState(StencilState const& stencilState) noexcept {
|
||||||
mPipelineRequirements.renderPass = renderPass;
|
mPipelineRequirements.stencilState = stencilState;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanPipelineCache::bindRenderPass(
|
||||||
|
fvkmemory::resource_ptr<VulkanRenderPass> renderPass,
|
||||||
|
int subpassIndex) noexcept {
|
||||||
|
mPipelineRequirements.renderPass = renderPass->getVkRenderPass();
|
||||||
mPipelineRequirements.subpassIndex = subpassIndex;
|
mPipelineRequirements.subpassIndex = subpassIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,7 +122,9 @@ public:
|
|||||||
void bindLayout(VkPipelineLayout layout) noexcept;
|
void bindLayout(VkPipelineLayout layout) noexcept;
|
||||||
void bindProgram(fvkmemory::resource_ptr<VulkanProgram> program) noexcept;
|
void bindProgram(fvkmemory::resource_ptr<VulkanProgram> program) noexcept;
|
||||||
void bindRasterState(RasterState const& rasterState) noexcept;
|
void bindRasterState(RasterState const& rasterState) noexcept;
|
||||||
void bindRenderPass(VkRenderPass renderPass, int subpassIndex) noexcept;
|
void bindStencilState(StencilState const& stencilState) noexcept;
|
||||||
|
void bindRenderPass(fvkmemory::resource_ptr<VulkanRenderPass> renderPass,
|
||||||
|
int subpassIndex) noexcept;
|
||||||
void bindPrimitiveTopology(VkPrimitiveTopology topology) noexcept;
|
void bindPrimitiveTopology(VkPrimitiveTopology topology) noexcept;
|
||||||
void bindVertexArray(VkVertexInputAttributeDescription const* attribDesc,
|
void bindVertexArray(VkVertexInputAttributeDescription const* attribDesc,
|
||||||
VkVertexInputBindingDescription const* bufferDesc, uint8_t count);
|
VkVertexInputBindingDescription const* bufferDesc, uint8_t count);
|
||||||
@@ -186,8 +188,8 @@ private:
|
|||||||
VertexInputAttributeDescription vertexAttributes[VERTEX_ATTRIBUTE_COUNT]; // 128 : 28
|
VertexInputAttributeDescription vertexAttributes[VERTEX_ATTRIBUTE_COUNT]; // 128 : 28
|
||||||
VertexInputBindingDescription vertexBuffers[VERTEX_ATTRIBUTE_COUNT]; // 128 : 156
|
VertexInputBindingDescription vertexBuffers[VERTEX_ATTRIBUTE_COUNT]; // 128 : 156
|
||||||
RasterState rasterState; // 16 : 284
|
RasterState rasterState; // 16 : 284
|
||||||
uint32_t padding; // 4 : 300
|
StencilState stencilState; // 12 : 300
|
||||||
VkPipelineLayout layout; // 8 : 304
|
VkPipelineLayout layout; // 8 : 312
|
||||||
};
|
};
|
||||||
|
|
||||||
// Provides information about any dynamic state that should be used in creation of the
|
// Provides information about any dynamic state that should be used in creation of the
|
||||||
@@ -203,7 +205,7 @@ private:
|
|||||||
uint8_t stereoscopicViewCount = 2;
|
uint8_t stereoscopicViewCount = 2;
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(PipelineKey) == 312, "PipelineKey must not have implicit padding.");
|
static_assert(sizeof(PipelineKey) == 320, "PipelineKey must not have implicit padding.");
|
||||||
|
|
||||||
using PipelineHashFn = utils::hash::MurmurHashFn<PipelineKey>;
|
using PipelineHashFn = utils::hash::MurmurHashFn<PipelineKey>;
|
||||||
|
|
||||||
|
|||||||
@@ -368,6 +368,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
|||||||
bool const isProtected = any(tusage & TextureUsage::PROTECTED);
|
bool const isProtected = any(tusage & TextureUsage::PROTECTED);
|
||||||
VkImageCreateInfo imageInfo{
|
VkImageCreateInfo imageInfo{
|
||||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||||
|
.flags = isProtected ? VK_IMAGE_CREATE_PROTECTED_BIT : 0u,
|
||||||
.imageType = target == SamplerType::SAMPLER_3D ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D,
|
.imageType = target == SamplerType::SAMPLER_3D ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D,
|
||||||
.format = vkFormat,
|
.format = vkFormat,
|
||||||
.extent = {w, h, depth},
|
.extent = {w, h, depth},
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ template ResourceType getTypeEnum<VulkanSync>() noexcept;
|
|||||||
template ResourceType getTypeEnum<VulkanMemoryMappedBuffer>() noexcept;
|
template ResourceType getTypeEnum<VulkanMemoryMappedBuffer>() noexcept;
|
||||||
template ResourceType getTypeEnum<VulkanSemaphore>() noexcept;
|
template ResourceType getTypeEnum<VulkanSemaphore>() noexcept;
|
||||||
template ResourceType getTypeEnum<VulkanStream>() noexcept;
|
template ResourceType getTypeEnum<VulkanStream>() noexcept;
|
||||||
|
template ResourceType getTypeEnum<VulkanFramebuffer>() noexcept;
|
||||||
|
template ResourceType getTypeEnum<VulkanRenderPass>() noexcept;
|
||||||
|
|
||||||
template<typename D>
|
template<typename D>
|
||||||
ResourceType getTypeEnum() noexcept {
|
ResourceType getTypeEnum() noexcept {
|
||||||
@@ -108,6 +110,12 @@ ResourceType getTypeEnum() noexcept {
|
|||||||
if constexpr (std::is_same_v<D, VulkanStream>) {
|
if constexpr (std::is_same_v<D, VulkanStream>) {
|
||||||
return ResourceType::STREAM;
|
return ResourceType::STREAM;
|
||||||
}
|
}
|
||||||
|
if constexpr (std::is_same_v<D, VulkanFramebuffer>) {
|
||||||
|
return ResourceType::FRAMEBUFFER;
|
||||||
|
}
|
||||||
|
if constexpr (std::is_same_v<D, VulkanRenderPass>) {
|
||||||
|
return ResourceType::RENDER_PASS;
|
||||||
|
}
|
||||||
return ResourceType::UNDEFINED_TYPE;
|
return ResourceType::UNDEFINED_TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +163,10 @@ std::string_view getTypeStr(ResourceType type) {
|
|||||||
return "Semaphore";
|
return "Semaphore";
|
||||||
case ResourceType::STREAM:
|
case ResourceType::STREAM:
|
||||||
return "VulkanStream";
|
return "VulkanStream";
|
||||||
|
case ResourceType::FRAMEBUFFER:
|
||||||
|
return "Framebuffer";
|
||||||
|
case ResourceType::RENDER_PASS:
|
||||||
|
return "RenderPass";
|
||||||
case ResourceType::UNDEFINED_TYPE:
|
case ResourceType::UNDEFINED_TYPE:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,9 @@ enum class ResourceType : uint8_t {
|
|||||||
MEMORY_MAPPED_BUFFER = 18,
|
MEMORY_MAPPED_BUFFER = 18,
|
||||||
SEMAPHORE = 19,
|
SEMAPHORE = 19,
|
||||||
STREAM = 20,
|
STREAM = 20,
|
||||||
UNDEFINED_TYPE = 21, // Must be the last enum because we use it for iterating over the enums.
|
FRAMEBUFFER = 21,
|
||||||
|
RENDER_PASS = 22,
|
||||||
|
UNDEFINED_TYPE = 23, // Must be the last enum because we use it for iterating over the enums.
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename D>
|
template<typename D>
|
||||||
|
|||||||
@@ -126,6 +126,12 @@ void ResourceManager::destroyWithType(ResourceType type, HandleId id) {
|
|||||||
case ResourceType::STREAM:
|
case ResourceType::STREAM:
|
||||||
destruct<VulkanStream>(Handle<VulkanStream>(id));
|
destruct<VulkanStream>(Handle<VulkanStream>(id));
|
||||||
break;
|
break;
|
||||||
|
case ResourceType::FRAMEBUFFER:
|
||||||
|
destruct<VulkanFramebuffer>(Handle<VulkanFramebuffer>(id));
|
||||||
|
break;
|
||||||
|
case ResourceType::RENDER_PASS:
|
||||||
|
destruct<VulkanRenderPass>(Handle<VulkanRenderPass>(id));
|
||||||
|
break;
|
||||||
case ResourceType::UNDEFINED_TYPE:
|
case ResourceType::UNDEFINED_TYPE:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,10 @@ VkFormat transformVkFormat(VkFormat format, bool sRGB) {
|
|||||||
return format;
|
return format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isFormatSrgb(VkFormat format) {
|
||||||
|
return format == VK_FORMAT_R8G8B8A8_SRGB || format == VK_FORMAT_R8G8B8_SRGB;
|
||||||
|
}
|
||||||
|
|
||||||
bool isProtectedFromUsage(uint64_t usage) {
|
bool isProtectedFromUsage(uint64_t usage) {
|
||||||
return usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT;
|
return usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT;
|
||||||
}
|
}
|
||||||
@@ -367,17 +371,22 @@ VulkanPlatform::ImageData VulkanPlatformAndroid::createVkImageFromExternal(
|
|||||||
.pViewFormats = formats,
|
.pViewFormats = formats,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (fvkExternalImage->sRGB) {
|
if (isFormatSrgb(metadata.format)) {
|
||||||
formats[0] = metadata.format;
|
formats[0] = metadata.format;
|
||||||
formats[1] = transformVkFormat(metadata.format, /*sRGB=*/false);
|
formats[1] = transformVkFormat(metadata.format, /*sRGB=*/false);
|
||||||
imageFormatListInfo.pNext = externalCreateInfo.pNext;
|
imageFormatListInfo.pNext = externalCreateInfo.pNext;
|
||||||
externalCreateInfo.pNext = &imageFormatListInfo;
|
externalCreateInfo.pNext = &imageFormatListInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VkImageCreateFlags imageFlags =
|
||||||
|
(isFormatSrgb(metadata.format) ? VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT : 0u) |
|
||||||
|
(any(metadata.filamentUsage & TextureUsage::PROTECTED)
|
||||||
|
? VK_IMAGE_CREATE_PROTECTED_BIT
|
||||||
|
: 0u);
|
||||||
VkImageCreateInfo const imageInfo = {
|
VkImageCreateInfo const imageInfo = {
|
||||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||||
.pNext = &externalCreateInfo,
|
.pNext = &externalCreateInfo,
|
||||||
.flags = fvkExternalImage->sRGB ? VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT : 0u,
|
.flags = imageFlags,
|
||||||
.imageType = VK_IMAGE_TYPE_2D,
|
.imageType = VK_IMAGE_TYPE_2D,
|
||||||
// For non external images, use the same format as the AHB, which isn't in SRGB
|
// For non external images, use the same format as the AHB, which isn't in SRGB
|
||||||
// Fix VUID-VkMemoryAllocateInfo-pNext-02387
|
// Fix VUID-VkMemoryAllocateInfo-pNext-02387
|
||||||
|
|||||||
@@ -633,6 +633,19 @@ VkCompareOp getCompareOp(SamplerCompareFunc func) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VkStencilOp getStencilOp(StencilOperation op) {
|
||||||
|
switch (op) {
|
||||||
|
case StencilOperation::KEEP: return VK_STENCIL_OP_KEEP;
|
||||||
|
case StencilOperation::ZERO: return VK_STENCIL_OP_ZERO;
|
||||||
|
case StencilOperation::REPLACE: return VK_STENCIL_OP_REPLACE;
|
||||||
|
case StencilOperation::INCR: return VK_STENCIL_OP_INCREMENT_AND_CLAMP;
|
||||||
|
case StencilOperation::INCR_WRAP: return VK_STENCIL_OP_INCREMENT_AND_WRAP;
|
||||||
|
case StencilOperation::DECR: return VK_STENCIL_OP_DECREMENT_AND_CLAMP;
|
||||||
|
case StencilOperation::DECR_WRAP: return VK_STENCIL_OP_DECREMENT_AND_WRAP;
|
||||||
|
case StencilOperation::INVERT: return VK_STENCIL_OP_INVERT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VkBlendFactor getBlendFactor(BlendFunction mode) {
|
VkBlendFactor getBlendFactor(BlendFunction mode) {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case BlendFunction::ZERO: return VK_BLEND_FACTOR_ZERO;
|
case BlendFunction::ZERO: return VK_BLEND_FACTOR_ZERO;
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ uint32_t getBytesPerPixel(TextureFormat format);
|
|||||||
uint8_t getTexelBlockSize(VkFormat format);
|
uint8_t getTexelBlockSize(VkFormat format);
|
||||||
|
|
||||||
VkCompareOp getCompareOp(SamplerCompareFunc func);
|
VkCompareOp getCompareOp(SamplerCompareFunc func);
|
||||||
|
VkStencilOp getStencilOp(StencilOperation op);
|
||||||
VkBlendFactor getBlendFactor(BlendFunction mode);
|
VkBlendFactor getBlendFactor(BlendFunction mode);
|
||||||
VkCullModeFlags getCullMode(CullingMode mode);
|
VkCullModeFlags getCullMode(CullingMode mode);
|
||||||
VkFrontFace getFrontFace(bool inverseFrontFaces);
|
VkFrontFace getFrontFace(bool inverseFrontFaces);
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
@@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 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 "BackendTest.h"
|
||||||
|
|
||||||
|
#include "Shader.h"
|
||||||
|
#include "SharedShaders.h"
|
||||||
|
#include "Skip.h"
|
||||||
|
#include "TrianglePrimitive.h"
|
||||||
|
|
||||||
|
#include <backend/PixelBufferDescriptor.h>
|
||||||
|
|
||||||
|
#include <utils/FixedCapacityVector.h>
|
||||||
|
|
||||||
|
namespace test {
|
||||||
|
|
||||||
|
using namespace filament;
|
||||||
|
using namespace filament::backend;
|
||||||
|
using namespace filament::math;
|
||||||
|
|
||||||
|
TEST_F(BackendTest, AutoresolveDifferingSampleCounts) {
|
||||||
|
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::OPENGL), "see b/486954356");
|
||||||
|
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::VULKAN), "see b/486954356");
|
||||||
|
|
||||||
|
auto& api = getDriverApi();
|
||||||
|
constexpr int kRenderTargetSize = 512;
|
||||||
|
|
||||||
|
auto swapChain = addCleanup(createSwapChain());
|
||||||
|
api.makeCurrent(swapChain, swapChain);
|
||||||
|
|
||||||
|
Shader shader = SharedShaders::makeShader(api, *mCleanup,
|
||||||
|
{
|
||||||
|
.mVertexType = VertexShaderType::Simple,
|
||||||
|
.mFragmentType = FragmentShaderType::SolidColored,
|
||||||
|
.mUniformType = ShaderUniformType::Simple,
|
||||||
|
});
|
||||||
|
|
||||||
|
TrianglePrimitive triangle(api);
|
||||||
|
PipelineState ps = getColorWritePipelineState();
|
||||||
|
shader.addProgramToPipelineState(ps);
|
||||||
|
|
||||||
|
auto ubuffer = addCleanup(api.createBufferObject(sizeof(SimpleMaterialParams),
|
||||||
|
BufferObjectBinding::UNIFORM, BufferUsage::STATIC));
|
||||||
|
shader.bindUniform<SimpleMaterialParams>(api, ubuffer);
|
||||||
|
|
||||||
|
// Create a texture with sample count = 1.
|
||||||
|
Handle<HwTexture> texture = addCleanup(api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||||
|
TextureFormat::RGBA8, 1, kRenderTargetSize, kRenderTargetSize, 1,
|
||||||
|
TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE));
|
||||||
|
|
||||||
|
// First render pass: render a red triangle to a RenderTarget with 8 samples.
|
||||||
|
{
|
||||||
|
Handle<HwRenderTarget> renderTarget =
|
||||||
|
addCleanup(api.createRenderTarget(TargetBufferFlags::COLOR, kRenderTargetSize,
|
||||||
|
kRenderTargetSize, 8, 1, { { texture } }, {}, {}));
|
||||||
|
|
||||||
|
shader.uploadUniform(api, ubuffer,
|
||||||
|
SimpleMaterialParams{
|
||||||
|
.color = float4(1, 0, 0, 1),
|
||||||
|
});
|
||||||
|
|
||||||
|
RenderPassParams params = getClearColorRenderPass(float4(0));
|
||||||
|
params.viewport = { 0, 0, kRenderTargetSize, kRenderTargetSize };
|
||||||
|
|
||||||
|
RenderFrame frame(api);
|
||||||
|
api.beginRenderPass(renderTarget, params);
|
||||||
|
ps.primitiveType = PrimitiveType::TRIANGLES;
|
||||||
|
ps.vertexBufferInfo = triangle.getVertexBufferInfo();
|
||||||
|
api.bindPipeline(ps);
|
||||||
|
api.bindRenderPrimitive(triangle.getRenderPrimitive());
|
||||||
|
api.draw2(0, 3, 1);
|
||||||
|
api.endRenderPass();
|
||||||
|
api.commit(swapChain);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second render pass: render a green triangle to a RenderTarget with 4 samples, attached to
|
||||||
|
// the same texture.
|
||||||
|
{
|
||||||
|
Handle<HwRenderTarget> renderTarget =
|
||||||
|
addCleanup(api.createRenderTarget(TargetBufferFlags::COLOR, kRenderTargetSize,
|
||||||
|
kRenderTargetSize, 4, 1, { { texture } }, {}, {}));
|
||||||
|
|
||||||
|
shader.uploadUniform(api, ubuffer,
|
||||||
|
SimpleMaterialParams{
|
||||||
|
.color = float4(0, 1, 0, 1),
|
||||||
|
});
|
||||||
|
|
||||||
|
RenderPassParams params = getClearColorRenderPass(float4(0));
|
||||||
|
params.viewport = { 0, 0, kRenderTargetSize, kRenderTargetSize };
|
||||||
|
|
||||||
|
RenderFrame frame(api);
|
||||||
|
api.beginRenderPass(renderTarget, params);
|
||||||
|
ps.primitiveType = PrimitiveType::TRIANGLES;
|
||||||
|
ps.vertexBufferInfo = triangle.getVertexBufferInfo();
|
||||||
|
api.bindPipeline(ps);
|
||||||
|
api.bindRenderPrimitive(triangle.getRenderPrimitive());
|
||||||
|
api.draw2(0, 3, 1);
|
||||||
|
api.endRenderPass();
|
||||||
|
|
||||||
|
EXPECT_IMAGE(renderTarget, ScreenshotParams(kRenderTargetSize, kRenderTargetSize,
|
||||||
|
"AutoresolveDifferingSampleCounts", 1048576));
|
||||||
|
|
||||||
|
api.commit(swapChain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace test
|
||||||
@@ -41,6 +41,17 @@ void main() {
|
|||||||
}
|
}
|
||||||
)");
|
)");
|
||||||
|
|
||||||
|
std::string fragmentCoord(R"(#version 450 core
|
||||||
|
layout(location = 0) out vec4 fragColor;
|
||||||
|
void main() {
|
||||||
|
// gl_FragCoord.xy is in pixels. (0.5, 0.5) is the center of the bottom-left pixel.
|
||||||
|
// We want to map this to a color to verify orientation.
|
||||||
|
// Red varies with X, Green varies with Y.
|
||||||
|
// We assume a 64x64 texture for this test.
|
||||||
|
vec2 coord = gl_FragCoord.xy / 64.0;
|
||||||
|
fragColor = vec4(coord.x, coord.y, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
)");
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace test {
|
namespace test {
|
||||||
@@ -51,8 +62,6 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
TEST_F(ReadTextureTest, ReadTexture2D) {
|
TEST_F(ReadTextureTest, ReadTexture2D) {
|
||||||
SKIP_IF(Backend::METAL, "readTexture not implemented for Metal");
|
|
||||||
|
|
||||||
DriverApi& api = getDriverApi();
|
DriverApi& api = getDriverApi();
|
||||||
const size_t textureSize = 64;
|
const size_t textureSize = 64;
|
||||||
|
|
||||||
@@ -146,8 +155,6 @@ TEST_F(ReadTextureTest, ReadTexture2D) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ReadTextureTest, ReadTextureArray) {
|
TEST_F(ReadTextureTest, ReadTextureArray) {
|
||||||
SKIP_IF(Backend::METAL, "readTexture not implemented for Metal");
|
|
||||||
|
|
||||||
DriverApi& api = getDriverApi();
|
DriverApi& api = getDriverApi();
|
||||||
const size_t textureSize = 64;
|
const size_t textureSize = 64;
|
||||||
const uint16_t layers = 2;
|
const uint16_t layers = 2;
|
||||||
@@ -214,4 +221,108 @@ TEST_F(ReadTextureTest, ReadTextureArray) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(ReadTextureTest, ReadTextureXCoordinates) {
|
||||||
|
SKIP_IF(Backend::OPENGL, "readTexture not implemented for OpenGL");
|
||||||
|
|
||||||
|
DriverApi& api = getDriverApi();
|
||||||
|
const size_t textureSize = 64;
|
||||||
|
|
||||||
|
Handle<HwSwapChain> swapChain =
|
||||||
|
addCleanup(api.createSwapChainHeadless(textureSize, textureSize, 0));
|
||||||
|
api.makeCurrent(swapChain, swapChain);
|
||||||
|
|
||||||
|
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE | TextureUsage::BLIT_SRC;
|
||||||
|
Handle<HwTexture> const texture = addCleanup(api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||||
|
TextureFormat::RGBA8, 1, textureSize, textureSize, 1, usage));
|
||||||
|
|
||||||
|
Handle<HwRenderTarget> renderTarget = addCleanup(api.createRenderTarget(
|
||||||
|
TargetBufferFlags::COLOR, textureSize, textureSize, 1, 0, { { texture, 0 } }, {}, {}));
|
||||||
|
|
||||||
|
// Draw a full-screen quad (using a large triangle)
|
||||||
|
TrianglePrimitive triangle(api);
|
||||||
|
const math::float2 fsVertices[3] = { { -1.0f, -1.0f }, { 3.0f, -1.0f }, { -1.0f, 3.0f } };
|
||||||
|
triangle.updateVertices(fsVertices);
|
||||||
|
|
||||||
|
std::string vertexShader =
|
||||||
|
SharedShaders::getVertexShaderText(VertexShaderType::Noop, ShaderUniformType::None);
|
||||||
|
Shader shader(api, *mCleanup,
|
||||||
|
ShaderConfig{
|
||||||
|
.vertexShader = vertexShader,
|
||||||
|
.fragmentShader = fragmentCoord,
|
||||||
|
.uniforms = {},
|
||||||
|
});
|
||||||
|
|
||||||
|
RenderPassParams params = getClearColorRenderPass(math::float4(0));
|
||||||
|
params.viewport.width = textureSize;
|
||||||
|
params.viewport.height = textureSize;
|
||||||
|
|
||||||
|
api.beginFrame(0, 0, 0);
|
||||||
|
api.beginRenderPass(renderTarget, params);
|
||||||
|
|
||||||
|
PipelineState state = getColorWritePipelineState();
|
||||||
|
shader.addProgramToPipelineState(state);
|
||||||
|
state.primitiveType = PrimitiveType::TRIANGLES;
|
||||||
|
state.vertexBufferInfo = triangle.getVertexBufferInfo();
|
||||||
|
api.bindPipeline(state);
|
||||||
|
api.bindRenderPrimitive(triangle.getRenderPrimitive());
|
||||||
|
api.draw2(0, 3, 1);
|
||||||
|
|
||||||
|
api.endRenderPass();
|
||||||
|
|
||||||
|
// Read texture back
|
||||||
|
size_t bufferSize = textureSize * textureSize * 4;
|
||||||
|
void* buffer = calloc(1, bufferSize);
|
||||||
|
|
||||||
|
struct UserData {
|
||||||
|
bool finished = false;
|
||||||
|
} userData;
|
||||||
|
|
||||||
|
PixelBufferDescriptor descriptor(
|
||||||
|
buffer, bufferSize, PixelDataFormat::RGBA, PixelDataType::UBYTE, 1, 0, 0, textureSize,
|
||||||
|
[](void* buffer, size_t size, void* user) {
|
||||||
|
UserData* data = (UserData*) user;
|
||||||
|
data->finished = true;
|
||||||
|
|
||||||
|
uint8_t* pixels = (uint8_t*) buffer;
|
||||||
|
const size_t width = 64;
|
||||||
|
const size_t height = 64;
|
||||||
|
|
||||||
|
// Check Bottom-Left (0, 0) -> Should be black (0, 0, 0)
|
||||||
|
// In buffer, this is at index 0
|
||||||
|
// Y=0, X=0
|
||||||
|
EXPECT_NEAR(pixels[0], 0, 5);
|
||||||
|
EXPECT_NEAR(pixels[1], 0, 5);
|
||||||
|
|
||||||
|
// Check Bottom-Right (1, 0) -> Should be Red (255, 0, 0)
|
||||||
|
// Y=0, X=63
|
||||||
|
size_t br_idx = (63) * 4;
|
||||||
|
EXPECT_NEAR(pixels[br_idx], 255, 5);
|
||||||
|
EXPECT_NEAR(pixels[br_idx + 1], 0, 5);
|
||||||
|
|
||||||
|
// Check Top-Left (0, 1) -> Should be Green (0, 255, 0)
|
||||||
|
// Y=63, X=0
|
||||||
|
size_t tl_idx = (63 * width) * 4;
|
||||||
|
EXPECT_NEAR(pixels[tl_idx], 0, 5);
|
||||||
|
EXPECT_NEAR(pixels[tl_idx + 1], 255, 5);
|
||||||
|
|
||||||
|
// Check Top-Right (1, 1) -> Should be Yellow (255, 255, 0)
|
||||||
|
// Y=63, X=63
|
||||||
|
size_t tr_idx = (63 * width + 63) * 4;
|
||||||
|
EXPECT_NEAR(pixels[tr_idx], 255, 5);
|
||||||
|
EXPECT_NEAR(pixels[tr_idx + 1], 255, 5);
|
||||||
|
|
||||||
|
free(buffer);
|
||||||
|
},
|
||||||
|
&userData);
|
||||||
|
|
||||||
|
api.readTexture(texture, 0, 0, 0, 0, textureSize, textureSize, std::move(descriptor));
|
||||||
|
|
||||||
|
api.commit(swapChain);
|
||||||
|
api.endFrame(0);
|
||||||
|
|
||||||
|
flushAndWait();
|
||||||
|
|
||||||
|
EXPECT_TRUE(userData.finished);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace test
|
} // namespace test
|
||||||
|
|||||||
@@ -271,8 +271,8 @@ std::unique_ptr<MaterialDefinition> MaterialDefinition::create(FEngine& engine,
|
|||||||
|
|
||||||
void MaterialDefinition::terminate(FEngine& engine) {
|
void MaterialDefinition::terminate(FEngine& engine) {
|
||||||
DriverApi& driver = engine.getDriverApi();
|
DriverApi& driver = engine.getDriverApi();
|
||||||
perViewDescriptorSetLayout.terminate(engine.getDescriptorSetLayoutFactory(), driver);
|
perViewDescriptorSetLayoutPcf.terminate(engine.getDescriptorSetLayoutFactory(), driver);
|
||||||
perViewDescriptorSetLayoutVsm.terminate(engine.getDescriptorSetLayoutFactory(), driver);
|
perViewDescriptorSetLayoutS2d.terminate(engine.getDescriptorSetLayoutFactory(), driver);
|
||||||
descriptorSetLayout.terminate(engine.getDescriptorSetLayoutFactory(), driver);
|
descriptorSetLayout.terminate(engine.getDescriptorSetLayoutFactory(), driver);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -607,23 +607,23 @@ void MaterialDefinition::processDescriptorSets(FEngine& engine) {
|
|||||||
refractionMode == RefractionMode::SCREEN_SPACE;
|
refractionMode == RefractionMode::SCREEN_SPACE;
|
||||||
bool const hasFog = !(variantFilterMask & UserVariantFilterMask(UserVariantFilterBit::FOG));
|
bool const hasFog = !(variantFilterMask & UserVariantFilterMask(UserVariantFilterBit::FOG));
|
||||||
|
|
||||||
this->perViewDescriptorSetLayoutDescription = descriptor_sets::getPerViewDescriptorSetLayout(
|
this->perViewDescriptorSetLayoutPcfDescription = descriptor_sets::getPerViewDescriptorSetLayout(
|
||||||
materialDomain, isLit, isSSR, hasFog, false);
|
materialDomain, isLit, isSSR, hasFog, false);
|
||||||
|
|
||||||
this->perViewDescriptorSetLayoutVsmDescription = descriptor_sets::getPerViewDescriptorSetLayout(
|
this->perViewDescriptorSetLayoutS2dDescription = descriptor_sets::getPerViewDescriptorSetLayout(
|
||||||
materialDomain, isLit, isSSR, hasFog, true);
|
materialDomain, isLit, isSSR, hasFog, true);
|
||||||
|
|
||||||
// set the labels
|
// set the labels
|
||||||
this->descriptorSetLayoutDescription.label = CString{ name }.append("_perMat");
|
this->descriptorSetLayoutDescription.label = CString{ name }.append("_perMat");
|
||||||
this->perViewDescriptorSetLayoutDescription.label = CString{ name }.append("_perView");
|
this->perViewDescriptorSetLayoutPcfDescription.label = CString{ name }.append("_perView");
|
||||||
this->perViewDescriptorSetLayoutVsmDescription.label = CString{ name }.append("_perViewVsm");
|
this->perViewDescriptorSetLayoutS2dDescription.label = CString{ name }.append("_perViewVsm");
|
||||||
|
|
||||||
// get the PER_RENDERABLE and PER_VIEW descriptor binding info
|
// get the PER_RENDERABLE and PER_VIEW descriptor binding info
|
||||||
for (auto&& [bindingPoint, dsl] : {
|
for (auto&& [bindingPoint, dsl] : {
|
||||||
std::pair{ DescriptorSetBindingPoints::PER_RENDERABLE,
|
std::pair{ DescriptorSetBindingPoints::PER_RENDERABLE,
|
||||||
descriptor_sets::getPerRenderableLayout() },
|
descriptor_sets::getPerRenderableLayout() },
|
||||||
std::pair{ DescriptorSetBindingPoints::PER_VIEW,
|
std::pair{ DescriptorSetBindingPoints::PER_VIEW,
|
||||||
this->perViewDescriptorSetLayoutDescription }}) {
|
this->perViewDescriptorSetLayoutPcfDescription }}) {
|
||||||
Program::DescriptorBindingsInfo& descriptors = programDescriptorBindings[+bindingPoint];
|
Program::DescriptorBindingsInfo& descriptors = programDescriptorBindings[+bindingPoint];
|
||||||
descriptors.reserve(dsl.descriptors.size());
|
descriptors.reserve(dsl.descriptors.size());
|
||||||
for (auto const& entry: dsl.descriptors) {
|
for (auto const& entry: dsl.descriptors) {
|
||||||
@@ -636,17 +636,17 @@ void MaterialDefinition::processDescriptorSets(FEngine& engine) {
|
|||||||
descriptorSetLayoutFactory, driver,
|
descriptorSetLayoutFactory, driver,
|
||||||
this->descriptorSetLayoutDescription };
|
this->descriptorSetLayoutDescription };
|
||||||
|
|
||||||
this->perViewDescriptorSetLayout = {
|
this->perViewDescriptorSetLayoutPcf = {
|
||||||
descriptorSetLayoutFactory, driver,
|
descriptorSetLayoutFactory, driver,
|
||||||
this->perViewDescriptorSetLayoutDescription };
|
this->perViewDescriptorSetLayoutPcfDescription };
|
||||||
|
|
||||||
this->perViewDescriptorSetLayoutVsm = {
|
this->perViewDescriptorSetLayoutS2d = {
|
||||||
descriptorSetLayoutFactory, driver,
|
descriptorSetLayoutFactory, driver,
|
||||||
this->perViewDescriptorSetLayoutVsmDescription };
|
this->perViewDescriptorSetLayoutS2dDescription };
|
||||||
}
|
}
|
||||||
|
|
||||||
backend::DescriptorSetLayout const& MaterialDefinition::getPerViewDescriptorSetLayoutDescription(
|
backend::DescriptorSetLayout const& MaterialDefinition::getPerViewDescriptorSetLayoutDescription(
|
||||||
Variant const variant, bool const useVsmDescriptorSetLayout) const noexcept {
|
Variant const variant, bool const useS2dDescriptorSetLayout) const noexcept {
|
||||||
if (materialDomain == MaterialDomain::SURFACE) {
|
if (materialDomain == MaterialDomain::SURFACE) {
|
||||||
if (Variant::isValidDepthVariant(variant)) {
|
if (Variant::isValidDepthVariant(variant)) {
|
||||||
// Use the layout description used to create the per view depth variant layout.
|
// Use the layout description used to create the per view depth variant layout.
|
||||||
@@ -657,10 +657,10 @@ backend::DescriptorSetLayout const& MaterialDefinition::getPerViewDescriptorSetL
|
|||||||
return descriptor_sets::getSsrVariantLayout();
|
return descriptor_sets::getSsrVariantLayout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (useVsmDescriptorSetLayout) {
|
if (useS2dDescriptorSetLayout) {
|
||||||
return perViewDescriptorSetLayoutVsmDescription;
|
return perViewDescriptorSetLayoutS2dDescription;
|
||||||
}
|
}
|
||||||
return perViewDescriptorSetLayoutDescription;
|
return perViewDescriptorSetLayoutPcfDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwProgram> MaterialDefinition::compileProgram(
|
Handle<HwProgram> MaterialDefinition::compileProgram(
|
||||||
@@ -689,7 +689,7 @@ Handle<HwProgram> MaterialDefinition::compileProgram(
|
|||||||
pb.descriptorLayout(+DescriptorSetBindingPoints::PER_VIEW,
|
pb.descriptorLayout(+DescriptorSetBindingPoints::PER_VIEW,
|
||||||
getPerViewDescriptorSetLayoutDescription(
|
getPerViewDescriptorSetLayoutDescription(
|
||||||
specialization.variant,
|
specialization.variant,
|
||||||
Variant::isVSMVariant(specialization.variant)));
|
Variant::isShadowSampler2DVariant(specialization.variant)));
|
||||||
pb.descriptorLayout(+DescriptorSetBindingPoints::PER_RENDERABLE,
|
pb.descriptorLayout(+DescriptorSetBindingPoints::PER_RENDERABLE,
|
||||||
descriptor_sets::getPerRenderableLayout());
|
descriptor_sets::getPerRenderableLayout());
|
||||||
pb.descriptorLayout(
|
pb.descriptorLayout(
|
||||||
|
|||||||
@@ -94,17 +94,17 @@ struct MaterialDefinition {
|
|||||||
backend::ShaderModel const sm, bool isStereoSupported) const noexcept;
|
backend::ShaderModel const sm, bool isStereoSupported) const noexcept;
|
||||||
|
|
||||||
backend::DescriptorSetLayout const& getPerViewDescriptorSetLayoutDescription(
|
backend::DescriptorSetLayout const& getPerViewDescriptorSetLayoutDescription(
|
||||||
Variant const variant, bool useVsmDescriptorSetLayout) const noexcept;
|
Variant const variant, bool useS2dDescriptorSetLayout) const noexcept;
|
||||||
|
|
||||||
// Keep track of the definitions of the descriptor set layouts, as these
|
// Keep track of the definitions of the descriptor set layouts, as these
|
||||||
// may be used by some backends in parallel compilation of programs.
|
// may be used by some backends in parallel compilation of programs.
|
||||||
backend::DescriptorSetLayout perViewDescriptorSetLayoutDescription;
|
backend::DescriptorSetLayout perViewDescriptorSetLayoutPcfDescription;
|
||||||
backend::DescriptorSetLayout perViewDescriptorSetLayoutVsmDescription;
|
backend::DescriptorSetLayout perViewDescriptorSetLayoutS2dDescription;
|
||||||
backend::DescriptorSetLayout descriptorSetLayoutDescription;
|
backend::DescriptorSetLayout descriptorSetLayoutDescription;
|
||||||
|
|
||||||
// try to order by frequency of use
|
// try to order by frequency of use
|
||||||
filament::DescriptorSetLayout perViewDescriptorSetLayout;
|
filament::DescriptorSetLayout perViewDescriptorSetLayoutPcf;
|
||||||
filament::DescriptorSetLayout perViewDescriptorSetLayoutVsm;
|
filament::DescriptorSetLayout perViewDescriptorSetLayoutS2d;
|
||||||
filament::DescriptorSetLayout descriptorSetLayout;
|
filament::DescriptorSetLayout descriptorSetLayout;
|
||||||
backend::Program::DescriptorSetInfo programDescriptorBindings;
|
backend::Program::DescriptorSetInfo programDescriptorBindings;
|
||||||
|
|
||||||
|
|||||||
@@ -802,7 +802,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::ssr(FrameGraph& fg,
|
|||||||
|
|
||||||
// use our special SSR variant, it can only be applied to object that have
|
// use our special SSR variant, it can only be applied to object that have
|
||||||
// the SCREEN_SPACE ReflectionMode.
|
// the SCREEN_SPACE ReflectionMode.
|
||||||
passBuilder.variant(Variant{ Variant::SPECIAL_SSR });
|
passBuilder.variant(Variant{ Variant::SPECIAL_SSR_VARIANT });
|
||||||
|
|
||||||
// generate all our drawing commands, except blended objects.
|
// generate all our drawing commands, except blended objects.
|
||||||
passBuilder.commandTypeFlags(RenderPass::CommandTypeFlags::SCREEN_SPACE_REFLECTIONS);
|
passBuilder.commandTypeFlags(RenderPass::CommandTypeFlags::SCREEN_SPACE_REFLECTIONS);
|
||||||
|
|||||||
@@ -568,7 +568,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(CommandTypeFlags extraFlag
|
|||||||
if constexpr (isDepthPass) {
|
if constexpr (isDepthPass) {
|
||||||
cmd.info.materialVariant = variant;
|
cmd.info.materialVariant = variant;
|
||||||
cmd.info.rasterState = {};
|
cmd.info.rasterState = {};
|
||||||
cmd.info.rasterState.colorWrite = Variant::isPickingVariant(variant) || Variant::isVSMVariant(variant);
|
cmd.info.rasterState.colorWrite = Variant::isPickingVariant(variant) || Variant::isDepthMomentsVariant(variant);
|
||||||
cmd.info.rasterState.depthWrite = true;
|
cmd.info.rasterState.depthWrite = true;
|
||||||
cmd.info.rasterState.depthFunc = RasterState::DepthFunc::GE;
|
cmd.info.rasterState.depthFunc = RasterState::DepthFunc::GE;
|
||||||
cmd.info.rasterState.alphaToCoverage = false;
|
cmd.info.rasterState.alphaToCoverage = false;
|
||||||
@@ -616,7 +616,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(CommandTypeFlags extraFlag
|
|||||||
bool const hasSkinningOrMorphing = hasSkinning || hasMorphing;
|
bool const hasSkinningOrMorphing = hasSkinning || hasMorphing;
|
||||||
|
|
||||||
// if we are already an SSR variant, the SRE bit is already set
|
// if we are already an SSR variant, the SRE bit is already set
|
||||||
static_assert(Variant::SPECIAL_SSR & Variant::SRE);
|
static_assert(Variant::SPECIAL_SSR_VARIANT & Variant::SRE);
|
||||||
Variant renderableVariant{ variant };
|
Variant renderableVariant{ variant };
|
||||||
|
|
||||||
// we can't have SSR and shadowing together by construction
|
// we can't have SSR and shadowing together by construction
|
||||||
@@ -798,16 +798,19 @@ RenderPass::Command* RenderPass::generateCommandsImpl(CommandTypeFlags extraFlag
|
|||||||
cmd.info.rasterState.culling = cullingMode;
|
cmd.info.rasterState.culling = cullingMode;
|
||||||
|
|
||||||
// FIXME: should writeDepthForShadowCasters take precedence over mi->getDepthWrite()?
|
// FIXME: should writeDepthForShadowCasters take precedence over mi->getDepthWrite()?
|
||||||
cmd.info.rasterState.depthWrite = (1 // only keep bit 0
|
cmd.info.rasterState.depthWrite =
|
||||||
& (mi->isDepthWriteEnabled() | (mode == TransparencyMode::TWO_PASSES_ONE_SIDE)
|
(1 // only keep bit 0
|
||||||
| isPickingVariant)
|
& (mi->isDepthWriteEnabled() |
|
||||||
& !(filterTranslucentObjects & translucent)
|
(mode == TransparencyMode::TWO_PASSES_ONE_SIDE) |
|
||||||
& !(depthFilterAlphaMaskedObjects & rs.alphaToCoverage))
|
isPickingVariant) &
|
||||||
| writeDepthForShadowCasters;
|
!(depthFilterAlphaMaskedObjects & rs.alphaToCoverage)) |
|
||||||
|
writeDepthForShadowCasters;
|
||||||
|
|
||||||
*curr = cmd;
|
*curr = cmd;
|
||||||
// cancel command if both front and back faces are culled
|
// cancel command if both front and back faces are culled
|
||||||
curr->key |= select(cullingMode == CullingMode::FRONT_AND_BACK);
|
curr->key |= select(cullingMode == CullingMode::FRONT_AND_BACK);
|
||||||
|
// cancel command if asked to filter translucent objects
|
||||||
|
curr->key |= select(filterTranslucentObjects & translucent);
|
||||||
}
|
}
|
||||||
|
|
||||||
++curr;
|
++curr;
|
||||||
@@ -901,6 +904,12 @@ void RenderPass::Executor::execute(FEngine const& engine, DriverApi& driver,
|
|||||||
size_t const capacity = engine.getMinCommandBufferSize();
|
size_t const capacity = engine.getMinCommandBufferSize();
|
||||||
CircularBuffer const& circularBuffer = driver.getCircularBuffer();
|
CircularBuffer const& circularBuffer = driver.getCircularBuffer();
|
||||||
|
|
||||||
|
// b/479079631: Log the number of commands in this render pass.
|
||||||
|
size_t const commandCount = last - first;
|
||||||
|
if (Platform* platform = engine.getPlatform(); platform->hasDebugUpdateStatFunc()) {
|
||||||
|
platform->debugUpdateStat("filament.renderer.render_pass.command_count", commandCount);
|
||||||
|
}
|
||||||
|
|
||||||
if (first != last) {
|
if (first != last) {
|
||||||
FILAMENT_TRACING_VALUE(FILAMENT_TRACING_CATEGORY_FILAMENT, "commandCount", last - first);
|
FILAMENT_TRACING_VALUE(FILAMENT_TRACING_CATEGORY_FILAMENT, "commandCount", last - first);
|
||||||
|
|
||||||
|
|||||||
@@ -258,7 +258,21 @@ RendererUtils::ColorPassOutput RendererUtils::colorPass(
|
|||||||
}
|
}
|
||||||
|
|
||||||
driver.beginRenderPass(out.target, out.params);
|
driver.beginRenderPass(out.target, out.params);
|
||||||
|
Platform* platform = engine.getPlatform();
|
||||||
|
CircularBuffer const& circularBuffer = driver.getCircularBuffer();
|
||||||
|
// b/479079631: Log the current command buffer size before and after executing the
|
||||||
|
// render pass.
|
||||||
|
if (platform->hasDebugUpdateStatFunc()) {
|
||||||
|
platform->debugUpdateStat(
|
||||||
|
"filament.renderer.color_pass.command_buffer_used_start",
|
||||||
|
circularBuffer.getUsed());
|
||||||
|
}
|
||||||
passExecutor.execute(engine, driver);
|
passExecutor.execute(engine, driver);
|
||||||
|
if (platform->hasDebugUpdateStatFunc()) {
|
||||||
|
platform->debugUpdateStat(
|
||||||
|
"filament.renderer.color_pass.command_buffer_used_end",
|
||||||
|
circularBuffer.getUsed());
|
||||||
|
}
|
||||||
driver.endRenderPass();
|
driver.endRenderPass();
|
||||||
|
|
||||||
// unbind all descriptor sets to avoid false dependencies with the next pass
|
// unbind all descriptor sets to avoid false dependencies with the next pass
|
||||||
|
|||||||
+58
-75
@@ -213,12 +213,14 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
|||||||
* Final shadow map transform
|
* Final shadow map transform
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Final shadow transform (focused warped light-space)
|
// Final shadow transform (focused warped light-space), world space to clip space
|
||||||
const mat4f S = F * (W * LMpMv);
|
const mat4f S = F * (W * LMpMv);
|
||||||
|
|
||||||
// Computes St the transform to use in the shader to access the shadow map texture
|
// Computes St the transform to use in the shader to access the shadow map texture
|
||||||
// i.e. it transforms a world-space vertex to a texture coordinate in the shadowmap
|
// i.e. it transforms a world-space vertex to a texture coordinate in the shadowmap
|
||||||
const auto [Mt, Mn] = getTextureCoordsMapping(shadowMapInfo, getViewport());
|
const auto [Mt, Mn] = getTextureCoordsMapping(shadowMapInfo, getViewport());
|
||||||
|
|
||||||
|
// world space to texture atlas space
|
||||||
const mat4f St = highPrecisionMultiply(Mt, S);
|
const mat4f St = highPrecisionMultiply(Mt, S);
|
||||||
|
|
||||||
ShaderParameters shaderParameters;
|
ShaderParameters shaderParameters;
|
||||||
@@ -226,14 +228,12 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
|||||||
|
|
||||||
// note: in texelSizeWorldSpace() below, we can use Mb * Mt * F * W because
|
// note: in texelSizeWorldSpace() below, we can use Mb * Mt * F * W because
|
||||||
// L * Mp * Mv is a rigid transform for directional lights, and doesn't matter.
|
// L * Mp * Mv is a rigid transform for directional lights, and doesn't matter.
|
||||||
// if Wp[3][1] is 0, then LISPSM was cancelled.
|
// if Wp[3][1] is 0, then LiSPSM was canceled.
|
||||||
if (useLispsm && Wp[3][1] != 0.0f) {
|
if (useLispsm && Wp[3][1] != 0.0f) {
|
||||||
shaderParameters.texelSizeAtOneMeterWs =
|
shaderParameters.texelSizeAtOneMeterWs = texelSizeWorldSpace(S, shadowMapInfo.shadowDimension);
|
||||||
texelSizeWorldSpace(Wp, mat4f(Mt * F), shadowMapInfo.shadowDimension);
|
|
||||||
} else {
|
} else {
|
||||||
// We know we're using an ortho projection
|
// We know we're using an ortho projection
|
||||||
shaderParameters.texelSizeAtOneMeterWs =
|
shaderParameters.texelSizeAtOneMeterWs = texelSizeWorldSpace(S.upperLeft(), shadowMapInfo.shadowDimension);
|
||||||
texelSizeWorldSpace(St.upperLeft(), shadowMapInfo.shadowDimension);
|
|
||||||
}
|
}
|
||||||
if (!shadowMapInfo.vsm) {
|
if (!shadowMapInfo.vsm) {
|
||||||
shaderParameters.lightSpace = St;
|
shaderParameters.lightSpace = St;
|
||||||
@@ -1117,84 +1117,67 @@ bool ShadowMap::intersectSegmentWithPlanarQuad(float3& UTILS_RESTRICT p,
|
|||||||
return hit;
|
return hit;
|
||||||
}
|
}
|
||||||
|
|
||||||
float ShadowMap::texelSizeWorldSpace(const mat3f& worldToShadowTexture,
|
float2 ShadowMap::texelSizeWorldSpace(const mat3f& clipFromWorld, uint16_t shadowDimension) noexcept {
|
||||||
uint16_t shadowDimension) noexcept {
|
|
||||||
// The Jacobian of the transformation from texture-to-world is the matrix itself for
|
// The Jacobian of the transformation from texture-to-world is the matrix itself for
|
||||||
// orthographic projections. We just need to inverse worldToShadowTexture,
|
// orthographic projections. We just need to inverse shadowMapFromWorld,
|
||||||
// which is guaranteed to be orthographic.
|
// which is guaranteed to be orthographic.
|
||||||
// The two first columns give us how a texel maps in world-space.
|
// The two first columns give us how a texel maps in world-space.
|
||||||
const float ures = 1.0f / float(shadowDimension);
|
float const oneTexel = 2.0f / float(shadowDimension);
|
||||||
const float vres = 1.0f / float(shadowDimension);
|
mat3f const worldFromClip(inverse(clipFromWorld));
|
||||||
const mat3f shadowTextureToWorld(inverse(worldToShadowTexture));
|
float3 const Jx = worldFromClip[0];
|
||||||
const float3 Jx = shadowTextureToWorld[0];
|
float3 const Jy = worldFromClip[1];
|
||||||
const float3 Jy = shadowTextureToWorld[1];
|
float2 const s = float2{ length(Jx), length(Jy) } * oneTexel;
|
||||||
const float s = std::max(length(Jx) * ures, length(Jy) * vres);
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
float ShadowMap::texelSizeWorldSpace(const mat4f& Wp, const mat4f& MbMtF,
|
/**
|
||||||
uint16_t shadowDimension) noexcept {
|
* Calculates the Jacobian matrix J = ∂(u,v,d)/∂(x,y,z) for a 4x4 perspective projection.
|
||||||
// Here we compute the Jacobian of inverse(MbMtF * Wp).
|
*
|
||||||
// The expression below has been computed with Mathematica. However, it's not very hard,
|
* Given a view-space point P = [x, y, z, 1]^T and a projection matrix M,
|
||||||
// albeit error-prone, to do it by hand because MbMtF is a linear transform.
|
* the projected homogeneous coordinates are [X, Y, Z, W]^T = M * P.
|
||||||
// So we really only need to calculate the Jacobian of inverse(Wp) at inverse(MbMtF).
|
* The resulting NDC coordinates are u = X/W, v = Y/W, and depth d = Z/W.
|
||||||
//
|
*
|
||||||
// Because we're only interested in the length of the columns of the Jacobian, we can use
|
* To find the Jacobian on the CPU, we apply the quotient rule to each component:
|
||||||
// Mb * Mt * F * Wp instead of the full expression Mb * Mt * F * Wp * Wv * L * Mp * Mv,
|
* ∂(X/W) / ∂xi = ( (∂X/∂xi) * W - X * (∂W/∂xi) ) / W^2
|
||||||
// because Wv * L * Mp * Mv is a rigid transform, which doesn't affect the length of
|
*
|
||||||
// the Jacobian's column vectors.
|
* In matrix form, this can be expressed as:
|
||||||
|
* J = (1/W) * [ M_sub - (1/W) * (T ⊗ w_grad) ]
|
||||||
|
*
|
||||||
|
* Where:
|
||||||
|
* - W: The homogeneous w-component after projection (usually -z for standard mats).
|
||||||
|
* - M_sub: The top-left 3x3 submatrix of M.
|
||||||
|
* - T: The column vector [X, Y, Z]^T (pre-perspective divide).
|
||||||
|
* - w_grad: The row vector [m30, m31, m32] (the first three elements of M's last row).
|
||||||
|
* - ⊗: The outer product, resulting in a 3x3 matrix.
|
||||||
|
*
|
||||||
|
* This Jacobian describes the local "stretch" of the projection. For LiSPSM or
|
||||||
|
* shadow mapping, the inverse Jacobian J^-1 provides the world-space footprint
|
||||||
|
* of a shadow texel, which is essential for calculating an accurate,
|
||||||
|
* non-constant depth bias to eliminate shadow acne.
|
||||||
|
*
|
||||||
|
* @param M The 4x4 projection matrix.
|
||||||
|
* @param p The 3D point in view-space where the Jacobian is evaluated.
|
||||||
|
* @return A 3x3 matrix representing the partial derivatives of (u,v,d) w.r.t (x,y,z).
|
||||||
|
*/
|
||||||
|
static mat3f jacobian(mat4f const& M, float3 const& p) noexcept {
|
||||||
|
float4 const T = M * p;
|
||||||
|
mat3f const M_sub = M.upperLeft();
|
||||||
|
float3 const w_grad = { M[0].w, M[1].w, M[2].w };
|
||||||
|
mat3f const t_cross_w{
|
||||||
|
w_grad.x * T.xyz,
|
||||||
|
w_grad.y * T.xyz,
|
||||||
|
w_grad.z * T.xyz
|
||||||
|
};
|
||||||
|
return (M_sub - t_cross_w / T.w) / T.w;
|
||||||
|
}
|
||||||
|
|
||||||
|
float2 ShadowMap::texelSizeWorldSpace(mat4f const& S, uint16_t const shadowDimension) noexcept {
|
||||||
// The Jacobian is not constant, so we evaluate it in the center of the shadow-map texture.
|
// The Jacobian is not constant, so we evaluate it in the center of the shadow-map texture.
|
||||||
// It might be better to do this computation in the vertex shader.
|
// It might be better to do this computation in the vertex shader.
|
||||||
float3 const p = { 0.5f, 0.5f, 0.0f };
|
float3 const p = { 0.0f, 0.0f, 0.0f }; // clip-space
|
||||||
|
float const oneTexel = 2.0f / float(shadowDimension);
|
||||||
const float ures = 1.0f / float(shadowDimension);
|
mat3f const J = jacobian(inverse(S), p);
|
||||||
const float vres = 1.0f / float(shadowDimension);
|
float2 const s = float2{ length(J[0]), length(J[1]) } * oneTexel;
|
||||||
const float dres = 1.0f / 65536.0f;
|
|
||||||
|
|
||||||
constexpr bool JACOBIAN_ESTIMATE = false;
|
|
||||||
if constexpr (JACOBIAN_ESTIMATE) {
|
|
||||||
// This estimates the Jacobian -- this is a lot heavier. This is mostly for reference
|
|
||||||
// and testing.
|
|
||||||
const mat4f Si(inverse(MbMtF * Wp));
|
|
||||||
const float3 p0 = mat4f::project(Si, p);
|
|
||||||
const float3 p1 = mat4f::project(Si, p + float3{ 1, 0, 0 } * ures);
|
|
||||||
const float3 p2 = mat4f::project(Si, p + float3{ 0, 1, 0 } * vres);
|
|
||||||
const float3 p3 = mat4f::project(Si, p + float3{ 0, 0, 1 } * dres);
|
|
||||||
const float3 Jx = p1 - p0;
|
|
||||||
const float3 Jy = p2 - p0;
|
|
||||||
const float3 UTILS_UNUSED Jz = p3 - p0;
|
|
||||||
const float s = std::max(length(Jx), length(Jy));
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
const float n = Wp[0][0];
|
|
||||||
const float A = Wp[1][1];
|
|
||||||
const float B = Wp[3][1];
|
|
||||||
const float sx = MbMtF[0][0];
|
|
||||||
const float sy = MbMtF[1][1];
|
|
||||||
const float sz = MbMtF[2][2];
|
|
||||||
const float ox = MbMtF[3][0];
|
|
||||||
const float oy = MbMtF[3][1];
|
|
||||||
const float oz = MbMtF[3][2];
|
|
||||||
|
|
||||||
const float X = p.x - ox;
|
|
||||||
const float Y = p.y - oy;
|
|
||||||
const float Z = p.z - oz;
|
|
||||||
|
|
||||||
const float dz = A * sy - Y;
|
|
||||||
const float nsxsz = n * sx * sz;
|
|
||||||
const float j = -(B * sy) / (nsxsz * dz * dz);
|
|
||||||
const mat3f J(mat3f::row_major_init{
|
|
||||||
j * dz * sz, j * X * sz, 0.0f,
|
|
||||||
0.0f, j * nsxsz, 0.0f,
|
|
||||||
0.0f, j * Z * sx, j * dz * sx
|
|
||||||
});
|
|
||||||
|
|
||||||
float3 const Jx = J[0] * ures;
|
|
||||||
float3 const Jy = J[1] * vres;
|
|
||||||
UTILS_UNUSED float3 const Jz = J[2] * dres;
|
|
||||||
const float s = std::max(length(Jx), length(Jy));
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ public:
|
|||||||
math::mat4f lightSpace{};
|
math::mat4f lightSpace{};
|
||||||
math::float4 lightFromWorldZ{};
|
math::float4 lightFromWorldZ{};
|
||||||
math::float4 scissorNormalized{};
|
math::float4 scissorNormalized{};
|
||||||
float texelSizeAtOneMeterWs{};
|
math::float2 texelSizeAtOneMeterWs{};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Call once per frame if the light, scene (or visible layers) or camera changes.
|
// Call once per frame if the light, scene (or visible layers) or camera changes.
|
||||||
@@ -319,11 +319,8 @@ private:
|
|||||||
|
|
||||||
math::float4 getClampToEdgeCoords(ShadowMapInfo const& shadowMapInfo) const noexcept;
|
math::float4 getClampToEdgeCoords(ShadowMapInfo const& shadowMapInfo) const noexcept;
|
||||||
|
|
||||||
static float texelSizeWorldSpace(const math::mat3f& worldToShadowTexture,
|
static math::float2 texelSizeWorldSpace(const math::mat3f& clipFromWorld, uint16_t shadowDimension) noexcept;
|
||||||
uint16_t shadowDimension) noexcept;
|
static math::float2 texelSizeWorldSpace(const math::mat4f& clipFromWorld, uint16_t shadowDimension) noexcept;
|
||||||
|
|
||||||
static float texelSizeWorldSpace(const math::mat4f& W, const math::mat4f& MbMtF,
|
|
||||||
uint16_t shadowDimension) noexcept;
|
|
||||||
|
|
||||||
static constexpr Segment sBoxSegments[12] = {
|
static constexpr Segment sBoxSegments[12] = {
|
||||||
{ 0, 1 }, { 1, 3 }, { 3, 2 }, { 2, 0 },
|
{ 0, 1 }, { 1, 3 }, { 3, 2 }, { 2, 0 },
|
||||||
|
|||||||
@@ -742,17 +742,16 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
|||||||
|
|
||||||
// Texel size is constant for directional light (although that's not true when LISPSM
|
// Texel size is constant for directional light (although that's not true when LISPSM
|
||||||
// is used, but in that case we're pretending it is).
|
// is used, but in that case we're pretending it is).
|
||||||
const float wsTexelSize = shaderParameters.texelSizeAtOneMeterWs;
|
float2 const wsTexelSize = shaderParameters.texelSizeAtOneMeterWs;
|
||||||
|
|
||||||
auto& s = mShadowUb.edit();
|
auto& s = mShadowUb.edit();
|
||||||
s.shadows[shadowIndex].layer = shadowMap.getLayer();
|
s.shadows[shadowIndex].layer = shadowMap.getLayer();
|
||||||
s.shadows[shadowIndex].lightFromWorldMatrix = shaderParameters.lightSpace;
|
s.shadows[shadowIndex].lightFromWorldMatrix = shaderParameters.lightSpace;
|
||||||
s.shadows[shadowIndex].scissorNormalized = shaderParameters.scissorNormalized;
|
s.shadows[shadowIndex].scissorNormalized = shaderParameters.scissorNormalized;
|
||||||
s.shadows[shadowIndex].normalBias = normalBias * wsTexelSize;
|
s.shadows[shadowIndex].normalBias = wsTexelSize * normalBias;
|
||||||
s.shadows[shadowIndex].texelSizeAtOneMeter = wsTexelSize;
|
|
||||||
s.shadows[shadowIndex].elvsm = options.vsm.elvsm;
|
s.shadows[shadowIndex].elvsm = options.vsm.elvsm;
|
||||||
s.shadows[shadowIndex].bulbRadiusLs =
|
s.shadows[shadowIndex].bulbRadiusLs =
|
||||||
mSoftShadowOptions.penumbraScale * options.shadowBulbRadius / wsTexelSize;
|
mSoftShadowOptions.penumbraScale * options.shadowBulbRadius / length(wsTexelSize);
|
||||||
|
|
||||||
shadowTechnique |= ShadowTechnique::SHADOW_MAP;
|
shadowTechnique |= ShadowTechnique::SHADOW_MAP;
|
||||||
cascadeHasVisibleShadows |= 0x1u << i;
|
cascadeHasVisibleShadows |= 0x1u << i;
|
||||||
@@ -833,7 +832,7 @@ void ShadowMapManager::prepareSpotShadowMap(ShadowMap& shadowMap, FEngine& engin
|
|||||||
// and if we need to generate it, update all the UBO data
|
// and if we need to generate it, update all the UBO data
|
||||||
if (shadowMap.hasVisibleShadows()) {
|
if (shadowMap.hasVisibleShadows()) {
|
||||||
const size_t shadowIndex = shadowMap.getShadowIndex();
|
const size_t shadowIndex = shadowMap.getShadowIndex();
|
||||||
const float wsTexelSizeAtOneMeter = shaderParameters.texelSizeAtOneMeterWs;
|
const float2 wsTexelSizeAtOneMeter = shaderParameters.texelSizeAtOneMeterWs;
|
||||||
// note: normalBias is set to zero for VSM
|
// note: normalBias is set to zero for VSM
|
||||||
const float normalBias = shadowMapInfo.vsm ? 0.0f : options->normalBias;
|
const float normalBias = shadowMapInfo.vsm ? 0.0f : options->normalBias;
|
||||||
|
|
||||||
@@ -845,12 +844,11 @@ void ShadowMapManager::prepareSpotShadowMap(ShadowMap& shadowMap, FEngine& engin
|
|||||||
s.shadows[shadowIndex].scissorNormalized = shaderParameters.scissorNormalized;
|
s.shadows[shadowIndex].scissorNormalized = shaderParameters.scissorNormalized;
|
||||||
s.shadows[shadowIndex].normalBias = normalBias * wsTexelSizeAtOneMeter;
|
s.shadows[shadowIndex].normalBias = normalBias * wsTexelSizeAtOneMeter;
|
||||||
s.shadows[shadowIndex].lightFromWorldZ = shaderParameters.lightFromWorldZ;
|
s.shadows[shadowIndex].lightFromWorldZ = shaderParameters.lightFromWorldZ;
|
||||||
s.shadows[shadowIndex].texelSizeAtOneMeter = wsTexelSizeAtOneMeter;
|
|
||||||
s.shadows[shadowIndex].nearOverFarMinusNear = float(n / (f - n));
|
s.shadows[shadowIndex].nearOverFarMinusNear = float(n / (f - n));
|
||||||
s.shadows[shadowIndex].elvsm = options->vsm.elvsm;
|
s.shadows[shadowIndex].elvsm = options->vsm.elvsm;
|
||||||
s.shadows[shadowIndex].bulbRadiusLs =
|
s.shadows[shadowIndex].bulbRadiusLs =
|
||||||
mSoftShadowOptions.penumbraScale * options->shadowBulbRadius
|
mSoftShadowOptions.penumbraScale * options->shadowBulbRadius
|
||||||
/ wsTexelSizeAtOneMeter;
|
/ length(wsTexelSizeAtOneMeter);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -926,7 +924,7 @@ void ShadowMapManager::preparePointShadowMap(ShadowMap& shadowMap,
|
|||||||
// and if we need to generate it, update all the UBO data
|
// and if we need to generate it, update all the UBO data
|
||||||
if (shadowMap.hasVisibleShadows()) {
|
if (shadowMap.hasVisibleShadows()) {
|
||||||
const size_t shadowIndex = shadowMap.getShadowIndex();
|
const size_t shadowIndex = shadowMap.getShadowIndex();
|
||||||
const float wsTexelSizeAtOneMeter = shaderParameters.texelSizeAtOneMeterWs;
|
const float2 wsTexelSizeAtOneMeter = shaderParameters.texelSizeAtOneMeterWs;
|
||||||
// note: normalBias is set to zero for VSM
|
// note: normalBias is set to zero for VSM
|
||||||
const float normalBias = shadowMapInfo.vsm ? 0.0f : options->normalBias;
|
const float normalBias = shadowMapInfo.vsm ? 0.0f : options->normalBias;
|
||||||
|
|
||||||
@@ -938,12 +936,11 @@ void ShadowMapManager::preparePointShadowMap(ShadowMap& shadowMap,
|
|||||||
s.shadows[shadowIndex].scissorNormalized = shaderParameters.scissorNormalized;
|
s.shadows[shadowIndex].scissorNormalized = shaderParameters.scissorNormalized;
|
||||||
s.shadows[shadowIndex].normalBias = normalBias * wsTexelSizeAtOneMeter;
|
s.shadows[shadowIndex].normalBias = normalBias * wsTexelSizeAtOneMeter;
|
||||||
s.shadows[shadowIndex].lightFromWorldZ = shaderParameters.lightFromWorldZ;
|
s.shadows[shadowIndex].lightFromWorldZ = shaderParameters.lightFromWorldZ;
|
||||||
s.shadows[shadowIndex].texelSizeAtOneMeter = wsTexelSizeAtOneMeter;
|
|
||||||
s.shadows[shadowIndex].nearOverFarMinusNear = float(n / (f - n));
|
s.shadows[shadowIndex].nearOverFarMinusNear = float(n / (f - n));
|
||||||
s.shadows[shadowIndex].elvsm = options->vsm.elvsm;
|
s.shadows[shadowIndex].elvsm = options->vsm.elvsm;
|
||||||
s.shadows[shadowIndex].bulbRadiusLs =
|
s.shadows[shadowIndex].bulbRadiusLs =
|
||||||
mSoftShadowOptions.penumbraScale * options->shadowBulbRadius
|
mSoftShadowOptions.penumbraScale * options->shadowBulbRadius
|
||||||
/ wsTexelSizeAtOneMeter;
|
/ length(wsTexelSizeAtOneMeter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -798,6 +798,12 @@ void FEngine::submitFrame() {
|
|||||||
void FEngine::flush() {
|
void FEngine::flush() {
|
||||||
// flush the command buffer
|
// flush the command buffer
|
||||||
flushCommandBuffer(mCommandBufferQueue);
|
flushCommandBuffer(mCommandBufferQueue);
|
||||||
|
|
||||||
|
// In single-threaded mode, we have to call execute() to drain the command
|
||||||
|
// buffer to really free up space
|
||||||
|
if constexpr (!UTILS_HAS_THREADING) {
|
||||||
|
execute();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FEngine::flushAndWait() {
|
void FEngine::flushAndWait() {
|
||||||
|
|||||||
@@ -257,9 +257,9 @@ filament::DescriptorSetLayout const& FMaterial::getPerViewDescriptorSetLayout(
|
|||||||
}
|
}
|
||||||
// mDefinition.perViewDescriptorSetLayout{Vsm} is already resolved for MaterialDomain
|
// mDefinition.perViewDescriptorSetLayout{Vsm} is already resolved for MaterialDomain
|
||||||
if (useVsmDescriptorSetLayout) {
|
if (useVsmDescriptorSetLayout) {
|
||||||
return mDefinition.perViewDescriptorSetLayoutVsm;
|
return mDefinition.perViewDescriptorSetLayoutS2d;
|
||||||
}
|
}
|
||||||
return mDefinition.perViewDescriptorSetLayout;
|
return mDefinition.perViewDescriptorSetLayoutPcf;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FMaterial::compile(CompilerPriorityQueue const priority,
|
void FMaterial::compile(CompilerPriorityQueue const priority,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user