Compare commits

..
Author SHA1 Message Date
Eliza Velasquez 6b0dc7babe wip 2026-03-09 16:34:29 +09:00
Eliza Velasquez c4ea6cb1bf android: fix build errors 2026-03-06 16:39:27 +09:00
Eliza Velasquez 87ed485b04 java: add bindings for MaterialInstance::compile() 2026-03-06 16:11:45 +09:00
Eliza Velasquez 68ae023108 engine: add MaterialInstance::compile() 2026-03-06 15:31:46 +09:00
Eliza Velasquez 474fe0c96c engine: override spec constants on instance-basis
This commit adds `getConstant()` and `setConstant()` methods to
`MaterialInstance`.

When a material instance is created, it doesn't create or manage any spec
constants on its own, but instead relies on `Material`. The client can change
constants at any time, after which the necessary resources are copied and
maintained by `MaterialInstance`.

As part of this change, most internal rendering methods which depended on
`Material` have been refactored to instead depend on `MaterialInstance`.
2026-03-05 15:51:55 +09:00
211 changed files with 2131 additions and 2998 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ runs:
uses: actions/cache@v4
with:
path: mesa
key: ${{ runner.os }}-${{ runner.arch }}-mesa-deps-${{ env.GITHUB_MESA_VERSION }}
key: ${{ runner.os }}-mesa-deps-${{ env.GITHUB_MESA_VERSION }}
- name: Get Mesa
run: |
bash build/common/get-mesa.sh
+1 -2
View File
@@ -9,7 +9,7 @@ runs:
id: cache-vulkan-sdk
with:
path: ~/VulkanSDK
key: vulkansdk-${{ env.GITHUB_VULKANSDK_VERSION }}-2-${{ runner.os }}-${{ runner.arch }}
key: vulkansdk-${{ env.GITHUB_VULKANSDK_VERSION }}-2-${{ runner.os }}
- name: Download Vulkan SDK
if: steps.cache-vulkan-sdk.outputs.cache-hit != 'true'
run: |
@@ -23,7 +23,6 @@ runs:
unpack_vulkan_installer
shell: bash
- name: Run Vulkan SDK setup
if: runner.os != 'Linux'
run: |
pushd .
cd ~/VulkanSDK/${GITHUB_VULKANSDK_VERSION}
+12 -1
View File
@@ -11,13 +11,24 @@ runs:
echo "set man-db/auto-update false" | sudo debconf-communicate
sudo dpkg-reconfigure man-db
# Install ninja
source ./build/common/get-ninja.sh
# Install CMake
mkdir -p cmake
cd cmake
sudo wget https://github.com/Kitware/CMake/releases/download/v$GITHUB_CMAKE_VERSION/cmake-$GITHUB_CMAKE_VERSION-Linux-x86_64.sh
sudo chmod +x ./cmake-$GITHUB_CMAKE_VERSION-Linux-x86_64.sh
sudo ./cmake-$GITHUB_CMAKE_VERSION-Linux-x86_64.sh --skip-license > /dev/null
sudo update-alternatives --install /usr/bin/cmake cmake $(pwd)/bin/cmake 1000 --force
cd ..
sudo wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo apt-get update
sudo apt-get install clang-$GITHUB_CLANG_VERSION libc++-$GITHUB_CLANG_VERSION-dev libc++abi-$GITHUB_CLANG_VERSION-dev
sudo apt-get install mesa-common-dev libxi-dev libxxf86vm-dev
sudo apt-get install cmake ninja-build
# For dawn
sudo apt-get install libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libx11-xcb-dev
@@ -8,15 +8,9 @@ runs:
- uses: ./.github/actions/get-mesa
- uses: ./.github/actions/get-vulkan-sdk
- name: Prerequisites
if: runner.os == 'macOS'
run: |
# Must have at least clang-16 for a webgpu/dawn build.
sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer
# this enforces the gltf_viewer build to use apple clang as oppose the brew clang used for get-mesa
echo "CC=/usr/bin/clang" >> $GITHUB_ENV
echo "CXX=/usr/bin/clang++" >> $GITHUB_ENV
echo "/usr/local/bin" >> $GITHUB_PATH
echo "/usr/bin" >> $GITHUB_PATH
shell: bash
- name: Generate images
run: |
@@ -1,4 +1,4 @@
FROM python:3.12.13
FROM python:3.10.4
RUN pip3 install pygithub==1.55
+2 -2
View File
@@ -1,4 +1,4 @@
name: 'Web Prerequisites'
name: 'Web Preqrequisites'
runs:
using: "composite"
steps:
@@ -8,7 +8,7 @@ runs:
uses: actions/cache@v4 # Use a specific version
with:
path: emsdk
key: ${{ runner.os }}-${{ runner.arch }}-emsdk-${{ env.GITHUB_EMSDK_VERSION }}
key: ${{ runner.os }}-emsdk-${{ env.GITHUB_EMSDK_VERSION }}
- name: Install Web Prerequisites
shell: bash
run: |
+2 -2
View File
@@ -10,7 +10,7 @@ jobs:
# a branch on filament-assets.
update-renderdiff-goldens:
name: update-renderdiff-goldens
runs-on: 'macos-14'
runs-on: 'macos-14-xlarge'
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -90,7 +90,7 @@ jobs:
# filament-assets
update-sizeguard:
name: update-sizeguard
runs-on: 'ubuntu-24.04-4core'
runs-on: 'ubuntu-24.04-16core'
steps:
- uses: actions/checkout@v4.1.6
with:
+16 -8
View File
@@ -12,7 +12,8 @@ jobs:
name: build-android
# We intentially use a larger runner here to enable larger disk space
# (standard linux runner will fail on disk space and faster build time).
runs-on: 'ubuntu-24.04-4core'
runs-on: 'ubuntu-24.04-16core'
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -25,7 +26,8 @@ jobs:
build-ios:
name: build-ios
runs-on: 'macos-14'
runs-on: macos-14-xlarge
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -44,7 +46,8 @@ jobs:
build-linux:
name: build-linux
runs-on: 'arm-ubuntu-24.04-16core'
runs-on: 'ubuntu-24.04-16core'
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -55,12 +58,13 @@ jobs:
cd build/linux && printf "y" | ./build.sh continuous
- uses: actions/upload-artifact@v4
with:
name: filament-arm-linux
path: out/filament-release-arm-linux.tgz
name: filament-linux
path: out/filament-release-linux.tgz
build-mac:
name: build-mac
runs-on: 'macos-14'
runs-on: macos-14-xlarge
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -73,10 +77,14 @@ jobs:
with:
name: filament-mac
path: out/filament-release-darwin.tgz
- name: Check public headers
run: |
test/check-headers/test.sh out/release/filament/include
build-web:
name: build-web
runs-on: 'arm-ubuntu-24.04-16core'
runs-on: 'ubuntu-24.04-16core'
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -93,7 +101,7 @@ jobs:
build-windows:
name: build-windows
runs-on: 'windows-2022'
runs-on: windows-2022-32core
steps:
- uses: actions/checkout@v4.1.6
+17 -178
View File
@@ -8,54 +8,10 @@ on:
branches:
- main
# This will cancel in-flight runs when there is an update to a PR
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# The conditional 'if' on each job is meant to skip presubmit jobs when a commit is pushed to main
# and that commit is cryptographically "verified". Typically, a verified commit (like a GitHub UI
# squash-and-merge) has already passed through presubmit during the Pull Request phase.
# The conditional explicitly checks:
# 1. always() && !cancelled(): Ensures the job runs even if 'check-verification' is skipped, but
# aborts if the workflow was manually cancelled.
# 2. github.event_name == 'pull_request': Presubmits should always run normally on PRs.
# 3. github.event_name == 'push' && ...: If it's a push to main, it only runs if the
# 'check-verification' job confirmed the commit is NOT verified.
jobs:
check-verification:
if: github.event_name == 'push'
runs-on: ubuntu-latest
outputs:
verified: ${{ steps.check.outputs.verified }}
steps:
- name: Check commit verification
id: check
uses: actions/github-script@v7
with:
script: |
const { data } = await github.rest.repos.getCommit({
owner: context.repo.owner,
repo: context.repo.repo,
ref: context.sha
});
core.setOutput('verified', data.commit.verification.verified);
build-desktop-mac:
name: build-mac
runs-on: 'macos-14-xlarge'
needs: [check-verification]
if: >
always() && !cancelled() &&
(
github.event_name == 'pull_request' ||
(
github.event_name == 'push' &&
needs.check-verification.result == 'success' &&
needs.check-verification.outputs.verified == 'false'
)
)
runs-on: macos-14-xlarge
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -64,30 +20,13 @@ jobs:
- name: Run build script
run: |
cd build/mac && printf "y" | ./build.sh presubmit-with-test
- name: Test - material parser
- name: Test material parser
run: |
out/cmake-release/filament/test/test_material_parser
- name: Test - public headers
run: |
# out/cmake-release should have the artifacts ready for installation. Here we install it
# to test the public headers
ninja -C out/cmake-release install
test/check-headers/test.sh out/release/filament/include
build-desktop-linux:
name: build-linux
runs-on: 'arm-ubuntu-24.04-16core'
needs: [check-verification]
if: >
always() && !cancelled() &&
(
github.event_name == 'pull_request' ||
(
github.event_name == 'push' &&
needs.check-verification.result == 'success' &&
needs.check-verification.outputs.verified == 'false'
)
)
runs-on: 'ubuntu-24.04-16core'
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -96,24 +35,13 @@ jobs:
- name: Run build script
run: |
cd build/linux && printf "y" | ./build.sh presubmit
- name: Test - material parser
- name: Test material parser
run: |
out/cmake-release/filament/test/test_material_parser
build-windows:
name: build-windows
runs-on: 'windows-2022'
needs: [check-verification]
if: >
always() && !cancelled() &&
(
github.event_name == 'pull_request' ||
(
github.event_name == 'push' &&
needs.check-verification.result == 'success' &&
needs.check-verification.outputs.verified == 'false'
)
)
runs-on: windows-2022-32core
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -125,18 +53,7 @@ jobs:
build-android:
name: build-android
runs-on: 'ubuntu-24.04-4core'
needs: [check-verification]
if: >
always() && !cancelled() &&
(
github.event_name == 'pull_request' ||
(
github.event_name == 'push' &&
needs.check-verification.result == 'success' &&
needs.check-verification.outputs.verified == 'false'
)
)
runs-on: 'ubuntu-24.04-16core'
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -163,18 +80,7 @@ jobs:
build-ios:
name: build-iOS
runs-on: 'macos-14-xlarge'
needs: [check-verification]
if: >
always() && !cancelled() &&
(
github.event_name == 'pull_request' ||
(
github.event_name == 'push' &&
needs.check-verification.result == 'success' &&
needs.check-verification.outputs.verified == 'false'
)
)
runs-on: macos-14-xlarge
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -189,18 +95,7 @@ jobs:
build-web:
name: build-web
runs-on: 'arm-ubuntu-24.04-16core'
needs: [check-verification]
if: >
always() && !cancelled() &&
(
github.event_name == 'pull_request' ||
(
github.event_name == 'push' &&
needs.check-verification.result == 'success' &&
needs.check-verification.outputs.verified == 'false'
)
)
runs-on: 'ubuntu-24.04-16core'
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -213,18 +108,7 @@ jobs:
validate-docs:
name: validate-docs
runs-on: 'ubuntu-24.04'
needs: [check-verification]
if: >
always() && !cancelled() &&
(
github.event_name == 'pull_request' ||
(
github.event_name == 'push' &&
needs.check-verification.result == 'success' &&
needs.check-verification.outputs.verified == 'false'
)
)
runs-on: 'ubuntu-24.04-4core'
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -237,18 +121,7 @@ jobs:
test-renderdiff:
name: test-renderdiff
runs-on: 'macos-14-xlarge'
needs: [check-verification]
if: >
always() && !cancelled() &&
(
github.event_name == 'pull_request' ||
(
github.event_name == 'push' &&
needs.check-verification.result == 'success' &&
needs.check-verification.outputs.verified == 'false'
)
)
runs-on: macos-14-xlarge
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -266,10 +139,10 @@ jobs:
echo "accept=false" >> "$GITHUB_OUTPUT"
fi
shell: bash
- name: Renderdiff generate
- name: Renderdiff Generate
if: steps.check_accept.outputs.accept != 'true'
uses: ./.github/actions/renderdiff-generate
- name: Compare rendered images
- name: Render and compare
if: steps.check_accept.outputs.accept != 'true'
id: render_compare
env:
@@ -301,13 +174,12 @@ jobs:
echo "$DELIMITER" >> "$GITHUB_OUTPUT"
fi
shell: bash
- name: Upload artifacts
uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v4
if: steps.check_accept.outputs.accept != 'true'
with:
name: presubmit-renderdiff-result
path: ./out/renderdiff
- name: Check results
- name: Compare result
if: steps.check_accept.outputs.accept != 'true'
run: |
ERROR_STR="${{ steps.render_compare.outputs.err }}"
@@ -316,20 +188,9 @@ jobs:
exit 1
fi
build-wgsl-webgpu:
validate-wgsl-webgpu:
name: validate-wgsl-webgpu
runs-on: 'arm-ubuntu-24.04-16core'
needs: [check-verification]
if: >
always() && !cancelled() &&
(
github.event_name == 'pull_request' ||
(
github.event_name == 'push' &&
needs.check-verification.result == 'success' &&
needs.check-verification.outputs.verified == 'false'
)
)
runs-on: 'ubuntu-24.04-16core'
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -343,17 +204,6 @@ jobs:
test-code-correctness:
name: test-code-correctness
runs-on: 'macos-14-xlarge'
needs: [check-verification]
if: >
always() && !cancelled() &&
(
github.event_name == 'pull_request' ||
(
github.event_name == 'push' &&
needs.check-verification.result == 'success' &&
needs.check-verification.outputs.verified == 'false'
)
)
steps:
- uses: actions/checkout@v4.1.6
with:
@@ -374,18 +224,7 @@ jobs:
test-backend:
name: test-backend
runs-on: 'macos-14-xlarge'
needs: [check-verification]
if: >
always() && !cancelled() &&
(
github.event_name == 'pull_request' ||
(
github.event_name == 'push' &&
needs.check-verification.result == 'success' &&
needs.check-verification.outputs.verified == 'false'
)
)
runs-on: macos-14-xlarge
steps:
- uses: actions/checkout@v4.1.6
with:
+1 -34
View File
@@ -63,39 +63,6 @@ jobs:
const globber = await glob.create('out/*.tgz');
await upload({ github, context }, await globber.glob(), TAG);
build-arm-linux:
name: build-arm-linux
runs-on: 'arm-ubuntu-24.04-16core'
if: github.event_name == 'release' || github.event.inputs.platform == 'desktop'
steps:
- name: Decide Git ref
id: git_ref
run: |
REF=${RELEASE_TAG:-${GITHUB_REF}}
TAG=${REF##*/}
echo "ref=${REF}" >> $GITHUB_OUTPUT
echo "tag=${TAG}" >> $GITHUB_OUTPUT
- uses: actions/checkout@v4.1.6
with:
ref: ${{ steps.git_ref.outputs.ref }}
- uses: ./.github/actions/linux-prereq
- name: Run build script
env:
TAG: ${{ steps.git_ref.outputs.tag }}
run: |
cd build/linux && printf "y" | ./build.sh release
cd ../..
mv out/filament-release-linux.tgz out/filament-${TAG}-arm-linux.tgz
- uses: actions/github-script@v6
env:
TAG: ${{ steps.git_ref.outputs.tag }}
with:
script: |
const upload = require('./build/common/upload-release-assets');
const { TAG } = process.env;
const globber = await glob.create('out/*.tgz');
await upload({ github, context }, await globber.glob(), TAG);
build-mac:
name: build-mac
runs-on: macos-14-xlarge
@@ -132,7 +99,7 @@ jobs:
build-web:
name: build-web
runs-on: 'arm-ubuntu-24.04-16core'
runs-on: 'ubuntu-24.04-16core'
if: github.event_name == 'release' || github.event.inputs.platform == 'web'
steps:
+7 -4
View File
@@ -11,11 +11,14 @@ jobs:
name: Verify Release Notes
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
# We *only* need to check out the .github/ directory.
# The verify RELEASE_NOTES script uses the GitHub API to verify that RELEASE_NOTES was
# modified.
- name: Check out action
uses: Bhacaz/checkout-files@73e17cfbe8d7e0c6b2672b20cb05a718e20d18d4
with:
fetch-depth: 0
sparse-checkout: |
.github
files: .github
token: ${{ secrets.GITHUB_TOKEN }}
- name: Verify release notes
uses: ./.github/actions/verify-release-notes
with:
+3 -10
View File
@@ -326,13 +326,11 @@ endif()
if (FILAMENT_ENABLE_LTO)
include(CheckIPOSupported)
check_ipo_supported(RESULT IPO_SUPPORT OUTPUT IPO_ERROR)
check_ipo_supported(RESULT IPO_SUPPORT)
if (IPO_SUPPORT)
message(STATUS "IPO / LTO is enabled")
message(STATUS "LTO support is enabled")
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
else()
message(WARNING "IPO / LTO is not supported by this architecture: ${IPO_ERROR}")
endif()
endif()
@@ -869,12 +867,6 @@ endfunction()
# Sub-projects
# ==================================================================================================
include(CheckSymbolExists)
check_symbol_exists(getopt_long "getopt.h" HAS_SYSTEM_GETOPT)
if (NOT HAS_SYSTEM_GETOPT)
add_subdirectory(${EXTERNAL}/getopt)
endif()
# Common to all platforms
add_subdirectory(${EXTERNAL}/libgtest/tnt)
add_subdirectory(${LIBRARIES}/camutils)
@@ -910,6 +902,7 @@ add_subdirectory(${EXTERNAL}/cgltf/tnt)
add_subdirectory(${EXTERNAL}/draco/tnt)
add_subdirectory(${EXTERNAL}/jsmn/tnt)
add_subdirectory(${EXTERNAL}/stb/tnt)
add_subdirectory(${EXTERNAL}/getopt)
add_subdirectory(${EXTERNAL}/perfetto/tnt)
add_subdirectory(${EXTERNAL}/basisu/tnt)
+2
View File
@@ -6,3 +6,5 @@
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut
- engine: add `MaterialInstance::setConstant()` and `MaterialInstance::getConstant()` methods. These allow for per-material instance specialization constant overrides.
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.70.2'
implementation 'com.google.android.filament:filament-android:1.69.5'
}
```
@@ -50,7 +50,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```shell
pod 'Filament', '~> 1.70.2'
pod 'Filament', '~> 1.69.5'
```
## Documentation
-6
View File
@@ -7,12 +7,6 @@ A new header is inserted each time a *tag* is created.
Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.70.2
## v1.70.1
## v1.70.0
- engine: fix crash when using variance shadow maps
+1 -1
View File
@@ -200,7 +200,7 @@ subprojects {
google()
}
if (!name.startsWith("sample") && name != "filament-tools" && name != "gradle-plugin") {
if (!name.startsWith("sample") && name != "filament-tools") {
apply plugin: 'com.android.library'
android {
@@ -25,7 +25,7 @@ configured, the corresponding task will be disabled.
```groovy
plugins {
id 'com.google.android.filament-tools'
id 'filament-plugin'
}
filament {
+21
View File
@@ -0,0 +1,21 @@
plugins {
id 'groovy-gradle-plugin'
}
gradlePlugin {
plugins {
create("filament-plugin") {
id = "filament-plugin"
implementationClass = "com.google.android.filament.gradle.FilamentPlugin"
}
}
}
repositories {
mavenCentral()
gradlePluginPortal()
}
dependencies {
implementation "com.google.gradle:osdetector-gradle-plugin:1.7.3"
}
@@ -60,10 +60,9 @@ class ToolsLocator {
def classifier =
project.extensions.getByType(com.google.gradle.osdetector.OsDetector).classifier
// If com.google.android.filament.tools-dir is set to a non-empty string, we'll use it as
// the tool's base path.
// If com.google.android.filament.tools-dir is set, we'll use it as the tool's base path.
def toolsDirProp = project.providers.gradleProperty("com.google.android.filament.tools-dir")
if (toolsDirProp.isPresent() && !toolsDirProp.get().trim().isEmpty()) {
if (toolsDirProp.isPresent()) {
def toolsDir = toolsDirProp.get()
def path = OperatingSystem.current().isWindows() ?
"${toolsDir}/bin/${name}.exe" :
@@ -20,6 +20,8 @@
#include <filament/Texture.h>
#include <filament/TextureSampler.h>
#include "common/CallbackUtils.h"
#include <math/mat3.h>
#include <math/mat4.h>
#include <math/vec2.h>
@@ -246,6 +248,69 @@ Java_com_google_android_filament_MaterialInstance_nSetFloatParameterArray(JNIEnv
env->ReleaseStringUTFChars(name_, name);
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_MaterialInstance_nGetConstantBool(JNIEnv *env, jclass,
jlong nativeMaterialInstance, jstring name_) {
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
const char *name = env->GetStringUTFChars(name_, 0);
jboolean result = instance->getConstant<bool>(name);
env->ReleaseStringUTFChars(name_, name);
return result;
}
extern "C"
JNIEXPORT jfloat JNICALL
Java_com_google_android_filament_MaterialInstance_nGetConstantFloat(JNIEnv *env, jclass,
jlong nativeMaterialInstance, jstring name_) {
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
const char *name = env->GetStringUTFChars(name_, 0);
jfloat result = instance->getConstant<float>(name);
env->ReleaseStringUTFChars(name_, name);
return result;
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_google_android_filament_MaterialInstance_nGetConstantInt(JNIEnv *env, jclass,
jlong nativeMaterialInstance, jstring name_) {
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
const char *name = env->GetStringUTFChars(name_, 0);
jint result = instance->getConstant<int32_t>(name);
env->ReleaseStringUTFChars(name_, name);
return result;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_MaterialInstance_nSetConstantBool(JNIEnv *env, jclass,
jlong nativeMaterialInstance, jstring name_, jboolean x) {
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
const char *name = env->GetStringUTFChars(name_, 0);
instance->setConstant<bool>(name, x);
env->ReleaseStringUTFChars(name_, name);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_MaterialInstance_nSetConstantFloat(JNIEnv *env, jclass,
jlong nativeMaterialInstance, jstring name_, jfloat x) {
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
const char *name = env->GetStringUTFChars(name_, 0);
instance->setConstant<float>(name, x);
env->ReleaseStringUTFChars(name_, name);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_MaterialInstance_nSetConstantInt(JNIEnv *env, jclass,
jlong nativeMaterialInstance, jstring name_, jint x) {
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
const char *name = env->GetStringUTFChars(name_, 0);
instance->setConstant<int32_t>(name, x);
env->ReleaseStringUTFChars(name_, name);
}
// defined in TextureSampler.cpp
namespace filament::JniUtils {
TextureSampler from_long(jlong params) noexcept;
@@ -580,3 +645,17 @@ Java_com_google_android_filament_MaterialInstance_nGetTransparencyMode(JNIEnv*,
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
return (jint) instance->getTransparencyMode();
}
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_MaterialInstance_nCompile(JNIEnv *env, jclass clazz,
jlong nativeMaterialInstance, jint priority, jint variants, jobject handler, jobject runnable) {
MaterialInstance* materialInstance = (MaterialInstance*) nativeMaterialInstance;
JniCallback* jniCallback = JniCallback::make(env, handler, runnable);
materialInstance->compile(
(MaterialInstance::CompilerPriorityQueue) priority,
(UserVariantFilterBit) variants,
jniCallback->getHandler(), [jniCallback](MaterialInstance*){
JniCallback::postToJavaAndDestroy(jniCallback);
});
}
@@ -18,6 +18,7 @@ package com.google.android.filament;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Size;
import com.google.android.filament.proguard.UsedByNative;
@@ -142,6 +143,28 @@ public class MaterialInstance {
return mMaterial;
}
/**
* Asynchronously ensures that a subset of this MaterialInstance's variants are compiled.
*
* <p>This function behaves identically to {@link Material#compile}, but takes into account
* the specific constants overridden by {@link #setConstant}.</p>
*
* @param priority Priority of the compile command.
* @param variants Variants to include to the compile command.
* @param handler An {@link java.util.concurrent.Executor Executor}. On Android this can also be a {@link android.os.Handler Handler}.
* @param callback callback called on the main thread when the compilation is done on
* by backend.
*
* @see Material#compile
* @see #setConstant
*/
public void compile(@NonNull Material.CompilerPriorityQueue priority,
int variants,
@Nullable Object handler,
@Nullable Runnable callback) {
nCompile(getNativeObject(), priority.ordinal(), variants, handler, callback);
}
/** @return the name associated with this instance */
@NonNull
public String getName() {
@@ -402,6 +425,69 @@ public class MaterialInstance {
nSetParameterFloat4(getNativeObject(), name, color[0], color[1], color[2], color[3]);
}
/**
* Overrides a specialization constant of this material instance.
*
* @param name The name of the constant as defined in the material.
* @param value The value of the constant.
* @see Material.Builder#constant
*/
public void setConstant(@NonNull String name, boolean value) {
nSetConstantBool(getNativeObject(), name, value);
}
/**
* Overrides a specialization constant of this material instance.
*
* @param name The name of the constant as defined in the material.
* @param value The value of the constant.
* @see Material.Builder#constant
*/
public void setConstant(@NonNull String name, float value) {
nSetConstantFloat(getNativeObject(), name, value);
}
/**
* Overrides a specialization constant of this material instance.
*
* @param name The name of the constant as defined in the material.
* @param value The value of the constant.
* @see Material.Builder#constant
*/
public void setConstant(@NonNull String name, int value) {
nSetConstantInt(getNativeObject(), name, value);
}
/**
* Gets the value of a specialization constant by name.
*
* @param name The name of the constant as defined in the material.
* @return The value of the constant.
*/
public boolean getConstantBoolean(@NonNull String name) {
return nGetConstantBool(getNativeObject(), name);
}
/**
* Gets the value of a specialization constant by name.
*
* @param name The name of the constant as defined in the material.
* @return The value of the constant.
*/
public float getConstantFloat(@NonNull String name) {
return nGetConstantFloat(getNativeObject(), name);
}
/**
* Gets the value of a specialization constant by name.
*
* @param name The name of the constant as defined in the material.
* @return The value of the constant.
*/
public int getConstantInt(@NonNull String name) {
return nGetConstantInt(getNativeObject(), name);
}
/**
* Set-up a custom scissor rectangle; by default it is disabled.
*
@@ -937,6 +1023,17 @@ public class MaterialInstance {
@NonNull String name, int element, @NonNull @Size(min = 1) float[] v,
@IntRange(from = 0) int offset, @IntRange(from = 1) int count);
private static native boolean nGetConstantBool(long nativeMaterialInstance, @NonNull String name);
private static native float nGetConstantFloat(long nativeMaterialInstance, @NonNull String name);
private static native int nGetConstantInt(long nativeMaterialInstance, @NonNull String name);
private static native void nSetConstantBool(long nativeMaterialInstance,
@NonNull String name, boolean x);
private static native void nSetConstantFloat(long nativeMaterialInstance,
@NonNull String name, float x);
private static native void nSetConstantInt(long nativeMaterialInstance,
@NonNull String name, int x);
private static native void nSetParameterTexture(long nativeMaterialInstance,
@NonNull String name, long nativeTexture, long sampler);
@@ -1000,4 +1097,5 @@ public class MaterialInstance {
private static native int nGetDepthFunc(long nativeMaterialInstance);
private static native void nSetTransparencyMode(long nativeMaterialInstance, int mode);
private static native int nGetTransparencyMode(long nativeMaterialInstance);
private static native void nCompile(long nativeMaterialInstance, int priority, int variants, Object handler, Runnable callback);
}
@@ -61,7 +61,6 @@ set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size
add_library(filament-utils-jni SHARED
src/main/cpp/AutomationEngine.cpp
src/main/cpp/Bookmark.cpp
src/main/cpp/DeviceUtils.cpp
src/main/cpp/HDRLoader.cpp
src/main/cpp/IBLPrefilterContext.cpp
src/main/cpp/Utils.cpp
@@ -1,85 +0,0 @@
/*
* Copyright (C) 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <jni.h>
#include <filament/Engine.h>
#include <backend/Platform.h>
#include <utils/CString.h>
#include <algorithm>
#include <array>
using namespace filament;
namespace {
constexpr std::array<backend::Platform::DeviceInfoType, 3> VULKAN_INFO = {
backend::Platform::DeviceInfoType::VULKAN_DEVICE_NAME,
backend::Platform::DeviceInfoType::VULKAN_DRIVER_NAME,
backend::Platform::DeviceInfoType::VULKAN_DRIVER_INFO,
};
constexpr std::array<backend::Platform::DeviceInfoType, 3> GL_INFO = {
backend::Platform::DeviceInfoType::OPENGL_VENDOR,
backend::Platform::DeviceInfoType::OPENGL_RENDERER,
backend::Platform::DeviceInfoType::OPENGL_VERSION,
};
} // namespace
extern "C" JNIEXPORT jstring JNICALL
Java_com_google_android_filament_utils_DeviceUtils_nGetGpuDriverInfo(JNIEnv* env, jclass,
jlong nativeEngine) {
auto emptyStr = [env]() { return env->NewStringUTF(""); };
Engine* engine = (Engine*) nativeEngine;
if (!engine) {
return emptyStr();
}
backend::Platform* platform = engine->getPlatform();
if (!platform) {
return emptyStr();
}
std::array<backend::Platform::DeviceInfoType, 3> infoTypes;
switch (engine->getBackend()) {
case backend::Backend::VULKAN:
infoTypes = VULKAN_INFO;
break;
case backend::Backend::OPENGL:
infoTypes = GL_INFO;
break;
default:
return emptyStr();
}
backend::Driver* driver = const_cast<backend::Driver*>(engine->getDriver());
utils::CString fullInfo;
std::for_each(infoTypes.begin(), infoTypes.end(),
[&](backend::Platform::DeviceInfoType infoType) {
utils::CString const newInfo = platform->getDeviceInfo(infoType, driver);
if (!newInfo.empty()) {
if (!fullInfo.empty()) {
fullInfo += " | ";
}
fullInfo += newInfo.c_str();
}
});
return env->NewStringUTF(fullInfo.c_str());
}
@@ -20,6 +20,8 @@
#include <imagediff/ImageDiff.h>
#include <utils/Log.h>
#include <vector>
using namespace imagediff;
using namespace utils;
@@ -100,48 +102,30 @@ jobject createResult(JNIEnv* env, ImageDiffResult const& result, bool generateDi
if (generateDiff && result.diffImage.getWidth() > 0) {
jclass bitmapClass = env->FindClass("android/graphics/Bitmap");
jmethodID createBitmap = env->GetStaticMethodID(bitmapClass, "createBitmap",
jmethodID createBitmap = env->GetStaticMethodID(bitmapClass, "createBitmap",
"(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
jclass configClass = env->FindClass("android/graphics/Bitmap$Config");
jfieldID argb8888 = env->GetStaticFieldID(configClass, "ARGB_8888", "Landroid/graphics/Bitmap$Config;");
jobject configObj = env->GetStaticObjectField(configClass, argb8888);
uint32_t width = result.diffImage.getWidth();
uint32_t height = result.diffImage.getHeight();
jobject diffBitmap = env->CallStaticObjectMethod(bitmapClass, createBitmap, (jint) width,
(jint) height, configObj);
jobject diffBitmap = env->CallStaticObjectMethod(bitmapClass, createBitmap, (jint)width, (jint)height, configObj);
if (diffBitmap) {
// We need to transport the bit differences accurately to the java side, so set
// premultiplied to false. From the java-side, if the bitmap is used to draw to a
// canvas, then client needs to set premultiplied to true again.
jmethodID setPremultiplied = env->GetMethodID(bitmapClass, "setPremultiplied", "(Z)V");
if (setPremultiplied) {
env->CallVoidMethod(diffBitmap, setPremultiplied, JNI_FALSE);
}
void* diffPixels;
if (AndroidBitmap_lockPixels(env, diffBitmap, &diffPixels) == 0) {
AndroidBitmapInfo info;
AndroidBitmap_getInfo(env, diffBitmap, &info);
float const* src = result.diffImage.getPixelRef();
uint8_t* dst = (uint8_t*) diffPixels;
uint32_t const channels = result.diffImage.getChannels(); // usually 4
for (size_t y = 0; y < height; ++y) {
uint8_t* row = dst + y * info.stride;
for (size_t x = 0; x < width; ++x) {
size_t srcIdx = (y * width + x) * channels;
for (int c = 0; c < 4; ++c) {
float v = 0.0f;
if (c < channels) v = src[srcIdx + c];
if (c == 3 && channels < 4) v = 1.0f; // Alpha 1.0 if missing
row[x * 4 + c] = uint8_t(
std::min(255.0f, std::max(0.0f, std::round(v * 255.0f))));
}
uint32_t channels = result.diffImage.getChannels(); // usually 4
for (size_t i = 0; i < width * height; ++i) {
for (int c = 0; c < 4; ++c) {
float v = 0.0f;
if (c < channels) v = src[i * channels + c];
if (c == 3 && channels < 4) v = 1.0f; // Alpha 1.0 if missing
dst[i * 4 + c] = (uint8_t) std::min(255.0f, std::max(0.0f, v * 255.0f));
}
}
AndroidBitmap_unlockPixels(env, diffBitmap);
@@ -149,7 +133,7 @@ jobject createResult(JNIEnv* env, ImageDiffResult const& result, bool generateDi
}
}
}
return resultObj;
}
@@ -163,7 +147,7 @@ Java_com_google_android_filament_utils_ImageDiff_nCompareBasic(JNIEnv* env, jcla
BitmapLock maskArg(env, maskBitmap);
if (!refArg.isValid() || !candArg.isValid()) {
ImageDiffResult emptyResult;
ImageDiffResult emptyResult;
emptyResult.status = ImageDiffResult::Status::SIZE_MISMATCH; // or ERROR
return createResult(env, emptyResult, false);
}
@@ -191,13 +175,13 @@ Java_com_google_android_filament_utils_ImageDiff_nCompareBasic(JNIEnv* env, jcla
extern "C" JNIEXPORT jobject JNICALL
Java_com_google_android_filament_utils_ImageDiff_nCompareJson(JNIEnv* env, jclass,
jobject refBitmap, jobject candBitmap, jstring jsonConfig, jobject maskBitmap) {
BitmapLock refArg(env, refBitmap);
BitmapLock candArg(env, candBitmap);
BitmapLock maskArg(env, maskBitmap);
if (!refArg.isValid() || !candArg.isValid()) {
ImageDiffResult emptyResult;
ImageDiffResult emptyResult;
emptyResult.status = ImageDiffResult::Status::SIZE_MISMATCH; // or ERROR
return createResult(env, emptyResult, false);
}
@@ -205,7 +189,7 @@ Java_com_google_android_filament_utils_ImageDiff_nCompareJson(JNIEnv* env, jclas
ImageDiffConfig config;
const char* nativeJson = env->GetStringUTFChars(jsonConfig, 0);
size_t length = env->GetStringUTFLength(jsonConfig);
bool parsed = parseConfig(nativeJson, length, &config);
env->ReleaseStringUTFChars(jsonConfig, nativeJson);
@@ -230,3 +214,4 @@ Java_com_google_android_filament_utils_ImageDiff_nCompareJson(JNIEnv* env, jclas
return createResult(env, result, generateDiff);
}
@@ -1,26 +0,0 @@
/*
* Copyright (C) 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.filament.utils;
import com.google.android.filament.Engine;
public class DeviceUtils {
public static String getGpuDriverInfo(Engine engine) {
return nGetGpuDriverInfo(engine.getNativeObject());
}
private static native String nGetGpuDriverInfo(long nativeEngine);
}
@@ -106,8 +106,8 @@ class ModelViewer(
var skyboxCubemap: Texture? = null
private lateinit var displayHelper: DisplayHelper
private var cameraManipulator: Manipulator? = null
private var gestureDetector: GestureDetector? = null
private lateinit var cameraManipulator: Manipulator
private lateinit var gestureDetector: GestureDetector
private var surfaceView: SurfaceView? = null
private var textureView: TextureView? = null
@@ -157,13 +157,15 @@ class ModelViewer(
surfaceView: SurfaceView,
engine: Engine = Engine.create(),
uiHelper: UiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK),
manipulator: Manipulator? = defaultCameraManipulator(surfaceView.width, surfaceView.height)
manipulator: Manipulator? = null
) : this(engine, uiHelper) {
cameraManipulator = manipulator ?: Manipulator.Builder()
.targetPosition(kDefaultObjectPosition.x, kDefaultObjectPosition.y, kDefaultObjectPosition.z)
.viewport(surfaceView.width, surfaceView.height)
.build(Manipulator.Mode.ORBIT)
this.surfaceView = surfaceView
cameraManipulator = manipulator
cameraManipulator?.let { c ->
gestureDetector = GestureDetector(surfaceView, c)
}
gestureDetector = GestureDetector(surfaceView, cameraManipulator)
displayHelper = DisplayHelper(surfaceView.context)
uiHelper.renderCallback = SurfaceCallback()
uiHelper.attachTo(surfaceView)
@@ -175,14 +177,15 @@ class ModelViewer(
textureView: TextureView,
engine: Engine = Engine.create(),
uiHelper: UiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK),
manipulator: Manipulator? = defaultCameraManipulator(textureView.width, textureView.height)
manipulator: Manipulator? = null
) : this(engine, uiHelper) {
cameraManipulator = manipulator
cameraManipulator = manipulator ?: Manipulator.Builder()
.targetPosition(kDefaultObjectPosition.x, kDefaultObjectPosition.y, kDefaultObjectPosition.z)
.viewport(textureView.width, textureView.height)
.build(Manipulator.Mode.ORBIT)
this.textureView = textureView
cameraManipulator = manipulator
cameraManipulator?.let { c ->
gestureDetector = GestureDetector(textureView, c)
}
gestureDetector = GestureDetector(textureView, cameraManipulator)
displayHelper = DisplayHelper(textureView.context)
uiHelper.renderCallback = SurfaceCallback()
uiHelper.attachTo(textureView)
@@ -266,35 +269,6 @@ class ModelViewer(
}
}
/**
* Resets the model's transform, animation, and camera state to defaults.
* Call this when reusing the same model across multiple tests.
*/
fun resetToDefaultState() {
// 1. Reset Camera parameters
cameraFocalLength = 28f
cameraNear = kNearPlane
cameraFar = kFarPlane
updateCameraProjection()
// 2. Reset the manipulator's look-at vectors to initial state
cameraManipulator?.let { cm ->
cm.jumpToBookmark(cm.homeBookmark)
}
// 3. Reset Animations
animator?.let {
if (it.animationCount > 0) {
it.applyAnimation(0, 0.0f)
}
it.updateBoneMatrices()
}
// 4. Re-apply the unit cube transform to clear custom scaling/translation
clearRootTransform()
transformToUnitCube()
}
/**
* Frees all entities associated with the most recently-loaded model.
*/
@@ -328,13 +302,11 @@ class ModelViewer(
asset?.let { populateScene(it) }
// Extract the camera basis from the helper and push it to the Filament camera.
cameraManipulator?.let { cm ->
cm.getLookAt(eyePos, target, upward)
camera.lookAt(
cameraManipulator.getLookAt(eyePos, target, upward)
camera.lookAt(
eyePos[0], eyePos[1], eyePos[2],
target[0], target[1], target[2],
upward[0], upward[1], upward[2])
}
// Render the scene, unless the renderer wants to skip the frame.
if (renderer.beginFrame(swapChain!!, frameTimeNanos)) {
@@ -426,7 +398,7 @@ class ModelViewer(
* Handles a [MotionEvent] to enable one-finger orbit, two-finger pan, and pinch-to-zoom.
*/
fun onTouchEvent(event: MotionEvent) {
gestureDetector?.onTouchEvent(event)
gestureDetector.onTouchEvent(event)
}
@SuppressWarnings("ClickableViewAccessibility")
@@ -479,7 +451,7 @@ class ModelViewer(
override fun onResized(width: Int, height: Int) {
view.viewport = Viewport(0, 0, width, height)
cameraManipulator?.setViewport(width, height)
cameraManipulator.setViewport(width, height)
updateCameraProjection()
synchronizePendingFrames(engine)
}
@@ -496,11 +468,5 @@ class ModelViewer(
companion object {
private val kDefaultObjectPosition = Float3(0.0f, 0.0f, -4.0f)
private fun defaultCameraManipulator(width: Int, height: Int) : Manipulator {
return Manipulator.Builder()
.targetPosition(kDefaultObjectPosition.x, kDefaultObjectPosition.y, kDefaultObjectPosition.z)
.viewport(width, height)
.build(Manipulator.Mode.ORBIT)
}
}
}
-31
View File
@@ -1,31 +0,0 @@
plugins {
id 'groovy-gradle-plugin'
id 'com.gradle.plugin-publish' version '2.1.0'
}
group = "com.google.android.filament"
version = "0.1.0"
gradlePlugin {
website = "https://github.com/google/filament/tree/main/android/gradle-plugin"
vcsUrl = "https://github.com/google/filament/tree/main/android/gradle-plugin"
plugins {
create("filament-tools") {
id = "com.google.android.filament-tools"
displayName = "Filament Tools Gradle Plugin"
description = "A plugin that helps integrate Filament into Android projects"
tags.addAll('android', 'graphics', 'rendering', 'filament', '3d', 'gltf', 'native')
implementationClass = "com.google.android.filament.gradle.FilamentPlugin"
}
}
}
repositories {
mavenCentral()
gradlePluginPortal()
}
dependencies {
implementation "com.google.gradle:osdetector-gradle-plugin:1.7.3"
}
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.70.2
VERSION_NAME=1.69.5
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.android.filament-tools'
id 'filament-plugin'
}
project.ext.isSample = true
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.android.filament-tools'
id 'filament-plugin'
}
project.ext.isSample = true
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.android.filament-tools'
id 'filament-plugin'
}
project.ext.isSample = true
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.android.filament-tools'
id 'filament-plugin'
}
project.ext.isSample = true
+1 -1
View File
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.android.filament-tools'
id 'filament-plugin'
}
project.ext.isSample = true
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.android.filament-tools'
id 'filament-plugin'
}
project.ext.isSample = true
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.android.filament-tools'
id 'filament-plugin'
}
project.ext.isSample = true
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.android.filament-tools'
id 'filament-plugin'
}
project.ext.isSample = true
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.android.filament-tools'
id 'filament-plugin'
}
project.ext.isSample = true
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.android.filament-tools'
id 'filament-plugin'
}
project.ext.isSample = true
@@ -30,11 +30,11 @@
"view": {
"postProcessingEnabled": true,
"dithering": "NONE"
},
"tolerance": {
"maxAbsDiff": 0.1,
"maxFailingPixelsFraction": 0.0
}
},
"tolerance": {
"maxAbsDiff": 0.1,
"maxFailingPixelsFraction": 0.0
}
},
{
@@ -66,26 +66,10 @@ class MainActivity : Activity(), ValidationRunner.Callback {
private lateinit var inputManager: ValidationInputManager
private var currentInput: ValidationInputManager.ValidationInput? = null
private var currentAlphaDiffBitmap: Bitmap? = null
private var globalEnhancementFactor: Float = 1.0f
private data class TestImages(
val testName: String,
val golden: Bitmap?,
val rendered: Bitmap?,
val diff: Bitmap?,
val alphaDiff: Bitmap?
)
private val diffImageViews = mutableListOf<ImageView>()
// UI Elements
private lateinit var runButton: Button
private lateinit var loadButton: Button
private lateinit var optionsButton: Button
private lateinit var enhancementContainer: LinearLayout
private lateinit var enhancementLabel: TextView
private lateinit var enhancementSlider: android.widget.SeekBar
private var resultManager: ValidationResultManager? = null
private var validationRunner: ValidationRunner? = null
@@ -114,19 +98,6 @@ class MainActivity : Activity(), ValidationRunner.Callback {
runButton = findViewById(R.id.run_button)
loadButton = findViewById(R.id.load_button)
optionsButton = findViewById(R.id.options_button)
enhancementContainer = findViewById(R.id.enhancement_container)
enhancementLabel = findViewById(R.id.enhancement_label)
enhancementSlider = findViewById(R.id.enhancement_slider)
enhancementSlider.setOnSeekBarChangeListener(object : android.widget.SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: android.widget.SeekBar?, progress: Int, fromUser: Boolean) {
globalEnhancementFactor = 1.0f + (progress / 100f) * 49.0f
enhancementLabel.text = String.format(Locale.US, "Enhancement: %.1fx", globalEnhancementFactor)
applyGlobalEnhancement()
}
override fun onStartTrackingTouch(seekBar: android.widget.SeekBar?) {}
override fun onStopTrackingTouch(seekBar: android.widget.SeekBar?) {}
})
// Setup Run Button
runButton.setOnClickListener {
@@ -149,7 +120,6 @@ class MainActivity : Activity(), ValidationRunner.Callback {
popup.menu.add(0, 3, 0, "Export Result")
popup.menu.add(0, 4, 0, "Test ADB Info")
popup.menu.add(0, 5, 0, "Result ADB Info")
popup.menu.add(0, 6, 0, "Toggle Enhancement Slider")
popup.setOnMenuItemClickListener { item ->
when (item.itemId) {
@@ -163,9 +133,6 @@ class MainActivity : Activity(), ValidationRunner.Callback {
3 -> exportTestResultsAction()
4 -> showTestAdbInfo()
5 -> showResultAdbInfo()
6 -> {
enhancementContainer.visibility = if (enhancementContainer.visibility == View.VISIBLE) View.GONE else View.VISIBLE
}
}
true
}
@@ -175,7 +142,7 @@ class MainActivity : Activity(), ValidationRunner.Callback {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
choreographer = Choreographer.getInstance()
modelViewer = ModelViewer(surfaceView=surfaceView, manipulator=null)
modelViewer = ModelViewer(surfaceView)
inputManager = ValidationInputManager(this)
// Initialize IBL
@@ -289,7 +256,6 @@ class MainActivity : Activity(), ValidationRunner.Callback {
// Clear existing results UI and state
resultsContainer.removeAllViews()
diffImageViews.clear()
resultManager = null
val newInput = ValidationInputManager.ValidationInput(
@@ -368,29 +334,15 @@ class MainActivity : Activity(), ValidationRunner.Callback {
}
}
private fun createResultManager(outputDir: File): ValidationResultManager {
val gpuDriverInfo = com.google.android.filament.utils.DeviceUtils.getGpuDriverInfo(modelViewer.engine)
return ValidationResultManager(
outputDir = outputDir,
gpuDriverInfo = gpuDriverInfo,
deviceName = android.os.Build.MODEL,
deviceCodeName = android.os.Build.DEVICE,
androidVersion = android.os.Build.VERSION.RELEASE,
androidBuildNumber = android.os.Build.DISPLAY
)
}
private fun startValidation(input: ValidationInputManager.ValidationInput) {
try {
resultsContainer.removeAllViews()
diffImageViews.clear()
enhancementSlider.isEnabled = false
Log.i(TAG, "Starting validation with config: ${input.config.name}")
Log.i(TAG, "Output dir: ${input.outputDir.absolutePath}")
testResultsHeader.text = "${input.config.name}"
resultManager = createResultManager(input.outputDir)
resultManager = ValidationResultManager(input.outputDir)
validationRunner = ValidationRunner(this, modelViewer, input.config, resultManager!!)
validationRunner?.callback = this
@@ -468,15 +420,7 @@ class MainActivity : Activity(), ValidationRunner.Callback {
val imagesRow = LinearLayout(this)
imagesRow.orientation = LinearLayout.HORIZONTAL
val testImages = TestImages(
testName = result.testName,
golden = currentGoldenBitmap,
rendered = currentRenderedBitmap,
diff = currentDiffBitmap,
alphaDiff = currentAlphaDiffBitmap
)
fun addImage(label: String, bitmap: Bitmap?, isDiff: Boolean) {
fun addImage(label: String, bitmap: Bitmap?) {
if (bitmap != null) {
val container = LinearLayout(this)
container.orientation = LinearLayout.VERTICAL
@@ -492,29 +436,16 @@ class MainActivity : Activity(), ValidationRunner.Callback {
iv.layoutParams = LinearLayout.LayoutParams(250, 250) // Smaller thumbnails
iv.scaleType = ImageView.ScaleType.FIT_CENTER
iv.setBackgroundColor(0xFF404040.toInt())
if (isDiff) {
diffImageViews.add(iv)
applyEnhancementToView(iv, globalEnhancementFactor)
}
iv.setOnClickListener {
showImageDialog(testImages, label)
}
container.addView(iv)
imagesRow.addView(container)
}
}
addImage("Rendered", currentRenderedBitmap, false)
addImage("Golden", currentGoldenBitmap, false)
addImage("Rendered", currentRenderedBitmap)
addImage("Golden", currentGoldenBitmap)
if (!result.passed) {
addImage("Diff", currentDiffBitmap, true)
}
if (currentAlphaDiffBitmap != null) {
addImage("Alpha Diff", currentAlphaDiffBitmap, true)
addImage("Diff", currentDiffBitmap)
}
resultContainer.addView(imagesRow)
@@ -524,14 +455,12 @@ class MainActivity : Activity(), ValidationRunner.Callback {
currentRenderedBitmap = null
currentGoldenBitmap = null
currentDiffBitmap = null
currentAlphaDiffBitmap = null
}
}
override fun onAllTestsFinished() {
runOnUiThread {
statusTextView.text = "All tests finished!"
enhancementSlider.isEnabled = true
Log.i(TAG, "All tests finished " + if (currentInput?.autoExport == true) "Exporting bundle" else "x")
if (currentInput?.autoExport == true) {
@@ -546,7 +475,7 @@ class MainActivity : Activity(), ValidationRunner.Callback {
private fun exportTestBundleAction() {
currentInput?.let { input ->
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
val rm = resultManager ?: createResultManager(input.outputDir)
val rm = resultManager ?: ValidationResultManager(input.outputDir)
val zip = rm.exportTestBundle(input.config, timestamp)
if (zip != null) {
val msg = "Exported Bundle: ${zip.name}"
@@ -562,7 +491,7 @@ class MainActivity : Activity(), ValidationRunner.Callback {
private fun exportTestResultsAction() {
currentInput?.let { input ->
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
val rm = resultManager ?: createResultManager(input.outputDir)
val rm = resultManager ?: ValidationResultManager(input.outputDir)
val zip = rm.exportTestResults(input.sourceZip, timestamp)
if (zip != null) {
val msg = "Exported Results: ${zip.name}"
@@ -594,165 +523,7 @@ class MainActivity : Activity(), ValidationRunner.Callback {
"Diff" -> {
currentDiffBitmap = bitmap
}
"Alpha Diff" -> {
currentAlphaDiffBitmap = bitmap
}
}
}
}
private fun applyEnhancementToView(iv: ImageView, factor: Float) {
val cm = android.graphics.ColorMatrix()
cm.setScale(factor, factor, factor, 1.0f)
iv.colorFilter = android.graphics.ColorMatrixColorFilter(cm)
}
private fun applyGlobalEnhancement() {
for (iv in diffImageViews) {
applyEnhancementToView(iv, globalEnhancementFactor)
}
}
private fun showImageDialog(images: TestImages, initialLabel: String) {
val dialogView = layoutInflater.inflate(R.layout.dialog_image_viewer, null)
val dialog = AlertDialog.Builder(this)
.setView(dialogView)
.create()
val titleView = dialogView.findViewById<TextView>(R.id.dialog_title)
val typeView = dialogView.findViewById<TextView>(R.id.dialog_image_type)
val imageView = dialogView.findViewById<ImageView>(R.id.dialog_image)
val btnClose = dialogView.findViewById<View>(R.id.btn_close)
val btnReset = dialogView.findViewById<View>(R.id.btn_reset)
val btnPrev = dialogView.findViewById<View>(R.id.btn_prev)
val btnNext = dialogView.findViewById<View>(R.id.btn_next)
val enhancementContainer = dialogView.findViewById<View>(R.id.dialog_enhancement_container)
val enhancementLabel = dialogView.findViewById<TextView>(R.id.dialog_enhancement_label)
val enhancementSlider = dialogView.findViewById<android.widget.SeekBar>(R.id.dialog_enhancement_slider)
titleView.text = images.testName
val availableImages = mutableListOf<Pair<String, Bitmap>>()
images.rendered?.let { availableImages.add(Pair("Rendered", it)) }
images.golden?.let { availableImages.add(Pair("Golden", it)) }
images.diff?.let { availableImages.add(Pair("Diff", it)) }
images.alphaDiff?.let { availableImages.add(Pair("Alpha Diff", it)) }
if (availableImages.isEmpty()) return
var currentIndex = availableImages.indexOfFirst { it.first == initialLabel }
if (currentIndex == -1) currentIndex = 0
var currentDialogEnhancement = globalEnhancementFactor
val matrix = android.graphics.Matrix()
// Save initial values for translation tracking
var lastTouchX = 0f
var lastTouchY = 0f
var isDragging = false
val scaleDetector = android.view.ScaleGestureDetector(this, object : android.view.ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: android.view.ScaleGestureDetector): Boolean {
matrix.postScale(detector.scaleFactor, detector.scaleFactor, detector.focusX, detector.focusY)
imageView.imageMatrix = matrix
return true
}
})
imageView.setOnTouchListener { _, event ->
scaleDetector.onTouchEvent(event)
when (event.actionMasked) {
android.view.MotionEvent.ACTION_DOWN -> {
lastTouchX = event.x
lastTouchY = event.y
isDragging = true
}
android.view.MotionEvent.ACTION_MOVE -> {
if (isDragging && !scaleDetector.isInProgress) {
val dx = event.x - lastTouchX
val dy = event.y - lastTouchY
matrix.postTranslate(dx, dy)
imageView.imageMatrix = matrix
}
lastTouchX = event.x
lastTouchY = event.y
}
android.view.MotionEvent.ACTION_UP, android.view.MotionEvent.ACTION_CANCEL -> {
isDragging = false
}
}
true
}
fun updateView() {
val (label, bitmap) = availableImages[currentIndex]
typeView.text = label
imageView.setImageBitmap(bitmap)
(imageView.drawable as? android.graphics.drawable.BitmapDrawable)?.setAntiAlias(false)
(imageView.drawable as? android.graphics.drawable.BitmapDrawable)?.setFilterBitmap(false)
imageView.imageMatrix = matrix
if (label == "Diff" || label == "Alpha Diff") {
enhancementContainer.visibility = View.VISIBLE
applyEnhancementToView(imageView, currentDialogEnhancement)
} else {
enhancementContainer.visibility = View.GONE
imageView.colorFilter = null
}
}
fun resetMatrix() {
val drawable = imageView.drawable ?: return
val width = imageView.width.toFloat()
val height = imageView.height.toFloat()
val dw = drawable.intrinsicWidth.toFloat()
val dh = drawable.intrinsicHeight.toFloat()
val scaleX = width / dw
val scaleY = height / dh
val scale = Math.min(scaleX, scaleY)
val dx = (width - dw * scale) / 2f
val dy = (height - dh * scale) / 2f
matrix.reset()
matrix.postScale(scale, scale)
matrix.postTranslate(dx, dy)
imageView.imageMatrix = matrix
}
btnClose.setOnClickListener { dialog.dismiss() }
btnReset.setOnClickListener { resetMatrix() }
btnPrev.setOnClickListener {
currentIndex = (currentIndex - 1 + availableImages.size) % availableImages.size
updateView()
}
btnNext.setOnClickListener {
currentIndex = (currentIndex + 1) % availableImages.size
updateView()
}
val defaultProgress = ((currentDialogEnhancement - 1.0f) / 49.0f * 100).toInt()
val safeProgress = Math.max(0, Math.min(100, defaultProgress))
enhancementSlider.progress = safeProgress
enhancementLabel.text = String.format(Locale.US, "Enhance: %.1fx", currentDialogEnhancement)
enhancementSlider.setOnSeekBarChangeListener(object : android.widget.SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: android.widget.SeekBar?, progress: Int, fromUser: Boolean) {
currentDialogEnhancement = 1.0f + (progress / 100f) * 49.0f
enhancementLabel.text = String.format(Locale.US, "Enhance: %.1fx", currentDialogEnhancement)
updateView()
}
override fun onStartTrackingTouch(seekBar: android.widget.SeekBar?) {}
override fun onStopTrackingTouch(seekBar: android.widget.SeekBar?) {}
})
imageView.post {
resetMatrix()
}
updateView()
dialog.show()
}
}
@@ -31,14 +31,7 @@ data class ValidationResult(
val diffMetric: Float = 0f
)
class ValidationResultManager(
private val outputDir: File,
private val gpuDriverInfo: String,
private val deviceName: String,
private val deviceCodeName: String,
private val androidVersion: String,
private val androidBuildNumber: String
) {
class ValidationResultManager(private val outputDir: File) {
companion object {
private const val TAG = "ValidationResultManager"
@@ -71,9 +64,9 @@ class ValidationResultManager(
return outputDir
}
fun finalizeResults(totalTimeMs: Long): File? {
fun finalizeResults(): File? {
// Write results JSON
writeResultsJson(totalTimeMs)
writeResultsJson()
return null
}
@@ -110,10 +103,10 @@ class ValidationResultManager(
zos.closeEntry()
}
// 3. Add images (only rendered images, exclude diffs)
outputDir.listFiles { _, name -> name.endsWith(".png") && !name.endsWith("_diff.png") }?.forEach { imgFile ->
zos.putNextEntry(ZipEntry(imgFile.name))
imgFile.inputStream().use { it.copyTo(zos) }
// 3. Add diff images (any file ending in _diff.png in outputDir)
outputDir.listFiles { _, name -> name.endsWith("_diff.png") }?.forEach { diffFile ->
zos.putNextEntry(ZipEntry(diffFile.name))
diffFile.inputStream().use { it.copyTo(zos) }
zos.closeEntry()
}
}
@@ -273,18 +266,7 @@ class ValidationResultManager(
}
}
private fun writeResultsJson(totalTimeMs: Long) {
val rootObject = JSONObject()
val metadataObject = JSONObject()
metadataObject.put("gpu_driver_info", gpuDriverInfo ?: "")
metadataObject.put("total_time_ms", totalTimeMs)
metadataObject.put("device_name", deviceName ?: "")
metadataObject.put("device_code_name", deviceCodeName ?: "")
metadataObject.put("android_version", androidVersion ?: "")
metadataObject.put("android_build_number", androidBuildNumber ?: "")
rootObject.put("metadata", metadataObject)
private fun writeResultsJson() {
val jsonArray = JSONArray()
for (result in results) {
val jsonObject = JSONObject()
@@ -293,12 +275,11 @@ class ValidationResultManager(
jsonObject.put("diff_metric", result.diffMetric)
jsonArray.put(jsonObject)
}
rootObject.put("results", jsonArray)
val jsonFile = File(outputDir, "results.json")
try {
FileOutputStream(jsonFile).use { out ->
out.write(rootObject.toString(4).toByteArray())
out.write(jsonArray.toString(4).toByteArray())
}
} catch (e: Exception) {
Log.e(TAG, "Failed to write results.json", e)
@@ -42,7 +42,6 @@ class ValidationRunner(
private var currentModelName: String? = null
private var frameCounter = 0
private var suiteStartTime: Long = 0
enum class State {
IDLE,
@@ -66,7 +65,6 @@ class ValidationRunner(
callback?.onAllTestsFinished()
return
}
suiteStartTime = System.currentTimeMillis()
currentTestIndex = 0
currentModelIndex = 0
startTest(config.tests[0])
@@ -83,17 +81,6 @@ class ValidationRunner(
}
private fun startModel(modelName: String) {
if (currentModelName == modelName) {
Log.i("ValidationRunner", "Reusing model $modelName")
callback?.onStatusChanged("Reusing $modelName for ${currentTestConfig?.name}")
modelViewer.resetToDefaultState()
frameCounter = 0
currentState = State.WARMUP
return
}
currentModelName = modelName
val modelPath = config.models[modelName]
if (modelPath == null) {
@@ -128,6 +115,10 @@ class ValidationRunner(
}
fun onFrame(frameTimeNanos: Long) {
if (frameCounter % 60 == 0) {
Log.i("ValidationRunner", "onFrame: $currentState (frame: $frameCounter)")
}
when (currentState) {
State.IDLE -> {}
State.WAITING_FOR_RESOURCES -> {
@@ -145,6 +136,7 @@ class ValidationRunner(
}
}
State.RUNNING_TEST -> {
// Log.i("ValidationRunner", "Running test...")
currentEngine?.let { engine ->
val content = AutomationEngine.ViewerContent()
content.view = modelViewer.view
@@ -265,54 +257,10 @@ class ValidationRunner(
passed = (result.status == ImageDiff.Result.Status.PASSED)
diffMetric = result.failingPixelCount.toFloat()
if (!passed) {
if (!passed) {
if (result.diffImage != null) {
val diffImg = result.diffImage!!
val width = diffImg.width
val height = diffImg.height
val pixels = IntArray(width * height)
diffImg.getPixels(pixels, 0, width, 0, 0, width, height)
var hasAlphaDiff = false
val alphaPixels = IntArray(width * height)
for (i in pixels.indices) {
val color = pixels[i]
val a = android.graphics.Color.alpha(color)
val r = android.graphics.Color.red(color)
val g = android.graphics.Color.green(color)
val b = android.graphics.Color.blue(color)
if (a > 0) {
hasAlphaDiff = true
}
// Map alpha diff to grayscale RGB
alphaPixels[i] = android.graphics.Color.argb(255, a, a, a)
// Force main diff image alpha to 255
pixels[i] = android.graphics.Color.argb(255, r, g, b)
}
// Apply updated pixels to diff image
diffImg.setPixels(pixels, 0, width, 0, 0, width, height)
// The C++ ImageDiff code sets isPremultiplied to false so Android
// doesn't erase RGB diff values when Alpha diff is 0. However, Android's
// Canvas will crash if we try to draw a non-premultiplied bitmap.
// Since we just forced all alpha values to 255 (fully opaque) in the
// loop above, we can safely mark it as premultiplied again here.
diffImg.isPremultiplied = true
callback?.onImageResult("Diff", diffImg)
resultManager.saveImage("${testFullName}_diff", diffImg)
if (hasAlphaDiff) {
val alphaDiffImg = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
alphaDiffImg.setPixels(alphaPixels, 0, width, 0, 0, width, height)
callback?.onImageResult("Alpha Diff", alphaDiffImg)
resultManager.saveImage("${testFullName}_alpha_diff", alphaDiffImg)
}
callback?.onImageResult("Diff", result.diffImage!!)
resultManager.saveImage("${testFullName}_diff", result.diffImage!!)
}
}
} else {
@@ -358,10 +306,7 @@ class ValidationRunner(
startTest(config.tests[currentTestIndex])
} else {
currentState = State.IDLE
val totalTimeMs = System.currentTimeMillis() - suiteStartTime
resultManager.finalizeResults(totalTimeMs)
resultManager.finalizeResults()
callback?.onAllTestsFinished()
}
}
@@ -81,33 +81,6 @@
android:contentDescription="More Options"
android:layout_marginStart="8dp"/>
</LinearLayout>
<!-- Global Enhancement Controls (Hidden by default) -->
<LinearLayout
android:id="@+id/enhancement_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:visibility="gone"
android:paddingBottom="8dp">
<TextView
android:id="@+id/enhancement_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Enhancement: 1.0x"
android:textSize="12sp"
android:minWidth="120dp" />
<SeekBar
android:id="@+id/enhancement_slider"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:max="100"
android:progress="0" />
</LinearLayout>
</LinearLayout>
<TextView
@@ -1,130 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#FF202020"
android:padding="8dp">
<!-- Header with title and close button -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:id="@+id/dialog_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Test Result"
android:textColor="#FFFFFF"
android:textSize="16sp"
android:textStyle="bold" />
<ImageButton
android:id="@+id/btn_close"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@android:drawable/ic_menu_close_clear_cancel" />
</LinearLayout>
<!-- Subtitle for Image Type -->
<TextView
android:id="@+id/dialog_image_type"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:text="Rendered"
android:textColor="#BBBBBB"
android:textSize="14sp"
android:paddingBottom="8dp" />
<!-- Image Area with Arrows -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintDimensionRatio="1:1">
<ImageView
android:id="@+id/dialog_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="matrix" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- Navigation Arrows -->
<LinearLayout
android:id="@+id/dialog_arrow_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:paddingTop="8dp"
android:paddingBottom="8dp">
<ImageButton
android:id="@+id/btn_prev"
android:layout_width="64dp"
android:layout_height="64dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@android:drawable/ic_media_previous"
app:tint="#FFFFFF" />
<ImageButton
android:id="@+id/btn_reset"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginHorizontal="16dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@android:drawable/ic_menu_revert"
app:tint="#FFFFFF" />
<ImageButton
android:id="@+id/btn_next"
android:layout_width="64dp"
android:layout_height="64dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@android:drawable/ic_media_next"
app:tint="#FFFFFF" />
</LinearLayout>
<!-- Enhancement Controls (only for diff images) -->
<LinearLayout
android:id="@+id/dialog_enhancement_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingTop="12dp"
android:paddingBottom="8dp"
android:visibility="gone">
<TextView
android:id="@+id/dialog_enhancement_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Enhance: 1.0x"
android:textColor="#FFFFFF"
android:textSize="12sp"
android:minWidth="100dp" />
<SeekBar
android:id="@+id/dialog_enhancement_slider"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:max="100"
android:progress="0" />
</LinearLayout>
</LinearLayout>
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.android.filament-tools'
id 'filament-plugin'
}
project.ext.isSample = true
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.android.filament-tools'
id 'filament-plugin'
}
project.ext.isSample = true
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.android.filament-tools'
id 'filament-plugin'
}
project.ext.isSample = true
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.android.filament-tools'
id 'filament-plugin'
}
project.ext.isSample = true
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.android.filament-tools'
id 'filament-plugin'
}
project.ext.isSample = true
-5
View File
@@ -1,8 +1,3 @@
// Filament tools plugin
pluginManagement {
includeBuild 'gradle-plugin'
}
// Libraries
include ':filament-android'
include ':filamat-android'
+1 -7
View File
@@ -54,13 +54,8 @@ if [[ "$OS_NAME" == "Linux" ]]; then
# is constantly being updated and sometimes not compatible with the current
# linux platform.
# Note that we assume this platform is compatible with ubuntu-22.04 x86_64
DPKG_ARCH=$(dpkg --print-architecture)
EXTRA_PACKAGES=""
if [[ "$DPKG_ARCH" == "amd64" ]]; then
EXTRA_PACKAGES="lib32gcc-s1 lib32stdc++6 libc6-i386"
fi
sudo apt-get -y install \
autoconf automake autopoint autotools-dev bindgen bison build-essential bzip2 cpp cpp-11 debhelper debugedit dh-autoreconf dh-strip-nondeterminism diffstat directx-headers-dev dpkg-dev dwz flex g++ g++-11 gcc gcc-11 gcc-11-base:${DPKG_ARCH} gettext glslang-tools icu-devtools intltool-debian ${EXTRA_PACKAGES} libarchive-zip-perl libasan6:${DPKG_ARCH} libatomic1:${DPKG_ARCH} libc-dev-bin libc6-dbg:${DPKG_ARCH} libc6-dev:${DPKG_ARCH} libcc1-0:${DPKG_ARCH} libclang-${GITHUB_CLANG_VERSION}-dev libclang-common-${GITHUB_CLANG_VERSION}-dev libclang-cpp${GITHUB_CLANG_VERSION} libclang-cpp${GITHUB_CLANG_VERSION}-dev libclang1-14 libclang1-${GITHUB_CLANG_VERSION} libclc-${GITHUB_CLANG_VERSION} libclc-${GITHUB_CLANG_VERSION}-dev libcrypt-dev:${DPKG_ARCH} libdebhelper-perl libdpkg-perl libdrm-amdgpu1:${DPKG_ARCH} libdrm-dev:${DPKG_ARCH} libdrm-intel1:${DPKG_ARCH} libdrm-nouveau2:${DPKG_ARCH} libdrm-radeon1:${DPKG_ARCH} libelf-dev:${DPKG_ARCH} libexpat1-dev:${DPKG_ARCH} libffi-dev:${DPKG_ARCH} libfile-stripnondeterminism-perl libgc1:${DPKG_ARCH} libgcc-11-dev:${DPKG_ARCH} libgl1:${DPKG_ARCH} libgl1-mesa-dri:${DPKG_ARCH} libglapi-mesa:${DPKG_ARCH} libglvnd-core-dev:${DPKG_ARCH} libglvnd0:${DPKG_ARCH} libglx-mesa0:${DPKG_ARCH} libglx0:${DPKG_ARCH} libgomp1:${DPKG_ARCH} libicu-dev:${DPKG_ARCH} libisl23:${DPKG_ARCH} libitm1:${DPKG_ARCH} libllvm14:${DPKG_ARCH} libllvm${GITHUB_CLANG_VERSION}:${DPKG_ARCH} libllvmspirvlib-${GITHUB_CLANG_VERSION}-dev:${DPKG_ARCH} libllvmspirvlib${GITHUB_CLANG_VERSION}:${DPKG_ARCH} liblsan0:${DPKG_ARCH} libmpc3:${DPKG_ARCH} libncurses-dev:${DPKG_ARCH} libnsl-dev:${DPKG_ARCH} libobjc-11-dev:${DPKG_ARCH} libobjc4:${DPKG_ARCH} libpciaccess-dev:${DPKG_ARCH} libpciaccess0f:${DPKG_ARCH} libpfm4:${DPKG_ARCH} libpthread-stubs0-dev:${DPKG_ARCH} libquadmath0:${DPKG_ARCH} libsensors-config libsensors-dev:${DPKG_ARCH} libsensors5:${DPKG_ARCH} libset-scalar-perl libstd-rust-1.75:${DPKG_ARCH} libstd-rust-dev:${DPKG_ARCH} libstdc++-11-dev:${DPKG_ARCH} libsub-override-perl libtinfo-dev:${DPKG_ARCH} libtirpc-dev:${DPKG_ARCH} libtool libtsan0:${DPKG_ARCH} libubsan1:${DPKG_ARCH} libva-dev:${DPKG_ARCH} libva-drm2:${DPKG_ARCH} libva-glx2:${DPKG_ARCH} libva-wayland2:${DPKG_ARCH} libva-x11-2:${DPKG_ARCH} libva2:${DPKG_ARCH} libvdpau-dev:${DPKG_ARCH} libvdpau1:${DPKG_ARCH} libvulkan-dev:${DPKG_ARCH} libvulkan1:${DPKG_ARCH} libwayland-bin libwayland-client0:${DPKG_ARCH} libwayland-cursor0:${DPKG_ARCH} libwayland-dev:${DPKG_ARCH} libwayland-egl-backend-dev:${DPKG_ARCH} libwayland-egl1:${DPKG_ARCH} libwayland-server0:${DPKG_ARCH} libx11-dev:${DPKG_ARCH} libx11-xcb-dev:${DPKG_ARCH} libx11-xcb1:${DPKG_ARCH} libxau-dev:${DPKG_ARCH} libxcb-dri2-0:${DPKG_ARCH} libxcb-dri2-0-dev:${DPKG_ARCH} libxcb-dri3-0:${DPKG_ARCH} libxcb-dri3-dev:${DPKG_ARCH} libxcb-glx0:${DPKG_ARCH} libxcb-glx0-dev:${DPKG_ARCH} libxcb-present-dev:${DPKG_ARCH} libxcb-present0:${DPKG_ARCH} libxcb-randr0:${DPKG_ARCH} libxcb-randr0-dev:${DPKG_ARCH} libxcb-render0:${DPKG_ARCH} libxcb-render0-dev:${DPKG_ARCH} libxcb-shape0:${DPKG_ARCH} libxcb-shape0-dev:${DPKG_ARCH} libxcb-shm0:${DPKG_ARCH} libxcb-shm0-dev:${DPKG_ARCH} libxcb-sync-dev:${DPKG_ARCH} libxcb-sync1:${DPKG_ARCH} libxcb-xfixes0:${DPKG_ARCH} libxcb-xfixes0-dev:${DPKG_ARCH} libxcb1-dev:${DPKG_ARCH} libxdmcp-dev:${DPKG_ARCH} libxext-dev:${DPKG_ARCH} libxfixes-dev:${DPKG_ARCH} libxfixes3:${DPKG_ARCH} libxml2-dev:${DPKG_ARCH} libxrandr-dev:${DPKG_ARCH} libxrandr2:${DPKG_ARCH} libxrender-dev:${DPKG_ARCH} libxrender1:${DPKG_ARCH} libxshmfence-dev:${DPKG_ARCH} libxshmfence1:${DPKG_ARCH} libxxf86vm-dev:${DPKG_ARCH} libxxf86vm1:${DPKG_ARCH} libz3-4:${DPKG_ARCH} libz3-dev:${DPKG_ARCH} libzstd-dev:${DPKG_ARCH} linux-libc-dev:${DPKG_ARCH} llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev llvm-${LLVM_VERSION}-linker-tools llvm-${LLVM_VERSION}-runtime llvm-${LLVM_VERSION}-tools llvm-spirv-${LLVM_VERSION} lto-disabled-list m4 make meson ninja-build pkg-config po-debconf python3-mako python3-ply python3-pygments quilt rpcsvc-proto rustc spirv-tools valgrind wayland-protocols x11proto-dev xorg-sgml-doctools xtrans-dev zlib1g-dev:${DPKG_ARCH} \
autoconf automake autopoint autotools-dev bindgen bison build-essential bzip2 cpp cpp-11 debhelper debugedit dh-autoreconf dh-strip-nondeterminism diffstat directx-headers-dev dpkg-dev dwz flex g++ g++-11 gcc gcc-11 gcc-11-base:amd64 gettext glslang-tools icu-devtools intltool-debian lib32gcc-s1 lib32stdc++6 libarchive-zip-perl libasan6:amd64 libatomic1:amd64 libc-dev-bin libc6-dbg:amd64 libc6-dev:amd64 libc6-i386 libcc1-0:amd64 libclang-${GITHUB_CLANG_VERSION}-dev libclang-common-${GITHUB_CLANG_VERSION}-dev libclang-cpp${GITHUB_CLANG_VERSION} libclang-cpp${GITHUB_CLANG_VERSION}-dev libclang1-14 libclang1-${GITHUB_CLANG_VERSION} libclc-${GITHUB_CLANG_VERSION} libclc-${GITHUB_CLANG_VERSION}-dev libcrypt-dev:amd64 libdebhelper-perl libdpkg-perl libdrm-amdgpu1:amd64 libdrm-dev:amd64 libdrm-intel1:amd64 libdrm-nouveau2:amd64 libdrm-radeon1:amd64 libelf-dev:amd64 libexpat1-dev:amd64 libffi-dev:amd64 libfile-stripnondeterminism-perl libgc1:amd64 libgcc-11-dev:amd64 libgl1:amd64 libgl1-mesa-dri:amd64 libglapi-mesa:amd64 libglvnd-core-dev:amd64 libglvnd0:amd64 libglx-mesa0:amd64 libglx0:amd64 libgomp1:amd64 libicu-dev:amd64 libisl23:amd64 libitm1:amd64 libllvm14:amd64 libllvm${GITHUB_CLANG_VERSION}:amd64 libllvmspirvlib-${GITHUB_CLANG_VERSION}-dev:amd64 libllvmspirvlib${GITHUB_CLANG_VERSION}:amd64 liblsan0:amd64 libmpc3:amd64 libncurses-dev:amd64 libnsl-dev:amd64 libobjc-11-dev:amd64 libobjc4:amd64 libpciaccess-dev:amd64 libpciaccess0f:amd64 libpfm4:amd64 libpthread-stubs0-dev:amd64 libquadmath0:amd64 libsensors-config libsensors-dev:amd64 libsensors5:amd64 libset-scalar-perl libstd-rust-1.75:amd64 libstd-rust-dev:amd64 libstdc++-11-dev:amd64 libsub-override-perl libtinfo-dev:amd64 libtirpc-dev:amd64 libtool libtsan0:amd64 libubsan1:amd64 libva-dev:amd64 libva-drm2:amd64 libva-glx2:amd64 libva-wayland2:amd64 libva-x11-2:amd64 libva2:amd64 libvdpau-dev:amd64 libvdpau1:amd64 libvulkan-dev:amd64 libvulkan1:amd64 libwayland-bin libwayland-client0:amd64 libwayland-cursor0:amd64 libwayland-dev:amd64 libwayland-egl-backend-dev:amd64 libwayland-egl1:amd64 libwayland-server0:amd64 libx11-dev:amd64 libx11-xcb-dev:amd64 libx11-xcb1:amd64 libxau-dev:amd64 libxcb-dri2-0:amd64 libxcb-dri2-0-dev:amd64 libxcb-dri3-0:amd64 libxcb-dri3-dev:amd64 libxcb-glx0:amd64 libxcb-glx0-dev:amd64 libxcb-present-dev:amd64 libxcb-present0:amd64 libxcb-randr0:amd64 libxcb-randr0-dev:amd64 libxcb-render0:amd64 libxcb-render0-dev:amd64 libxcb-shape0:amd64 libxcb-shape0-dev:amd64 libxcb-shm0:amd64 libxcb-shm0-dev:amd64 libxcb-sync-dev:amd64 libxcb-sync1:amd64 libxcb-xfixes0:amd64 libxcb-xfixes0-dev:amd64 libxcb1-dev:amd64 libxdmcp-dev:amd64 libxext-dev:amd64 libxfixes-dev:amd64 libxfixes3:amd64 libxml2-dev:amd64 libxrandr-dev:amd64 libxrandr2:amd64 libxrender-dev:amd64 libxrender1:amd64 libxshmfence-dev:amd64 libxshmfence1:amd64 libxxf86vm-dev:amd64 libxxf86vm1:amd64 libz3-4:amd64 libz3-dev:amd64 libzstd-dev:amd64 linux-libc-dev:amd64 llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev llvm-${LLVM_VERSION}-linker-tools llvm-${LLVM_VERSION}-runtime llvm-${LLVM_VERSION}-tools llvm-spirv-${LLVM_VERSION} lto-disabled-list m4 make meson ninja-build pkg-config po-debconf python3-mako python3-ply python3-pygments quilt rpcsvc-proto rustc spirv-tools valgrind wayland-protocols x11proto-dev xorg-sgml-doctools xtrans-dev zlib1g-dev:amd64 \
clang-$GITHUB_CLANG_VERSION libc++-$GITHUB_CLANG_VERSION-dev libc++abi-$GITHUB_CLANG_VERSION-dev
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang-${GITHUB_CLANG_VERSION} 100
@@ -85,7 +80,6 @@ elif [[ "$OS_NAME" == "Darwin" ]]; then
if command -v brew > /dev/null 2>&1; then
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=true brew install autoconf automake libx11 libxext libxrandr \
llvm@${LLVM_VERSION} ninja meson pkg-config libxshmfence
brew link --overwrite llvm@${LLVM_VERSION}
# For reasons unknown, this is necessary for pkg-config to find homebrew's packages
LOCAL_PKG_CONFIG_PATH="/opt/homebrew/lib/pkgconfig:$PKG_CONFIG_PATH"
elif command -v port > /dev/null 2>&1; then
+3 -14
View File
@@ -63,10 +63,6 @@ function _preferred_os_filename() {
function download_vulkan_installer() {
local os=$(_get_os)
if [[ "$os" == "linux" ]]; then
echo "Linux uses apt to install vulkan dependencies, skipping tarball download." >&2
return 0
fi
local dl_filename=$(_os_filename ${VULKAN_SDK_VERSION})
local filename=$(_preferred_os_filename)
local url=https://sdk.lunarg.com/sdk/download/$VULKAN_SDK_VERSION/$os/$dl_filename?Human=true
@@ -83,22 +79,15 @@ function download_vulkan_installer() {
function unpack_vulkan_installer() {
local os=$(_get_os)
if [[ "$os" == "linux" ]]; then
install_${os}
return 0
fi
local filename=$(_preferred_os_filename $os)
test -f $filename
install_${os}
}
function install_linux() {
echo "Installing Vulkan dependencies via apt for Linux..." >&2
sudo apt-get update -y
sudo apt-get install -y libvulkan-dev vulkan-validationlayers glslang-tools spirv-tools
# Create a dummy SDK dir to satisfy scripts expecting a VulkanSDK folder structure
mkdir -p ~/VulkanSDK/${VULKAN_SDK_VERSION}
test -d $VULKAN_SDK_DIR && test -f vulkan_sdk.tar.gz
echo "extract just the SDK's prebuilt binaries ($VULKAN_SDK_VERSION/x86_64) from vulkan_sdk.tar.gz into $VULKAN_SDK" >&2
tar -C "$VULKAN_SDK_DIR" --strip-components 2 -xf vulkan_sdk.tar.gz $VULKAN_SDK_VERSION/x86_64
}
function install_mac() {
@@ -46,7 +46,6 @@ list(SORT NDK_VERSIONS)
list(GET NDK_VERSIONS -1 NDK_VERSION)
get_filename_component(NDK_VERSION ${NDK_VERSION} NAME)
set(TOOLCHAIN ${ANDROID_HOME_UNIX}/ndk/${NDK_VERSION}/toolchains/llvm/prebuilt/${HOST_NAME_L}-x86_64)
set(CMAKE_ANDROID_NDK_VERSION ${NDK_VERSION})
# specify the cross compiler
set(COMPILER_SUFFIX)
-1
View File
@@ -47,7 +47,6 @@ list(SORT NDK_VERSIONS)
list(GET NDK_VERSIONS -1 NDK_VERSION)
get_filename_component(NDK_VERSION ${NDK_VERSION} NAME)
set(TOOLCHAIN ${ANDROID_HOME_UNIX}/ndk/${NDK_VERSION}/toolchains/llvm/prebuilt/${HOST_NAME_L}-x86_64)
set(CMAKE_ANDROID_NDK_VERSION ${NDK_VERSION})
# specify the cross compiler
set(COMPILER_SUFFIX)
-1
View File
@@ -46,7 +46,6 @@ list(SORT NDK_VERSIONS)
list(GET NDK_VERSIONS -1 NDK_VERSION)
get_filename_component(NDK_VERSION ${NDK_VERSION} NAME)
set(TOOLCHAIN ${ANDROID_HOME_UNIX}/ndk/${NDK_VERSION}/toolchains/llvm/prebuilt/${HOST_NAME_L}-x86_64)
set(CMAKE_ANDROID_NDK_VERSION ${NDK_VERSION})
# specify the cross compiler
set(COMPILER_SUFFIX)
@@ -46,7 +46,6 @@ list(SORT NDK_VERSIONS)
list(GET NDK_VERSIONS -1 NDK_VERSION)
get_filename_component(NDK_VERSION ${NDK_VERSION} NAME)
set(TOOLCHAIN ${ANDROID_HOME_UNIX}/ndk/${NDK_VERSION}/toolchains/llvm/prebuilt/${HOST_NAME_L}-x86_64)
set(CMAKE_ANDROID_NDK_VERSION ${NDK_VERSION})
# specify the cross compiler
set(COMPILER_SUFFIX)
+2 -2
View File
@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.70.0'
implementation 'com.google.android.filament:filament-android:1.69.5'
}
</code></pre>
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
@@ -195,7 +195,7 @@ dependencies {
</div>
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
<p>iOS projects can use CocoaPods to install the latest release:</p>
<pre><code class="language-shell">pod 'Filament', '~&gt; 1.70.0'
<pre><code class="language-shell">pod 'Filament', '~&gt; 1.69.5'
</code></pre>
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
<ul>
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3 -2
View File
@@ -600,7 +600,6 @@ if (FILAMENT_BUILD_TESTING)
test/test_CircularBuffer.cpp
test/test_CommandBufferQueue.cpp
test/test_Template.cpp
test/test_Platform.cpp
)
if (FILAMENT_SUPPORTS_WEBGPU)
list(APPEND BACKEND_TEST_SRC
@@ -616,6 +615,7 @@ if (FILAMENT_BUILD_TESTING)
set(BACKEND_TEST_LIBS
absl::str_format
backend
getopt
gtest
imageio
filamat
@@ -724,9 +724,10 @@ if (FILAMENT_BUILD_TESTING)
target_link_libraries(metal_utils_test PRIVATE
backend
getopt
gtest
)
set_target_properties(metal_utils_test PROPERTIES FOLDER Tests)
endif()
endif()
endif()
+1 -23
View File
@@ -216,18 +216,6 @@ public:
MULTIVIEW,
};
/**
* Types of device/driver information that can be queried from the platform.
*/
enum class DeviceInfoType {
OPENGL_RENDERER, //!< glGetString(GL_RENDERER)
OPENGL_VENDOR, //!< glGetString(GL_VENDOR)
OPENGL_VERSION, //!< glGetString(GL_VERSION)
VULKAN_DEVICE_NAME, //!< VkPhysicalDeviceProperties::deviceName
VULKAN_DRIVER_NAME, //!< VkPhysicalDeviceDriverProperties::driverName
VULKAN_DRIVER_INFO, //!< VkPhysicalDeviceDriverProperties::driverInfo
};
/**
* This controls the priority level for GPU work scheduling, which helps prioritize the
* submitted GPU work and enables preemption.
@@ -328,7 +316,7 @@ public:
*/
StereoscopicType stereoscopicType = StereoscopicType::NONE;
/**
/*
* The number of eyes to render when stereoscopic rendering is enabled. Supported values are
* between 1 and Engine::getMaxStereoscopicEyes() (inclusive).
*/
@@ -389,16 +377,6 @@ public:
*/
virtual int getOSVersion() const noexcept = 0;
/**
* Queries device/driver information of the graphics API.
* @param infoType the type of information to query.
* @param driver a pointer to the current driver.
* @return a CString containing the requested information.
*/
virtual utils::CString getDeviceInfo(DeviceInfoType infoType,
Driver* UTILS_NULLABLE driver) const noexcept = 0;
/**
* Creates and initializes the low-level API (e.g. an OpenGL context or Vulkan instance),
* then creates the concrete Driver.
@@ -52,9 +52,6 @@ protected:
~OpenGLPlatform() noexcept override;
utils::CString getDeviceInfo(DeviceInfoType infoType,
Driver* UTILS_NULLABLE driver) const noexcept override;
public:
struct ExternalTexture {
unsigned int target; // GLenum target
@@ -73,12 +70,6 @@ public:
*/
static utils::CString getRendererString(Driver const* UTILS_NONNULL driver);
/**
* Return the OpenGL version string of the specified Driver instance.
* @return The GL_VERSION string
*/
static utils::CString getVersionString(Driver const* UTILS_NONNULL driver);
/**
* Called by the driver to destroy the OpenGL context. This should clean up any windows
* or buffers from initialization. This is for instance where `eglDestroyContext` would be
@@ -159,12 +159,6 @@ protected:
EGLConfig getEglConfig() const noexcept { return mEGLConfig; }
EGLConfig getSuitableConfigForSwapChain(uint64_t flags, bool window, bool pbuffer) const;
// Sets the EGLDisplay to be used by this platform. This should only be called by derived
// classes before invoking createDriver. Calling it after that point will result in
// undefined behaviour. This class will take ownership of the display and call eglTerminate
// on it during shutdown.
void setEglDisplay(EGLDisplay display) noexcept;
// supported extensions detected at runtime
struct {
struct {
@@ -185,7 +185,7 @@ private:
};
int mOSVersion;
ExternalStreamManagerAndroid* mExternalStreamManager = nullptr;
ExternalStreamManagerAndroid& mExternalStreamManager;
AndroidDetails& mAndroidDetails;
utils::PerformanceHintManager mPerformanceHintManager;
utils::PerformanceHintManager::Session mPerformanceHintSession;
@@ -38,7 +38,6 @@ public:
Driver* createDriver(void* sharedContext, const Platform::DriverConfig& driverConfig) override;
int getOSVersion() const noexcept override { return 0; }
utils::CString getDeviceInfo(DeviceInfoType, Driver*) const noexcept override { return {}; }
/**
* Optionally initializes the Metal platform by acquiring resources necessary for rendering.
@@ -31,6 +31,7 @@
#include <cstring>
#include <cstddef>
#include <functional>
#include <string>
#include <tuple>
#include <unordered_set>
@@ -68,7 +69,7 @@ public:
struct ExtensionHashFn {
std::size_t operator()(utils::CString const& s) const noexcept {
return std::hash<utils::CString>{}(s.data());
return std::hash<std::string>{}(s.data());
}
};
// Note: utils::CString::operator== has an edge case that breaks for the extension set.
@@ -142,8 +143,6 @@ public:
return 0;
}
utils::CString getDeviceInfo(DeviceInfoType infoType, Driver* driver) const noexcept override;
// ----------------------------------------------------
// ---------- Platform Customization options ----------
struct Customization {
@@ -176,12 +175,6 @@ public:
* presentation. Default is true.
*/
bool transitionSwapChainImageLayoutForPresent = true;
/**
* The number of frames before an unused framebuffer is evicted from the cache.
* Default is 3.
*/
uint32_t timeBeforeEvictionFbo = 3;
};
/**
@@ -46,9 +46,6 @@ public:
~WebGPUPlatform() override = default;
[[nodiscard]] int getOSVersion() const noexcept final { return 0; }
[[nodiscard]] utils::CString getDeviceInfo(DeviceInfoType, Driver*) const noexcept override {
return {};
}
[[nodiscard]] wgpu::Instance& getInstance() noexcept { return mInstance; }
+1 -1
View File
@@ -32,7 +32,7 @@
#include <cstddef>
#include <functional>
#include <string_view>
#include <string>
#include <utility>
#ifdef __ANDROID__
-1
View File
@@ -26,7 +26,6 @@ class PlatformNoop final : public Platform {
public:
int getOSVersion() const noexcept final { return 0; }
utils::CString getDeviceInfo(DeviceInfoType, Driver*) const noexcept override { return {}; }
~PlatformNoop() noexcept override = default;
@@ -341,44 +341,7 @@ void GLDescriptorSet::bind(
if (arg.handle) {
GLTexture const* const t = handleAllocator.handle_cast<GLTexture*>(arg.handle);
gl.bindTexture(unit, t->gl.target, t->gl.id, t->gl.external);
SamplerParams params = arg.params;
#if defined(__EMSCRIPTEN__)
// From https://registry.khronos.org/OpenGL-Refpages/es2.0/
// GLES 2.0 will draw mipmapped samplers as black if the following conditions are not met:
//
// "...if the width or height of a texture image are not powers of two and either the
// GL_TEXTURE_MIN_FILTER is set to one of the functions that requires mipmaps or the
// GL_TEXTURE_WRAP_S or GL_TEXTURE_WRAP_T is not set to GL_CLAMP_TO_EDGE, then the
// texture image unit will return (R, G, B, A) = (0, 0, 0, 1)."
//
// "If the texture has dimensions w × h , there are [floor(log2(max(w, h))) + 1] mipmap levels.
// Level 0 is the original texture; level [floor(log2(max(w, h)))] is the final 1 × 1 mipmap."
// "Suppose that a texture is accessed from a fragment shader or vertex shader and has set GL_TEXTURE_MIN_FILTER
// to one of the functions that requires mipmaps. If either the dimensions of the texture images currently
// defined (with previous calls to glTexImage2D, glCompressedTexImage2D, or glCopyTexImage2D) do not follow
// the proper sequence for mipmaps (described above), or there are fewer texture images defined than are needed,
// or the set of texture images were defined with different formats or types, then the texture image unit
// will return (R, G, B, A) = (0, 0, 0, 1)."
//
// So we force the sampler to a valid state in those cases.
auto isPowerOfTwo = [](uint32_t x) -> bool { return (x & (x - 1)) == 0 && x > 0; };
bool textureIsPowerOfTwo = isPowerOfTwo(t->width) && isPowerOfTwo(t->height);
uint32_t requiredMipLevels = (std::floor(std::log2(std::max(t->width, std::max(t->height, t->depth)))) + 1);
bool textureHasRequiredMipLevels = t->levels == requiredMipLevels;
bool samplerCanBeInvalid = params.filterMin > SamplerMinFilter::LINEAR;
if (samplerCanBeInvalid && (!textureIsPowerOfTwo || !textureHasRequiredMipLevels)) {
if (params.filterMin == SamplerMinFilter::NEAREST_MIPMAP_NEAREST
|| params.filterMin == SamplerMinFilter::LINEAR_MIPMAP_NEAREST) {
params.filterMin = SamplerMinFilter::NEAREST;
} else {
params.filterMin = SamplerMinFilter::LINEAR;
}
}
#endif
SamplerParams const params = arg.params;
glTexParameteri(t->gl.target, GL_TEXTURE_MIN_FILTER,
(GLint)GLUtils::getTextureFilter(params.filterMin));
glTexParameteri(t->gl.target, GL_TEXTURE_MAG_FILTER,
@@ -222,15 +222,6 @@ OpenGLContext::OpenGLContext(OpenGLPlatform& platform,
#endif
#endif
#if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_disjoint_timer_query)
if (ext.EXT_disjoint_timer_query) {
mGpuTimeSupported = true;
} else
#endif
if (platform.canCreateFence()) {
mGpuTimeSupported = true;
}
// in practice KHR_Debug has never been useful, and actually is confusing. We keep this
// only for our own debugging, in case we need it some day.
#if false && !defined(NDEBUG) && defined(GL_KHR_debug)
+1 -5
View File
@@ -158,10 +158,8 @@ public:
constexpr static inline size_t getIndexForBufferTarget(GLenum target) noexcept;
ShaderModel getShaderModel() const noexcept { return mShaderModel; }
void resetState() noexcept;
bool isGpuTimeSupported() const noexcept { return mGpuTimeSupported; }
inline void useProgram(GLuint program) noexcept;
@@ -544,8 +542,6 @@ private:
Platform::DriverConfig const mDriverConfig;
bool mGpuTimeSupported = false;
void bindFramebufferResolved(GLenum target, GLuint buffer) noexcept;
const std::array<std::tuple<bool const&, char const*, char const*>, sizeof(bugs)> mBugDatabase{{
+3 -8
View File
@@ -467,8 +467,7 @@ void OpenGLDriver::bindTexture(GLuint const unit, GLTexture const* t) noexcept {
bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
bool success = true;
if (mBoundProgram != p) {
// compile/link the program if needed and call glUseProgram.
// This call may block until the program linking process is complete.
// compile/link the program if needed and call glUseProgram
success = p->use(this, mContext);
assert_invariant(success == p->isValid());
if (success) {
@@ -717,11 +716,7 @@ Handle<HwSwapChain> OpenGLDriver::createSwapChainHeadlessS() noexcept {
}
Handle<HwTimerQuery> OpenGLDriver::createTimerQueryS() noexcept {
Handle<HwTimerQuery> tqh = initHandle<GLTimerQuery>();
// The state must be constructed here, as a synchronous call to getTimerQueryValue might happen
// before createTimerQueryR is executed on the backend thread.
handle_cast<GLTimerQuery*>(tqh)->state = std::make_shared<GLTimerQuery::State>();
return tqh;
return initHandle<GLTimerQuery>();
}
Handle<HwDescriptorSetLayout> OpenGLDriver::createDescriptorSetLayoutS() noexcept {
@@ -2860,7 +2855,7 @@ bool OpenGLDriver::isFrameBufferFetchMultiSampleSupported() {
}
bool OpenGLDriver::isFrameTimeSupported() {
return mContext.isGpuTimeSupported();
return TimerQueryFactory::isGpuTimeSupported();
}
bool OpenGLDriver::isAutoDepthResolveSupported() {
@@ -250,10 +250,6 @@ private:
return utils::CString{ mContext.state.renderer };
}
utils::CString getVersionString() const noexcept override {
return utils::CString{ mContext.state.version };
}
JobQueue* getJobQueue() const noexcept { return mJobQueue.get(); }
JobWorker* getJobWorker() const noexcept { return mJobWorker.get(); }
@@ -34,7 +34,6 @@ protected:
public:
virtual utils::CString getVendorString() const noexcept = 0;
virtual utils::CString getRendererString() const noexcept = 0;
virtual utils::CString getVersionString() const noexcept = 0;
};
} // filament::backend
@@ -45,21 +45,6 @@ Driver* OpenGLPlatform::createDefaultDriver(OpenGLPlatform* platform,
OpenGLPlatform::~OpenGLPlatform() noexcept = default;
utils::CString OpenGLPlatform::getDeviceInfo(DeviceInfoType infoType,
Driver* driver) const noexcept {
switch (infoType) {
case DeviceInfoType::OPENGL_RENDERER:
return getRendererString(driver);
case DeviceInfoType::OPENGL_VENDOR:
return getVendorString(driver);
case DeviceInfoType::OPENGL_VERSION:
return getVersionString(driver);
default:
FILAMENT_CHECK_POSTCONDITION(false) << "Unsupported DeviceInfoType for OpenGLPlatform";
return {};
}
}
utils::CString OpenGLPlatform::getVendorString(Driver const* driver) {
auto const p = static_cast<OpenGLDriverBase const*>(driver);
#if UTILS_HAS_RTTI
@@ -78,15 +63,6 @@ utils::CString OpenGLPlatform::getRendererString(Driver const* driver) {
return p->getRendererString();
}
utils::CString OpenGLPlatform::getVersionString(Driver const* driver) {
auto const p = static_cast<OpenGLDriverBase const*>(driver);
#if UTILS_HAS_RTTI
FILAMENT_CHECK_POSTCONDITION(dynamic_cast<OpenGLDriverBase const*>(driver))
<< "Driver* has not been allocated with OpenGLPlatform";
#endif
return p->getVersionString();
}
void OpenGLPlatform::makeCurrent(SwapChain* drawSwapChain, SwapChain* readSwapChain,
utils::Invocable<void()>, utils::Invocable<void(size_t)>) {
makeCurrent(getCurrentContextType(), drawSwapChain, readSwapChain);
@@ -48,7 +48,7 @@ class OpenGLDriver;
// ------------------------------------------------------------------------------------------------
bool TimerQueryFactory::mGpuTimeSupported = false;
TimerQueryFactoryInterface* TimerQueryFactory::init(
OpenGLPlatform& platform, OpenGLContext& context) {
@@ -65,14 +65,17 @@ TimerQueryFactoryInterface* TimerQueryFactory::init(
} else {
impl = new(std::nothrow) TimerQueryNativeFactory(context);
}
mGpuTimeSupported = true;
} else
#endif
if (platform.canCreateFence()) {
// no timer queries, but we can use fences
impl = new(std::nothrow) TimerQueryFenceFactory(platform);
mGpuTimeSupported = true;
} else {
// no queries, no fences -- that's a problem
impl = new(std::nothrow) TimerQueryFallbackFactory();
mGpuTimeSupported = false;
}
assert_invariant(impl);
return impl;
@@ -85,14 +88,15 @@ TimerQueryFactoryInterface::~TimerQueryFactoryInterface() = default;
// This is a backend synchronous call
TimerQueryResult TimerQueryFactoryInterface::getTimerQueryValue(
GLTimerQuery* tq, uint64_t* elapsedTime) noexcept {
assert_invariant(tq->state);
int64_t const elapsed = tq->state->elapsed.load(std::memory_order_relaxed);
if (elapsed > 0) {
*elapsedTime = elapsed;
return TimerQueryResult::AVAILABLE;
if (UTILS_LIKELY(tq->state)) {
int64_t const elapsed = tq->state->elapsed.load(std::memory_order_relaxed);
if (elapsed > 0) {
*elapsedTime = elapsed;
return TimerQueryResult::AVAILABLE;
}
return TimerQueryResult(elapsed);
}
return TimerQueryResult(elapsed);
return TimerQueryResult::ERROR;
}
// ------------------------------------------------------------------------------------------------
@@ -106,8 +110,9 @@ TimerQueryNativeFactory::TimerQueryNativeFactory(OpenGLContext& context)
TimerQueryNativeFactory::~TimerQueryNativeFactory() = default;
void TimerQueryNativeFactory::createTimerQuery(GLTimerQuery* tq) {
assert_invariant(tq->state);
assert_invariant(!tq->state);
tq->state = std::make_shared<GLTimerQuery::State>();
mContext.procs.genQueries(1u, &tq->state->gl.query);
CHECK_GL_ERROR()
}
@@ -176,7 +181,8 @@ TimerQueryFenceFactory::~TimerQueryFenceFactory() {
}
void TimerQueryFenceFactory::createTimerQuery(GLTimerQuery* tq) {
assert_invariant(tq->state);
assert_invariant(!tq->state);
tq->state = std::make_shared<GLTimerQuery::State>();
}
void TimerQueryFenceFactory::destroyTimerQuery(GLTimerQuery* tq) {
@@ -232,7 +238,8 @@ TimerQueryFallbackFactory::TimerQueryFallbackFactory() = default;
TimerQueryFallbackFactory::~TimerQueryFallbackFactory() = default;
void TimerQueryFallbackFactory::createTimerQuery(GLTimerQuery* tq) {
assert_invariant(tq->state);
assert_invariant(!tq->state);
tq->state = std::make_shared<GLTimerQuery::State>();
}
void TimerQueryFallbackFactory::destroyTimerQuery(GLTimerQuery* tq) {
@@ -61,9 +61,14 @@ struct GLTimerQuery : public HwTimerQuery {
*/
class TimerQueryFactory {
static bool mGpuTimeSupported;
public:
static TimerQueryFactoryInterface* init(
OpenGLPlatform& platform, OpenGLContext& context);
static bool isGpuTimeSupported() noexcept {
return mGpuTimeSupported;
}
};
class TimerQueryFactoryInterface {
@@ -117,13 +117,6 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
cond.wait(l, [this] { return signaled; });
}
// Used in THREAD_POOL mode. Returns true if the token is signaled, meaning it's ready to be
// used.
bool isReady() const noexcept {
std::unique_lock const l(lock);
return signaled;
}
// This is invoked upon token completion, which occurs after a successful `gl.program`
// population or upon cancellation. In either scenario, the callback handle must be submitted
// to notify the caller that resource loading has concluded.
@@ -367,15 +360,14 @@ void ShaderCompilerService::compileProgram(
<< " failed to link or compile";
#endif
}
// The program blob is cached prior to signaling to ensure data integrity.
// Since the receiving thread may immediately modify gl.program (e.g., via
// glUniformBlockBinding) upon receipt of the signal, caching must be
// finalized first.
tryCachingProgram(mBlobCache, mDriver.mPlatform, token);
// Now `token->gl.program` must be populated, so we signal the completion
// of the linking. We don't need to check the result of the program here
// because it'll be done in the engine thread.
token->signal();
// We try caching the program blob after sending the signal. This allows us
// to unblock the engine thread as soon as the token is ready while
// performing an expensive caching operation still in the pool.
tryCachingProgram(mBlobCache, mDriver.mPlatform, token);
// Updates the token's state. If the token is canceled while this function
// executes, this update notifies `tick` that GL resource loading is
// complete, allowing `tick` to proceed with resource destruction.
@@ -532,15 +524,8 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) {
}
void ShaderCompilerService::ensureTokenIsReady(program_token_t const& token) {
if (mMode == Mode::THREAD_POOL) {
// Check if `token->gl.program` is populated
if (token->isReady()) {
return;
}
} else {
if (token->gl.program) {
return;
}
if (token->gl.program) {
return;// It's ready.
}
switch (mMode) {
@@ -950,7 +935,6 @@ void ShaderCompilerService::cancelPendingSynchronousProgram(program_token_t cons
return false;
}
token->retrievedFromBlobCache = true;
token->signal(); // notify that `token->gl.program` is ready to use
return true;
}
@@ -80,9 +80,6 @@ JNIEnv* ExternalStreamManagerAndroid::getEnvironmentSlow() noexcept {
Stream* ExternalStreamManagerAndroid::acquire(jobject surfaceTexture) noexcept {
// note: This is called on the application thread (not the GL thread)
// The application thread *MUST* be attached to the JVM to pass the java 'surfaceTexture' down
// to filament natively, so we are guaranteed to get a valid JNIEnv from getThreadEnvironment()
// without needing AttachCurrentThread.
JNIEnv* env = VirtualMachineEnv::getThreadEnvironment();
if (!env) {
return nullptr; // this should not happen
@@ -34,7 +34,6 @@
#include <utils/Invocable.h>
#include <utils/Logger.h>
#include <utils/Panic.h>
#include <utils/debug.h>
#include <utils/ostream.h>
@@ -120,41 +119,31 @@ bool PlatformEGL::isOpenGL() const noexcept {
PlatformEGL::ExternalImageEGL::~ExternalImageEGL() = default;
void PlatformEGL::setEglDisplay(EGLDisplay display) noexcept {
FILAMENT_CHECK_PRECONDITION(mEGLDisplay == EGL_NO_DISPLAY)
<< "EGL Display has already been set.";
FILAMENT_CHECK_PRECONDITION(display != EGL_NO_DISPLAY)
<< "Must specify a valid EGL Display.";
mEGLDisplay = display;
}
Driver* PlatformEGL::createDriver(void* sharedContext, const DriverConfig& driverConfig) {
if (mEGLDisplay == EGL_NO_DISPLAY) {
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
assert_invariant(mEGLDisplay != EGL_NO_DISPLAY);
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
assert_invariant(mEGLDisplay != EGL_NO_DISPLAY);
EGLint major, minor;
EGLBoolean initialized = eglInitialize(mEGLDisplay, &major, &minor);
EGLint major, minor;
EGLBoolean initialized = eglInitialize(mEGLDisplay, &major, &minor);
if (!initialized) {
EGLDeviceEXT eglDevice;
EGLint numDevices;
PFNEGLQUERYDEVICESEXTPROC const eglQueryDevicesEXT =
PFNEGLQUERYDEVICESEXTPROC(eglGetProcAddress("eglQueryDevicesEXT"));
if (eglQueryDevicesEXT != nullptr) {
eglQueryDevicesEXT(1, &eglDevice, &numDevices);
if(auto* getPlatformDisplay = reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
eglGetProcAddress("eglGetPlatformDisplay"))) {
mEGLDisplay = getPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, eglDevice, nullptr);
initialized = eglInitialize(mEGLDisplay, &major, &minor);
}
if (!initialized) {
EGLDeviceEXT eglDevice;
EGLint numDevices;
PFNEGLQUERYDEVICESEXTPROC const eglQueryDevicesEXT =
PFNEGLQUERYDEVICESEXTPROC(eglGetProcAddress("eglQueryDevicesEXT"));
if (eglQueryDevicesEXT != nullptr) {
eglQueryDevicesEXT(1, &eglDevice, &numDevices);
if(auto* getPlatformDisplay = reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
eglGetProcAddress("eglGetPlatformDisplay"))) {
mEGLDisplay = getPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, eglDevice, nullptr);
initialized = eglInitialize(mEGLDisplay, &major, &minor);
}
}
}
if (UTILS_UNLIKELY(!initialized)) {
LOG(ERROR) << "eglInitialize failed";
return nullptr;
}
if (UTILS_UNLIKELY(!initialized)) {
LOG(ERROR) << "eglInitialize failed";
return nullptr;
}
#if defined(FILAMENT_IMPORT_ENTRY_POINTS)
@@ -717,11 +706,10 @@ bool PlatformEGL::setExternalImage(ExternalImageHandleRef externalImage,
// -----------------------------------------------------------------------------------------------
void PlatformEGL::initializeGlExtensions() noexcept {
// We're guaranteed to be on an ES platform, since we're using EGL
const char* const extensions = (const char*)glGetString(GL_EXTENSIONS);
if (extensions) {
GLUtils::unordered_string_set const glExtensions = GLUtils::split(extensions);
ext.gl.OES_EGL_image_external_essl3 = glExtensions.has("GL_OES_EGL_image_external_essl3");
}
GLUtils::unordered_string_set const glExtensions = GLUtils::split(extensions);
ext.gl.OES_EGL_image_external_essl3 = glExtensions.has("GL_OES_EGL_image_external_essl3");
}
EGLContext PlatformEGL::getContextForType(ContextType const type) const noexcept {
@@ -120,7 +120,8 @@ struct PlatformEGLAndroid::AndroidDetails {
// ---------------------------------------------------------------------------------------------
PlatformEGLAndroid::PlatformEGLAndroid() noexcept
: mAndroidDetails(*(new(std::nothrow) AndroidDetails{})) {
: mExternalStreamManager(ExternalStreamManagerAndroid::create()),
mAndroidDetails(*(new(std::nothrow) AndroidDetails{})) {
mOSVersion = android_get_device_api_level();
if (mOSVersion < 0) {
mOSVersion = __ANDROID_API_FUTURE__;
@@ -134,10 +135,7 @@ PlatformEGLAndroid::~PlatformEGLAndroid() noexcept {
void PlatformEGLAndroid::terminate() noexcept {
mPerformanceHintManager.terminate();
mAndroidDetails.androidFrameCallback.terminate();
if (mExternalStreamManager) {
ExternalStreamManagerAndroid::destroy(mExternalStreamManager);
mExternalStreamManager = nullptr;
}
ExternalStreamManagerAndroid::destroy(&mExternalStreamManager);
PlatformEGL::terminate();
}
@@ -227,10 +225,6 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
mPerformanceHintSession = PerformanceHintManager::Session{
mPerformanceHintManager, &tid, 1, 16'666'667 };
mExternalStreamManager = &ExternalStreamManagerAndroid::create();
FILAMENT_CHECK_POSTCONDITION(mExternalStreamManager)
<< "Failed to create ExternalStreamManagerAndroid";
Driver* driver = PlatformEGL::createDriver(sharedContext, driverConfig);
auto const extensions = GLUtils::split(eglQueryString(getEglDisplay(), EGL_EXTENSIONS));
@@ -617,13 +611,11 @@ void PlatformEGLAndroid::setPresentationTime(int64_t const presentationTimeInNan
}
Platform::Stream* PlatformEGLAndroid::createStream(void* nativeStream) noexcept {
assert_invariant(mExternalStreamManager);
return mExternalStreamManager->acquire(static_cast<jobject>(nativeStream));
return mExternalStreamManager.acquire(static_cast<jobject>(nativeStream));
}
void PlatformEGLAndroid::destroyStream(Stream* stream) noexcept {
assert_invariant(mExternalStreamManager);
mExternalStreamManager->release(stream);
mExternalStreamManager.release(stream);
}
Platform::Sync* PlatformEGLAndroid::createSync() noexcept {
@@ -675,23 +667,19 @@ void PlatformEGLAndroid::destroySync(Sync* sync) noexcept {
}
void PlatformEGLAndroid::attach(Stream* stream, intptr_t const tname) noexcept {
assert_invariant(mExternalStreamManager);
mExternalStreamManager->attach(stream, tname);
mExternalStreamManager.attach(stream, tname);
}
void PlatformEGLAndroid::detach(Stream* stream) noexcept {
assert_invariant(mExternalStreamManager);
mExternalStreamManager->detach(stream);
mExternalStreamManager.detach(stream);
}
void PlatformEGLAndroid::updateTexImage(Stream* stream, int64_t* timestamp) noexcept {
assert_invariant(mExternalStreamManager);
mExternalStreamManager->updateTexImage(stream, timestamp);
mExternalStreamManager.updateTexImage(stream, timestamp);
}
math::mat3f PlatformEGLAndroid::getTransformMatrix(Stream* stream) noexcept {
assert_invariant(mExternalStreamManager);
return mExternalStreamManager->getTransformMatrix(stream);
return mExternalStreamManager.getTransformMatrix(stream);
}
int PlatformEGLAndroid::getOSVersion() const noexcept {
@@ -137,14 +137,6 @@ public:
return mFenceExportFlags;
}
inline const char* getPhysicalDeviceName() const noexcept {
return mPhysicalDeviceProperties.properties.deviceName;
}
inline const char* getDriverName() const noexcept { return mDriverProperties.driverName; }
inline const char* getDriverInfo() const noexcept { return mDriverProperties.driverInfo; }
inline bool isImageCubeArraySupported() const noexcept {
return mPhysicalDeviceFeatures.features.imageCubeArray == VK_TRUE;
}
@@ -220,16 +212,11 @@ public:
return mGlobalPrioritySupported;
}
inline bool isDriverPropertiesSupported() const noexcept { return mDriverPropertiesSupported; }
private:
VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
};
VkPhysicalDeviceDriverProperties mDriverProperties = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES,
};
VkPhysicalDeviceVulkan11Features mPhysicalDeviceVk11Features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES,
};
@@ -259,7 +246,6 @@ private:
bool mProtectedMemorySupported = false;
bool mVertexInputDynamicStateSupported = false;
bool mGlobalPrioritySupported = false;
bool mDriverPropertiesSupported = false;
// These are options that can be enabled or disabled at an application level.
bool mAsyncPipelineCachePrewarmingEnabled = false;
+1 -2
View File
@@ -260,8 +260,7 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext& context,
mPipelineCache(*this, mPlatform->getDevice(), mContext),
mStagePool(mAllocator, &mResourceManager, &mCommands, &mContext.getPhysicalDeviceLimits()),
mBufferCache(mContext, mResourceManager, mAllocator),
mFramebufferCache(mPlatform->getDevice(),
mPlatform->getCustomization().timeBeforeEvictionFbo),
mFramebufferCache(mPlatform->getDevice()),
mYcbcrConversionCache(mPlatform->getDevice()),
mSamplerCache(mPlatform->getDevice()),
mBlitter(mPlatform->getPhysicalDevice(), &mCommands),
+22 -29
View File
@@ -20,7 +20,6 @@
#include "VulkanHandles.h"
#include "vulkan/utils/Image.h"
#include <utils/compiler.h>
#include <utils/Panic.h>
// If any VkRenderPass or VkFramebuffer is unused for more than TIME_BEFORE_EVICTION frames, it
@@ -63,9 +62,8 @@ bool VulkanFboCache::FboKeyEqualFn::operator()(const FboKey& k1, const FboKey& k
return true;
}
VulkanFboCache::VulkanFboCache(VkDevice device, uint32_t timeBeforeEvictionFbo)
: mDevice(device),
mTimeBeforeEvictionFbo(timeBeforeEvictionFbo) {}
VulkanFboCache::VulkanFboCache(VkDevice device)
: mDevice(device) {}
VulkanFboCache::~VulkanFboCache() {
FILAMENT_CHECK_POSTCONDITION(mFramebufferCache.empty() && mRenderPassCache.empty())
@@ -378,36 +376,31 @@ void VulkanFboCache::gc() noexcept {
FVK_SYSTRACE_START("fbocache::gc");
// If this is one of the first few frames, return early to avoid wrapping unsigned integers.
++mCurrentTime;
if (++mCurrentTime <= TIME_BEFORE_EVICTION) {
return;
}
const uint32_t evictTime = mCurrentTime - TIME_BEFORE_EVICTION;
if (UTILS_UNLIKELY(mCurrentTime > mTimeBeforeEvictionFbo)) {
const uint32_t evictTimeFbo = mCurrentTime - mTimeBeforeEvictionFbo;
for (FboMap::iterator iter = mFramebufferCache.begin(); iter != mFramebufferCache.end();) {
const FboVal fbo = iter->second;
if (fbo.timestamp < evictTimeFbo && fbo.handle) {
mRenderPassRefCount[iter->first.renderPass]--;
for (FboMap::iterator iter = mFramebufferCache.begin(); iter != mFramebufferCache.end(); ) {
const FboVal fbo = iter->second;
if (fbo.timestamp < evictTime && fbo.handle) {
mRenderPassRefCount[iter->first.renderPass]--;
// erase(iterator) returns the iterator to the next element.
iter = mFramebufferCache.erase(iter);
} else {
++iter;
}
// erase(iterator) returns the iterator to the next element.
iter = mFramebufferCache.erase(iter);
} else {
++iter;
}
}
if (UTILS_UNLIKELY(mCurrentTime > TIME_BEFORE_EVICTION)) {
const uint32_t evictTimeRp = mCurrentTime - TIME_BEFORE_EVICTION;
for (RenderPassMap::iterator iter = mRenderPassCache.begin();
iter != mRenderPassCache.end();) {
const VkRenderPass handle = iter->second.handle->getVkRenderPass();
if (iter->second.timestamp < evictTimeRp && handle &&
mRenderPassRefCount[handle] == 0) {
// erase(iterator) returns the iterator to the next element.
iter = mRenderPassCache.erase(iter);
mRenderPassRefCount.erase(handle);
} else {
++iter;
}
for (RenderPassMap::iterator iter = mRenderPassCache.begin(); iter != mRenderPassCache.end(); ) {
const VkRenderPass handle = iter->second.handle->getVkRenderPass();
if (iter->second.timestamp < evictTime && handle && mRenderPassRefCount[handle] == 0) {
// erase(iterator) returns the iterator to the next element.
iter = mRenderPassCache.erase(iter);
mRenderPassRefCount.erase(handle);
} else {
++iter;
}
}
+1 -2
View File
@@ -100,7 +100,7 @@ public:
bool operator()(const FboKey& k1, const FboKey& k2) const;
};
explicit VulkanFboCache(VkDevice device, uint32_t timeBeforeEvictionFbo);
explicit VulkanFboCache(VkDevice device);
~VulkanFboCache();
// Retrieves or creates a VkFramebuffer handle.
@@ -130,7 +130,6 @@ private:
RenderPassMap mRenderPassCache;
tsl::robin_map<VkRenderPass, uint32_t> mRenderPassRefCount;
uint32_t mCurrentTime = 0;
uint32_t mTimeBeforeEvictionFbo;
};
} // namespace filament::backend
@@ -704,30 +704,6 @@ VulkanPlatform::VulkanPlatform() = default;
VulkanPlatform::~VulkanPlatform() = default;
utils::CString VulkanPlatform::getDeviceInfo(DeviceInfoType infoType,
Driver* driver) const noexcept {
if (mImpl->mPhysicalDevice == VK_NULL_HANDLE) {
return {};
}
auto& context = mImpl->mContext;
switch (infoType) {
case DeviceInfoType::VULKAN_DEVICE_NAME: {
return utils::CString(context.getPhysicalDeviceName());
}
case DeviceInfoType::VULKAN_DRIVER_NAME: {
return context.isDriverPropertiesSupported() ? utils::CString(context.getDriverName())
: utils::CString();
}
case DeviceInfoType::VULKAN_DRIVER_INFO: {
return context.isDriverPropertiesSupported() ? utils::CString(context.getDriverInfo())
: utils::CString();
}
default:
FILAMENT_CHECK_POSTCONDITION(false) << "Unsupported DeviceInfoType for VulkanPlatform";
return {};
}
}
VulkanPlatform::SwapChainBundle VulkanPlatform::getSwapChainBundle(SwapChainPtr handle) {
return static_cast<VulkanPlatformSwapChainBase*>(handle)->getSwapChainBundle();
}
@@ -976,11 +952,6 @@ void VulkanPlatform::queryAndSetDeviceFeatures(Platform::DriverConfig const& dri
chainStruct(&context.mPhysicalDeviceFeatures, &globalPriorityFeatures);
}
if (vkGetPhysicalDeviceProperties2) {
chainStruct(&context.mPhysicalDeviceProperties, &context.mDriverProperties);
context.mDriverPropertiesSupported = true;
}
// Initialize the following fields: physicalDeviceProperties, memoryProperties,
// physicalDeviceFeatures.
vkGetPhysicalDeviceProperties2(mImpl->mPhysicalDevice, &context.mPhysicalDeviceProperties);
+34 -28
View File
@@ -192,15 +192,8 @@ void WebGPUDriver::flush(int) {
}
void WebGPUDriver::finish(int /* dummy */) {
mReadPixelMapsCounter.waitForAllToFinish();
mQueueManager.finish();
// We use polling to advance webgpu's callback counter until all the read backs have been
// processed. Note that blocking with mReadPixelMapsCounter.waitForAllToFinish will only
// deadlock since we could not advance the counter.
while (!mReadPixelMapsCounter.isIdle()) {
mAdapter.GetInstance().ProcessEvents();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
void WebGPUDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
@@ -947,6 +940,10 @@ size_t WebGPUDriver::getUniformBufferOffsetAlignment(){
void WebGPUDriver::updateIndexBuffer(Handle<HwIndexBuffer> indexBufferHandle,
BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset) {
// make sure command elements (draws, etc.) prior to the buffer update are processed before the
// update on the GPU, otherwise the expected data may not be available at the time certain
// draw calls are made.
flush();
handleCast<WebGPUIndexBuffer>(indexBufferHandle)
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
scheduleDestroy(std::move(bufferDescriptor));
@@ -961,6 +958,10 @@ void WebGPUDriver::updateIndexBufferAsyncR(AsyncCallId jobId,
void WebGPUDriver::updateBufferObject(Handle<HwBufferObject> bufferObjectHandle,
BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset) {
// make sure command elements (draws, etc.) prior to the buffer update are processed before the
// update on the GPU, otherwise the expected data may not be available at the time certain
// draw calls are made.
flush();
handleCast<WebGPUBufferObject>(bufferObjectHandle)
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
scheduleDestroy(std::move(bufferDescriptor));
@@ -1007,6 +1008,10 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
const uint32_t xoffset, const uint32_t yoffset, const uint32_t zoffset,
const uint32_t width, const uint32_t height, const uint32_t depth,
PixelBufferDescriptor&& pixelBufferDescriptor) {
// one way or another this function writes texture(s), thus any commands (draw calls etc.) should
// get submitted prior to these updates so that subsequent commands/draws run with the
// image/texture(s) updated as expected.
flush();
PixelBufferDescriptor* inputData{ &pixelBufferDescriptor };
PixelBufferDescriptor reshapedData;
if (reshape(pixelBufferDescriptor, reshapedData)) {
@@ -1178,6 +1183,11 @@ void WebGPUDriver::generateMipmaps(Handle<HwTexture> textureHandle) {
return; // nothing to do
}
// make sure command elements (draws, etc.) prior to the texture update are processed before the
// update on the GPU.
// this ensures subsequent draw calls are run after the mipmaps have been generated as expected
flush();
const auto usage = wgpuTexture.GetUsage();
FILAMENT_CHECK_PRECONDITION(usage & wgpu::TextureUsage::TextureBinding)
<< "Texture for mipmap generation must have TextureBinding usage.";
@@ -1366,9 +1376,6 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
customDepthStencilMsaaSidecarTextureView);
mRenderPassEncoder = commandEncoder.BeginRenderPass(&renderPassDescriptor);
// TODO: there's a bug here because the webgpu viewport has the origin at top-left, whereas
// filament expects it to be bottom left.
mRenderPassEncoder.SetViewport(
static_cast<float>(params.viewport.left),
static_cast<float>(params.viewport.bottom),
@@ -1456,6 +1463,7 @@ void WebGPUDriver::stopCapture(int /* dummy */) {
void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, const uint32_t x,
const uint32_t y, const uint32_t width, const uint32_t height,
PixelBufferDescriptor&& pixelBufferDescriptor) {
flush();
const auto srcTarget{ handleCast<WebGPURenderTarget>(sourceRenderTargetHandle) };
assert_invariant(srcTarget);
@@ -1513,7 +1521,10 @@ void WebGPUDriver::readTextureToBuffer(wgpu::Texture srcTexture, uint32_t level,
return;
}
auto commandEncoder = mQueueManager.getCommandEncoder();
const wgpu::CommandEncoderDescriptor commandEncoderDescriptor{
.label = "read_texture_to_buffer_command",
};
auto commandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
FILAMENT_CHECK_POSTCONDITION(commandEncoder)
<< "Failed to create command encoder for readTextureToBuffer?";
@@ -1635,6 +1646,9 @@ void WebGPUDriver::readTextureToBuffer(wgpu::Texture srcTexture, uint32_t level,
.depthOrArrayLayers = 1,
};
commandEncoder.CopyTextureToBuffer(&source, &destination, &copySize);
wgpu::CommandBuffer commandBuffer = commandEncoder.Finish();
assert_invariant(commandBuffer);
mDevice.GetQueue().Submit(1, &commandBuffer);
// Map the buffer to read the data
struct UserData final {
@@ -1655,9 +1669,6 @@ void WebGPUDriver::readTextureToBuffer(wgpu::Texture srcTexture, uint32_t level,
});
mReadPixelMapsCounter.startTask();
// We need to flush here before we can readback from the staging buffer since the copy op is
// pending to be submitted.
mQueueManager.flush();
userData->buffer.MapAsync(
wgpu::MapMode::Read, 0, bufferSize, wgpu::CallbackMode::AllowSpontaneous,
[](wgpu::MapAsyncStatus status, const char* message, UserData* userdata) {
@@ -1750,6 +1761,7 @@ void WebGPUDriver::blitDEPRECATED(TargetBufferFlags buffers,
const wgpu::Extent2D destinationSize{ static_cast<uint32_t>(destinationViewport.width),
static_cast<uint32_t>(destinationViewport.height) };
mQueueManager.flush();
wgpu::CommandEncoder commandEncoder = mQueueManager.getCommandEncoder();
const WebGPUBlitter::BlitArgs blitArgs{
.source = { .texture = sourceTexture->getTexture(),
@@ -1769,6 +1781,8 @@ void WebGPUDriver::blitDEPRECATED(TargetBufferFlags buffers,
.filter = filter,
};
mBlitter.blit(mDevice.GetQueue(), commandEncoder, blitArgs);
mQueueManager.flush();
}
void WebGPUDriver::resolve(Handle<HwTexture> destinationTextureHandle, const uint8_t sourceLevel,
@@ -1808,6 +1822,7 @@ void WebGPUDriver::blit(Handle<HwTexture> destinationTextureHandle, const uint8_
Handle<HwTexture> sourceTextureHandle, const uint8_t destinationLevel,
const uint8_t destinationLayer, const math::uint2 sourceOrigin, const math::uint2 size) {
FWGPU_SYSTRACE_SCOPE();
mQueueManager.flush();
wgpu::CommandEncoder commandEncoder = mQueueManager.getCommandEncoder();
const auto sourceTexture{ handleCast<WebGPUTexture>(sourceTextureHandle) };
const auto destinationTexture{ handleCast<WebGPUTexture>(destinationTextureHandle) };
@@ -1831,6 +1846,7 @@ void WebGPUDriver::blit(Handle<HwTexture> destinationTextureHandle, const uint8_
.filter = SamplerMagFilter::NEAREST,
};
mBlitter.blit(mDevice.GetQueue(), commandEncoder, blitArgs);
mQueueManager.flush();
}
void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
@@ -1970,19 +1986,9 @@ void WebGPUDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGr
//todo
}
void WebGPUDriver::scissor(Viewport scissor) {
assert_invariant(mRenderPassEncoder);
assert_invariant(mCurrentRenderTarget);
// The WebGPU scissor starts from the top-left corner
assert_invariant(scissor.left >= 0 &&
mCurrentRenderTarget->height >= scissor.bottom + scissor.height /*top >= 0*/ &&
scissor.width <= mCurrentRenderTarget->width &&
scissor.height <= mCurrentRenderTarget->height);
mRenderPassEncoder.SetScissorRect(scissor.left,
mCurrentRenderTarget->height - scissor.bottom - scissor.height /*top*/, scissor.width,
scissor.height);
void WebGPUDriver::scissor(
Viewport scissor) {
//todo
}
void WebGPUDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
@@ -50,9 +50,6 @@ public:
[[nodiscard]] bool isHeadless() const { return mType == SwapChainType::HEADLESS; }
[[nodiscard]] uint32_t getWidth() const { return mConfig.width; }
[[nodiscard]] uint32_t getHeight() const { return mConfig.height; }
void present(DriverBase& driver);
void setFrameScheduledCallback(CallbackHandler* handler, FrameScheduledCallback&& callback) {
@@ -41,10 +41,5 @@ void AsyncTaskCounter::waitForAllToFinish() {
mFinishedCondition.wait(lock, [this] { return mTasksInProgress == 0; });
}
bool AsyncTaskCounter::isIdle() {
std::lock_guard<std::mutex> lock{ mMutex };
return mTasksInProgress == 0;
}
} // namespace filament::backend::webgpuutils
@@ -51,11 +51,6 @@ public:
*/
void waitForAllToFinish();
/**
* Check if all tasks have finished (work counter is 0) (thread-safe)
*/
bool isIdle();
private:
std::mutex mMutex;
std::condition_variable mFinishedCondition;
+8 -8
View File
@@ -16,7 +16,7 @@
#include "PlatformRunner.h"
#include <utils/getopt.h>
#include <getopt/getopt.h>
#include <iostream>
#include <string>
@@ -30,18 +30,18 @@ TestArguments parseArguments(int argc, char* argv[]) {
// The first colon in OPTSTR turns on silent error reporting. This is important, as the
// arguments may also contain gtest parameters we don't know about.
static constexpr const char* OPTSTR = ":a:kc";
static const utils::getopt::option OPTIONS[] = {
{ "api", utils::getopt::required_argument, nullptr, 'a' },
{ "headless_only", utils::getopt::no_argument, nullptr, 'k' },
{ "ci", utils::getopt::no_argument, nullptr, 'c' },
{ nullptr, 0, nullptr, 0 } // termination of the utils::getopt::option list
static const struct option OPTIONS[] = {
{ "api", required_argument, nullptr, 'a' },
{ "headless_only", no_argument, nullptr, 'k' },
{ "ci", no_argument, nullptr, 'c' },
{ nullptr, 0, nullptr, 0 } // termination of the option list
};
int opt;
int optionIndex = 0;
while ((opt = utils::getopt::getopt_long(argc, argv, OPTSTR, OPTIONS, &optionIndex)) >= 0) {
std::string arg(utils::getopt::optarg ? utils::getopt::optarg : "");
while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &optionIndex)) >= 0) {
std::string arg(optarg ? optarg : "");
switch (opt) {
case 'a':
if (arg == "opengl") {
+3 -4
View File
@@ -35,10 +35,9 @@ test::NativeView getNativeView() {
namespace {
std::array<test::Backend, 3> const VALID_BACKENDS{
test::Backend::OPENGL,
test::Backend::VULKAN,
test::Backend::WEBGPU
std::array<test::Backend, 2> const VALID_BACKENDS{
test::Backend::OPENGL,
test::Backend::VULKAN,
};
}// namespace

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