Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
891003e076 |
@@ -27,24 +27,17 @@ runs:
|
||||
echo "$HASH" > /tmp/commit_hash.txt
|
||||
- name: Find commit message (PR)
|
||||
shell: bash
|
||||
id: checkout_code
|
||||
if: github.event_name == 'pull_request'
|
||||
run: |
|
||||
PR_NUMBER="${{ github.event.pull_request.number }}"
|
||||
# Fetch the head of the PR explicitly to handle forks. Depth 50 ensures we can traverse past recent merge commits.
|
||||
git fetch --depth=50 origin "pull/$PR_NUMBER/head:pr-head"
|
||||
|
||||
COMMIT_HASH=$(git log -1 --no-merges pr-head --format=%H)
|
||||
AUTHOR_NAME=$(git log -1 --no-merges pr-head --format=%an)
|
||||
AUTHOR_EMAIL=$(git log -1 --no-merges pr-head --format=%ae)
|
||||
TSTAMP=$(git log -1 --no-merges pr-head --format=%aI)
|
||||
COMMIT_MESSAGE=$(git log -1 --no-merges pr-head --format=%B)
|
||||
|
||||
echo "commit $COMMIT_HASH" > /tmp/commit_msg.txt
|
||||
echo "Author: ${AUTHOR_NAME}<${AUTHOR_EMAIL}>" >> /tmp/commit_msg.txt
|
||||
echo "Date: ${TSTAMP}" >> /tmp/commit_msg.txt
|
||||
echo "" >> /tmp/commit_msg.txt
|
||||
echo "$COMMIT_MESSAGE" >> /tmp/commit_msg.txt
|
||||
echo "$COMMIT_HASH" > /tmp/commit_hash.txt
|
||||
BEFORE_HASH=$(git rev-parse HEAD)
|
||||
echo "hash=$BEFORE_HASH" >> "$GITHUB_OUTPUT"
|
||||
# Next we will checkout the actual head (not the merge commits) of the PR
|
||||
AFTER_HASH="${{ github.event.pull_request.head.sha }}"
|
||||
git checkout $AFTER_HASH
|
||||
COMMIT_MESSAGE=$(git log -1 --no-merges)
|
||||
echo "$COMMIT_MESSAGE" > /tmp/commit_msg.txt
|
||||
echo "$AFTER_HASH" > /tmp/commit_hash.txt
|
||||
- shell: bash
|
||||
id: action_output
|
||||
run: |
|
||||
@@ -54,4 +47,9 @@ runs:
|
||||
cat /tmp/commit_msg.txt >> "$GITHUB_OUTPUT"
|
||||
echo "$DELIMITER" >> "$GITHUB_OUTPUT"
|
||||
# Get the commit hash
|
||||
echo "hash=$(cat /tmp/commit_hash.txt)" >> "$GITHUB_OUTPUT"
|
||||
echo "hash=$(cat /tmp/commit_hash.txt)" >> "$GITHUB_OUTPUT"
|
||||
- name: Cleanup Find commit message (PR)
|
||||
shell: bash
|
||||
if: github.event_name == 'pull_request'
|
||||
run: |
|
||||
git checkout ${{ steps.checkout_code.outputs.hash }}
|
||||
@@ -188,54 +188,6 @@ jobs:
|
||||
const globber = await glob.create(['out/*.aar', 'out/*.apk', 'out/*.tgz'].join('\n'));
|
||||
await upload({ github, context }, await globber.glob(), TAG);
|
||||
|
||||
sonatype-publish:
|
||||
name: sonatype-publish
|
||||
runs-on: 'ubuntu-24.04-16core'
|
||||
# Depends on the the Android build for the Android binaries.
|
||||
# Depends on the Mac, Linux, and Windows builds for host tools.
|
||||
needs: [build-mac, build-linux, build-windows, build-android]
|
||||
if: github.event_name == 'release' || github.event.inputs.platform == 'android'
|
||||
|
||||
steps:
|
||||
- name: Decide Git ref
|
||||
id: git_ref
|
||||
run: |
|
||||
REF=${RELEASE_TAG:-${GITHUB_REF}}
|
||||
TAG=${REF##*/}
|
||||
echo "ref=${REF}" >> $GITHUB_OUTPUT
|
||||
echo "tag=${TAG}" >> $GITHUB_OUTPUT
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
ref: ${{ steps.git_ref.outputs.ref }}
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
- uses: ./.github/actions/linux-prereq
|
||||
- name: Download Android Release
|
||||
run: |
|
||||
gh release download ${TAG} \
|
||||
--repo ${{ github.repository }} \
|
||||
--pattern 'filament-*-android-native.tgz'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ steps.git_ref.outputs.tag }}
|
||||
- name: Unzip Android Release
|
||||
run: |
|
||||
mkdir -p out/android-release
|
||||
tar -xzvf filament-${TAG}-android-native.tgz -C out/android-release/
|
||||
env:
|
||||
TAG: ${{ steps.git_ref.outputs.tag }}
|
||||
- name: Publish To Sonatype
|
||||
run: |
|
||||
cd android
|
||||
./gradlew publishToSonatype closeSonatypeStagingRepository
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.SONATYPE_USERNAME }}
|
||||
ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.SONATYPE_PASSWORD }}
|
||||
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.MAVEN_SIGNING_KEY }}
|
||||
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.MAVEN_SIGNING_PASSWORD }}
|
||||
|
||||
build-ios:
|
||||
name: build-ios
|
||||
runs-on: macos-14-xlarge
|
||||
|
||||
@@ -6,3 +6,6 @@
|
||||
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||
|
||||
## Release notes for next branch cut
|
||||
|
||||
- engine: fix crash when using variance shadow maps
|
||||
- materials: better shadow normal-bias calculations [⚠️ **New Material Version**]
|
||||
|
||||
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.70.1'
|
||||
implementation 'com.google.android.filament:filament-android:1.69.4'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -50,7 +50,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
||||
iOS projects can use CocoaPods to install the latest release:
|
||||
|
||||
```shell
|
||||
pod 'Filament', '~> 1.70.1'
|
||||
pod 'Filament', '~> 1.69.4'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -7,14 +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.1
|
||||
|
||||
|
||||
## v1.70.0
|
||||
|
||||
- engine: fix crash when using variance shadow maps
|
||||
- materials: better shadow normal-bias calculations [⚠️ **New Material Version**]
|
||||
|
||||
## v1.69.5
|
||||
|
||||
- engine: fix crash when using variance shadow maps
|
||||
|
||||
@@ -40,21 +40,15 @@
|
||||
// - Build and upload artifacts with ./gradlew publish
|
||||
// - Close and release staging repo on Nexus with ./gradlew closeAndReleaseStagingRepository
|
||||
//
|
||||
// The following properties need to be set (either in ~/gradle/gradle.properties, on the command
|
||||
// line, or as environment variables, e.g.: ORG_GRADLE_PROJECT_property=value):
|
||||
// The following is needed in ~/gradle/gradle.properties:
|
||||
//
|
||||
// sonatypeUsername=nexus_user
|
||||
// sonatypePassword=nexus_password
|
||||
//
|
||||
// To sign with a key ring file:
|
||||
// signing.keyId=pgp_key_id
|
||||
// signing.password=pgp_key_password
|
||||
// signing.secretKeyRingFile=/Users/user/.gnupg/maven_signing.key
|
||||
//
|
||||
// To sign with in-memory keys (useful for CI):,
|
||||
// signingKey=ASCII armored key (begins with -----BEGIN PGP PRIVATE KEY BLOCK-----)
|
||||
// signingPassword=key password
|
||||
//
|
||||
|
||||
buildscript {
|
||||
def path = providers
|
||||
@@ -200,7 +194,7 @@ subprojects {
|
||||
google()
|
||||
}
|
||||
|
||||
if (!name.startsWith("sample") && name != "filament-tools" && name != "gradle-plugin") {
|
||||
if (!name.startsWith("sample") && name != "filament-tools") {
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
|
||||
@@ -25,7 +25,7 @@ configured, the corresponding task will be disabled.
|
||||
|
||||
```groovy
|
||||
plugins {
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
filament {
|
||||
@@ -0,0 +1,21 @@
|
||||
plugins {
|
||||
id 'groovy-gradle-plugin'
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
create("filament-plugin") {
|
||||
id = "filament-plugin"
|
||||
implementationClass = "com.google.android.filament.gradle.FilamentPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "com.google.gradle:osdetector-gradle-plugin:1.7.3"
|
||||
}
|
||||
+2
-3
@@ -60,10 +60,9 @@ class ToolsLocator {
|
||||
def classifier =
|
||||
project.extensions.getByType(com.google.gradle.osdetector.OsDetector).classifier
|
||||
|
||||
// If com.google.android.filament.tools-dir is set to a non-empty string, we'll use it as
|
||||
// the tool's base path.
|
||||
// If com.google.android.filament.tools-dir is set, we'll use it as the tool's base path.
|
||||
def toolsDirProp = project.providers.gradleProperty("com.google.android.filament.tools-dir")
|
||||
if (toolsDirProp.isPresent() && !toolsDirProp.get().trim().isEmpty()) {
|
||||
if (toolsDirProp.isPresent()) {
|
||||
def toolsDir = toolsDirProp.get()
|
||||
def path = OperatingSystem.current().isWindows() ?
|
||||
"${toolsDir}/bin/${name}.exe" :
|
||||
@@ -2121,7 +2121,7 @@ public class View {
|
||||
*/
|
||||
public boolean highPrecision = false;
|
||||
/**
|
||||
* @deprecated has no effect.
|
||||
* VSM minimum variance scale, must be positive.
|
||||
*/
|
||||
public float minVarianceScale = 0.5f;
|
||||
/**
|
||||
|
||||
+20
-25
@@ -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)
|
||||
@@ -299,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)) {
|
||||
@@ -397,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")
|
||||
@@ -450,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)
|
||||
}
|
||||
@@ -467,11 +468,5 @@ class ModelViewer(
|
||||
|
||||
companion object {
|
||||
private val kDefaultObjectPosition = Float3(0.0f, 0.0f, -4.0f)
|
||||
private fun defaultCameraManipulator(width: Int, height: Int) : Manipulator {
|
||||
return Manipulator.Builder()
|
||||
.targetPosition(kDefaultObjectPosition.x, kDefaultObjectPosition.y, kDefaultObjectPosition.z)
|
||||
.viewport(width, height)
|
||||
.build(Manipulator.Mode.ORBIT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
plugins {
|
||||
id 'groovy-gradle-plugin'
|
||||
id 'com.gradle.plugin-publish' version '2.1.0'
|
||||
}
|
||||
|
||||
group = "com.google.android.filament"
|
||||
version = "0.1.0"
|
||||
|
||||
gradlePlugin {
|
||||
website = "https://github.com/google/filament/tree/main/android/gradle-plugin"
|
||||
vcsUrl = "https://github.com/google/filament/tree/main/android/gradle-plugin"
|
||||
|
||||
plugins {
|
||||
create("filament-tools") {
|
||||
id = "com.google.android.filament-tools"
|
||||
displayName = "Filament Tools Gradle Plugin"
|
||||
description = "A plugin that helps integrate Filament into Android projects"
|
||||
tags.addAll('android', 'graphics', 'rendering', 'filament', '3d', 'gltf', 'native')
|
||||
implementationClass = "com.google.android.filament.gradle.FilamentPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "com.google.gradle:osdetector-gradle-plugin:1.7.3"
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.70.1
|
||||
VERSION_NAME=1.69.4
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -94,13 +94,6 @@ afterEvaluate { project ->
|
||||
}
|
||||
|
||||
signing {
|
||||
def signingKey = findProperty("signingKey")
|
||||
def signingPassword = findProperty("signingPassword")
|
||||
if (signingKey && signingPassword) {
|
||||
println("Signing with in-memory keys")
|
||||
useInMemoryPgpKeys(signingKey, signingPassword)
|
||||
}
|
||||
|
||||
publishing.publications.all { publication ->
|
||||
sign publication
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
@@ -26,8 +26,7 @@ tasks.register('copyDamagedHelmetGltf', Copy) {
|
||||
preBuild.dependsOn copyDamagedHelmetGltf
|
||||
|
||||
clean.doFirst {
|
||||
delete "src/main/assets/envs"
|
||||
delete "src/main/assets/models"
|
||||
delete "src/main/assets"
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -49,7 +48,6 @@ android {
|
||||
dependencies {
|
||||
implementation deps.kotlin
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'com.google.android.material:material:1.10.0'
|
||||
implementation deps.coroutines.core
|
||||
implementation project(':filament-android')
|
||||
implementation project(':gltfio-android')
|
||||
|
||||
@@ -12,11 +12,10 @@
|
||||
android:supportsRtl="true"
|
||||
android:largeHeap="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:theme="@style/Theme.Material3.DayNight.NoActionBar">
|
||||
android:theme="@android:style/Theme.NoTitleBar">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
|
||||
android:screenOrientation="fullSensor">
|
||||
<intent-filter>
|
||||
|
||||
@@ -1,899 +0,0 @@
|
||||
{
|
||||
"name": "Default Test",
|
||||
"backends": [
|
||||
"opengl"
|
||||
],
|
||||
"models": {
|
||||
"DamagedHelmet": "helmet.glb"
|
||||
},
|
||||
"presets": [
|
||||
{
|
||||
"name": "base",
|
||||
"models": [
|
||||
"DamagedHelmet"
|
||||
],
|
||||
"rendering": {
|
||||
"camera": {
|
||||
"enabled": true,
|
||||
"horizontalFov": 45.0,
|
||||
"center": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"lookAt": [
|
||||
0,
|
||||
0,
|
||||
-1
|
||||
]
|
||||
},
|
||||
"view": {
|
||||
"postProcessingEnabled": true,
|
||||
"dithering": "NONE"
|
||||
},
|
||||
"tolerance": {
|
||||
"maxAbsDiff": 0.1,
|
||||
"maxFailingPixelsFraction": 0.0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tilted",
|
||||
"rendering": {
|
||||
"camera": {
|
||||
"enabled": true,
|
||||
"horizontalFov": 45.0,
|
||||
"center": [
|
||||
-4,
|
||||
-2,
|
||||
-3
|
||||
],
|
||||
"lookAt": [
|
||||
0,
|
||||
0,
|
||||
-4
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"tests": [
|
||||
{
|
||||
"name": "basic",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "rotated",
|
||||
"apply_presets": [
|
||||
"base",
|
||||
"tilted"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ssao",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "msaa",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.msaa.enabled": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom",
|
||||
"apply_presets": [
|
||||
"base",
|
||||
"tilted"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "aa_none",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.antiAliasing": "NONE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "aa_fxaa",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.antiAliasing": "FXAA"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dithering_none",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.dithering": "NONE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "msaa_8",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.msaa.enabled": true,
|
||||
"view.msaa.sampleCount": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "taa_custom",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.taa.enabled": true,
|
||||
"view.taa.feedback": 0.2,
|
||||
"view.taa.jitterPattern": "HALTON_23_X16"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_gtao",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.aoType": "GTAO"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_sao",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.aoType": "SAO"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_radius_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.radius": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_radius_low",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.radius": 0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_power_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.power": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_power_low",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.power": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_bias_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.bias": 0.05
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_resolution_half",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.resolution": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_resolution_full",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.resolution": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_intensity_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.intensity": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_bent_normals",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.bentNormals": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_quality_ultra",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.quality": "ULTRA"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_quality_low",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.quality": "LOW"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_lowPassFilter_ultra",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.lowPassFilter": "ULTRA"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssao_upsampling_ultra",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.ssao.enabled": true,
|
||||
"view.ssao.upsampling": "ULTRA"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssr_basic",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.screenSpaceReflections.enabled": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssr_thickness_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.screenSpaceReflections.enabled": true,
|
||||
"view.screenSpaceReflections.thickness": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssr_bias_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.screenSpaceReflections.enabled": true,
|
||||
"view.screenSpaceReflections.bias": 0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssr_maxDistance_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.screenSpaceReflections.enabled": true,
|
||||
"view.screenSpaceReflections.maxDistance": 5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssr_stride_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.screenSpaceReflections.enabled": true,
|
||||
"view.screenSpaceReflections.stride": 4.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssr_stride_low",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.screenSpaceReflections.enabled": true,
|
||||
"view.screenSpaceReflections.stride": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssr_thickness_low",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.screenSpaceReflections.enabled": true,
|
||||
"view.screenSpaceReflections.thickness": 0.01
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssr_maxDistance_low",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.screenSpaceReflections.enabled": true,
|
||||
"view.screenSpaceReflections.maxDistance": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssr_bias_low",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.screenSpaceReflections.enabled": true,
|
||||
"view.screenSpaceReflections.bias": 0.001
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ssr_quality_combo",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.screenSpaceReflections.enabled": true,
|
||||
"view.screenSpaceReflections.thickness": 0.2,
|
||||
"view.screenSpaceReflections.stride": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_levels_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.levels": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_levels_low",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.levels": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_resolution_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.resolution": 1024
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_strength_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.strength": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_strength_low",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.strength": 0.01
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_blendMode_interpolate",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.blendMode": "INTERPOLATE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_no_threshold",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.threshold": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_quality_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.quality": "HIGH"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_lensflare",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.lensFlare": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_lensflare_no_starburst",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.lensFlare": true,
|
||||
"view.bloom.starburst": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_lensflare_chromatic",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.lensFlare": true,
|
||||
"view.bloom.chromaticAberration": 0.05
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_lensflare_ghosts",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.lensFlare": true,
|
||||
"view.bloom.ghostCount": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_lensflare_ghostSpacing",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.lensFlare": true,
|
||||
"view.bloom.ghostSpacing": 0.8
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_halo_thick",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.lensFlare": true,
|
||||
"view.bloom.haloThickness": 0.2
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bloom_halo_radius",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.bloom.enabled": true,
|
||||
"view.bloom.lensFlare": true,
|
||||
"view.bloom.haloRadius": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dof_basic",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.dof.enabled": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dof_cocScale_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.dof.enabled": true,
|
||||
"view.dof.cocScale": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dof_cocScale_low",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.dof.enabled": true,
|
||||
"view.dof.cocScale": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dof_cocAspectRatio",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.dof.enabled": true,
|
||||
"view.dof.cocAspectRatio": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dof_maxApertureDiameter",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.dof.enabled": true,
|
||||
"view.dof.maxApertureDiameter": 0.05
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dof_filter_none",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.dof.enabled": true,
|
||||
"view.dof.filter": "NONE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dof_nativeResolution",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.dof.enabled": true,
|
||||
"view.dof.nativeResolution": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dof_rings_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.dof.enabled": true,
|
||||
"view.dof.foregroundRingCount": 5,
|
||||
"view.dof.backgroundRingCount": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dof_rings_low",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.dof.enabled": true,
|
||||
"view.dof.foregroundRingCount": 3,
|
||||
"view.dof.backgroundRingCount": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dof_max_coc",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.dof.enabled": true,
|
||||
"view.dof.maxForegroundCOC": 16,
|
||||
"view.dof.maxBackgroundCOC": 16
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fog_basic",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.fog.enabled": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fog_distance",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.fog.enabled": true,
|
||||
"view.fog.distance": 10.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fog_cutOffDistance",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.fog.enabled": true,
|
||||
"view.fog.cutOffDistance": 100.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fog_maximumOpacity",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.fog.enabled": true,
|
||||
"view.fog.maximumOpacity": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fog_height",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.fog.enabled": true,
|
||||
"view.fog.height": 5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fog_heightFalloff",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.fog.enabled": true,
|
||||
"view.fog.heightFalloff": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fog_density_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.fog.enabled": true,
|
||||
"view.fog.density": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fog_inScatteringStart",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.fog.enabled": true,
|
||||
"view.fog.inScatteringStart": 5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fog_inScatteringSize",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.fog.enabled": true,
|
||||
"view.fog.inScatteringSize": 10.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fog_fogColorFromIbl",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.fog.enabled": true,
|
||||
"view.fog.fogColorFromIbl": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "vignette_basic",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.vignette.enabled": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "vignette_midPoint",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.vignette.enabled": true,
|
||||
"view.vignette.midPoint": 0.8
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "vignette_roundness_circle",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.vignette.enabled": true,
|
||||
"view.vignette.roundness": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "vignette_roundness_rect",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.vignette.enabled": true,
|
||||
"view.vignette.roundness": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "vignette_feather_sharp",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.vignette.enabled": true,
|
||||
"view.vignette.feather": 0.1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cg_filmic",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.colorGrading.toneMapping": "FILMIC"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cg_aces",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.colorGrading.toneMapping": "ACES"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cg_agx",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.colorGrading.toneMapping": "AGX"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cg_pbr_neutral",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.colorGrading.toneMapping": "PBR_NEUTRAL"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cg_contrast_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.colorGrading.contrast": 1.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cg_saturation_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.colorGrading.saturation": 1.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cg_exposure_high",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.colorGrading.exposure": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "shadow_vsm",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.shadowType": "VSM"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "shadow_vsm_anisotropy",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.shadowType": "VSM",
|
||||
"view.vsmShadowOptions.anisotropy": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "shadow_vsm_highPrecision",
|
||||
"apply_presets": [
|
||||
"base"
|
||||
],
|
||||
"rendering": {
|
||||
"view.shadowType": "VSM",
|
||||
"view.vsmShadowOptions.highPrecision": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+87
-266
@@ -17,35 +17,36 @@
|
||||
package com.google.android.filament.validation
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.text.Html
|
||||
import android.util.Log
|
||||
import android.view.Choreographer
|
||||
import android.view.SurfaceView
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Spinner
|
||||
import android.widget.ScrollView
|
||||
import android.widget.TextView
|
||||
import com.google.android.filament.utils.KTX1Loader
|
||||
import com.google.android.filament.utils.ModelViewer
|
||||
import com.google.android.filament.utils.Utils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import com.google.android.filament.utils.KTX1Loader
|
||||
import com.google.android.filament.IndirectLight
|
||||
import com.google.android.filament.Skybox
|
||||
import android.graphics.Color
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.nio.ByteBuffer
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Button
|
||||
import android.widget.Spinner
|
||||
import android.widget.AdapterView
|
||||
|
||||
class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
|
||||
@@ -61,17 +62,13 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
private lateinit var choreographer: Choreographer
|
||||
private lateinit var modelViewer: ModelViewer
|
||||
private lateinit var statusTextView: TextView
|
||||
private lateinit var testResultsHeader: TextView
|
||||
private lateinit var resultsContainer: LinearLayout
|
||||
private lateinit var inputManager: ValidationInputManager
|
||||
private var currentInput: ValidationInputManager.ValidationInput? = null
|
||||
|
||||
// UI Elements
|
||||
private lateinit var modeSpinner: Spinner
|
||||
private lateinit var runButton: Button
|
||||
private lateinit var loadButton: Button
|
||||
private lateinit var optionsButton: Button
|
||||
|
||||
private var resultManager: ValidationResultManager? = null
|
||||
|
||||
private var validationRunner: ValidationRunner? = null
|
||||
|
||||
// Frame callback
|
||||
@@ -92,53 +89,25 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
surfaceView.holder.setFixedSize(512, 512)
|
||||
|
||||
statusTextView = findViewById(R.id.status_text)
|
||||
testResultsHeader = findViewById(R.id.test_results_header)
|
||||
modeSpinner = findViewById(R.id.mode_spinner)
|
||||
runButton = findViewById(R.id.run_button)
|
||||
resultsContainer = findViewById(R.id.results_container)
|
||||
|
||||
runButton = findViewById(R.id.run_button)
|
||||
loadButton = findViewById(R.id.load_button)
|
||||
optionsButton = findViewById(R.id.options_button)
|
||||
// Setup Spinner
|
||||
val modes = arrayOf("Run Validation", "Generate Goldens")
|
||||
val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, modes)
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
modeSpinner.adapter = adapter
|
||||
|
||||
// Setup Run Button
|
||||
runButton.setOnClickListener {
|
||||
currentInput?.let { input ->
|
||||
// Always use the generateGoldens flag from the intent/input
|
||||
startValidation(input)
|
||||
val generateGoldens = modeSpinner.selectedItemPosition == 1
|
||||
val newInput = input.copy(generateGoldens = generateGoldens)
|
||||
startValidation(newInput)
|
||||
}
|
||||
}
|
||||
|
||||
// Setup Load Button
|
||||
loadButton.setOnClickListener {
|
||||
showLoadDialog()
|
||||
}
|
||||
|
||||
// Setup Options Menu Button
|
||||
optionsButton.setOnClickListener { view ->
|
||||
val popup = android.widget.PopupMenu(this, view)
|
||||
popup.menu.add(0, 1, 0, "Generate Golden")
|
||||
popup.menu.add(0, 2, 0, "Export Test")
|
||||
popup.menu.add(0, 3, 0, "Export Result")
|
||||
popup.menu.add(0, 4, 0, "Test ADB Info")
|
||||
popup.menu.add(0, 5, 0, "Result ADB Info")
|
||||
|
||||
popup.setOnMenuItemClickListener { item ->
|
||||
when (item.itemId) {
|
||||
1 -> {
|
||||
currentInput?.let { input ->
|
||||
val goldenInput = input.copy(generateGoldens = true)
|
||||
startValidation(goldenInput)
|
||||
}
|
||||
}
|
||||
2 -> exportTestBundleAction()
|
||||
3 -> exportTestResultsAction()
|
||||
4 -> showTestAdbInfo()
|
||||
5 -> showResultAdbInfo()
|
||||
}
|
||||
true
|
||||
}
|
||||
popup.show()
|
||||
}
|
||||
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
|
||||
choreographer = Choreographer.getInstance()
|
||||
@@ -151,134 +120,6 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
handleIntent()
|
||||
}
|
||||
|
||||
private fun showLoadDialog() {
|
||||
val exportDir = getExternalFilesDir(null) ?: filesDir
|
||||
// Filter out result zips (starting with "results_") to only show test bundles
|
||||
val zips = exportDir.listFiles { _, name ->
|
||||
name.endsWith(".zip") && !name.startsWith("results_")
|
||||
}?.sortedByDescending { it.lastModified() } ?: emptyList()
|
||||
|
||||
if (zips.isEmpty()) {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle("Load Test")
|
||||
.setMessage("No test bundles found.")
|
||||
.setPositiveButton("OK", null)
|
||||
.show()
|
||||
return
|
||||
}
|
||||
|
||||
val builder = AlertDialog.Builder(this)
|
||||
builder.setTitle("Select Test Bundle")
|
||||
|
||||
val items = zips.map { it.name }.toTypedArray()
|
||||
|
||||
builder.setItems(items) { dialog, which ->
|
||||
val selectedFile = zips[which]
|
||||
loadZipBundle(selectedFile)
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
builder.setNegativeButton("Cancel", null)
|
||||
builder.show()
|
||||
}
|
||||
|
||||
private fun showTestAdbInfo() {
|
||||
val exportDir = getExternalFilesDir(null) ?: filesDir
|
||||
val path = exportDir.absolutePath
|
||||
val isInternal = path.startsWith(filesDir.absolutePath)
|
||||
val message = StringBuilder()
|
||||
|
||||
message.append("Storage Path: $path<br><br>")
|
||||
|
||||
message.append("<b>--- PULL FROM DEVICE ---</b><br>")
|
||||
if (isInternal) {
|
||||
message.append("<tt>adb shell \"run-as $packageName cat files/<filename>\" > <filename></tt><br><br>")
|
||||
} else {
|
||||
message.append("<tt>adb pull $path/<filename> .</tt><br><br>")
|
||||
}
|
||||
|
||||
message.append("<b>--- PUSH TO DEVICE ---</b><br>")
|
||||
if (isInternal) {
|
||||
message.append("1. <tt>adb push <filename> /sdcard/Download/</tt><br>")
|
||||
message.append("2. <tt>adb shell \"run-as $packageName cp /sdcard/Download/<filename> files/\"</tt><br>")
|
||||
} else {
|
||||
message.append("<tt>adb push <filename> $path/</tt><br>")
|
||||
}
|
||||
message.append("<br>Note: Use underscores instead of spaces in <filename>.")
|
||||
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle("Test Bundle ADB Info")
|
||||
.setMessage(Html.fromHtml(message.toString(), Html.FROM_HTML_MODE_LEGACY))
|
||||
.setPositiveButton("OK", null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun showResultAdbInfo() {
|
||||
val exportDir = getExternalFilesDir(null) ?: filesDir
|
||||
val path = exportDir.absolutePath
|
||||
val isInternal = path.startsWith(filesDir.absolutePath)
|
||||
val message = StringBuilder()
|
||||
|
||||
message.append("<b>--- PULL RESULTS ---</b><br>")
|
||||
if (isInternal) {
|
||||
message.append("<tt>adb shell \"run-as $packageName cat files/<filename>\" > <filename></tt><br><br>")
|
||||
} else {
|
||||
message.append("<tt>adb pull $path/<filename> .</tt><br><br>")
|
||||
}
|
||||
|
||||
message.append("<b>--- AVAILABLE RESULTS ---</b><br>")
|
||||
val zips = exportDir.listFiles { _, name ->
|
||||
name.endsWith(".zip") && name.startsWith("results_")
|
||||
}?.sortedByDescending { it.lastModified() } ?: emptyList()
|
||||
|
||||
if (zips.isEmpty()) {
|
||||
message.append("No result zips found.<br>")
|
||||
} else {
|
||||
zips.forEach { file ->
|
||||
message.append("${file.name}<br>")
|
||||
}
|
||||
}
|
||||
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle("Result ADB Info")
|
||||
.setMessage(Html.fromHtml(message.toString(), Html.FROM_HTML_MODE_LEGACY))
|
||||
.setPositiveButton("OK", null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun loadZipBundle(file: File) {
|
||||
statusTextView.text = "Loading ${file.name}..."
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
try {
|
||||
val config = inputManager.loadFromZip(file)
|
||||
val baseDir = getExternalFilesDir(null) ?: filesDir
|
||||
val outputDir = File(baseDir, "validation_results").apply { mkdirs() }
|
||||
|
||||
// Clear existing results UI and state
|
||||
resultsContainer.removeAllViews()
|
||||
resultManager = null
|
||||
|
||||
val newInput = ValidationInputManager.ValidationInput(
|
||||
config = config,
|
||||
outputDir = outputDir,
|
||||
generateGoldens = false,
|
||||
autoRun = false,
|
||||
autoExport = false,
|
||||
autoExportResults = false,
|
||||
sourceZip = file
|
||||
)
|
||||
|
||||
currentInput = newInput
|
||||
statusTextView.text = "Loaded ${config.name}"
|
||||
Log.i(TAG, "Setting header to: Test Results: ${config.name}")
|
||||
testResultsHeader.text = "${config.name}"
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to load zip", e)
|
||||
statusTextView.text = "Error: ${e.message}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createIndirectLight() {
|
||||
try {
|
||||
val engine = modelViewer.engine
|
||||
@@ -315,18 +156,12 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
try {
|
||||
val input = inputManager.resolveConfig(intent)
|
||||
|
||||
// Update header
|
||||
Log.i(TAG, "handleIntent: Setting header to: Test Results: ${input.config.name}")
|
||||
testResultsHeader.text = "${input.config.name}"
|
||||
currentInput = input
|
||||
|
||||
if (input.autoRun) {
|
||||
startValidation(input)
|
||||
} else {
|
||||
// Just show status
|
||||
statusTextView.text = "Ready: ${input.config.name}"
|
||||
}
|
||||
// Sync spinner with intent
|
||||
modeSpinner.setSelection(if (input.generateGoldens) 1 else 0)
|
||||
|
||||
startValidation(input)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to resolve config", e)
|
||||
statusTextView.text = "Error: ${e.message}"
|
||||
@@ -340,8 +175,6 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
Log.i(TAG, "Starting validation with config: ${input.config.name}")
|
||||
Log.i(TAG, "Output dir: ${input.outputDir.absolutePath}")
|
||||
|
||||
testResultsHeader.text = "${input.config.name}"
|
||||
|
||||
resultManager = ValidationResultManager(input.outputDir)
|
||||
|
||||
validationRunner = ValidationRunner(this, modelViewer, input.config, resultManager!!)
|
||||
@@ -349,6 +182,9 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
validationRunner?.generateGoldens = input.generateGoldens
|
||||
validationRunner?.start()
|
||||
|
||||
// Sync spinner in case it was called programmatically or changed implicitly
|
||||
modeSpinner.setSelection(if (input.generateGoldens) 1 else 0)
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to start validation", e)
|
||||
statusTextView.text = "Error: ${e.message}"
|
||||
@@ -360,12 +196,6 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
choreographer.postFrameCallback(frameScheduler)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
setIntent(intent)
|
||||
handleIntent()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
choreographer.removeFrameCallback(frameScheduler)
|
||||
@@ -391,30 +221,13 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
resultContainer.orientation = LinearLayout.VERTICAL
|
||||
resultContainer.setPadding(0, 10, 0, 20)
|
||||
|
||||
// Header Layout
|
||||
val headerRow = LinearLayout(this)
|
||||
headerRow.orientation = LinearLayout.HORIZONTAL
|
||||
headerRow.gravity = android.view.Gravity.CENTER_VERTICAL
|
||||
|
||||
// Status Icon + Name
|
||||
val statusView = TextView(this)
|
||||
val icon = if (result.passed) "✔" else "✖"
|
||||
|
||||
statusView.text = "$icon ${result.testName}"
|
||||
statusView.setTextColor(
|
||||
if (result.passed) Color.parseColor("#4CAF50") else Color.parseColor("#F44336")
|
||||
)
|
||||
statusView.textSize = 12f
|
||||
statusView.setTypeface(null, android.graphics.Typeface.BOLD)
|
||||
headerRow.addView(statusView)
|
||||
|
||||
// Diff Metric (only show if it's relevant/there's a diff or we just always show it like before)
|
||||
val diffView = TextView(this)
|
||||
diffView.text = " (Diff: ${result.diffMetric})"
|
||||
diffView.textSize = 12f
|
||||
diffView.setTextColor(Color.GRAY)
|
||||
headerRow.addView(diffView)
|
||||
resultContainer.addView(headerRow)
|
||||
// Header
|
||||
val resultView = TextView(this)
|
||||
resultView.text = "${result.testName}: ${if(result.passed) "PASS" else "FAIL"} (Diff: ${result.diffMetric})"
|
||||
resultView.setTextColor(if(result.passed) Color.GREEN else Color.RED)
|
||||
resultView.textSize = 16f
|
||||
resultView.setTypeface(null, android.graphics.Typeface.BOLD)
|
||||
resultContainer.addView(resultView)
|
||||
|
||||
// Images Row
|
||||
val imagesRow = LinearLayout(this)
|
||||
@@ -428,7 +241,7 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
|
||||
val labelView = TextView(this)
|
||||
labelView.text = label
|
||||
labelView.textSize = 9f
|
||||
labelView.textSize = 12f
|
||||
container.addView(labelView)
|
||||
|
||||
val iv = ImageView(this)
|
||||
@@ -461,46 +274,7 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
override fun onAllTestsFinished() {
|
||||
runOnUiThread {
|
||||
statusTextView.text = "All tests finished!"
|
||||
Log.i(TAG, "All tests finished " + if (currentInput?.autoExport == true) "Exporting bundle" else "x")
|
||||
|
||||
if (currentInput?.autoExport == true) {
|
||||
exportTestBundleAction()
|
||||
}
|
||||
if (currentInput?.autoExportResults == true) {
|
||||
exportTestResultsAction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun exportTestBundleAction() {
|
||||
currentInput?.let { input ->
|
||||
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
|
||||
val rm = resultManager ?: ValidationResultManager(input.outputDir)
|
||||
val zip = rm.exportTestBundle(input.config, timestamp)
|
||||
if (zip != null) {
|
||||
val msg = "Exported Bundle: ${zip.name}"
|
||||
statusTextView.text = msg
|
||||
Log.i(TAG, "Exported test bundle to ${zip.absolutePath}")
|
||||
} else {
|
||||
statusTextView.text = "Export Bundle failed"
|
||||
Log.e(TAG, "Export Bundle failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun exportTestResultsAction() {
|
||||
currentInput?.let { input ->
|
||||
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
|
||||
val rm = resultManager ?: ValidationResultManager(input.outputDir)
|
||||
val zip = rm.exportTestResults(input.sourceZip, timestamp)
|
||||
if (zip != null) {
|
||||
val msg = "Exported Results: ${zip.name}"
|
||||
statusTextView.text = msg
|
||||
Log.i(TAG, "Exported results to ${zip.absolutePath}")
|
||||
} else {
|
||||
statusTextView.text = "Export Results failed"
|
||||
Log.e(TAG, "Export Results failed")
|
||||
}
|
||||
Log.i(TAG, "All tests finished")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -527,3 +301,50 @@ class MainActivity : Activity(), ValidationRunner.Callback {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Scripts for reference:
|
||||
*
|
||||
* generate_goldens.sh:
|
||||
* --------------------
|
||||
* #!/bin/bash
|
||||
* set -e
|
||||
*
|
||||
* # Config path (on device)
|
||||
* CONFIG_PATH=$1
|
||||
* if [ -z "$CONFIG_PATH" ]; then
|
||||
* echo "Usage: $0 <device_config_path>"
|
||||
* echo "Example: $0 /sdcard/Android/data/com.google.android.filament.validation/files/default_test.json"
|
||||
* exit 1
|
||||
* fi
|
||||
*
|
||||
* echo "Starting Golden Generation for $CONFIG_PATH..."
|
||||
* adb shell am force-stop com.google.android.filament.validation
|
||||
* adb shell am start -n com.google.android.filament.validation/.MainActivity \
|
||||
* -e test_config "$CONFIG_PATH" \
|
||||
* --ez generate_goldens true
|
||||
*
|
||||
* echo "Check device or logcat for progress."
|
||||
* echo "adb logcat -s FilamentValidation:I ValidationRunner:I"
|
||||
* echo "To pull results: ./samples/sample-render-validation/pull_goldens.sh"
|
||||
*
|
||||
* pull_goldens.sh:
|
||||
* ----------------
|
||||
* #!/bin/bash
|
||||
* set -e
|
||||
*
|
||||
* # Default destination is local golden directory relative to script
|
||||
* SCRIPT_DIR=$(cd $(dirname $0); pwd)
|
||||
* DEST_DIR=${1:-"$SCRIPT_DIR/golden"}
|
||||
*
|
||||
* echo "Pulling goldens to $DEST_DIR..."
|
||||
* mkdir -p "$DEST_DIR"
|
||||
*
|
||||
* # Path on device
|
||||
* DEVICE_GOLDEN_DIR="/storage/emulated/0/Android/data/com.google.android.filament.validation/files/golden/."
|
||||
*
|
||||
* adb pull "$DEVICE_GOLDEN_DIR" "$DEST_DIR"
|
||||
*
|
||||
* echo "Done."
|
||||
* ls -l "$DEST_DIR"
|
||||
*/
|
||||
|
||||
+7
-101
@@ -26,7 +26,6 @@ import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
/**
|
||||
* Handles the retrieval and preparation of test configuration and assets.
|
||||
@@ -43,11 +42,7 @@ class ValidationInputManager(private val context: Context) {
|
||||
data class ValidationInput(
|
||||
val config: RenderTestConfig,
|
||||
val outputDir: File,
|
||||
val generateGoldens: Boolean,
|
||||
val autoRun: Boolean = false,
|
||||
val autoExport: Boolean = false,
|
||||
val autoExportResults: Boolean = false,
|
||||
val sourceZip: File? = null
|
||||
val generateGoldens: Boolean
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -58,111 +53,22 @@ class ValidationInputManager(private val context: Context) {
|
||||
val testConfigPath = intent.getStringExtra("test_config")
|
||||
val urlConfig = intent.getStringExtra("url_config")
|
||||
val urlModelsBase = intent.getStringExtra("url_models_base")
|
||||
val zipPath = intent.getStringExtra("zip_path")
|
||||
val generateGoldens = intent.getBooleanExtra("generate_goldens", false)
|
||||
val autoRun = intent.getBooleanExtra("auto_run", false)
|
||||
val autoExport = intent.getBooleanExtra("auto_export", false)
|
||||
val autoExportResults = intent.getBooleanExtra("auto_export_results", false)
|
||||
val outputPath = intent.getStringExtra("output_path")
|
||||
|
||||
Log.i(TAG, "Resolving config with outputPath: $outputPath")
|
||||
|
||||
val baseDir = context.getExternalFilesDir(null) ?: context.filesDir
|
||||
Log.i(TAG, "Base directory for resolution: ${baseDir.absolutePath}")
|
||||
|
||||
val outputDir = if (!outputPath.isNullOrBlank()) {
|
||||
val file = File(outputPath)
|
||||
val resolved = if (file.isAbsolute) {
|
||||
file
|
||||
} else {
|
||||
File(baseDir, outputPath)
|
||||
}
|
||||
|
||||
// Critical check: if resolved is root or very short, it's likely wrong
|
||||
if (resolved.absolutePath == "/" || resolved.parent == null) {
|
||||
Log.w(TAG, "Resolved outputDir is root ($resolved), defaulting to app-specific dir")
|
||||
File(baseDir, "validation_results")
|
||||
} else {
|
||||
resolved
|
||||
}
|
||||
val outputDir = if (outputPath != null) {
|
||||
File(outputPath).apply { mkdirs() }
|
||||
} else {
|
||||
File(baseDir, "validation_results")
|
||||
}
|
||||
|
||||
if (!outputDir.exists() && !outputDir.mkdirs()) {
|
||||
Log.e(TAG, "Failed to create outputDir: ${outputDir.absolutePath}")
|
||||
}
|
||||
Log.i(TAG, "Final outputDir: ${outputDir.absolutePath}")
|
||||
|
||||
val sourceZipFile = if (zipPath != null) {
|
||||
val file = File(zipPath)
|
||||
if (file.isAbsolute) {
|
||||
file
|
||||
} else {
|
||||
File(baseDir, zipPath)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
File(context.getExternalFilesDir(null), "validation_results").apply { mkdirs() }
|
||||
}
|
||||
|
||||
val config = when {
|
||||
sourceZipFile != null && sourceZipFile.exists() -> loadFromZip(sourceZipFile)
|
||||
urlConfig != null -> downloadConfig(urlConfig, urlModelsBase)
|
||||
testConfigPath != null -> ConfigParser.parseFromPath(testConfigPath)
|
||||
else -> extractDefaultAssets()
|
||||
}
|
||||
|
||||
return@withContext ValidationInput(config, outputDir, generateGoldens, autoRun, autoExport, autoExportResults, sourceZipFile)
|
||||
}
|
||||
|
||||
suspend fun loadFromZip(zipFile: File): RenderTestConfig {
|
||||
Log.i(TAG, "Unzipping validation bundle: ${zipFile.absolutePath}")
|
||||
// Use a unique cache dir based on timestamp or just overwrite a common one
|
||||
// Overwriting is safer to avoid filling up disk, but we must ensure we don't conflict with current run
|
||||
val baseCacheDir = context.externalCacheDir ?: context.cacheDir
|
||||
val cacheDir = File(baseCacheDir, "unzipped_validation")
|
||||
if (cacheDir.exists()) cacheDir.deleteRecursively()
|
||||
cacheDir.mkdirs()
|
||||
|
||||
Log.i(TAG, "Using cacheDir: ${cacheDir.absolutePath}")
|
||||
|
||||
ZipFile(zipFile).use { zip ->
|
||||
val entries = zip.entries()
|
||||
while (entries.hasMoreElements()) {
|
||||
val entry = entries.nextElement()
|
||||
val entryFile = File(cacheDir, entry.name)
|
||||
// specific check to avoid zip slip vulnerability (though low risk here)
|
||||
if (!entryFile.canonicalPath.startsWith(cacheDir.canonicalPath)) {
|
||||
throw SecurityException("Zip entry is outside of the target dir: ${entry.name}")
|
||||
}
|
||||
|
||||
if (entry.isDirectory) {
|
||||
entryFile.mkdirs()
|
||||
} else {
|
||||
entryFile.parentFile?.mkdirs()
|
||||
zip.getInputStream(entry).use { input ->
|
||||
FileOutputStream(entryFile).use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find config.json
|
||||
// We look for a file ending in .json within the unzipped structure
|
||||
// Exclude results.json if it happened to be there
|
||||
val jsonFiles = cacheDir.walkTopDown()
|
||||
.filter { it.isFile && it.extension == "json" && it.name != "results.json" }
|
||||
.toList()
|
||||
|
||||
if (jsonFiles.isEmpty()) throw IllegalStateException("No config.json found in zip")
|
||||
|
||||
// Prefer one named config.json or take the first one
|
||||
val configFile = jsonFiles.find { it.name == "config.json" } ?: jsonFiles.first()
|
||||
|
||||
Log.i(TAG, "Parsed config from ${configFile.absolutePath}")
|
||||
return ConfigParser.parseFromPath(configFile.absolutePath)
|
||||
return@withContext ValidationInput(config, outputDir, generateGoldens)
|
||||
}
|
||||
|
||||
private suspend fun extractDefaultAssets(): RenderTestConfig {
|
||||
@@ -184,9 +90,9 @@ class ValidationInputManager(private val context: Context) {
|
||||
// Copy DamagedHelmet.glb
|
||||
val modelsDir = File(filesDir, "models")
|
||||
modelsDir.mkdirs()
|
||||
val modelOut = File(modelsDir, "helmet.glb")
|
||||
val modelOut = File(modelsDir, "DamagedHelmet.glb")
|
||||
|
||||
assetManager.open("models/helmet.glb").use { input ->
|
||||
assetManager.open("DamagedHelmet.glb").use { input ->
|
||||
FileOutputStream(modelOut).use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
|
||||
+8
-187
@@ -67,201 +67,22 @@ class ValidationResultManager(private val outputDir: File) {
|
||||
fun finalizeResults(): File? {
|
||||
// Write results JSON
|
||||
writeResultsJson()
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports a zip containing:
|
||||
* - results.json
|
||||
* - input test bundle (as nested zip), if provided
|
||||
* - diff images (if any failure)
|
||||
*/
|
||||
fun exportTestResults(sourceZip: File?, timestamp: String): File? {
|
||||
// Safe parent dir resolution
|
||||
val parentDir = outputDir.canonicalFile.parentFile ?: outputDir.parentFile
|
||||
if (parentDir == null) return null
|
||||
|
||||
val resultZipName = "results_$timestamp"
|
||||
val zipFile = File(parentDir, "$resultZipName.zip")
|
||||
|
||||
Log.i(TAG, "Exporting results to ${zipFile.absolutePath}")
|
||||
|
||||
// Zip results
|
||||
val zipFile = File(outputDir, "results.zip")
|
||||
try {
|
||||
ZipOutputStream(FileOutputStream(zipFile)).use { zos ->
|
||||
// 1. Add results.json
|
||||
val resultsJson = File(outputDir, "results.json")
|
||||
if (resultsJson.exists()) {
|
||||
zos.putNextEntry(ZipEntry("results.json"))
|
||||
resultsJson.inputStream().use { it.copyTo(zos) }
|
||||
zos.closeEntry()
|
||||
}
|
||||
|
||||
// 2. Add source zip if exists
|
||||
if (sourceZip != null && sourceZip.exists()) {
|
||||
zos.putNextEntry(ZipEntry(sourceZip.name))
|
||||
sourceZip.inputStream().use { it.copyTo(zos) }
|
||||
zos.closeEntry()
|
||||
}
|
||||
|
||||
// 3. Add 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) }
|
||||
outputDir.walkTopDown().filter { it.isFile && it.name != "results.zip" }.forEach { file ->
|
||||
val entryName = file.relativeTo(outputDir).path
|
||||
zos.putNextEntry(ZipEntry(entryName))
|
||||
file.inputStream().use { it.copyTo(zos) }
|
||||
zos.closeEntry()
|
||||
}
|
||||
}
|
||||
Log.i(TAG, "Exported results to ${zipFile.absolutePath}")
|
||||
Log.i(TAG, "Zipped results to ${zipFile.absolutePath}")
|
||||
return zipFile
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to export results", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports a zip bundle containing:
|
||||
* - Modified config.json (with updated name and relative paths)
|
||||
* - Models (in models/ subdirectory)
|
||||
* - Golden images (in goldens/ subdirectory)
|
||||
*
|
||||
* Structure:
|
||||
* test_name_TIMESTAMP/
|
||||
* config.json
|
||||
* models/
|
||||
* model.glb
|
||||
* goldens/
|
||||
* test_result.png
|
||||
*/
|
||||
fun exportTestBundle(config: RenderTestConfig, timestamp: String): File? {
|
||||
Log.i(TAG, "Starting exportTestBundle for ${config.name} at $timestamp")
|
||||
Log.i(TAG, "OutputDir: ${outputDir.absolutePath}")
|
||||
|
||||
val parentDir = outputDir.canonicalFile.parentFile ?: outputDir.parentFile
|
||||
if (parentDir == null) {
|
||||
Log.e(TAG, "OutputDir parent is null: ${outputDir.absolutePath}")
|
||||
return null
|
||||
}
|
||||
Log.i(TAG, "Using parentDir for export: ${parentDir.absolutePath}")
|
||||
|
||||
val testNameWithTimestamp = "${config.name}_$timestamp"
|
||||
val exportNameNoSpaces = testNameWithTimestamp.replace(" ", "_")
|
||||
|
||||
val exportDir = File(parentDir, "export_temp_$timestamp")
|
||||
|
||||
Log.i(TAG, "Creating export temp dir: ${exportDir.absolutePath}")
|
||||
if (exportDir.exists()) exportDir.deleteRecursively()
|
||||
if (!exportDir.mkdirs()) {
|
||||
Log.e(TAG, "Failed to create export dir: ${exportDir.absolutePath}")
|
||||
return null
|
||||
}
|
||||
|
||||
val rootDir = File(exportDir, exportNameNoSpaces)
|
||||
rootDir.mkdirs()
|
||||
|
||||
val modelsDir = File(rootDir, "models")
|
||||
modelsDir.mkdirs()
|
||||
|
||||
val goldensDir = File(rootDir, "goldens")
|
||||
goldensDir.mkdirs()
|
||||
|
||||
try {
|
||||
// 1. Copy Models and update config map
|
||||
val newModelsMap = mutableMapOf<String, String>()
|
||||
Log.i(TAG, "Copying models...")
|
||||
for ((modelName, modelPath) in config.models) {
|
||||
val sourceFile = File(modelPath)
|
||||
if (sourceFile.exists()) {
|
||||
val destFile = File(modelsDir, sourceFile.name)
|
||||
Log.d(TAG, "Copying model $modelName: $modelPath -> ${destFile.name}")
|
||||
sourceFile.copyTo(destFile, overwrite = true)
|
||||
// Use relative path for the new config
|
||||
newModelsMap[modelName] = "models/${sourceFile.name}"
|
||||
} else {
|
||||
Log.w(TAG, "Model file not found: $modelPath")
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Copy Golden Images
|
||||
// We assume goldens are in outputDir with .png extension
|
||||
Log.i(TAG, "Copying goldens from ${outputDir.absolutePath}...")
|
||||
outputDir.listFiles { _, name -> name.endsWith(".png") }?.forEach { file ->
|
||||
Log.d(TAG, "Copying golden: ${file.name}")
|
||||
file.copyTo(File(goldensDir, file.name), overwrite = true)
|
||||
}
|
||||
|
||||
// 3. Create modified config JSON
|
||||
Log.i(TAG, "Creating config.json...")
|
||||
val newConfigJson = JSONObject()
|
||||
newConfigJson.put("name", testNameWithTimestamp) // Keep spaces in JSON name
|
||||
|
||||
// Reconstruct backends
|
||||
val backendsArray = JSONArray()
|
||||
config.backends.forEach { backendsArray.put(it) }
|
||||
newConfigJson.put("backends", backendsArray)
|
||||
|
||||
// Reconstruct models
|
||||
val modelsJson = JSONObject()
|
||||
for ((k, v) in newModelsMap) {
|
||||
modelsJson.put(k, v)
|
||||
}
|
||||
newConfigJson.put("models", modelsJson)
|
||||
|
||||
// Reconstruct tests
|
||||
val testsArray = JSONArray()
|
||||
for (test in config.tests) {
|
||||
val testJson = JSONObject()
|
||||
testJson.put("name", test.name)
|
||||
if (test.description != null) testJson.put("description", test.description)
|
||||
|
||||
// Models for this test (set of strings)
|
||||
val testModelsArray = JSONArray()
|
||||
test.models.forEach { testModelsArray.put(it) }
|
||||
testJson.put("models", testModelsArray)
|
||||
|
||||
// Rendering settings
|
||||
testJson.put("rendering", test.rendering)
|
||||
|
||||
// Tolerance
|
||||
if (test.tolerance != null) {
|
||||
testJson.put("tolerance", test.tolerance)
|
||||
}
|
||||
|
||||
// Backends (optional override)
|
||||
val testBackends = JSONArray()
|
||||
test.backends.forEach { testBackends.put(it) }
|
||||
testJson.put("backends", testBackends)
|
||||
|
||||
testsArray.put(testJson)
|
||||
}
|
||||
newConfigJson.put("tests", testsArray)
|
||||
|
||||
// Write config.json
|
||||
File(rootDir, "config.json").writeText(newConfigJson.toString(4))
|
||||
|
||||
// 4. Zip it
|
||||
val zipFile = File(parentDir, "$exportNameNoSpaces.zip")
|
||||
Log.i(TAG, "Zipping to ${zipFile.absolutePath}...")
|
||||
|
||||
ZipOutputStream(FileOutputStream(zipFile)).use { zos ->
|
||||
rootDir.walkTopDown().forEach { file ->
|
||||
if (file.isFile) {
|
||||
val entryName = file.relativeTo(exportDir).path
|
||||
zos.putNextEntry(ZipEntry(entryName))
|
||||
file.inputStream().use { it.copyTo(zos) }
|
||||
zos.closeEntry()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup temp dir
|
||||
exportDir.deleteRecursively()
|
||||
|
||||
Log.i(TAG, "Exported test bundle to ${zipFile.absolutePath}")
|
||||
return zipFile
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to export test bundle", e)
|
||||
exportDir.deleteRecursively()
|
||||
Log.e(TAG, "Failed to zip results", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
+53
-47
@@ -41,13 +41,18 @@ class ValidationRunner(
|
||||
private var currentTestConfig: TestConfig? = null
|
||||
private var currentModelName: String? = null
|
||||
|
||||
private var loadStartFence: com.google.android.filament.Fence? = null
|
||||
private var loadStartTime = 0L
|
||||
private var frameCounter = 0
|
||||
|
||||
enum class State {
|
||||
IDLE,
|
||||
LOADING_MODEL,
|
||||
WAITING_FOR_FENCE,
|
||||
WAITING_FOR_RESOURCES,
|
||||
WARMUP,
|
||||
RUNNING_TEST
|
||||
RUNNING_TEST,
|
||||
COMPARING
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
@@ -88,6 +93,8 @@ class ValidationRunner(
|
||||
nextModel()
|
||||
return
|
||||
}
|
||||
|
||||
currentState = State.LOADING_MODEL
|
||||
callback?.onStatusChanged("Loading $modelName for ${currentTestConfig?.name}")
|
||||
|
||||
// Load model on main thread (required by ModelViewer)
|
||||
@@ -103,24 +110,51 @@ class ValidationRunner(
|
||||
Log.i("ValidationRunner", "Loading GLB buffer... (${bytes.size} bytes)")
|
||||
val buffer = ByteBuffer.wrap(bytes)
|
||||
modelViewer.loadModelGlb(buffer)
|
||||
Log.i("ValidationRunner", "Model loaded.")
|
||||
Log.i("ValidationRunner", "Model loaded. initializing fence.")
|
||||
modelViewer.transformToUnitCube()
|
||||
currentState = State.WAITING_FOR_RESOURCES
|
||||
frameCounter = 0
|
||||
Log.i("ValidationRunner", "State set to WAITING_FOR_RESOURCES")
|
||||
loadStartFence = modelViewer.engine.createFence()
|
||||
loadStartTime = System.nanoTime()
|
||||
currentState = State.WAITING_FOR_FENCE
|
||||
frameCounter = 0 // Reset for fence timeout tracking
|
||||
Log.i("ValidationRunner", "State set to WAITING_FOR_FENCE")
|
||||
} catch (e: Exception) {
|
||||
Log.e("ValidationRunner", "Failed to load $path", e)
|
||||
nextModel()
|
||||
Log.e("ValidationRunner", "Failed to load $path", e)
|
||||
nextModel()
|
||||
}
|
||||
}
|
||||
|
||||
fun onFrame(frameTimeNanos: Long) {
|
||||
if (frameCounter % 60 == 0) {
|
||||
Log.i("ValidationRunner", "onFrame: $currentState (frame: $frameCounter)")
|
||||
Log.i("ValidationRunner", "onFrame: $currentState (frame: $frameCounter)")
|
||||
}
|
||||
|
||||
when (currentState) {
|
||||
State.IDLE -> {}
|
||||
State.WAITING_FOR_FENCE -> {
|
||||
frameCounter++
|
||||
if (frameCounter > 600) {
|
||||
Log.w("ValidationRunner", "Fence timed out after 600 frames! Forcing proceed.")
|
||||
modelViewer.engine.destroyFence(loadStartFence!!)
|
||||
loadStartFence = null
|
||||
currentState = State.WAITING_FOR_RESOURCES
|
||||
return
|
||||
}
|
||||
|
||||
loadStartFence?.let { fence ->
|
||||
if (fence.wait(com.google.android.filament.Fence.Mode.FLUSH, 0) ==
|
||||
com.google.android.filament.Fence.FenceStatus.CONDITION_SATISFIED) {
|
||||
modelViewer.engine.destroyFence(fence)
|
||||
loadStartFence = null
|
||||
|
||||
// Compile materials (simplified)
|
||||
modelViewer.scene.forEach { entity ->
|
||||
// ... existing material compilation logic ...
|
||||
}
|
||||
|
||||
currentState = State.WAITING_FOR_RESOURCES
|
||||
}
|
||||
}
|
||||
}
|
||||
State.WAITING_FOR_RESOURCES -> {
|
||||
val progress = modelViewer.progress
|
||||
if (progress >= 1.0f) {
|
||||
@@ -132,12 +166,12 @@ class ValidationRunner(
|
||||
State.WARMUP -> {
|
||||
frameCounter++
|
||||
if (frameCounter > 5) { // 5 frames warmup
|
||||
startAutomation()
|
||||
startAutomation()
|
||||
}
|
||||
}
|
||||
State.RUNNING_TEST -> {
|
||||
// Log.i("ValidationRunner", "Running test...")
|
||||
currentEngine?.let { engine ->
|
||||
// Log.i("ValidationRunner", "Running test...")
|
||||
currentEngine?.let { engine ->
|
||||
val content = AutomationEngine.ViewerContent()
|
||||
content.view = modelViewer.view
|
||||
content.renderer = modelViewer.renderer
|
||||
@@ -152,11 +186,13 @@ class ValidationRunner(
|
||||
if (engine.shouldClose()) {
|
||||
Log.i("ValidationRunner", "Finishing test (frames: $frameCounter)")
|
||||
// Test finished (for this spec)
|
||||
currentState = State.IDLE
|
||||
currentState = State.COMPARING
|
||||
captureAndCompare()
|
||||
}
|
||||
}
|
||||
}
|
||||
State.COMPARING -> {} // Busy
|
||||
State.LOADING_MODEL -> {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,36 +233,7 @@ class ValidationRunner(
|
||||
|
||||
// Golden path
|
||||
val modelFile = File(config.models.get(modelName)!!)
|
||||
val modelParent = modelFile.parentFile!!
|
||||
|
||||
// Search for golden in:
|
||||
// 1. ../golden/ (standard structure)
|
||||
// 2. ../goldens/ (exported structure, sibling of models)
|
||||
// 3. ./goldens/ (backup)
|
||||
|
||||
val searchPaths = mutableListOf<File>()
|
||||
modelParent.parentFile?.let {
|
||||
searchPaths.add(it.resolve("golden"))
|
||||
searchPaths.add(it.resolve("goldens"))
|
||||
}
|
||||
searchPaths.add(modelParent.resolve("goldens"))
|
||||
|
||||
var goldenFile: File? = null
|
||||
for (path in searchPaths) {
|
||||
val f = path.resolve("${testFullName}.png")
|
||||
if (f.exists()) {
|
||||
goldenFile = f
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (goldenFile != null) {
|
||||
Log.i("ValidationRunner", "Found golden at ${goldenFile.absolutePath}")
|
||||
} else {
|
||||
Log.w("ValidationRunner", "Golden not found for $testFullName. Searched in: ${searchPaths.joinToString { it.absolutePath }}")
|
||||
// Fallback to old behavior for reference if everything else fails
|
||||
goldenFile = modelParent.parentFile?.resolve("golden/${testFullName}.png") ?: File("nonexistent")
|
||||
}
|
||||
val goldenFile = modelFile.parentFile!!.parentFile!!.resolve("golden/${testFullName}.png")
|
||||
|
||||
Thread {
|
||||
try {
|
||||
@@ -238,15 +245,14 @@ class ValidationRunner(
|
||||
var diffMetric = 0f
|
||||
|
||||
if (generateGoldens) {
|
||||
val targetGolden = goldenFile ?: modelParent.parentFile?.resolve("golden/${testFullName}.png") ?: File(modelParent, "golden/${testFullName}.png")
|
||||
targetGolden.parentFile?.mkdirs()
|
||||
FileOutputStream(targetGolden).use { out ->
|
||||
goldenFile.parentFile?.mkdirs()
|
||||
FileOutputStream(goldenFile).use { out ->
|
||||
flipped.compress(Bitmap.CompressFormat.PNG, 100, out)
|
||||
}
|
||||
passed = true // Generating goldens always passes if successful
|
||||
callback?.onStatusChanged("Golden generated")
|
||||
} else {
|
||||
if (goldenFile != null && goldenFile.exists()) {
|
||||
if (goldenFile.exists()) {
|
||||
val golden = android.graphics.BitmapFactory.decodeFile(goldenFile.absolutePath)
|
||||
if (golden != null) {
|
||||
callback?.onImageResult("Golden", golden)
|
||||
@@ -267,7 +273,7 @@ class ValidationRunner(
|
||||
callback?.onStatusChanged("Failed to load golden")
|
||||
}
|
||||
} else {
|
||||
Log.w("ValidationRunner", "Golden not found: ${goldenFile?.absolutePath}")
|
||||
Log.w("ValidationRunner", "Golden not found: ${goldenFile.absolutePath}")
|
||||
callback?.onStatusChanged("Golden not found")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
@@ -10,8 +10,6 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintWidth_percent="0.6"
|
||||
android:layout_marginTop="32dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
@@ -22,83 +20,10 @@
|
||||
android:layout_height="match_parent" />
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/controls_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:paddingTop="20dp"
|
||||
android:paddingBottom="0dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/surface_container"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Initializing..."
|
||||
android:textSize="12sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingBottom="10dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/run_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
style="@style/Widget.Material3.Button"
|
||||
android:text="Run Test" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/load_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:text="Load Test"
|
||||
android:layout_marginStart="8dp"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/options_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.Material3.Button.IconButton.Outlined"
|
||||
app:icon="@android:drawable/ic_menu_more"
|
||||
android:contentDescription="More Options"
|
||||
android:layout_marginStart="8dp"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/test_results_header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Test Results"
|
||||
android:gravity="center"
|
||||
android:textSize="14sp"
|
||||
android:paddingTop="0dp"
|
||||
android:paddingBottom="8dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/controls_container"
|
||||
app:layout_constraintTop_toBottomOf="@id/surface_container"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
@@ -107,9 +32,41 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:paddingBottom="20dp">
|
||||
android:padding="20dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Initializing..."
|
||||
android:textSize="14sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingBottom="10dp">
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/mode_spinner"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/run_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Run" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Test Results"
|
||||
android:textSize="18sp"
|
||||
android:paddingTop="20dp"
|
||||
android:paddingBottom="10dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/results_container"
|
||||
@@ -117,6 +74,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" />
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'com.google.android.filament-tools'
|
||||
id 'filament-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
// Filament tools plugin
|
||||
pluginManagement {
|
||||
includeBuild 'gradle-plugin'
|
||||
}
|
||||
|
||||
// Libraries
|
||||
include ':filament-android'
|
||||
include ':filamat-android'
|
||||
|
||||
+2
-2
@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.69.5'
|
||||
implementation 'com.google.android.filament:filament-android:1.69.4'
|
||||
}
|
||||
</code></pre>
|
||||
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
|
||||
@@ -195,7 +195,7 @@ dependencies {
|
||||
</div>
|
||||
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
|
||||
<p>iOS projects can use CocoaPods to install the latest release:</p>
|
||||
<pre><code class="language-shell">pod 'Filament', '~> 1.69.5'
|
||||
<pre><code class="language-shell">pod 'Filament', '~> 1.69.4'
|
||||
</code></pre>
|
||||
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
|
||||
<ul>
|
||||
|
||||
@@ -225,7 +225,7 @@ into <strong>branch</strong> of <code>filament-assets</code>. This branch is pai
|
||||
<code>filament</code> repo.</p>
|
||||
<p>As an example, imagine I am working on a PR, and I've uploaded my change, which is in a
|
||||
branch called <code>my-pr-branch</code>, to <code>filament</code>. This PR requires updating the golden. We would do
|
||||
it in the following fashion on a macOS machine:</p>
|
||||
it in the following fashion</p>
|
||||
<h3 id="using-a-script-to-update-the-golden-repo"><a class="header" href="#using-a-script-to-update-the-golden-repo">Using a script to update the golden repo</a></h3>
|
||||
<ul>
|
||||
<li>Make sure you've completed the steps in 'Setting up python'</li>
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -93,7 +93,6 @@ set(SRCS
|
||||
src/MorphTargetBuffer.cpp
|
||||
src/PostProcessManager.cpp
|
||||
src/ProgramSpecialization.cpp
|
||||
src/LocalProgramCache.cpp
|
||||
src/RenderPass.cpp
|
||||
src/RenderPrimitive.cpp
|
||||
src/RenderTarget.cpp
|
||||
@@ -187,7 +186,6 @@ set(PRIVATE_HDRS
|
||||
src/MaterialCache.h
|
||||
src/MaterialDefinition.h
|
||||
src/MaterialParser.h
|
||||
src/LocalProgramCache.h
|
||||
src/MaterialInstanceManager.h
|
||||
src/PIDController.h
|
||||
src/PostProcessManager.h
|
||||
|
||||
+152
-153
@@ -564,171 +564,170 @@ endif()
|
||||
# ==================================================================================================
|
||||
# Test
|
||||
# ==================================================================================================
|
||||
if (FILAMENT_BUILD_TESTING)
|
||||
option(INSTALL_BACKEND_TEST "Install the backend test library so it can be consumed on iOS" OFF)
|
||||
option(INSTALL_BACKEND_TEST "Install the backend test library so it can be consumed on iOS" OFF)
|
||||
|
||||
if (APPLE OR LINUX)
|
||||
set(BACKEND_TEST_SRC
|
||||
test/BackendTest.cpp
|
||||
test/ShaderGenerator.cpp
|
||||
test/TrianglePrimitive.cpp
|
||||
test/Arguments.cpp
|
||||
test/ImageExpectations.cpp
|
||||
test/Lifetimes.cpp
|
||||
test/PlatformRunner.cpp
|
||||
test/Shader.cpp
|
||||
test/SharedShaders.cpp
|
||||
test/Skip.cpp
|
||||
test/test_Autoresolve.cpp
|
||||
test/test_FeedbackLoops.cpp
|
||||
test/test_Blit.cpp
|
||||
test/test_MissingRequiredAttributes.cpp
|
||||
test/test_ReadPixels.cpp
|
||||
test/test_ReadTexture.cpp
|
||||
test/test_BufferUpdates.cpp
|
||||
test/test_Callbacks.cpp
|
||||
test/test_JobQueue.cpp
|
||||
test/test_MemoryMappedBuffer.cpp
|
||||
test/test_MsaaSwapChain.cpp
|
||||
test/test_MRT.cpp
|
||||
test/test_PushConstants.cpp
|
||||
test/test_LoadImage.cpp
|
||||
test/test_StencilBuffer.cpp
|
||||
test/test_Scissor.cpp
|
||||
test/test_MipLevels.cpp
|
||||
test/test_Handles.cpp
|
||||
test/test_CircularBuffer.cpp
|
||||
test/test_CommandBufferQueue.cpp
|
||||
test/test_Template.cpp
|
||||
test/test_Platform.cpp
|
||||
if (APPLE OR LINUX)
|
||||
set(BACKEND_TEST_SRC
|
||||
test/BackendTest.cpp
|
||||
test/ShaderGenerator.cpp
|
||||
test/TrianglePrimitive.cpp
|
||||
test/Arguments.cpp
|
||||
test/ImageExpectations.cpp
|
||||
test/Lifetimes.cpp
|
||||
test/PlatformRunner.cpp
|
||||
test/Shader.cpp
|
||||
test/SharedShaders.cpp
|
||||
test/Skip.cpp
|
||||
test/test_Autoresolve.cpp
|
||||
test/test_FeedbackLoops.cpp
|
||||
test/test_Blit.cpp
|
||||
test/test_MissingRequiredAttributes.cpp
|
||||
test/test_ReadPixels.cpp
|
||||
test/test_ReadTexture.cpp
|
||||
test/test_BufferUpdates.cpp
|
||||
test/test_Callbacks.cpp
|
||||
test/test_JobQueue.cpp
|
||||
test/test_MemoryMappedBuffer.cpp
|
||||
test/test_MsaaSwapChain.cpp
|
||||
test/test_MRT.cpp
|
||||
test/test_PushConstants.cpp
|
||||
test/test_LoadImage.cpp
|
||||
test/test_StencilBuffer.cpp
|
||||
test/test_Scissor.cpp
|
||||
test/test_MipLevels.cpp
|
||||
test/test_Handles.cpp
|
||||
test/test_CircularBuffer.cpp
|
||||
test/test_CommandBufferQueue.cpp
|
||||
test/test_Template.cpp
|
||||
)
|
||||
if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
list(APPEND BACKEND_TEST_SRC
|
||||
test/test_WebGPUAsyncTaskCounter.cpp
|
||||
test/test_WebGPUComposeSwizzle.cpp)
|
||||
endif()
|
||||
if (APPLE)
|
||||
# Metal-specific tests
|
||||
list(APPEND BACKEND_TEST_SRC
|
||||
test/test_MetalBlitter.mm
|
||||
)
|
||||
if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
list(APPEND BACKEND_TEST_SRC
|
||||
test/test_WebGPUAsyncTaskCounter.cpp
|
||||
test/test_WebGPUComposeSwizzle.cpp)
|
||||
endif()
|
||||
if (APPLE)
|
||||
# Metal-specific tests
|
||||
list(APPEND BACKEND_TEST_SRC
|
||||
test/test_MetalBlitter.mm
|
||||
)
|
||||
endif()
|
||||
set(BACKEND_TEST_LIBS
|
||||
absl::str_format
|
||||
backend
|
||||
endif()
|
||||
set(BACKEND_TEST_LIBS
|
||||
absl::str_format
|
||||
backend
|
||||
getopt
|
||||
gtest
|
||||
imageio
|
||||
filamat
|
||||
SPIRV
|
||||
spirv-cross-glsl)
|
||||
# Create input/output directories for test result images.
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/actual_images)
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/diff_images)
|
||||
file(COPY test/expected_images DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/images)
|
||||
endif()
|
||||
|
||||
# TODO: Disabling IOS test due to breakage wrt glslang update
|
||||
if (APPLE AND NOT IOS)
|
||||
# TODO: we should expand this test to Linux and other platforms.
|
||||
list(APPEND BACKEND_TEST_SRC
|
||||
test/test_RenderExternalImage.cpp)
|
||||
add_library(backend_test STATIC ${BACKEND_TEST_SRC})
|
||||
target_link_libraries(backend_test PUBLIC ${BACKEND_TEST_LIBS})
|
||||
target_compile_options(backend_test PRIVATE "-fobjc-arc")
|
||||
|
||||
set(BACKEND_TEST_DEPS
|
||||
OSDependent
|
||||
SPIRV
|
||||
SPIRV-Tools
|
||||
SPIRV-Tools-opt
|
||||
backend_test
|
||||
getopt
|
||||
gtest
|
||||
imageio
|
||||
filamat
|
||||
SPIRV
|
||||
spirv-cross-glsl)
|
||||
# Create input/output directories for test result images.
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/actual_images)
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/diff_images)
|
||||
file(COPY test/expected_images DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/images)
|
||||
glslang
|
||||
spirv-cross-core
|
||||
spirv-cross-glsl
|
||||
spirv-cross-msl)
|
||||
|
||||
if (NOT IOS)
|
||||
target_link_libraries(backend_test PRIVATE image imageio)
|
||||
list(APPEND BACKEND_TEST_DEPS image)
|
||||
endif()
|
||||
if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
target_link_libraries(backend_test PRIVATE webgpu_dawn dawncpp_headers)
|
||||
list(APPEND BACKEND_TEST_DEPS webgpu_dawn dawncpp_headers)
|
||||
endif()
|
||||
|
||||
# TODO: Disabling IOS test due to breakage wrt glslang update
|
||||
if (APPLE AND NOT IOS)
|
||||
# TODO: we should expand this test to Linux and other platforms.
|
||||
list(APPEND BACKEND_TEST_SRC
|
||||
test/test_RenderExternalImage.cpp)
|
||||
add_library(backend_test STATIC ${BACKEND_TEST_SRC})
|
||||
target_link_libraries(backend_test PUBLIC ${BACKEND_TEST_LIBS})
|
||||
target_compile_options(backend_test PRIVATE "-fobjc-arc")
|
||||
set(BACKEND_TEST_COMBINED_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/libbackendtest_combined.a")
|
||||
combine_static_libs(backend_test "${BACKEND_TEST_COMBINED_OUTPUT}" "${BACKEND_TEST_DEPS}")
|
||||
|
||||
set(BACKEND_TEST_DEPS
|
||||
OSDependent
|
||||
SPIRV
|
||||
SPIRV-Tools
|
||||
SPIRV-Tools-opt
|
||||
backend_test
|
||||
getopt
|
||||
gtest
|
||||
glslang
|
||||
spirv-cross-core
|
||||
spirv-cross-glsl
|
||||
spirv-cross-msl)
|
||||
set(BACKEND_TEST_LIB_NAME ${CMAKE_STATIC_LIBRARY_PREFIX}backend_test${CMAKE_STATIC_LIBRARY_SUFFIX})
|
||||
|
||||
if (NOT IOS)
|
||||
target_link_libraries(backend_test PRIVATE image imageio)
|
||||
list(APPEND BACKEND_TEST_DEPS image)
|
||||
endif()
|
||||
if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
target_link_libraries(backend_test PRIVATE webgpu_dawn dawncpp_headers)
|
||||
list(APPEND BACKEND_TEST_DEPS webgpu_dawn dawncpp_headers)
|
||||
endif()
|
||||
|
||||
set(BACKEND_TEST_COMBINED_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/libbackendtest_combined.a")
|
||||
combine_static_libs(backend_test "${BACKEND_TEST_COMBINED_OUTPUT}" "${BACKEND_TEST_DEPS}")
|
||||
|
||||
set(BACKEND_TEST_LIB_NAME ${CMAKE_STATIC_LIBRARY_PREFIX}backend_test${CMAKE_STATIC_LIBRARY_SUFFIX})
|
||||
|
||||
if (INSTALL_BACKEND_TEST)
|
||||
install(FILES "${BACKEND_TEST_COMBINED_OUTPUT}" DESTINATION lib/${DIST_DIR} RENAME ${BACKEND_TEST_LIB_NAME})
|
||||
install(FILES test/PlatformRunner.h DESTINATION include/backend_test)
|
||||
endif()
|
||||
|
||||
set_target_properties(backend_test PROPERTIES FOLDER Tests)
|
||||
|
||||
if (APPLE AND NOT IOS)
|
||||
add_executable(backend_test_mac test/mac_runner.mm)
|
||||
target_link_libraries(backend_test_mac PRIVATE "-framework Metal -framework AppKit -framework QuartzCore")
|
||||
# Because each test case is a separate file, the -force_load flag is necessary to prevent the
|
||||
# linker from removing "unused" symbols.
|
||||
target_link_libraries(backend_test_mac PRIVATE -force_load backend_test)
|
||||
set_target_properties(backend_test_mac PROPERTIES FOLDER Tests)
|
||||
|
||||
# This is needed after XCode 15.3
|
||||
set_target_properties(backend_test_mac PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE)
|
||||
set_target_properties(backend_test_mac PROPERTIES INSTALL_RPATH /usr/local/lib)
|
||||
endif()
|
||||
if (INSTALL_BACKEND_TEST)
|
||||
install(FILES "${BACKEND_TEST_COMBINED_OUTPUT}" DESTINATION lib/${DIST_DIR} RENAME ${BACKEND_TEST_LIB_NAME})
|
||||
install(FILES test/PlatformRunner.h DESTINATION include/backend_test)
|
||||
endif()
|
||||
|
||||
if (LINUX)
|
||||
add_executable(backend_test_linux test/linux_runner.cpp ${BACKEND_TEST_SRC})
|
||||
target_link_libraries(backend_test_linux PRIVATE ${BACKEND_TEST_LIBS})
|
||||
set_target_properties(backend_test_linux PROPERTIES FOLDER Tests)
|
||||
if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
target_link_libraries(backend_test_linux PRIVATE webgpu_dawn dawncpp_headers)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# ==================================================================================================
|
||||
# Compute tests
|
||||
#
|
||||
#if (NOT IOS AND NOT WEBGL)
|
||||
#
|
||||
#add_executable(compute_test
|
||||
# test/ComputeTest.cpp
|
||||
# test/Arguments.cpp
|
||||
# test/test_ComputeBasic.cpp
|
||||
# )
|
||||
#
|
||||
#target_link_libraries(compute_test PRIVATE
|
||||
# backend
|
||||
# getopt
|
||||
# gtest
|
||||
# )
|
||||
#
|
||||
#set_target_properties(compute_test PROPERTIES FOLDER Tests)
|
||||
#
|
||||
#endif()
|
||||
|
||||
# ==================================================================================================
|
||||
# Metal utils tests
|
||||
set_target_properties(backend_test PROPERTIES FOLDER Tests)
|
||||
|
||||
if (APPLE AND NOT IOS)
|
||||
add_executable(metal_utils_test test/MetalTest.mm)
|
||||
add_executable(backend_test_mac test/mac_runner.mm)
|
||||
target_link_libraries(backend_test_mac PRIVATE "-framework Metal -framework AppKit -framework QuartzCore")
|
||||
# Because each test case is a separate file, the -force_load flag is necessary to prevent the
|
||||
# linker from removing "unused" symbols.
|
||||
target_link_libraries(backend_test_mac PRIVATE -force_load backend_test)
|
||||
set_target_properties(backend_test_mac PROPERTIES FOLDER Tests)
|
||||
|
||||
target_compile_options(metal_utils_test PRIVATE "-fobjc-arc")
|
||||
|
||||
target_link_libraries(metal_utils_test PRIVATE
|
||||
backend
|
||||
getopt
|
||||
gtest
|
||||
)
|
||||
|
||||
set_target_properties(metal_utils_test PROPERTIES FOLDER Tests)
|
||||
# This is needed after XCode 15.3
|
||||
set_target_properties(backend_test_mac PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE)
|
||||
set_target_properties(backend_test_mac PROPERTIES INSTALL_RPATH /usr/local/lib)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (LINUX)
|
||||
add_executable(backend_test_linux test/linux_runner.cpp ${BACKEND_TEST_SRC})
|
||||
target_link_libraries(backend_test_linux PRIVATE ${BACKEND_TEST_LIBS})
|
||||
set_target_properties(backend_test_linux PROPERTIES FOLDER Tests)
|
||||
if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
target_link_libraries(backend_test_linux PRIVATE webgpu_dawn dawncpp_headers)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# ==================================================================================================
|
||||
# Compute tests
|
||||
#
|
||||
#if (NOT IOS AND NOT WEBGL)
|
||||
#
|
||||
#add_executable(compute_test
|
||||
# test/ComputeTest.cpp
|
||||
# test/Arguments.cpp
|
||||
# test/test_ComputeBasic.cpp
|
||||
# )
|
||||
#
|
||||
#target_link_libraries(compute_test PRIVATE
|
||||
# backend
|
||||
# getopt
|
||||
# gtest
|
||||
# )
|
||||
#
|
||||
#set_target_properties(compute_test PROPERTIES FOLDER Tests)
|
||||
#
|
||||
#endif()
|
||||
|
||||
# ==================================================================================================
|
||||
# Metal utils tests
|
||||
|
||||
if (APPLE AND NOT IOS)
|
||||
|
||||
add_executable(metal_utils_test test/MetalTest.mm)
|
||||
|
||||
target_compile_options(metal_utils_test PRIVATE "-fobjc-arc")
|
||||
|
||||
target_link_libraries(metal_utils_test PRIVATE
|
||||
backend
|
||||
getopt
|
||||
gtest
|
||||
)
|
||||
|
||||
set_target_properties(metal_utils_test PROPERTIES FOLDER Tests)
|
||||
|
||||
endif()
|
||||
|
||||
@@ -216,17 +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)
|
||||
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.
|
||||
@@ -327,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).
|
||||
*/
|
||||
@@ -388,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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -143,8 +143,6 @@ public:
|
||||
return 0;
|
||||
}
|
||||
|
||||
utils::CString getDeviceInfo(DeviceInfoType infoType, Driver* driver) const noexcept override;
|
||||
|
||||
// ----------------------------------------------------
|
||||
// ---------- Platform Customization options ----------
|
||||
struct Customization {
|
||||
@@ -177,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; }
|
||||
|
||||
|
||||
@@ -25,11 +25,6 @@
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
|
||||
namespace filament {
|
||||
|
||||
using namespace utils;
|
||||
@@ -110,21 +105,8 @@ JNIEnv* VirtualMachineEnv::getEnvironmentSlow() {
|
||||
FILAMENT_CHECK_PRECONDITION(mVirtualMachine)
|
||||
<< "JNI_OnLoad() has not been called";
|
||||
|
||||
#ifdef __ANDROID__
|
||||
JavaVMAttachArgs args;
|
||||
args.version = JNI_VERSION_1_6;
|
||||
args.group = nullptr;
|
||||
char threadName[16]; // pthread_getname_np returns at most 16 bytes
|
||||
if (__builtin_available(android 26, *)) {
|
||||
if (pthread_getname_np(pthread_self(), threadName, sizeof(threadName)) == 0) {
|
||||
args.name = threadName;
|
||||
} else {
|
||||
args.name = nullptr;
|
||||
}
|
||||
} else {
|
||||
args.name = nullptr;
|
||||
}
|
||||
jint const result = mVirtualMachine->AttachCurrentThread(&mJniEnv, &args);
|
||||
#if defined(__ANDROID__)
|
||||
jint const result = mVirtualMachine->AttachCurrentThread(&mJniEnv, nullptr);
|
||||
#else
|
||||
jint const result = mVirtualMachine->AttachCurrentThread(reinterpret_cast<void**>(&mJniEnv), nullptr);
|
||||
#endif
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -45,19 +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);
|
||||
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
|
||||
|
||||
@@ -706,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 {
|
||||
|
||||
@@ -29,12 +29,6 @@ using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
std::shared_ptr<VulkanCmdFence> VulkanCmdFence::completed() noexcept {
|
||||
auto cmdFence = std::make_shared<VulkanCmdFence>(VK_NULL_HANDLE);
|
||||
cmdFence->mStatus = VK_SUCCESS;
|
||||
return cmdFence;
|
||||
}
|
||||
|
||||
FenceStatus VulkanCmdFence::wait(VkDevice device, uint64_t const timeout,
|
||||
std::chrono::steady_clock::time_point const until) {
|
||||
|
||||
|
||||
@@ -40,13 +40,6 @@ struct VulkanCmdFence {
|
||||
explicit VulkanCmdFence(VkFence fence) : mFence(fence) { }
|
||||
~VulkanCmdFence() = default;
|
||||
|
||||
// Creates a VulkanCmdFence with its status set to VK_SUCCESS. It holds
|
||||
// a null handle; it is assumed that any user of this object will avoid
|
||||
// using the fence handle directly if getStatus() returns VK_SUCCESS, as
|
||||
// in that case, it's likely the fence is being reused for other passes,
|
||||
// and is not in the expected state anyway.
|
||||
static std::shared_ptr<VulkanCmdFence> completed() noexcept;
|
||||
|
||||
void setStatus(VkResult const value) {
|
||||
std::lock_guard const l(mLock);
|
||||
mStatus = value;
|
||||
|
||||
@@ -277,11 +277,7 @@ private:
|
||||
fvkmemory::resource_ptr<VulkanSemaphore> mLastSubmit;
|
||||
|
||||
VkFence mLastFence = VK_NULL_HANDLE;
|
||||
// Start out with a completed fence, because if no commands have
|
||||
// been queued or submited, then by definition, all pending work
|
||||
// is complete.
|
||||
std::shared_ptr<VulkanCmdFence> mLastFenceStatus =
|
||||
VulkanCmdFence::completed();
|
||||
std::shared_ptr<VulkanCmdFence> mLastFenceStatus;
|
||||
|
||||
VkPipelineStageFlags mInjectedDependencyWaitStage = 0;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -332,6 +332,7 @@ void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands,
|
||||
void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
uint8_t binding, fvkmemory::resource_ptr<VulkanBufferObject> bufferObject,
|
||||
VkDeviceSize offset, VkDeviceSize size) noexcept {
|
||||
prepareForUpdate(set);
|
||||
VkDescriptorBufferInfo const info = {
|
||||
.buffer = bufferObject->getVkBuffer(),
|
||||
.offset = offset,
|
||||
@@ -355,8 +356,30 @@ void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescri
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture,
|
||||
VkSampler sampler, VkDescriptorSetLayout externalSamplerLayout) noexcept {
|
||||
uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler,
|
||||
VkDescriptorSetLayout externalSamplerLayout) noexcept {
|
||||
|
||||
// We have to update a bound set for two use cases
|
||||
// - streaming API (a changing feed of AHardwareBuffer)
|
||||
// - external samplers - potential changing of dataspace per-frame
|
||||
|
||||
// TODO: Fix the stream flow case base on the above comment!!
|
||||
if (set->isAnExternalSamplerBound) {
|
||||
auto layout = set->getLayout();
|
||||
// Build a new descriptor set from the new layout
|
||||
VkDescriptorSetLayout const genLayout = set->boundLayout;
|
||||
VkDescriptorSet const newSet = getVkSet(layout->count, genLayout);
|
||||
|
||||
VkDescriptorSet const srcSet = set->getVkSet();
|
||||
copySet(srcSet, newSet, layout);
|
||||
set->addNewSet(newSet,
|
||||
[this, layoutCount = layout->count, genLayout, newSet](VulkanDescriptorSet*) {
|
||||
this->manualRecycle(layoutCount, genLayout, newSet);
|
||||
});
|
||||
} else {
|
||||
prepareForUpdate(set);
|
||||
}
|
||||
|
||||
VkDescriptorSet const vkset = set->getVkSet();
|
||||
VkImageSubresourceRange range = texture->getPrimaryViewRange();
|
||||
VkImageViewType const expectedType = texture->getViewType();
|
||||
@@ -405,28 +428,6 @@ fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetCache::createSet
|
||||
return set;
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetCache::cloneSet(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
fvkutils::SamplerBitmask samplerMask) noexcept {
|
||||
auto const& layout = set->getLayout();
|
||||
// Build a new descriptor set from layout
|
||||
VkDescriptorSetLayout const genLayout = set->boundLayout;
|
||||
VkDescriptorSet const newSet = getVkSet(layout->count, genLayout);
|
||||
|
||||
// Each bitmask denotes a binding index, and separated into two stages - vertex and buffer
|
||||
// We fold the two stages into just the lower half of the bits to denote a combined set of
|
||||
// bindings.
|
||||
Bitmask const ubo = layout->bitmask.ubo | layout->bitmask.dynamicUbo;
|
||||
// Don't copy the samplers in the mask
|
||||
Bitmask samplers = layout->bitmask.sampler ^ samplerMask;
|
||||
Bitmask const copyBindings = foldBitsInHalf(ubo | samplers);
|
||||
|
||||
VkDescriptorSet const srcSet = set->getVkSet();
|
||||
copySet(srcSet, newSet, copyBindings);
|
||||
set->addNewSet(newSet,
|
||||
[this, layoutCount = layout->count, genLayout, newSet](
|
||||
VulkanDescriptorSet*) { this->manualRecycle(layoutCount, genLayout, newSet); });
|
||||
}
|
||||
|
||||
VkDescriptorSet VulkanDescriptorSetCache::getVkSet(DescriptorCount const& count,
|
||||
VkDescriptorSetLayout vklayout) {
|
||||
return mDescriptorPool->obtainSet(count, vklayout);
|
||||
@@ -443,21 +444,41 @@ void VulkanDescriptorSetCache::manualRecycle(VulkanDescriptorSetLayout::Count co
|
||||
|
||||
void VulkanDescriptorSetCache::gc() { mStashedSets = {}; }
|
||||
|
||||
void VulkanDescriptorSetCache::prepareForUpdate(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set) noexcept {
|
||||
if (set->isBound()) {
|
||||
auto layout = set->getLayout();
|
||||
VkDescriptorSetLayout const vklayout = set->boundLayout;
|
||||
VkDescriptorSet const newSet = mDescriptorPool->obtainSet(layout->count, vklayout);
|
||||
copySet(set->getVkSet(), newSet, layout);
|
||||
set->addNewSet(newSet,
|
||||
[this, layoutCount = layout->count, vklayout, newSet](VulkanDescriptorSet*) {
|
||||
this->manualRecycle(layoutCount, vklayout, newSet);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetCache::copySet(VkDescriptorSet srcSet, VkDescriptorSet dstSet,
|
||||
fvkutils::SamplerBitmask bindings) const {
|
||||
// TODO: fix the size for better memory management
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) const {
|
||||
Bitmask const ubo = layout->bitmask.ubo | layout->bitmask.dynamicUbo;
|
||||
Bitmask const samplers = layout->bitmask.sampler;
|
||||
Bitmask const inputAttachments = layout->bitmask.inputAttachment;
|
||||
Bitmask const bindings = foldBitsInHalf(ubo | samplers | inputAttachments);
|
||||
|
||||
std::vector<VkCopyDescriptorSet> copies;
|
||||
bindings.forEachSetBit([&](size_t index) {
|
||||
copies.push_back({
|
||||
.sType = VK_STRUCTURE_TYPE_COPY_DESCRIPTOR_SET,
|
||||
.srcSet = srcSet,
|
||||
.srcBinding = (uint32_t) index,
|
||||
.dstSet = dstSet,
|
||||
.dstBinding = (uint32_t) index,
|
||||
.descriptorCount = 1,
|
||||
.sType = VK_STRUCTURE_TYPE_COPY_DESCRIPTOR_SET,
|
||||
.srcSet = srcSet,
|
||||
.srcBinding = (uint32_t) index,
|
||||
.dstSet = dstSet,
|
||||
.dstBinding = (uint32_t) index,
|
||||
.descriptorCount = 1,
|
||||
});
|
||||
});
|
||||
vkUpdateDescriptorSets(mDevice, 0, nullptr, copies.size(), copies.data());
|
||||
if (!copies.empty()) {
|
||||
vkUpdateDescriptorSets(mDevice, 0, nullptr, (uint32_t) copies.size(), copies.data());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -76,11 +76,6 @@ public:
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> createSet(Handle<HwDescriptorSet> handle,
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
||||
|
||||
// Create and set as current a new VkDescriptorSet using the `set` currently bound layout and
|
||||
// copy all the bindings and ignoring the samplers bindings in the `samplerMask`.
|
||||
void cloneSet(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
fvkutils::SamplerBitmask samplerMask) noexcept;
|
||||
|
||||
// This method is meant to be used with external samplers
|
||||
VkDescriptorSet getVkSet(DescriptorCount const& count, VkDescriptorSetLayout vklayout);
|
||||
|
||||
@@ -95,8 +90,10 @@ public:
|
||||
void resetCachedState() noexcept { mLastBoundInfo = {}; }
|
||||
|
||||
private:
|
||||
void prepareForUpdate(fvkmemory::resource_ptr<VulkanDescriptorSet> set) noexcept;
|
||||
|
||||
void copySet(VkDescriptorSet srcSet, VkDescriptorSet destSet,
|
||||
fvkutils::SamplerBitmask copyBindings) const;
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) const;
|
||||
|
||||
class DescriptorInfinitePool;
|
||||
|
||||
|
||||
@@ -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),
|
||||
@@ -1914,6 +1913,9 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
|
||||
auto rt = resource_ptr<VulkanRenderTarget>::cast(&mResourceManager, rth);
|
||||
|
||||
VulkanCommandBuffer* commandBuffer = rt->isProtected() ?
|
||||
&mCommands.getProtected() : &mCommands.get();
|
||||
|
||||
// Filament has the expectation that the contents of the swap chain are not preserved on the
|
||||
// first render pass. Note however that its contents are often preserved on subsequent render
|
||||
// passes, due to multiple views.
|
||||
@@ -1928,11 +1930,6 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
}
|
||||
}
|
||||
|
||||
// Note that this needs to come after the acquireNextswapchainImage() above because that path
|
||||
// might flush the current command buffer.
|
||||
VulkanCommandBuffer* commandBuffer =
|
||||
rt->isProtected() ? &mCommands.getProtected() : &mCommands.get();
|
||||
|
||||
// Note that retrieving the extent must come after the acquireNextSwapchainImage() above;
|
||||
// otherwise it might be 0.
|
||||
VkExtent2D const extent = rt->getExtent();
|
||||
@@ -1990,7 +1987,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
rt->emitBarriersBeginRenderPass(*commandBuffer);
|
||||
|
||||
fvkmemory::resource_ptr<VulkanFramebuffer> vkfb =
|
||||
mFramebufferCache.getFramebuffer(fbkey, &mResourceManager, rt);
|
||||
mFramebufferCache.getFramebuffer(fbkey, &mResourceManager);
|
||||
|
||||
// Assign a label to the framebuffer for debugging purposes.
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS | FVK_DEBUG_DEBUG_UTILS)
|
||||
|
||||
@@ -94,9 +94,6 @@ void VulkanExternalImageManager::updateSetAndLayout(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> const& layout = set->getLayout();
|
||||
set->boundLayout = mDescriptorSetLayoutCache->getVkLayout(layout->bitmask,
|
||||
actualExternalSamplers, outSamplers);
|
||||
|
||||
mDescriptorSetCache->cloneSet(set, actualExternalSamplers);
|
||||
|
||||
// Update the external samplers in the set
|
||||
for (auto& [binding, sampler, image]: samplerAndBindings) {
|
||||
// We cannot call updateSamplerForExternalSamplerSet because some samplers are non NULL
|
||||
|
||||
@@ -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,18 +62,16 @@ 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())
|
||||
<< "Please explicitly call terminate() while the VkDevice is still alive.";
|
||||
}
|
||||
|
||||
fvkmemory::resource_ptr<VulkanFramebuffer> VulkanFboCache::getFramebuffer(FboKey const& config,
|
||||
fvkmemory::ResourceManager* resManager,
|
||||
fvkmemory::resource_ptr<VulkanRenderTarget> renderTarget) noexcept {
|
||||
fvkmemory::resource_ptr<VulkanFramebuffer> VulkanFboCache::getFramebuffer(
|
||||
FboKey const& config, fvkmemory::ResourceManager* resManager) noexcept {
|
||||
FboMap::iterator iter = mFramebufferCache.find(config);
|
||||
if (UTILS_LIKELY(iter != mFramebufferCache.end())) {
|
||||
iter.value().timestamp = mCurrentTime;
|
||||
@@ -123,9 +120,8 @@ fvkmemory::resource_ptr<VulkanFramebuffer> VulkanFboCache::getFramebuffer(FboKey
|
||||
FILAMENT_CHECK_POSTCONDITION(error == VK_SUCCESS) << "Unable to create framebuffer."
|
||||
<< " error=" << static_cast<int32_t>(error);
|
||||
fvkmemory::resource_ptr<VulkanFramebuffer> fbh =
|
||||
fvkmemory::resource_ptr<VulkanFramebuffer>::construct(resManager, mDevice, framebuffer,
|
||||
renderTarget);
|
||||
mFramebufferCache[config] = { fbh, mCurrentTime };
|
||||
fvkmemory::resource_ptr<VulkanFramebuffer>::construct(resManager, mDevice, framebuffer);
|
||||
mFramebufferCache[config] = {fbh, mCurrentTime};
|
||||
return fbh;
|
||||
}
|
||||
|
||||
@@ -378,36 +374,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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,13 +100,12 @@ 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.
|
||||
fvkmemory::resource_ptr<VulkanFramebuffer> getFramebuffer(FboKey const& config,
|
||||
fvkmemory::ResourceManager* resManager,
|
||||
fvkmemory::resource_ptr<VulkanRenderTarget> renderTarget) noexcept;
|
||||
fvkmemory::resource_ptr<VulkanFramebuffer> getFramebuffer(
|
||||
FboKey const& config, fvkmemory::ResourceManager* resManager) noexcept;
|
||||
|
||||
// Retrieves or creates a VkRenderPass handle.
|
||||
fvkmemory::resource_ptr<VulkanRenderPass> getRenderPass(
|
||||
@@ -130,7 +129,6 @@ private:
|
||||
RenderPassMap mRenderPassCache;
|
||||
tsl::robin_map<VkRenderPass, uint32_t> mRenderPassRefCount;
|
||||
uint32_t mCurrentTime = 0;
|
||||
uint32_t mTimeBeforeEvictionFbo;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -260,8 +260,8 @@ void VulkanDescriptorSet::gc() {
|
||||
|
||||
void VulkanDescriptorSet::addNewSet(VkDescriptorSet vkSet, OnRecycle&& onRecycleFn) {
|
||||
gc();
|
||||
mCurrentSetIndex = mSets.size();
|
||||
mSets.push_back({ vkSet, std::move(onRecycleFn) });
|
||||
mCurrentSetIndex = (uint8_t) (mSets.size() - 1);
|
||||
}
|
||||
|
||||
PushConstantDescription::PushConstantDescription(backend::Program const& program) {
|
||||
@@ -715,11 +715,8 @@ VulkanRenderPrimitive::VulkanRenderPrimitive(PrimitiveType pt,
|
||||
vertexBuffer(vb),
|
||||
indexBuffer(ib) {}
|
||||
|
||||
VulkanFramebuffer::VulkanFramebuffer(VkDevice device, VkFramebuffer framebuffer,
|
||||
fvkmemory::resource_ptr<VulkanRenderTarget> renderTarget)
|
||||
: mDevice(device),
|
||||
mFramebuffer(framebuffer),
|
||||
mRenderTarget(renderTarget) {}
|
||||
VulkanFramebuffer::VulkanFramebuffer(VkDevice device, VkFramebuffer framebuffer)
|
||||
: mDevice(device), mFramebuffer(framebuffer) {}
|
||||
|
||||
VulkanFramebuffer::~VulkanFramebuffer() {
|
||||
vkDestroyFramebuffer(mDevice, mFramebuffer, VKALLOC);
|
||||
|
||||
@@ -191,11 +191,12 @@ public:
|
||||
void referencedBy(VulkanCommandBuffer& commands);
|
||||
|
||||
bool isBound() const {
|
||||
return bool(mSets[mCurrentSetIndex].fenceStatus);
|
||||
auto const& set = mSets[mCurrentSetIndex];
|
||||
return set.fenceStatus && set.fenceStatus->getStatus() != VK_SUCCESS;
|
||||
}
|
||||
|
||||
// The current layout used by the descriptor set. This one will match the bindings, including
|
||||
// external samplers data.
|
||||
// external samplers data.
|
||||
// This will not necessarilly be the same as `mLayout`.
|
||||
VkDescriptorSetLayout boundLayout = VK_NULL_HANDLE;
|
||||
|
||||
@@ -203,7 +204,7 @@ public:
|
||||
|
||||
fvkutils::UniformBufferBitmask const& dynamicUboMask;
|
||||
uint8_t const uniqueDynamicUboCount;
|
||||
|
||||
|
||||
// Flag to indicate if the current layout needs to be recreated or not.
|
||||
// This should only set to `true` when a external sampler image is bound to the descriptor set.
|
||||
bool isLayoutDirty = false;
|
||||
@@ -586,9 +587,7 @@ struct VulkanRenderPrimitive : public HwRenderPrimitive, fvkmemory::Resource {
|
||||
};
|
||||
|
||||
struct VulkanFramebuffer : public fvkmemory::Resource {
|
||||
VulkanFramebuffer(VkDevice device,
|
||||
VkFramebuffer framebuffer,
|
||||
fvkmemory::resource_ptr<VulkanRenderTarget> renderTarget);
|
||||
VulkanFramebuffer(VkDevice device, VkFramebuffer framebuffer);
|
||||
~VulkanFramebuffer();
|
||||
|
||||
inline VkFramebuffer getVkFramebuffer() const noexcept {
|
||||
@@ -598,11 +597,6 @@ struct VulkanFramebuffer : public fvkmemory::Resource {
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
VkFramebuffer mFramebuffer;
|
||||
|
||||
// We need to keep a reference to the renderTarget because the key of the framebuffer in the
|
||||
// cache has references to the image views that are derived from the textures of the render
|
||||
// target.
|
||||
fvkmemory::resource_ptr<VulkanRenderTarget> mRenderTarget;
|
||||
};
|
||||
|
||||
struct VulkanRenderPass : public fvkmemory::Resource {
|
||||
|
||||
@@ -780,10 +780,16 @@ void VulkanTexture::samplerToAttachmentBarrier(VulkanCommandBuffer* commands,
|
||||
VkCommandBuffer const cmdbuf = commands->buffer();
|
||||
VkImageLayout const layout =
|
||||
fvkutils::getVkLayout(getLayout(range.baseArrayLayer, range.baseMipLevel));
|
||||
|
||||
const bool isDepth = fvkutils::isVkDepthFormat(mState->mVkFormat);
|
||||
|
||||
VkImageMemoryBarrier barrier = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.srcAccessMask = VK_ACCESS_SHADER_READ_BIT,
|
||||
.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
.dstAccessMask = static_cast<VkAccessFlags>(isDepth ? (VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
|
||||
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT)
|
||||
: (VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
|
||||
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT)),
|
||||
.oldLayout = layout,
|
||||
.newLayout = layout,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
@@ -791,9 +797,13 @@ void VulkanTexture::samplerToAttachmentBarrier(VulkanCommandBuffer* commands,
|
||||
.image = mState->mTextureImage,
|
||||
.subresourceRange = range,
|
||||
};
|
||||
|
||||
VkPipelineStageFlags dstStage = isDepth ? (VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
|
||||
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT)
|
||||
: VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
|
||||
vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
dstStage,
|
||||
0, 0, nullptr, 0, nullptr, 1, &barrier);
|
||||
}
|
||||
|
||||
@@ -802,9 +812,13 @@ void VulkanTexture::attachmentToSamplerBarrier(VulkanCommandBuffer* commands,
|
||||
VkCommandBuffer const cmdbuf = commands->buffer();
|
||||
VkImageLayout const layout
|
||||
= fvkutils::getVkLayout(getLayout(range.baseArrayLayer, range.baseMipLevel));
|
||||
|
||||
const bool isDepth = fvkutils::isVkDepthFormat(mState->mVkFormat);
|
||||
|
||||
VkImageMemoryBarrier barrier = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||||
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
.srcAccessMask = static_cast<VkAccessFlags>(isDepth ? VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT
|
||||
: VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT),
|
||||
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
|
||||
.oldLayout = layout,
|
||||
.newLayout = layout,
|
||||
@@ -813,7 +827,12 @@ void VulkanTexture::attachmentToSamplerBarrier(VulkanCommandBuffer* commands,
|
||||
.image = mState->mTextureImage,
|
||||
.subresourceRange = range,
|
||||
};
|
||||
vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
|
||||
VkPipelineStageFlags srcStage = isDepth ? (VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
|
||||
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT)
|
||||
: VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
|
||||
vkCmdPipelineBarrier(cmdbuf, srcStage,
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,23 +41,55 @@ void ResourceManager::gc() noexcept {
|
||||
list.clear();
|
||||
};
|
||||
|
||||
FrameGcList frameGc;
|
||||
|
||||
{
|
||||
// Note that we're not copying mThreadSafeGcList because the objects here do not have
|
||||
// resource_ptrs to other handle objects, so their desctruction would not add more elements
|
||||
// to mThreadSafeGcList.
|
||||
std::unique_lock<utils::Mutex> lock(mThreadSafeGcListMutex);
|
||||
destroyAll(mThreadSafeGcList);
|
||||
std::swap(frameGc.threadSafeGcList, mThreadSafeGcList);
|
||||
}
|
||||
|
||||
GcList gcs;
|
||||
std::swap(gcs, mGcList);
|
||||
destroyAll(gcs);
|
||||
std::swap(frameGc.gcList, mGcList);
|
||||
|
||||
mFramesGcList.push_back(std::move(frameGc));
|
||||
|
||||
if (mFramesGcList.size() > 4) {
|
||||
FrameGcList oldest = std::move(mFramesGcList.front());
|
||||
mFramesGcList.erase(mFramesGcList.begin());
|
||||
destroyAll(oldest.threadSafeGcList);
|
||||
destroyAll(oldest.gcList);
|
||||
}
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void ResourceManager::terminate() noexcept {
|
||||
while (!mThreadSafeGcList.empty() || !mGcList.empty()) {
|
||||
gc();
|
||||
auto destroyAll = [this](GcList& list) {
|
||||
for (auto const& [type, id]: list) {
|
||||
destroyWithType(type, id);
|
||||
}
|
||||
list.clear();
|
||||
};
|
||||
|
||||
while (!mThreadSafeGcList.empty() || !mGcList.empty() || !mFramesGcList.empty()) {
|
||||
if (!mFramesGcList.empty()) {
|
||||
FrameGcList oldest = std::move(mFramesGcList.front());
|
||||
mFramesGcList.erase(mFramesGcList.begin());
|
||||
destroyAll(oldest.threadSafeGcList);
|
||||
destroyAll(oldest.gcList);
|
||||
}
|
||||
|
||||
GcList threadSafeList;
|
||||
{
|
||||
std::unique_lock<utils::Mutex> lock(mThreadSafeGcListMutex);
|
||||
std::swap(threadSafeList, mThreadSafeGcList);
|
||||
}
|
||||
destroyAll(threadSafeList);
|
||||
|
||||
GcList gcs;
|
||||
std::swap(gcs, mGcList);
|
||||
destroyAll(gcs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,6 +100,12 @@ private:
|
||||
GcList mThreadSafeGcList;
|
||||
GcList mGcList;
|
||||
|
||||
struct FrameGcList {
|
||||
GcList threadSafeGcList;
|
||||
GcList gcList;
|
||||
};
|
||||
std::vector<FrameGcList> mFramesGcList;
|
||||
|
||||
template<typename D>
|
||||
friend struct resource_ptr;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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, ©Size);
|
||||
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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,50 +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 <backend/Platform.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <private/backend/PlatformFactory.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
TEST(PlatformTest, GetDeviceInfo) {
|
||||
Backend backend = Backend::DEFAULT;
|
||||
Platform* platform = PlatformFactory::create(&backend);
|
||||
ASSERT_NE(platform, nullptr);
|
||||
|
||||
// Test valid queries for the current platform (will be either GL, Vulkan, or Metal depending on
|
||||
// host)
|
||||
if (backend == Backend::OPENGL) {
|
||||
platform->getDeviceInfo(Platform::DeviceInfoType::OPENGL_RENDERER, nullptr);
|
||||
platform->getDeviceInfo(Platform::DeviceInfoType::OPENGL_VENDOR, nullptr);
|
||||
|
||||
// Death tests for Vulkan info on OpenGL platform
|
||||
EXPECT_DEATH(platform->getDeviceInfo(Platform::DeviceInfoType::VULKAN_DEVICE_NAME, nullptr),
|
||||
"Unsupported DeviceInfoType");
|
||||
} else if (backend == Backend::VULKAN) {
|
||||
platform->getDeviceInfo(Platform::DeviceInfoType::VULKAN_DEVICE_NAME, nullptr);
|
||||
platform->getDeviceInfo(Platform::DeviceInfoType::VULKAN_DRIVER_NAME, nullptr);
|
||||
platform->getDeviceInfo(Platform::DeviceInfoType::VULKAN_DRIVER_INFO, nullptr);
|
||||
|
||||
// Death tests for OpenGL info on Vulkan platform
|
||||
EXPECT_DEATH(platform->getDeviceInfo(Platform::DeviceInfoType::OPENGL_RENDERER, nullptr),
|
||||
"Unsupported DeviceInfoType");
|
||||
}
|
||||
|
||||
PlatformFactory::destroy(&platform);
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -372,7 +372,7 @@ public:
|
||||
} vsm;
|
||||
|
||||
/**
|
||||
* Light bulb radius used for soft shadows. Currently, this is only used when DPCF or PCSS is
|
||||
* Light bulb radius used for soft shadows. Currently this is only used when DPCF or PCSS is
|
||||
* enabled. (2cm by default).
|
||||
*/
|
||||
float shadowBulbRadius = 0.02f;
|
||||
|
||||
@@ -735,7 +735,7 @@ struct VsmShadowOptions {
|
||||
bool highPrecision = false;
|
||||
|
||||
/**
|
||||
* @deprecated has no effect.
|
||||
* VSM minimum variance scale, must be positive.
|
||||
*/
|
||||
float minVarianceScale = 0.5f;
|
||||
|
||||
|
||||
@@ -227,6 +227,7 @@ bool Froxelizer::prepare(
|
||||
const mat4f& projection, float const projectionNear, float const projectionFar,
|
||||
float4 const& clipTransform) noexcept {
|
||||
assert_invariant(projectionFar > projectionNear);
|
||||
assert_invariant(projectionNear > 0);
|
||||
setViewport(viewport);
|
||||
setProjection(projection, projectionNear, projectionFar);
|
||||
|
||||
|
||||
@@ -1,265 +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 "LocalProgramCache.h"
|
||||
#include "MaterialParser.h"
|
||||
|
||||
#include "details/Engine.h"
|
||||
#include "details/Material.h"
|
||||
|
||||
#include <backend/DriverApiForward.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
using namespace backend;
|
||||
using namespace utils;
|
||||
|
||||
LocalProgramCache::LocalProgramCache(LocalProgramCache const& other)
|
||||
: mMaterial(other.mMaterial),
|
||||
mCachedPrograms(other.mCachedPrograms.size()),
|
||||
mSpecializationConstants((other.mMaterial != nullptr)
|
||||
? other.mMaterial->getEngine()
|
||||
.getMaterialCache()
|
||||
.getSpecializationConstantsInternPool()
|
||||
.acquire(other.mSpecializationConstants)
|
||||
: SpecializationConstants()) {}
|
||||
|
||||
LocalProgramCache& LocalProgramCache::operator=(LocalProgramCache const& other) {
|
||||
assert_invariant(mMaterial == nullptr);
|
||||
assert_invariant(mCachedPrograms.empty());
|
||||
assert_invariant(mSpecializationConstants.empty());
|
||||
|
||||
mMaterial = other.mMaterial;
|
||||
if (mMaterial != nullptr) {
|
||||
mCachedPrograms = FixedCapacityVector<Handle<HwProgram>>(other.mCachedPrograms.size());
|
||||
mSpecializationConstants = other.mMaterial->getEngine()
|
||||
.getMaterialCache()
|
||||
.getSpecializationConstantsInternPool()
|
||||
.acquire(other.mSpecializationConstants);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void LocalProgramCache::initializeForMaterial(FEngine& engine, FMaterial const& material,
|
||||
utils::FixedCapacityVector<backend::Program::SpecializationConstant>
|
||||
specializationConstants) {
|
||||
assert_invariant(mMaterial == nullptr);
|
||||
assert_invariant(mCachedPrograms.empty());
|
||||
assert_invariant(mSpecializationConstants.empty());
|
||||
|
||||
mMaterial = &material;
|
||||
|
||||
mSpecializationConstants =
|
||||
engine.getMaterialCache().getSpecializationConstantsInternPool().acquire(
|
||||
std::move(specializationConstants));
|
||||
|
||||
size_t cachedProgramsSize;
|
||||
switch (material.getMaterialDomain()) {
|
||||
case filament::MaterialDomain::SURFACE:
|
||||
cachedProgramsSize = 1 << VARIANT_BITS;
|
||||
break;
|
||||
case filament::MaterialDomain::POST_PROCESS:
|
||||
cachedProgramsSize = 1 << POST_PROCESS_VARIANT_BITS;
|
||||
break;
|
||||
case filament::MaterialDomain::COMPUTE:
|
||||
cachedProgramsSize = 1;
|
||||
break;
|
||||
}
|
||||
mCachedPrograms = FixedCapacityVector<Handle<HwProgram>>(cachedProgramsSize);
|
||||
|
||||
material.getDefinition().acquirePrograms(engine, mCachedPrograms.as_slice(),
|
||||
material.getMaterialParser(), mSpecializationConstants, material.isDefaultMaterial());
|
||||
}
|
||||
|
||||
void LocalProgramCache::initializeForMaterialInstance(FEngine& engine, FMaterial const& material) {
|
||||
assert_invariant(mMaterial == nullptr);
|
||||
assert_invariant(mCachedPrograms.empty());
|
||||
assert_invariant(mSpecializationConstants.empty());
|
||||
|
||||
mMaterial = &material;
|
||||
LocalProgramCache const& programs = material.getPrograms();
|
||||
|
||||
mSpecializationConstants =
|
||||
engine.getMaterialCache().getSpecializationConstantsInternPool().acquire(
|
||||
programs.getSpecializationConstants());
|
||||
|
||||
mCachedPrograms =
|
||||
FixedCapacityVector<Handle<HwProgram>>(material.getPrograms().mCachedPrograms.size());
|
||||
}
|
||||
|
||||
Handle<HwProgram> LocalProgramCache::prepareProgramSlow(DriverApi& driver, Variant const variant,
|
||||
CompilerPriorityQueue const priorityQueue) const noexcept {
|
||||
assert_invariant(mMaterial != nullptr);
|
||||
|
||||
FEngine& engine = mMaterial->getEngine();
|
||||
if (mMaterial->isSharedVariant(variant)) {
|
||||
FMaterial const* defaultMaterial = engine.getDefaultMaterial();
|
||||
assert_invariant(defaultMaterial);
|
||||
LocalProgramCache const& defaultPrograms = defaultMaterial->getPrograms();
|
||||
Handle<HwProgram> program = defaultPrograms.mCachedPrograms[variant.key];
|
||||
if (program) {
|
||||
return mCachedPrograms[variant.key] = program;
|
||||
}
|
||||
return mCachedPrograms[variant.key] =
|
||||
defaultPrograms.prepareProgram(driver, variant, priorityQueue);
|
||||
}
|
||||
return mCachedPrograms[variant.key] = mMaterial->getDefinition().prepareProgram(engine, driver,
|
||||
mMaterial->getMaterialParser(), getProgramSpecialization(variant), priorityQueue);
|
||||
}
|
||||
|
||||
ProgramSpecialization LocalProgramCache::getProgramSpecialization(Variant variant) const noexcept {
|
||||
assert_invariant(mMaterial != nullptr);
|
||||
|
||||
return ProgramSpecialization {
|
||||
.materialCrc32 = mMaterial->getMaterialParser().getCrc32(),
|
||||
.variant = variant,
|
||||
.specializationConstants = mSpecializationConstants,
|
||||
};
|
||||
}
|
||||
|
||||
void LocalProgramCache::terminate(FEngine& engine) {
|
||||
assert_invariant(mMaterial != nullptr);
|
||||
|
||||
mMaterial->getDefinition().releasePrograms(engine, mCachedPrograms.as_slice(),
|
||||
mMaterial->getMaterialParser(), mSpecializationConstants,
|
||||
mMaterial->isDefaultMaterial());
|
||||
engine.getMaterialCache().releaseMaterial(engine, mMaterial->getDefinition());
|
||||
engine.getMaterialCache().getSpecializationConstantsInternPool().release(
|
||||
mSpecializationConstants);
|
||||
}
|
||||
|
||||
void LocalProgramCache::clear(FEngine& engine) {
|
||||
assert_invariant(mMaterial != nullptr);
|
||||
|
||||
mMaterial->getDefinition().releasePrograms(engine, mCachedPrograms.as_slice(),
|
||||
mMaterial->getMaterialParser(), mSpecializationConstants,
|
||||
mMaterial->isDefaultMaterial());
|
||||
}
|
||||
|
||||
Variant LocalProgramCache::filterVariantForGetProgram(Variant variant) const noexcept {
|
||||
if (UTILS_UNLIKELY(mMaterial->getEngine().features.material.enable_fog_as_postprocess)) {
|
||||
// if the fog as post-process feature is enabled, we need to proceed "as-if" the material
|
||||
// didn't have the FOG variant bit.
|
||||
if (mMaterial->getMaterialDomain() == MaterialDomain::SURFACE) {
|
||||
BlendingMode const blendingMode = mMaterial->getBlendingMode();
|
||||
bool const hasScreenSpaceRefraction =
|
||||
mMaterial->getRefractionMode() == RefractionMode::SCREEN_SPACE;
|
||||
bool const isBlendingCommand = !hasScreenSpaceRefraction &&
|
||||
(blendingMode != BlendingMode::OPAQUE && blendingMode != BlendingMode::MASKED);
|
||||
if (!isBlendingCommand) {
|
||||
variant.setFog(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
return variant;
|
||||
}
|
||||
|
||||
Program::SpecializationConstant LocalProgramCache::getConstantImpl(uint32_t id) const noexcept {
|
||||
return mSpecializationConstants[id];
|
||||
}
|
||||
|
||||
Program::SpecializationConstant LocalProgramCache::getConstantImpl(
|
||||
std::string_view name) const noexcept {
|
||||
assert_invariant(mMaterial != nullptr);
|
||||
|
||||
MaterialDefinition const& definition = mMaterial->getDefinition();
|
||||
auto it = definition.specializationConstantsNameToIndex.find(name);
|
||||
if (it != definition.specializationConstantsNameToIndex.cend()) {
|
||||
return getConstantImpl(it->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS);
|
||||
}
|
||||
|
||||
std::string name_cstring(name);
|
||||
PANIC_PRECONDITION("No such constant exists: %s", name_cstring.c_str());
|
||||
return {};
|
||||
}
|
||||
|
||||
void LocalProgramCache::setConstants(
|
||||
std::initializer_list<std::pair<uint32_t, Program::SpecializationConstant>>
|
||||
constants) noexcept {
|
||||
assert_invariant(mMaterial != nullptr);
|
||||
|
||||
auto newSpecializationConstants =
|
||||
FixedCapacityVector<Program::SpecializationConstant>(mSpecializationConstants);
|
||||
|
||||
bool hasChanged = false;
|
||||
for (const auto& [id, value] : constants) {
|
||||
if (newSpecializationConstants[id] != value) {
|
||||
newSpecializationConstants[id] = value;
|
||||
hasChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasChanged) {
|
||||
setConstantsImpl(std::move(newSpecializationConstants));
|
||||
}
|
||||
}
|
||||
|
||||
void LocalProgramCache::setConstants(
|
||||
std::initializer_list<std::pair<std::string_view, Program::SpecializationConstant>>
|
||||
constants) noexcept {
|
||||
assert_invariant(mMaterial != nullptr);
|
||||
|
||||
auto newSpecializationConstants =
|
||||
FixedCapacityVector<Program::SpecializationConstant>(mSpecializationConstants);
|
||||
|
||||
bool hasChanged = false;
|
||||
for (const auto& [name, value] : constants) {
|
||||
MaterialDefinition const& definition = mMaterial->getDefinition();
|
||||
auto it = definition.specializationConstantsNameToIndex.find(name);
|
||||
if (it != definition.specializationConstantsNameToIndex.cend()) {
|
||||
uint32_t id = it->second + CONFIG_MAX_RESERVED_SPEC_CONSTANTS;
|
||||
if (newSpecializationConstants[id] != value) {
|
||||
newSpecializationConstants[id] = value;
|
||||
hasChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasChanged) {
|
||||
setConstantsImpl(std::move(newSpecializationConstants));
|
||||
}
|
||||
}
|
||||
|
||||
void LocalProgramCache::setConstantsImpl(
|
||||
FixedCapacityVector<Program::SpecializationConstant> constants) noexcept {
|
||||
FEngine& engine = mMaterial->getEngine();
|
||||
|
||||
auto& internPool = engine.getMaterialCache().getSpecializationConstantsInternPool();
|
||||
MaterialParser const& materialParser = mMaterial->getMaterialParser();
|
||||
MaterialDefinition const& definition = mMaterial->getDefinition();
|
||||
const bool isDefaultMaterial = mMaterial->isDefaultMaterial();
|
||||
|
||||
// Release old resources...
|
||||
definition.releasePrograms(engine, mCachedPrograms.as_slice(), materialParser,
|
||||
mSpecializationConstants, isDefaultMaterial);
|
||||
internPool.release(mSpecializationConstants);
|
||||
|
||||
// Then acquire new ones.
|
||||
mSpecializationConstants = internPool.acquire(std::move(constants));
|
||||
definition.acquirePrograms(engine, mCachedPrograms.as_slice(), materialParser,
|
||||
mSpecializationConstants, isDefaultMaterial);
|
||||
}
|
||||
|
||||
template int32_t LocalProgramCache::getConstant<int32_t>(uint32_t id) const noexcept;
|
||||
template float LocalProgramCache::getConstant<float>(uint32_t id) const noexcept;
|
||||
template bool LocalProgramCache::getConstant<bool>(uint32_t id) const noexcept;
|
||||
|
||||
template int32_t LocalProgramCache::getConstant<int32_t>(std::string_view name) const noexcept;
|
||||
template float LocalProgramCache::getConstant<float>(std::string_view name) const noexcept;
|
||||
template bool LocalProgramCache::getConstant<bool>(std::string_view name) const noexcept;
|
||||
|
||||
} // namespace filament
|
||||
@@ -1,144 +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.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_LOCALPROGRAMCACHE_H
|
||||
#define TNT_FILAMENT_LOCALPROGRAMCACHE_H
|
||||
|
||||
#include "MaterialDefinition.h"
|
||||
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <private/filament/Variant.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/DriverApiForward.h>
|
||||
#include <backend/Program.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace filament {
|
||||
|
||||
class FMaterial;
|
||||
|
||||
// L0 cache for material programs. Manages recompiling them on-demand; owned by Material and
|
||||
// MaterialInstance.
|
||||
class LocalProgramCache {
|
||||
template<typename T>
|
||||
using is_supported_constant_parameter_t =
|
||||
std::enable_if_t<std::is_same_v<int32_t, T> || std::is_same_v<float, T> ||
|
||||
std::is_same_v<bool, T>>;
|
||||
|
||||
public:
|
||||
using Programs = utils::Slice<const backend::Handle<backend::HwProgram>>;
|
||||
using SpecializationConstants = utils::Slice<const backend::Program::SpecializationConstant>;
|
||||
|
||||
LocalProgramCache() = default;
|
||||
LocalProgramCache(LocalProgramCache const& other);
|
||||
|
||||
LocalProgramCache& operator=(LocalProgramCache const& other);
|
||||
|
||||
// Initialize for use in a Material.
|
||||
void initializeForMaterial(FEngine& engine, FMaterial const& material,
|
||||
utils::FixedCapacityVector<backend::Program::SpecializationConstant>
|
||||
specializationConstants);
|
||||
|
||||
// Initialize for use in a MaterialInstance. Copies the set of spec constants currently in use
|
||||
// from its Material.
|
||||
void initializeForMaterialInstance(FEngine& engine, FMaterial const& material);
|
||||
|
||||
bool isInitialized() const noexcept { return mMaterial != nullptr; }
|
||||
|
||||
// prepareProgram creates the program for the material's given variant at the backend level.
|
||||
// Must be called outside of backend render pass.
|
||||
// Must be called before getProgram() below.
|
||||
backend::Handle<backend::HwProgram> prepareProgram(backend::DriverApi& driver,
|
||||
Variant const variant,
|
||||
backend::CompilerPriorityQueue const priorityQueue) const noexcept {
|
||||
backend::Handle<backend::HwProgram> program = mCachedPrograms[variant.key];
|
||||
if (UTILS_LIKELY(program)) {
|
||||
return program;
|
||||
}
|
||||
return prepareProgramSlow(driver, variant, priorityQueue);
|
||||
}
|
||||
|
||||
// getProgram returns the backend program for the material's given variant.
|
||||
// Must be called after prepareProgram().
|
||||
[[nodiscard]]
|
||||
backend::Handle<backend::HwProgram> getProgram(Variant variant) const noexcept {
|
||||
variant = filterVariantForGetProgram(variant);
|
||||
backend::Handle<backend::HwProgram> program = mCachedPrograms[variant.key];
|
||||
assert_invariant(program);
|
||||
return program;
|
||||
}
|
||||
|
||||
SpecializationConstants getSpecializationConstants() const noexcept {
|
||||
return mSpecializationConstants;
|
||||
}
|
||||
|
||||
Programs getPrograms() const noexcept { return mCachedPrograms.as_slice(); }
|
||||
|
||||
// Free all engine resources associated with this instance.
|
||||
void terminate(FEngine& engine);
|
||||
|
||||
// Clear all cached programs. Used primarily by matdbg to "sever" a Material's connection to the
|
||||
// global material cache.
|
||||
void clear(FEngine& engine);
|
||||
|
||||
// Get constant by ID.
|
||||
template<typename T, typename = is_supported_constant_parameter_t<T>>
|
||||
T getConstant(uint32_t id) const noexcept {
|
||||
return std::get<T>(getConstantImpl(id));
|
||||
}
|
||||
|
||||
// Get constant by name.
|
||||
template<typename T, typename = is_supported_constant_parameter_t<T>>
|
||||
T getConstant(std::string_view name) const noexcept {
|
||||
return std::get<T>(getConstantImpl(name));
|
||||
}
|
||||
|
||||
// Set constants by ID.
|
||||
void setConstants(
|
||||
std::initializer_list<std::pair<uint32_t, backend::Program::SpecializationConstant>>
|
||||
constants) noexcept;
|
||||
|
||||
// Set constants by name.
|
||||
void setConstants(std::initializer_list<
|
||||
std::pair<std::string_view, backend::Program::SpecializationConstant>>
|
||||
constants) noexcept;
|
||||
|
||||
private:
|
||||
backend::Handle<backend::HwProgram> prepareProgramSlow(backend::DriverApi& driver,
|
||||
Variant const variant,
|
||||
backend::CompilerPriorityQueue const priorityQueue) const noexcept;
|
||||
|
||||
ProgramSpecialization getProgramSpecialization(Variant variant) const noexcept;
|
||||
|
||||
Variant filterVariantForGetProgram(Variant const variant) const noexcept;
|
||||
|
||||
void setConstantsImpl(utils::FixedCapacityVector<backend::Program::SpecializationConstant>
|
||||
constants) noexcept;
|
||||
|
||||
backend::Program::SpecializationConstant getConstantImpl(uint32_t id) const noexcept;
|
||||
backend::Program::SpecializationConstant getConstantImpl(std::string_view name) const noexcept;
|
||||
|
||||
FMaterial const* mMaterial = nullptr;
|
||||
mutable utils::FixedCapacityVector<backend::Handle<backend::HwProgram>> mCachedPrograms;
|
||||
SpecializationConstants mSpecializationConstants;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
#endif // TNT_FILAMENT_LOCALPROGRAMCACHE_H
|
||||
@@ -39,25 +39,12 @@ bool MaterialCache::MaterialKey::operator==(MaterialKey const& rhs) const noexce
|
||||
return parser == rhs.parser;
|
||||
}
|
||||
|
||||
MaterialCache::MaterialCache()
|
||||
: mDefinitions("MaterialCache::mDefinitions", 0),
|
||||
mPrograms("MaterialCache::mPrograms", 0) {}
|
||||
|
||||
MaterialCache::~MaterialCache() {
|
||||
assert_invariant(mDefinitions.empty());
|
||||
assert_invariant(mPrograms.empty());
|
||||
assert_invariant(mSpecializationConstantsInternPool.empty());
|
||||
}
|
||||
|
||||
void MaterialCache::terminate(FEngine& engine) {
|
||||
mPrograms.clearLruCache([&engine](backend::Handle<backend::HwProgram>& program) {
|
||||
engine.getDriverApi().destroyProgram(program);
|
||||
});
|
||||
mDefinitions.clearLruCache([&engine](MaterialDefinition& definition) {
|
||||
definition.terminate(engine);
|
||||
});
|
||||
}
|
||||
|
||||
MaterialDefinition* UTILS_NULLABLE MaterialCache::acquireMaterial(FEngine& engine,
|
||||
const void* UTILS_NONNULL data, size_t size) noexcept {
|
||||
std::unique_ptr<MaterialParser> parser = MaterialDefinition::createParser(engine.getBackend(),
|
||||
|
||||
@@ -56,13 +56,8 @@ public:
|
||||
using ProgramCache =
|
||||
utils::RefCountedMap<ProgramSpecialization, backend::Handle<backend::HwProgram>>;
|
||||
|
||||
MaterialCache();
|
||||
~MaterialCache();
|
||||
|
||||
// All reference-counted resources should be freed by the time MaterialCache is destructed, but
|
||||
// the LRU cache needs to be explicitly freed in addition.
|
||||
void terminate(FEngine& engine);
|
||||
|
||||
SpecializationConstantInternPool& getSpecializationConstantsInternPool() {
|
||||
return mSpecializationConstantsInternPool;
|
||||
}
|
||||
|
||||
@@ -271,8 +271,8 @@ std::unique_ptr<MaterialDefinition> MaterialDefinition::create(FEngine& engine,
|
||||
|
||||
void MaterialDefinition::terminate(FEngine& engine) {
|
||||
DriverApi& driver = engine.getDriverApi();
|
||||
perViewDescriptorSetLayoutPcf.terminate(engine.getDescriptorSetLayoutFactory(), driver);
|
||||
perViewDescriptorSetLayoutS2d.terminate(engine.getDescriptorSetLayoutFactory(), driver);
|
||||
perViewDescriptorSetLayout.terminate(engine.getDescriptorSetLayoutFactory(), driver);
|
||||
perViewDescriptorSetLayoutVsm.terminate(engine.getDescriptorSetLayoutFactory(), driver);
|
||||
descriptorSetLayout.terminate(engine.getDescriptorSetLayoutFactory(), driver);
|
||||
}
|
||||
|
||||
@@ -607,23 +607,23 @@ void MaterialDefinition::processDescriptorSets(FEngine& engine) {
|
||||
refractionMode == RefractionMode::SCREEN_SPACE;
|
||||
bool const hasFog = !(variantFilterMask & UserVariantFilterMask(UserVariantFilterBit::FOG));
|
||||
|
||||
this->perViewDescriptorSetLayoutPcfDescription = descriptor_sets::getPerViewDescriptorSetLayout(
|
||||
this->perViewDescriptorSetLayoutDescription = descriptor_sets::getPerViewDescriptorSetLayout(
|
||||
materialDomain, isLit, isSSR, hasFog, false);
|
||||
|
||||
this->perViewDescriptorSetLayoutS2dDescription = descriptor_sets::getPerViewDescriptorSetLayout(
|
||||
this->perViewDescriptorSetLayoutVsmDescription = descriptor_sets::getPerViewDescriptorSetLayout(
|
||||
materialDomain, isLit, isSSR, hasFog, true);
|
||||
|
||||
// set the labels
|
||||
this->descriptorSetLayoutDescription.label = CString{ name }.append("_perMat");
|
||||
this->perViewDescriptorSetLayoutPcfDescription.label = CString{ name }.append("_perView");
|
||||
this->perViewDescriptorSetLayoutS2dDescription.label = CString{ name }.append("_perViewVsm");
|
||||
this->perViewDescriptorSetLayoutDescription.label = CString{ name }.append("_perView");
|
||||
this->perViewDescriptorSetLayoutVsmDescription.label = CString{ name }.append("_perViewVsm");
|
||||
|
||||
// get the PER_RENDERABLE and PER_VIEW descriptor binding info
|
||||
for (auto&& [bindingPoint, dsl] : {
|
||||
std::pair{ DescriptorSetBindingPoints::PER_RENDERABLE,
|
||||
descriptor_sets::getPerRenderableLayout() },
|
||||
std::pair{ DescriptorSetBindingPoints::PER_VIEW,
|
||||
this->perViewDescriptorSetLayoutPcfDescription }}) {
|
||||
this->perViewDescriptorSetLayoutDescription }}) {
|
||||
Program::DescriptorBindingsInfo& descriptors = programDescriptorBindings[+bindingPoint];
|
||||
descriptors.reserve(dsl.descriptors.size());
|
||||
for (auto const& entry: dsl.descriptors) {
|
||||
@@ -636,17 +636,17 @@ void MaterialDefinition::processDescriptorSets(FEngine& engine) {
|
||||
descriptorSetLayoutFactory, driver,
|
||||
this->descriptorSetLayoutDescription };
|
||||
|
||||
this->perViewDescriptorSetLayoutPcf = {
|
||||
this->perViewDescriptorSetLayout = {
|
||||
descriptorSetLayoutFactory, driver,
|
||||
this->perViewDescriptorSetLayoutPcfDescription };
|
||||
this->perViewDescriptorSetLayoutDescription };
|
||||
|
||||
this->perViewDescriptorSetLayoutS2d = {
|
||||
this->perViewDescriptorSetLayoutVsm = {
|
||||
descriptorSetLayoutFactory, driver,
|
||||
this->perViewDescriptorSetLayoutS2dDescription };
|
||||
this->perViewDescriptorSetLayoutVsmDescription };
|
||||
}
|
||||
|
||||
backend::DescriptorSetLayout const& MaterialDefinition::getPerViewDescriptorSetLayoutDescription(
|
||||
Variant const variant, bool const useS2dDescriptorSetLayout) const noexcept {
|
||||
Variant const variant, bool const useVsmDescriptorSetLayout) const noexcept {
|
||||
if (materialDomain == MaterialDomain::SURFACE) {
|
||||
if (Variant::isValidDepthVariant(variant)) {
|
||||
// Use the layout description used to create the per view depth variant layout.
|
||||
@@ -657,10 +657,10 @@ backend::DescriptorSetLayout const& MaterialDefinition::getPerViewDescriptorSetL
|
||||
return descriptor_sets::getSsrVariantLayout();
|
||||
}
|
||||
}
|
||||
if (useS2dDescriptorSetLayout) {
|
||||
return perViewDescriptorSetLayoutS2dDescription;
|
||||
if (useVsmDescriptorSetLayout) {
|
||||
return perViewDescriptorSetLayoutVsmDescription;
|
||||
}
|
||||
return perViewDescriptorSetLayoutPcfDescription;
|
||||
return perViewDescriptorSetLayoutDescription;
|
||||
}
|
||||
|
||||
Handle<HwProgram> MaterialDefinition::compileProgram(
|
||||
@@ -689,7 +689,7 @@ Handle<HwProgram> MaterialDefinition::compileProgram(
|
||||
pb.descriptorLayout(+DescriptorSetBindingPoints::PER_VIEW,
|
||||
getPerViewDescriptorSetLayoutDescription(
|
||||
specialization.variant,
|
||||
Variant::isShadowSampler2DVariant(specialization.variant)));
|
||||
Variant::isVSMVariant(specialization.variant)));
|
||||
pb.descriptorLayout(+DescriptorSetBindingPoints::PER_RENDERABLE,
|
||||
descriptor_sets::getPerRenderableLayout());
|
||||
pb.descriptorLayout(
|
||||
|
||||
@@ -94,17 +94,17 @@ struct MaterialDefinition {
|
||||
backend::ShaderModel const sm, bool isStereoSupported) const noexcept;
|
||||
|
||||
backend::DescriptorSetLayout const& getPerViewDescriptorSetLayoutDescription(
|
||||
Variant const variant, bool useS2dDescriptorSetLayout) const noexcept;
|
||||
Variant const variant, bool useVsmDescriptorSetLayout) const noexcept;
|
||||
|
||||
// Keep track of the definitions of the descriptor set layouts, as these
|
||||
// may be used by some backends in parallel compilation of programs.
|
||||
backend::DescriptorSetLayout perViewDescriptorSetLayoutPcfDescription;
|
||||
backend::DescriptorSetLayout perViewDescriptorSetLayoutS2dDescription;
|
||||
backend::DescriptorSetLayout perViewDescriptorSetLayoutDescription;
|
||||
backend::DescriptorSetLayout perViewDescriptorSetLayoutVsmDescription;
|
||||
backend::DescriptorSetLayout descriptorSetLayoutDescription;
|
||||
|
||||
// try to order by frequency of use
|
||||
filament::DescriptorSetLayout perViewDescriptorSetLayoutPcf;
|
||||
filament::DescriptorSetLayout perViewDescriptorSetLayoutS2d;
|
||||
filament::DescriptorSetLayout perViewDescriptorSetLayout;
|
||||
filament::DescriptorSetLayout perViewDescriptorSetLayoutVsm;
|
||||
filament::DescriptorSetLayout descriptorSetLayout;
|
||||
backend::Program::DescriptorSetInfo programDescriptorBindings;
|
||||
|
||||
|
||||
@@ -19,61 +19,90 @@
|
||||
|
||||
#include "details/Engine.h"
|
||||
#include "details/Material.h"
|
||||
#include "details/MaterialInstance.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <utils/debug.h>
|
||||
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
using namespace utils;
|
||||
using Record = MaterialInstanceManager::Record;
|
||||
|
||||
MaterialInstanceManager::MaterialInstanceManager() noexcept = default;
|
||||
std::pair<FMaterialInstance*, int32_t> Record::getInstance() {
|
||||
if (mAvailable < mInstances.size()) {
|
||||
auto index = mAvailable++;
|
||||
return { mInstances[index], index };
|
||||
}
|
||||
assert_invariant(mAvailable == mInstances.size());
|
||||
auto& name = mMaterial->getName();
|
||||
FMaterialInstance* inst = mMaterial->createInstance(name.c_str_safe());
|
||||
mInstances.push_back(inst);
|
||||
return { inst, mAvailable++ };
|
||||
}
|
||||
|
||||
FMaterialInstance* Record::getInstance(int32_t const fixedInstanceindex) const {
|
||||
assert_invariant(fixedInstanceindex >= 0 && fixedInstanceindex < int32_t(mInstances.size()));
|
||||
return mInstances[fixedInstanceindex];
|
||||
}
|
||||
|
||||
// Defined in cpp to avoid inlining
|
||||
Record::Record(Record const& rhs) noexcept = default;
|
||||
Record& Record::operator=(Record const& rhs) noexcept = default;
|
||||
Record::Record(Record&& rhs) noexcept = default;
|
||||
Record& Record::operator=(Record&& rhs) noexcept = default;
|
||||
|
||||
void Record::terminate(FEngine& engine) {
|
||||
std::for_each(mInstances.begin(), mInstances.end(),
|
||||
[&engine](auto instance) { engine.destroy(instance); });
|
||||
}
|
||||
|
||||
MaterialInstanceManager::MaterialInstanceManager() noexcept {}
|
||||
|
||||
MaterialInstanceManager::MaterialInstanceManager(
|
||||
MaterialInstanceManager const& rhs) noexcept = default;
|
||||
MaterialInstanceManager::MaterialInstanceManager(MaterialInstanceManager&& rhs) noexcept = default;
|
||||
MaterialInstanceManager& MaterialInstanceManager::operator=(MaterialInstanceManager&& rhs) noexcept = default;
|
||||
MaterialInstanceManager& MaterialInstanceManager::operator=(
|
||||
MaterialInstanceManager const& rhs) noexcept = default;
|
||||
MaterialInstanceManager& MaterialInstanceManager::operator=(
|
||||
MaterialInstanceManager&& rhs) noexcept = default;
|
||||
|
||||
MaterialInstanceManager::~MaterialInstanceManager() = default;
|
||||
|
||||
void MaterialInstanceManager::terminate(FEngine& engine) {
|
||||
for (auto const& [key, instance] : mMaterialInstances) {
|
||||
engine.destroy(instance);
|
||||
}
|
||||
mMaterialInstances.clear();
|
||||
|
||||
for (auto const& [material, pool] : mAnonymousMaterialInstances) {
|
||||
for (auto instance : pool.instances) {
|
||||
engine.destroy(instance);
|
||||
}
|
||||
}
|
||||
mAnonymousMaterialInstances.clear();
|
||||
std::for_each(mMaterials.begin(), mMaterials.end(), [&engine](auto& record) {
|
||||
record.terminate(engine);
|
||||
});
|
||||
}
|
||||
|
||||
void MaterialInstanceManager::reset() {
|
||||
for (auto& [material, pool] : mAnonymousMaterialInstances) {
|
||||
pool.highWaterMark = 0;
|
||||
Record& MaterialInstanceManager::getRecord(FMaterial const* const ma) const {
|
||||
auto itr = std::find_if(mMaterials.begin(), mMaterials.end(), [ma](auto& record) {
|
||||
return ma == record.mMaterial;
|
||||
});
|
||||
if (itr == mMaterials.end()) {
|
||||
mMaterials.emplace_back(ma);
|
||||
itr = std::prev(mMaterials.end());
|
||||
}
|
||||
}
|
||||
|
||||
FMaterialInstance* MaterialInstanceManager::getMaterialInstance(FMaterial const* ma, uint32_t tag) const {
|
||||
Key const key{ma, tag};
|
||||
auto it = mMaterialInstances.find(key);
|
||||
if (it != mMaterialInstances.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
FMaterialInstance* const instance = ma->createInstance(ma->getName().c_str_safe());
|
||||
mMaterialInstances.emplace(key, instance);
|
||||
return instance;
|
||||
return *itr;
|
||||
}
|
||||
|
||||
FMaterialInstance* MaterialInstanceManager::getMaterialInstance(FMaterial const* ma) const {
|
||||
AnonymousPool& pool = mAnonymousMaterialInstances[ma];
|
||||
if (pool.highWaterMark < pool.instances.size()) {
|
||||
return pool.instances[pool.highWaterMark++];
|
||||
}
|
||||
|
||||
FMaterialInstance* const instance = ma->createInstance(ma->getName().c_str_safe());
|
||||
pool.instances.push_back(instance);
|
||||
pool.highWaterMark++;
|
||||
return instance;
|
||||
auto [inst, index] = getRecord(ma).getInstance();
|
||||
return inst;
|
||||
}
|
||||
|
||||
FMaterialInstance* MaterialInstanceManager::getMaterialInstance(FMaterial const* ma,
|
||||
int32_t const fixedIndex) const {
|
||||
return getRecord(ma).getInstance(fixedIndex);
|
||||
}
|
||||
|
||||
std::pair<FMaterialInstance*, int32_t> MaterialInstanceManager::getFixedMaterialInstance(
|
||||
FMaterial const* ma) {
|
||||
return getRecord(ma).getInstance();
|
||||
}
|
||||
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -16,12 +16,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <utils/bitset.h>
|
||||
|
||||
#include <utils/Hash.h>
|
||||
#include <vector>
|
||||
|
||||
namespace filament {
|
||||
|
||||
@@ -33,10 +30,37 @@ class FEngine;
|
||||
// re-use instances across frames.
|
||||
class MaterialInstanceManager {
|
||||
public:
|
||||
class Record {
|
||||
public:
|
||||
Record(FMaterial const* material)
|
||||
: mMaterial(material),
|
||||
mAvailable(0) {}
|
||||
~Record() = default;
|
||||
|
||||
Record(Record const& rhs) noexcept;
|
||||
Record& operator=(Record const& rhs) noexcept;
|
||||
Record(Record&& rhs) noexcept;
|
||||
Record& operator=(Record&& rhs) noexcept;
|
||||
|
||||
void terminate(FEngine& engine);
|
||||
void reset() { mAvailable = 0; }
|
||||
std::pair<FMaterialInstance*, int32_t> getInstance();
|
||||
FMaterialInstance* getInstance(int32_t fixedInstanceindex) const;
|
||||
|
||||
private:
|
||||
FMaterial const* mMaterial = nullptr;
|
||||
std::vector<FMaterialInstance*> mInstances;
|
||||
uint32_t mAvailable;
|
||||
|
||||
friend class MaterialInstanceManager;
|
||||
};
|
||||
|
||||
constexpr static int32_t INVALID_FIXED_INDEX = -1;
|
||||
|
||||
MaterialInstanceManager() noexcept;
|
||||
MaterialInstanceManager(MaterialInstanceManager const& rhs) = delete;
|
||||
MaterialInstanceManager(MaterialInstanceManager const& rhs) noexcept;
|
||||
MaterialInstanceManager(MaterialInstanceManager&& rhs) noexcept;
|
||||
MaterialInstanceManager& operator=(MaterialInstanceManager const& rhs) = delete;
|
||||
MaterialInstanceManager& operator=(MaterialInstanceManager const& rhs) noexcept;
|
||||
MaterialInstanceManager& operator=(MaterialInstanceManager&& rhs) noexcept;
|
||||
|
||||
~MaterialInstanceManager();
|
||||
@@ -47,60 +71,40 @@ public:
|
||||
*/
|
||||
void terminate(FEngine& engine);
|
||||
|
||||
/**
|
||||
* Resets the anonymous material instances cache.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* This returns a material instance given a material and a tag.
|
||||
*
|
||||
* If the material instance doesn't exist in the cache, it is created and cached.
|
||||
*
|
||||
* @param ma FMaterial to get a MaterialInstance for
|
||||
* @param tag A unique tag identifying the MaterialInstance
|
||||
* @return A FMaterialInstance pointer
|
||||
*/
|
||||
FMaterialInstance* getMaterialInstance(FMaterial const* ma, uint32_t tag) const;
|
||||
|
||||
/**
|
||||
* This returns a material instance given a material from a cache.
|
||||
*
|
||||
* If the material instance doesn't exist in the cache, it is created and cached.
|
||||
*
|
||||
* It is permissible to call the method several times, in which case a different MaterialInstance will be returned.
|
||||
* It is guaranteed to be different from MaterialInstances returned with a tag.
|
||||
*
|
||||
* @param ma FMaterial to get a MaterialInstance for
|
||||
* @return A FMaterialInstance pointer
|
||||
/*
|
||||
* This returns a material instance given a material. The implementation will try to find an
|
||||
* available instance in the cache. If one is not found, then a new instance will be created and
|
||||
* added to the cache.
|
||||
*/
|
||||
FMaterialInstance* getMaterialInstance(FMaterial const* ma) const;
|
||||
|
||||
/*
|
||||
* This returns a material instance given a material and an index. The `fixedIndex` should be
|
||||
* a value returned by getiFixedMaterialInstance.
|
||||
*/
|
||||
FMaterialInstance* getMaterialInstance(FMaterial const* ma, int32_t const fixedIndex) const;
|
||||
|
||||
/*
|
||||
* This returns a material instance and an index given a material. This is needed for the
|
||||
* case when two framegraph passes need to refer to the same material instance.
|
||||
* The returned index can be used with `getFixedMaterialInstance` to get a specific instance
|
||||
* of a material (and not a random entry in the record cache).
|
||||
*/
|
||||
std::pair<FMaterialInstance*, int32_t> getFixedMaterialInstance(FMaterial const* ma);
|
||||
|
||||
|
||||
/*
|
||||
* Marks all of the material instances as unused. Typically, you'd call this at the beginning of
|
||||
* a frame.
|
||||
*/
|
||||
void reset() {
|
||||
std::for_each(mMaterials.begin(), mMaterials.end(), [](auto& record) { record.reset(); });
|
||||
}
|
||||
|
||||
private:
|
||||
struct Key {
|
||||
FMaterial const* material;
|
||||
uint32_t tag;
|
||||
bool operator==(Key const& rhs) const noexcept {
|
||||
return material == rhs.material && tag == rhs.tag;
|
||||
}
|
||||
};
|
||||
Record& getRecord(FMaterial const* material) const;
|
||||
|
||||
struct Hasher {
|
||||
std::size_t operator()(Key const& key) const noexcept {
|
||||
std::size_t seed = 0;
|
||||
utils::hash::combine(seed, key.material);
|
||||
utils::hash::combine(seed, key.tag);
|
||||
return seed;
|
||||
}
|
||||
};
|
||||
|
||||
struct AnonymousPool {
|
||||
std::vector<FMaterialInstance*> instances;
|
||||
uint32_t highWaterMark = 0;
|
||||
};
|
||||
|
||||
mutable std::unordered_map<Key, FMaterialInstance*, Hasher> mMaterialInstances;
|
||||
mutable std::unordered_map<FMaterial const*, AnonymousPool> mAnonymousMaterialInstances;
|
||||
mutable std::vector<Record> mMaterials;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -49,7 +49,6 @@
|
||||
#include "fsr.h"
|
||||
#include "FrameHistory.h"
|
||||
#include "RenderPass.h"
|
||||
#include "ShadowMapManager.h"
|
||||
|
||||
#include "details/Camera.h"
|
||||
#include "details/ColorGrading.h"
|
||||
@@ -226,6 +225,7 @@ const PostProcessManager::JitterSequence<32>
|
||||
|
||||
PostProcessManager::PostProcessManager(FEngine& engine) noexcept
|
||||
: mEngine(engine),
|
||||
mFixedMaterialInstanceIndex {},
|
||||
mWorkaroundSplitEasu(false),
|
||||
mWorkaroundAllowReadOnlyAncillaryFeedbackLoop(false) {
|
||||
// don't use Engine here, it's not fully initialized yet
|
||||
@@ -455,6 +455,7 @@ Handle<HwTexture> PostProcessManager::getZeroTextureArray() const {
|
||||
|
||||
void PostProcessManager::resetForRender() {
|
||||
mMaterialInstanceManager.reset();
|
||||
mFixedMaterialInstanceIndex = {};
|
||||
}
|
||||
|
||||
void PostProcessManager::unbindAllDescriptorSets(DriverApi& driver) noexcept {
|
||||
@@ -801,7 +802,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::ssr(FrameGraph& fg,
|
||||
|
||||
// use our special SSR variant, it can only be applied to object that have
|
||||
// the SCREEN_SPACE ReflectionMode.
|
||||
passBuilder.variant(Variant{ Variant::SPECIAL_SSR_VARIANT });
|
||||
passBuilder.variant(Variant{ Variant::SPECIAL_SSR });
|
||||
|
||||
// generate all our drawing commands, except blended objects.
|
||||
passBuilder.commandTypeFlags(RenderPass::CommandTypeFlags::SCREEN_SPACE_REFLECTIONS);
|
||||
@@ -999,10 +1000,13 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::screenSpaceAmbientOcclusion(
|
||||
auto& material = getPostProcessMaterial(materialName);
|
||||
|
||||
FMaterial* ma = material.getMaterial(mEngine, driver);
|
||||
ma->getPrograms().setConstants({
|
||||
{ "useVisibilityBitmasks", options.gtao.useVisibilityBitmasks },
|
||||
{ "linearThickness", options.gtao.linearThickness },
|
||||
});
|
||||
{
|
||||
FMaterial::SpecializationConstantsBuilder maConstants =
|
||||
ma->getSpecializationConstantsBuilder();
|
||||
maConstants.set("useVisibilityBitmasks", options.gtao.useVisibilityBitmasks);
|
||||
maConstants.set("linearThickness", options.gtao.linearThickness);
|
||||
ma->setSpecializationConstants(std::move(maConstants));
|
||||
}
|
||||
|
||||
ma = material.getMaterial(mEngine, driver);
|
||||
FMaterialInstance* const mi = getMaterialInstance(ma);
|
||||
@@ -2548,7 +2552,11 @@ void PostProcessManager::colorGradingSubpass(DriverApi& driver,
|
||||
auto const& material = getPostProcessMaterial("colorGradingAsSubpass");
|
||||
FMaterial const* const ma = material.getMaterial(mEngine, driver, variant);
|
||||
// the UBO has been set and committed in colorGradingPrepareSubpass()
|
||||
FMaterialInstance const* mi = mMaterialInstanceManager.getMaterialInstance(ma, colorGradingConfig.translucent);
|
||||
int32_t const fixedIndex = colorGradingConfig.translucent
|
||||
? mFixedMaterialInstanceIndex.colorGradingTranslucent
|
||||
: mFixedMaterialInstanceIndex.colorGradingOpaque;
|
||||
|
||||
FMaterialInstance const* mi = mMaterialInstanceManager.getMaterialInstance(ma, fixedIndex);
|
||||
mi->use(driver);
|
||||
auto const pipeline = getPipelineState(ma, variant);
|
||||
driver.nextSubpass();
|
||||
@@ -2559,7 +2567,8 @@ void PostProcessManager::colorGradingSubpass(DriverApi& driver,
|
||||
void PostProcessManager::customResolvePrepareSubpass(DriverApi& driver, CustomResolveOp const op) noexcept {
|
||||
auto const& material = getPostProcessMaterial("customResolveAsSubpass");
|
||||
auto const ma = material.getMaterial(mEngine, driver, PostProcessVariant::OPAQUE);
|
||||
auto* const mi = mMaterialInstanceManager.getMaterialInstance(ma, 0);
|
||||
auto [mi, fixedIndex] = mMaterialInstanceManager.getFixedMaterialInstance(ma);
|
||||
mFixedMaterialInstanceIndex.customResolve = fixedIndex;
|
||||
mi->setParameter("direction", op == CustomResolveOp::COMPRESS ? 1.0f : -1.0f),
|
||||
mi->commit(driver, getUboManager());
|
||||
}
|
||||
@@ -2571,7 +2580,8 @@ void PostProcessManager::customResolveSubpass(DriverApi& driver) noexcept {
|
||||
auto const& material = getPostProcessMaterial("customResolveAsSubpass");
|
||||
FMaterial const* const ma = material.getMaterial(mEngine, driver);
|
||||
// the UBO has been set and committed in customResolvePrepareSubpass()
|
||||
FMaterialInstance const* mi = mMaterialInstanceManager.getMaterialInstance(ma, 0);
|
||||
FMaterialInstance const* mi = mMaterialInstanceManager.getMaterialInstance(ma,
|
||||
mFixedMaterialInstanceIndex.customResolve);
|
||||
mi->use(driver);
|
||||
|
||||
auto const pipeline = getPipelineState(ma);
|
||||
@@ -2610,8 +2620,9 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::customResolveUncompressPass(
|
||||
void PostProcessManager::clearAncillaryBuffersPrepare(DriverApi& driver,
|
||||
Variant::type_t variant) noexcept {
|
||||
auto const& material = getPostProcessMaterial("clearDepth");
|
||||
auto const ma = material.getMaterial(mEngine, driver, variant);
|
||||
auto const mi = mMaterialInstanceManager.getMaterialInstance(ma, 0);
|
||||
auto ma = material.getMaterial(mEngine, driver, variant);
|
||||
auto [mi, fixedIndex] = mMaterialInstanceManager.getFixedMaterialInstance(ma);
|
||||
mFixedMaterialInstanceIndex.clearDepth = fixedIndex;
|
||||
mi->commit(driver, getUboManager());
|
||||
}
|
||||
|
||||
@@ -2629,7 +2640,8 @@ void PostProcessManager::clearAncillaryBuffers(DriverApi& driver,
|
||||
FMaterial const* const ma = material.getMaterial(mEngine, driver, variant);
|
||||
|
||||
// the UBO has been set and committed in clearAncillaryBuffersPrepare()
|
||||
FMaterialInstance const* const mi = mMaterialInstanceManager.getMaterialInstance(ma, 0);
|
||||
FMaterialInstance const* const mi = mMaterialInstanceManager.getMaterialInstance(ma,
|
||||
mFixedMaterialInstanceIndex.clearDepth);
|
||||
mi->use(driver);
|
||||
|
||||
auto pipeline = getPipelineState(ma, variant);
|
||||
@@ -2915,18 +2927,20 @@ void PostProcessManager::configureTemporalAntiAliasingMaterial(backend::DriverAp
|
||||
TemporalAntiAliasingOptions const& taaOptions) noexcept {
|
||||
|
||||
FMaterial* const ma = getPostProcessMaterial("taa").getMaterial(mEngine, driver);
|
||||
ma->getPrograms().setConstants({
|
||||
{ "upscaling", taaOptions.upscaling > 1.0f },
|
||||
{ "historyReprojection", taaOptions.historyReprojection },
|
||||
{ "filterHistory", taaOptions.filterHistory },
|
||||
{ "filterInput", taaOptions.filterInput },
|
||||
{ "useYCoCg", taaOptions.useYCoCg },
|
||||
{ "hdr", taaOptions.hdr },
|
||||
{ "preventFlickering", taaOptions.preventFlickering },
|
||||
{ "boxType", int32_t(taaOptions.boxType) },
|
||||
{ "boxClipping", int32_t(taaOptions.boxClipping) },
|
||||
{ "varianceGamma", taaOptions.varianceGamma },
|
||||
});
|
||||
FMaterial::SpecializationConstantsBuilder maConstants = ma->getSpecializationConstantsBuilder();
|
||||
|
||||
maConstants.set("upscaling", taaOptions.upscaling > 1.0f);
|
||||
maConstants.set("historyReprojection", taaOptions.historyReprojection);
|
||||
maConstants.set("filterHistory", taaOptions.filterHistory);
|
||||
maConstants.set("filterInput", taaOptions.filterInput);
|
||||
maConstants.set("useYCoCg", taaOptions.useYCoCg);
|
||||
maConstants.set("hdr", taaOptions.hdr);
|
||||
maConstants.set("preventFlickering", taaOptions.preventFlickering);
|
||||
maConstants.set("boxType", int32_t(taaOptions.boxType));
|
||||
maConstants.set("boxClipping", int32_t(taaOptions.boxClipping));
|
||||
maConstants.set("varianceGamma", taaOptions.varianceGamma);
|
||||
|
||||
ma->setSpecializationConstants(std::move(maConstants));
|
||||
}
|
||||
|
||||
FMaterialInstance* PostProcessManager::configureColorGradingMaterial(backend::DriverApi& driver,
|
||||
@@ -2934,16 +2948,23 @@ FMaterialInstance* PostProcessManager::configureColorGradingMaterial(backend::Dr
|
||||
ColorGradingConfig const& colorGradingConfig, VignetteOptions const& vignetteOptions,
|
||||
uint32_t const width, uint32_t const height) noexcept {
|
||||
FMaterial* ma = material.getMaterial(mEngine, driver);
|
||||
ma->getPrograms().setConstants({
|
||||
{ "isOneDimensional", colorGrading->isOneDimensional() },
|
||||
{ "isLDR", colorGrading->isLDR() },
|
||||
});
|
||||
{
|
||||
FMaterial::SpecializationConstantsBuilder maConstants =
|
||||
ma->getSpecializationConstantsBuilder();
|
||||
maConstants.set("isOneDimensional", colorGrading->isOneDimensional());
|
||||
maConstants.set("isLDR", colorGrading->isLDR());
|
||||
ma->setSpecializationConstants(std::move(maConstants));
|
||||
}
|
||||
|
||||
PostProcessVariant const variant = colorGradingConfig.translucent
|
||||
? PostProcessVariant::TRANSLUCENT
|
||||
: PostProcessVariant::OPAQUE;
|
||||
ma = material.getMaterial(mEngine, driver, variant);
|
||||
FMaterialInstance* mi = mMaterialInstanceManager.getMaterialInstance(ma, colorGradingConfig.translucent);
|
||||
FMaterialInstance* mi = nullptr;
|
||||
int32_t& fixedIndex = colorGradingConfig.translucent
|
||||
? mFixedMaterialInstanceIndex.colorGradingTranslucent
|
||||
: mFixedMaterialInstanceIndex.colorGradingOpaque;
|
||||
std::tie(mi, fixedIndex) = mMaterialInstanceManager.getFixedMaterialInstance(ma);
|
||||
|
||||
const SamplerParams params = SamplerParams{
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
@@ -3673,12 +3694,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolve(FrameGraph& fg,
|
||||
}
|
||||
|
||||
// The Metal / Vulkan backends currently don't support depth/stencil resolve.
|
||||
// TODO: Stencil resolve is actually *not* supported. Trying to resolve a stencil texture will
|
||||
// trigger an assert on debug builds. We need to investigate how this can be accomplished
|
||||
// through shaders or some other manipulation.
|
||||
if ((isDepthFormat(inDesc.format) || isStencilFormat(inDesc.format)) &&
|
||||
(!mDepthStencilResolveSupported)) {
|
||||
return resolveDepthWithShader(fg, outputBufferName, input, outDesc);
|
||||
if (isDepthFormat(inDesc.format) && (!mDepthStencilResolveSupported)) {
|
||||
return resolveDepth(fg, outputBufferName, input, outDesc);
|
||||
}
|
||||
|
||||
outDesc.width = inDesc.width;
|
||||
@@ -3693,6 +3710,9 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolve(FrameGraph& fg,
|
||||
|
||||
auto const& ppResolve = fg.addPass<ResolveData>("resolve",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
// we currently don't support stencil resolve.
|
||||
assert_invariant(!isStencilFormat(inDesc.format));
|
||||
|
||||
data.input = builder.read(input, FrameGraphTexture::Usage::BLIT_SRC);
|
||||
data.output = builder.createTexture(outputBufferName, outDesc);
|
||||
data.output = builder.write(data.output, FrameGraphTexture::Usage::BLIT_DST);
|
||||
@@ -3717,7 +3737,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolve(FrameGraph& fg,
|
||||
return ppResolve->output;
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::resolveDepthWithShader(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::resolveDepth(FrameGraph& fg,
|
||||
utils::StaticString outputBufferName, FrameGraphId<FrameGraphTexture> const input,
|
||||
FrameGraphTexture::Descriptor outDesc) noexcept {
|
||||
|
||||
@@ -3742,8 +3762,11 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolveDepthWithShader(Frame
|
||||
FrameGraphId<FrameGraphTexture> output;
|
||||
};
|
||||
|
||||
auto const& ppResolve = fg.addPass<ResolveData>("resolveDepthWithShader",
|
||||
auto const& ppResolve = fg.addPass<ResolveData>("resolveDepth",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
// we currently don't support stencil resolve
|
||||
assert_invariant(!isStencilFormat(inDesc.format));
|
||||
|
||||
data.input = builder.sample(input);
|
||||
data.output = builder.createTexture(outputBufferName, outDesc);
|
||||
data.output = builder.write(data.output, FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
|
||||
@@ -3752,9 +3775,6 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::resolveDepthWithShader(Frame
|
||||
.clearFlags = TargetBufferFlags::DEPTH });
|
||||
},
|
||||
[=, this](FrameGraphResources const& resources, auto const& data, DriverApi& driver) {
|
||||
// we currently don't support stencil resolve
|
||||
assert_invariant(!isStencilFormat(inDesc.format));
|
||||
|
||||
bindPostProcessDescriptorSet(driver);
|
||||
bindPerRenderableDescriptorSet(driver);
|
||||
auto const& input = resources.getTexture(data.input);
|
||||
@@ -3803,13 +3823,15 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::vsmMipmapPass(FrameGraph& fg
|
||||
auto const& inDesc = resources.getDescriptor(data.in);
|
||||
auto width = inDesc.width;
|
||||
assert_invariant(width == inDesc.height);
|
||||
uint32_t const dim = std::max(1u, width >> (level + 1));
|
||||
int const dim = width >> (level + 1);
|
||||
|
||||
auto& material = getPostProcessMaterial("vsmMipmap");
|
||||
FMaterial const* const ma = material.getMaterial(mEngine, driver);
|
||||
|
||||
// When generating shadow map mip levels, we want to preserve the 1 texel border.
|
||||
// (note clearing never respects the scissor in Filament)
|
||||
auto const pipeline = getPipelineState(ma);
|
||||
backend::Viewport const scissor = { 0, 0, dim, dim };
|
||||
backend::Viewport const scissor = { 1u, 1u, dim - 2u, dim - 2u };
|
||||
|
||||
FMaterialInstance* const mi = getMaterialInstance(ma);
|
||||
mi->setParameter("color", in, SamplerParams{
|
||||
@@ -3831,7 +3853,6 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::vsmMipmapPass(FrameGraph& fg
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::debugShadowCascades(FrameGraph& fg,
|
||||
ShadowMapManager const& smm,
|
||||
FrameGraphId<FrameGraphTexture> const input,
|
||||
FrameGraphId<FrameGraphTexture> const depth) noexcept {
|
||||
|
||||
@@ -3849,31 +3870,17 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::debugShadowCascades(FrameGra
|
||||
data.output = builder.createTexture("Shadow Cascade Debug", desc);
|
||||
builder.declareRenderPass(data.output);
|
||||
},
|
||||
[=, &smm, this](FrameGraphResources const& resources, auto const& data, DriverApi& driver) {
|
||||
[=, this](FrameGraphResources const& resources, auto const& data, DriverApi& driver) {
|
||||
bindPostProcessDescriptorSet(driver);
|
||||
bindPerRenderableDescriptorSet(driver);
|
||||
auto color = resources.getTexture(data.color);
|
||||
auto depth = resources.getTexture(data.depth);
|
||||
auto const out = resources.getRenderPassInfo();
|
||||
|
||||
auto const& smu = smm.getShadowMappingUniforms();
|
||||
mat4f lightFromWorldMatrix[4] = {};
|
||||
float4 scissorNormalized[4] = {};
|
||||
for (size_t i = 0, c = std::max(4u, (smu.cascades & 0xF)) ; i < c; i++) {
|
||||
auto const& csp = smm.getCascadeShaderParameters(i);
|
||||
lightFromWorldMatrix[i] = csp.lightSpace;
|
||||
scissorNormalized[i] = csp.scissorNormalized;
|
||||
}
|
||||
|
||||
auto const& material = getPostProcessMaterial("debugShadowCascades");
|
||||
FMaterialInstance* const mi = getMaterialInstance(mEngine, driver, material);
|
||||
FMaterialInstance* const mi =
|
||||
getMaterialInstance(mEngine, driver, material);
|
||||
mi->setParameter("color", color, SamplerParams{}); // nearest
|
||||
mi->setParameter("depth", depth, SamplerParams{}); // nearest
|
||||
mi->setParameter("cascadeSplits", smu.cascadeSplits);
|
||||
mi->setParameter("cascadeCount", smu.cascades & 0xF);
|
||||
mi->setParameter("shadowAtlasResolution", smu.atlasResolution);
|
||||
mi->setParameter("lightFromWorldMatrix", lightFromWorldMatrix, 4);
|
||||
mi->setParameter("scissorNormalized", scissorNormalized, 4);
|
||||
commitAndRenderFullScreenQuad(driver, out, mi);
|
||||
unbindAllDescriptorSets(driver);
|
||||
});
|
||||
|
||||
@@ -66,7 +66,6 @@ class FMaterialInstance;
|
||||
class FrameGraph;
|
||||
class RenderPass;
|
||||
class RenderPassBuilder;
|
||||
class ShadowMapManager;
|
||||
class UboManager;
|
||||
struct CameraInfo;
|
||||
|
||||
@@ -305,10 +304,9 @@ public:
|
||||
utils::StaticString outputBufferName, FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphTexture::Descriptor outDesc) noexcept;
|
||||
|
||||
// Resolves base level of input and outputs a texture from outDesc using a shader instead of
|
||||
// driver-implemented API.
|
||||
// Resolves base level of input and outputs a texture from outDesc.
|
||||
// outDesc with, height, format and samples will be overridden.
|
||||
FrameGraphId<FrameGraphTexture> resolveDepthWithShader(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> resolveDepth(FrameGraph& fg,
|
||||
utils::StaticString outputBufferName, FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphTexture::Descriptor outDesc) noexcept;
|
||||
|
||||
@@ -323,7 +321,6 @@ public:
|
||||
bool reinhard, size_t kernelWidth, float sigma) noexcept;
|
||||
|
||||
FrameGraphId<FrameGraphTexture> debugShadowCascades(FrameGraph& fg,
|
||||
ShadowMapManager const& smm,
|
||||
FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphId<FrameGraphTexture> depth) noexcept;
|
||||
|
||||
@@ -489,6 +486,13 @@ private:
|
||||
|
||||
MaterialInstanceManager mMaterialInstanceManager;
|
||||
|
||||
struct {
|
||||
int32_t colorGradingTranslucent = MaterialInstanceManager::INVALID_FIXED_INDEX;
|
||||
int32_t colorGradingOpaque = MaterialInstanceManager::INVALID_FIXED_INDEX;
|
||||
int32_t customResolve = MaterialInstanceManager::INVALID_FIXED_INDEX;
|
||||
int32_t clearDepth = MaterialInstanceManager::INVALID_FIXED_INDEX;
|
||||
} mFixedMaterialInstanceIndex;
|
||||
|
||||
backend::Handle<backend::HwTexture> mStarburstTexture;
|
||||
|
||||
std::uniform_real_distribution<float> mUniformDistribution{0.0f, 1.0f};
|
||||
|
||||
@@ -568,7 +568,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(CommandTypeFlags extraFlag
|
||||
if constexpr (isDepthPass) {
|
||||
cmd.info.materialVariant = variant;
|
||||
cmd.info.rasterState = {};
|
||||
cmd.info.rasterState.colorWrite = Variant::isPickingVariant(variant) || Variant::isDepthMomentsVariant(variant);
|
||||
cmd.info.rasterState.colorWrite = Variant::isPickingVariant(variant) || Variant::isVSMVariant(variant);
|
||||
cmd.info.rasterState.depthWrite = true;
|
||||
cmd.info.rasterState.depthFunc = RasterState::DepthFunc::GE;
|
||||
cmd.info.rasterState.alphaToCoverage = false;
|
||||
@@ -616,7 +616,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(CommandTypeFlags extraFlag
|
||||
bool const hasSkinningOrMorphing = hasSkinning || hasMorphing;
|
||||
|
||||
// if we are already an SSR variant, the SRE bit is already set
|
||||
static_assert(Variant::SPECIAL_SSR_VARIANT & Variant::SRE);
|
||||
static_assert(Variant::SPECIAL_SSR & Variant::SRE);
|
||||
Variant renderableVariant{ variant };
|
||||
|
||||
// we can't have SSR and shadowing together by construction
|
||||
|
||||
@@ -54,7 +54,7 @@ RenderableManager::getInstance(Entity const e) const noexcept {
|
||||
}
|
||||
|
||||
void RenderableManager::destroy(Entity const e) noexcept {
|
||||
return downcast(this)->clientDestroy(e);
|
||||
return downcast(this)->destroy(e);
|
||||
}
|
||||
|
||||
void RenderableManager::setAxisAlignedBoundingBox(Instance const instance, const Box& aabb) {
|
||||
|
||||
+66
-78
@@ -62,8 +62,6 @@ ShadowMap::ShadowMap(FEngine& engine) noexcept
|
||||
: mPerShadowMapUniforms(engine),
|
||||
mShadowType(ShadowType::DIRECTIONAL),
|
||||
mHasVisibleShadows(false),
|
||||
mVsm(false),
|
||||
mReservedBit(false),
|
||||
mFace(0) {
|
||||
Entity entities[2];
|
||||
engine.getEntityManager().create(2, entities);
|
||||
@@ -87,7 +85,7 @@ void ShadowMap::terminate(FEngine& engine) {
|
||||
|
||||
ShadowMap::~ShadowMap() = default;
|
||||
|
||||
void ShadowMap::initialize(size_t const lightIndex, ShadowType const shadowType, bool const vsm,
|
||||
void ShadowMap::initialize(size_t const lightIndex, ShadowType const shadowType,
|
||||
uint16_t const shadowIndex, uint8_t const face,
|
||||
LightManager::ShadowOptions const* options) {
|
||||
mLightIndex = lightIndex;
|
||||
@@ -95,7 +93,6 @@ void ShadowMap::initialize(size_t const lightIndex, ShadowType const shadowType,
|
||||
mOptions = options;
|
||||
mShadowType = shadowType;
|
||||
mFace = face;
|
||||
mVsm = vsm;
|
||||
}
|
||||
|
||||
mat4f ShadowMap::getDirectionalLightViewMatrix(float3 direction, float3 up,
|
||||
@@ -1184,8 +1181,9 @@ float2 ShadowMap::texelSizeWorldSpace(mat4f const& S, uint16_t const shadowDimen
|
||||
return s;
|
||||
}
|
||||
|
||||
template<typename Visitor>
|
||||
void ShadowMap::visitScene(const FScene& scene, uint32_t const visibleLayers, Visitor visitor) noexcept {
|
||||
template<typename Casters, typename Receivers>
|
||||
void ShadowMap::visitScene(const FScene& scene, uint32_t const visibleLayers,
|
||||
Casters casters, Receivers receivers) noexcept {
|
||||
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
|
||||
|
||||
using State = FRenderableManager::Visibility;
|
||||
@@ -1198,11 +1196,14 @@ void ShadowMap::visitScene(const FScene& scene, uint32_t const visibleLayers, Vi
|
||||
size_t const c = soa.size();
|
||||
for (size_t i = 0; i < c; i++) {
|
||||
if (layers[i] & visibleLayers) {
|
||||
Aabb const aabb{
|
||||
worldAABBCenter[i] - worldAABBExtent[i],
|
||||
worldAABBCenter[i] + worldAABBExtent[i]
|
||||
};
|
||||
visitor(aabb, visibleMasks[i], visibility[i]);
|
||||
const Aabb aabb{ worldAABBCenter[i] - worldAABBExtent[i],
|
||||
worldAABBCenter[i] + worldAABBExtent[i] };
|
||||
if (visibility[i].castShadows) {
|
||||
casters(aabb, visibleMasks[i]);
|
||||
}
|
||||
if (visibility[i].receiveShadows) {
|
||||
receivers(aabb, visibleMasks[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1221,15 +1222,13 @@ ShadowMap::SceneInfo::SceneInfo(
|
||||
wsShadowCastersVolume = {};
|
||||
wsShadowReceiversVolume = {};
|
||||
visitScene(scene, visibleLayers,
|
||||
[this](Aabb const& aabb, Culler::result_type, FRenderableManager::Visibility const visibility) {
|
||||
if (visibility.castShadows) {
|
||||
wsShadowCastersVolume.min = min(wsShadowCastersVolume.min, aabb.min);
|
||||
wsShadowCastersVolume.max = max(wsShadowCastersVolume.max, aabb.max);
|
||||
}
|
||||
if (visibility.receiveShadows) {
|
||||
wsShadowReceiversVolume.min = min(wsShadowReceiversVolume.min, aabb.min);
|
||||
wsShadowReceiversVolume.max = max(wsShadowReceiversVolume.max, aabb.max);
|
||||
}
|
||||
[&](Aabb caster, Culler::result_type) {
|
||||
wsShadowCastersVolume.min = min(wsShadowCastersVolume.min, caster.min);
|
||||
wsShadowCastersVolume.max = max(wsShadowCastersVolume.max, caster.max);
|
||||
},
|
||||
[&](Aabb receiver, Culler::result_type) {
|
||||
wsShadowReceiversVolume.min = min(wsShadowReceiversVolume.min, receiver.min);
|
||||
wsShadowReceiversVolume.max = max(wsShadowReceiversVolume.max, receiver.max);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1243,20 +1242,18 @@ void ShadowMap::updateSceneInfoDirectional(mat4f const& Mv, FScene const& scene,
|
||||
sceneInfo.lsCastersNearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
|
||||
sceneInfo.lsReceiversNearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
|
||||
visitScene(scene, sceneInfo.visibleLayers,
|
||||
[&](Aabb const& aabb, Culler::result_type const vis, FRenderableManager::Visibility const visibility) {
|
||||
if (visibility.castShadows) {
|
||||
auto const r = Aabb::transform(Mv.upperLeft(), Mv[3].xyz, aabb);
|
||||
sceneInfo.lsCastersNearFar.x = max(sceneInfo.lsCastersNearFar.x, r.max.z);
|
||||
sceneInfo.lsCastersNearFar.y = min(sceneInfo.lsCastersNearFar.y, r.min.z);
|
||||
}
|
||||
if (visibility.castShadows) {
|
||||
// account only for objects that are visible by the camera
|
||||
constexpr auto mask = 1u << VISIBLE_RENDERABLE_BIT;
|
||||
if ((vis & mask) == mask) {
|
||||
auto r = Aabb::transform(Mv.upperLeft(), Mv[3].xyz, aabb);
|
||||
sceneInfo.lsReceiversNearFar.x = max(sceneInfo.lsReceiversNearFar.x, r.max.z);
|
||||
sceneInfo.lsReceiversNearFar.y = min(sceneInfo.lsReceiversNearFar.y, r.min.z);
|
||||
}
|
||||
[&](Aabb caster, Culler::result_type) {
|
||||
auto r = Aabb::transform(Mv.upperLeft(), Mv[3].xyz, caster);
|
||||
sceneInfo.lsCastersNearFar.x = max(sceneInfo.lsCastersNearFar.x, r.max.z);
|
||||
sceneInfo.lsCastersNearFar.y = min(sceneInfo.lsCastersNearFar.y, r.min.z);
|
||||
},
|
||||
[&](Aabb receiver, Culler::result_type const vis) {
|
||||
// account only for objects that are visible by the camera
|
||||
auto mask = 1u << VISIBLE_RENDERABLE_BIT;
|
||||
if ((vis & mask) == mask) {
|
||||
auto r = Aabb::transform(Mv.upperLeft(), Mv[3].xyz, receiver);
|
||||
sceneInfo.lsReceiversNearFar.x = max(sceneInfo.lsReceiversNearFar.x, r.max.z);
|
||||
sceneInfo.lsReceiversNearFar.y = min(sceneInfo.lsReceiversNearFar.y, r.min.z);
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -1271,15 +1268,15 @@ void ShadowMap::updateSceneInfoSpot(mat4f const& Mv, FScene const& scene,
|
||||
sceneInfo.lsCastersNearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
|
||||
// account only for objects that are visible by both the camera and the light
|
||||
visitScene(scene, sceneInfo.visibleLayers,
|
||||
[&](Aabb const& aabb, Culler::result_type const vis, FRenderableManager::Visibility const visibility) {
|
||||
if (visibility.castShadows) {
|
||||
constexpr auto mask = VISIBLE_DYN_SHADOW_RENDERABLE;
|
||||
if ((vis & mask) == mask) {
|
||||
auto const r = Aabb::transform(Mv.upperLeft(), Mv[3].xyz, aabb);
|
||||
sceneInfo.lsCastersNearFar.x = std::max(sceneInfo.lsCastersNearFar.x, r.max.z); // near
|
||||
sceneInfo.lsCastersNearFar.y = std::min(sceneInfo.lsCastersNearFar.y, r.min.z); // far
|
||||
}
|
||||
[&](Aabb caster, Culler::result_type const vis) {
|
||||
auto mask = VISIBLE_DYN_SHADOW_RENDERABLE;
|
||||
if ((vis & mask) == mask) {
|
||||
auto r = Aabb::transform(Mv.upperLeft(), Mv[3].xyz, caster);
|
||||
sceneInfo.lsCastersNearFar.x = std::max(sceneInfo.lsCastersNearFar.x, r.max.z); // near
|
||||
sceneInfo.lsCastersNearFar.y = std::min(sceneInfo.lsCastersNearFar.y, r.min.z); // far
|
||||
}
|
||||
},
|
||||
[&](Aabb receiver, Culler::result_type) {
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1289,56 +1286,47 @@ void ShadowMap::setAllocation(uint8_t const layer, backend::Viewport viewport) n
|
||||
}
|
||||
|
||||
backend::Viewport ShadowMap::getViewport() const noexcept {
|
||||
// This is used for calculating the light space; in particular, if the shadowmap is in a 2D atlas, this
|
||||
// returns the valid area of the shadowmap. By definition all values must be integer.
|
||||
|
||||
// We set a viewport with a 1-texel border for when we index outside the texture, which should only happen for
|
||||
// directional lights when "focus shadow casters" is used, or when shadowFar is smaller than the camera far.
|
||||
// For directional lights, the border is filed with "fully lit".
|
||||
//
|
||||
// For point-lights, we also use a 1-texel border, but for a different reason; the border is filled with data
|
||||
// so that bilinear (PCF) filtering works properly.
|
||||
//
|
||||
// Spot-light a treated like point lights (the border will be filled with "fully-lit" anyways)
|
||||
|
||||
// We set a viewport with a 1-texel border for when we index outside the texture.
|
||||
// This happens only for directional lights when "focus shadow casters" is used,
|
||||
// or when shadowFar is smaller than the camera far.
|
||||
// For spot- and point-lights we also use a 1-texel border, so that bilinear filtering
|
||||
// can work properly if the shadowmap is in an atlas (and we can't rely on h/w clamp).
|
||||
const uint32_t dim = mOptions->mapSize;
|
||||
const uint16_t border = 1u;
|
||||
return { mOffset.x + border, mOffset.y + border, dim - 2u * border, dim - 2u * border };
|
||||
}
|
||||
|
||||
backend::Viewport ShadowMap::getScissor() const noexcept {
|
||||
// This is used while rendering the shadowmap.
|
||||
|
||||
// We set a viewport with a 1-texel border for when we index outside the texture.
|
||||
// This happens only for directional lights when "focus shadow casters" is used,
|
||||
// or when shadowFar is smaller than the camera far.
|
||||
// For spot- and point-lights we also use a 1-texel border, so that bilinear filtering
|
||||
// can work properly if the shadowmap is in an atlas (and we can't rely on h/w clamp), so we
|
||||
// don't scissor the border, so it gets filled with correct neighboring texels.
|
||||
const uint32_t dim = mOptions->mapSize;
|
||||
const uint16_t border = 1u;
|
||||
switch (mShadowType) { // NOLINT(*-multiway-paths-covered)
|
||||
case ShadowType::DIRECTIONAL: {
|
||||
// Don't render anything into the border; it's already filled with "fully lit".
|
||||
return {mOffset.x + border, mOffset.y + border, dim - 2u * border, dim - 2u * border};
|
||||
}
|
||||
switch (mShadowType) {
|
||||
case ShadowType::DIRECTIONAL:
|
||||
return { mOffset.x + border, mOffset.y + border, dim - 2u * border, dim - 2u * border };
|
||||
case ShadowType::SPOT:
|
||||
case ShadowType::POINT: {
|
||||
// Render into the border (which will get the adjacent faces texels)
|
||||
return {mOffset.x, mOffset.y, dim, dim};
|
||||
}
|
||||
case ShadowType::POINT:
|
||||
return { mOffset.x, mOffset.y, dim, dim };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
float4 ShadowMap::getClampToEdgeCoords(ShadowMapInfo const& shadowMapInfo) const noexcept {
|
||||
// This is used to clamp the texture coordinates when sampling the shadowmap
|
||||
|
||||
float border = 0; // shadowmap border in texels
|
||||
switch (mShadowType) { // NOLINT(*-multiway-paths-covered)
|
||||
float border; // shadowmap border in texels
|
||||
switch (mShadowType) {
|
||||
case ShadowType::DIRECTIONAL:
|
||||
// For directional lights, we need to allow the sampling to reach the center of border texels,
|
||||
// but no further, so we don't read into the adjacent texture in the 2D atlas (taking bilinear filtering
|
||||
// into account).
|
||||
// For directional lights, we need to allow the sampling to reach the border, it
|
||||
// happens when "focus shadow casters" is used for instance.
|
||||
border = 0.5f;
|
||||
break;
|
||||
case ShadowType::SPOT:
|
||||
case ShadowType::POINT:
|
||||
// For point-light, the border is only needed for bilinear filtering (of the other faces)
|
||||
// For spot and point light, this is equal to the viewport. i.e. the valid
|
||||
// texels are inside the viewport (w/ 1-texel border), the border will be used
|
||||
// for bilinear filtering.
|
||||
border = 1.0f;
|
||||
break;
|
||||
}
|
||||
@@ -1367,8 +1355,8 @@ void ShadowMap::prepareCamera(Transaction const& transaction,
|
||||
}
|
||||
|
||||
void ShadowMap::prepareViewport(Transaction const& transaction,
|
||||
backend::Viewport const& physicalViewport, backend::Viewport const& logicalViewport) noexcept {
|
||||
ShadowMapDescriptorSet::prepareViewport(transaction, physicalViewport, logicalViewport);
|
||||
backend::Viewport const& viewport) noexcept {
|
||||
ShadowMapDescriptorSet::prepareViewport(transaction, viewport);
|
||||
}
|
||||
|
||||
void ShadowMap::prepareTime(Transaction const& transaction,
|
||||
@@ -1382,8 +1370,8 @@ void ShadowMap::prepareMaterialGlobals(Transaction const& transaction,
|
||||
}
|
||||
|
||||
void ShadowMap::prepareShadowMapping(Transaction const& transaction,
|
||||
float const vsmExponent, float const vsmMaxMoment) noexcept {
|
||||
ShadowMapDescriptorSet::prepareShadowMapping(transaction, vsmExponent, vsmMaxMoment);
|
||||
bool const highPrecision) noexcept {
|
||||
ShadowMapDescriptorSet::prepareShadowMapping(transaction, highPrecision);
|
||||
}
|
||||
|
||||
ShadowMapDescriptorSet::Transaction ShadowMap::open(DriverApi& driver) noexcept {
|
||||
|
||||
@@ -128,7 +128,7 @@ public:
|
||||
static math::mat4f getPointLightViewMatrix(backend::TextureCubemapFace face,
|
||||
math::float3 position) noexcept;
|
||||
|
||||
void initialize(size_t lightIndex, ShadowType shadowType, bool vsm, uint16_t shadowIndex, uint8_t face,
|
||||
void initialize(size_t lightIndex, ShadowType shadowType, uint16_t shadowIndex, uint8_t face,
|
||||
LightManager::ShadowOptions const* options);
|
||||
|
||||
struct ShaderParameters {
|
||||
@@ -173,10 +173,7 @@ public:
|
||||
static void updateSceneInfoSpot(const math::mat4f& Mv, FScene const& scene,
|
||||
SceneInfo& sceneInfo);
|
||||
|
||||
LightManager::ShadowOptions const& getShadowOptions() const noexcept {
|
||||
assert_invariant(mOptions);
|
||||
return *mOptions;
|
||||
}
|
||||
LightManager::ShadowOptions const* getShadowOptions() const noexcept { return mOptions; }
|
||||
size_t getLightIndex() const { return mLightIndex; }
|
||||
uint16_t getShadowIndex() const { return mShadowIndex; }
|
||||
void setAllocation(uint8_t layer, backend::Viewport viewport) noexcept;
|
||||
@@ -196,13 +193,13 @@ public:
|
||||
static void prepareCamera(Transaction const& transaction,
|
||||
FEngine const& engine, const CameraInfo& cameraInfo) noexcept;
|
||||
static void prepareViewport(Transaction const& transaction,
|
||||
backend::Viewport const& physicalViewport, backend::Viewport const& logicalViewport) noexcept;
|
||||
backend::Viewport const& viewport) noexcept;
|
||||
static void prepareTime(Transaction const& transaction,
|
||||
FEngine const& engine, math::float4 const& userTime) noexcept;
|
||||
static void prepareMaterialGlobals(Transaction const& transaction,
|
||||
std::array<math::float4, 4> const& materialGlobals) noexcept;
|
||||
static void prepareShadowMapping(Transaction const& transaction,
|
||||
float vsmExponent, float vsmMaxMoment) noexcept;
|
||||
bool highPrecision) noexcept;
|
||||
static ShadowMapDescriptorSet::Transaction open(backend::DriverApi& driver) noexcept;
|
||||
void commit(Transaction& transaction, FEngine& engine, backend::DriverApi& driver) const noexcept;
|
||||
void bind(backend::DriverApi& driver) const noexcept;
|
||||
@@ -276,8 +273,9 @@ private:
|
||||
static inline math::float4 computeBoundingSphere(
|
||||
math::float3 const* vertices, size_t count) noexcept;
|
||||
|
||||
template<typename Visitor>
|
||||
static void visitScene(FScene const& scene, uint32_t visibleLayers, Visitor visitor) noexcept;
|
||||
template<typename Casters, typename Receivers>
|
||||
static void visitScene(FScene const& scene, uint32_t visibleLayers,
|
||||
Casters casters, Receivers receivers) noexcept;
|
||||
|
||||
static inline Aabb compute2DBounds(const math::mat4f& lightView,
|
||||
math::float3 const* wsVertices, size_t count) noexcept;
|
||||
@@ -351,11 +349,9 @@ private:
|
||||
uint8_t mLayer = 0; // our layer in the shadowMap texture // 1
|
||||
ShadowType mShadowType : 2; // :2
|
||||
bool mHasVisibleShadows : 1; // :1
|
||||
bool mVsm : 1; // :1
|
||||
UTILS_UNUSED bool mReservedBit : 1; // :1
|
||||
uint8_t mFace : 3; // :3
|
||||
math::ushort2 mOffset{}; // 4
|
||||
UTILS_UNUSED uint8_t reserved[4] = {}; // 4
|
||||
UTILS_UNUSED uint8_t reserved[4]; // 4
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -151,14 +151,12 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::update(
|
||||
|
||||
mDirectionalShadowMapCount = builder.mDirectionalShadowMapCount;
|
||||
mSpotShadowMapCount = builder.mSpotShadowMapCount;
|
||||
const bool vsm = view.hasVSM();
|
||||
|
||||
for (auto const& entry : builder.mShadowMaps) {
|
||||
auto& shadowMap = getShadowMap(entry.shadowIndex);
|
||||
shadowMap.initialize(
|
||||
entry.lightIndex,
|
||||
entry.shadowType,
|
||||
vsm,
|
||||
entry.shadowIndex,
|
||||
entry.face,
|
||||
entry.options);
|
||||
@@ -232,12 +230,15 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
FView& view, CameraInfo const& mainCameraInfo,
|
||||
float4 const& userTime) noexcept {
|
||||
|
||||
const float moment2 = std::numeric_limits<half>::max();
|
||||
const float moment1 = std::sqrt(moment2);
|
||||
const float4 vsmClearColor{ moment1, moment2, -moment1, moment2 };
|
||||
|
||||
FScene* scene = view.getScene();
|
||||
assert_invariant(scene);
|
||||
|
||||
// make a copy here, because it's a very small structure
|
||||
VsmShadowOptions const& vsmShadowOptions = view.getVsmShadowOptions();
|
||||
TextureAtlasRequirements const textureRequirements = mTextureAtlasRequirements;
|
||||
const TextureAtlasRequirements textureRequirements = mTextureAtlasRequirements;
|
||||
assert_invariant(textureRequirements.layers <= CONFIG_MAX_SHADOW_LAYERS);
|
||||
|
||||
// -------------------------------------------------------------------------------------------
|
||||
@@ -257,6 +258,8 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
utils::FixedCapacityVector<ShadowPass> passList;
|
||||
};
|
||||
|
||||
VsmShadowOptions const& vsmShadowOptions = view.getVsmShadowOptions();
|
||||
|
||||
auto& prepareShadowPass = fg.addPass<PrepareShadowPassData>("Prepare Shadow Pass",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
data.passList.reserve(getMaxShadowMapCount());
|
||||
@@ -389,18 +392,16 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
|
||||
auto transaction = ShadowMap::open(driver);
|
||||
ShadowMap::prepareCamera(transaction, engine, cameraInfo);
|
||||
ShadowMap::prepareViewport(transaction,
|
||||
{ 0, 0, textureRequirements.size, textureRequirements.size },
|
||||
shadowMap.getViewport());
|
||||
ShadowMap::prepareViewport(transaction, shadowMap.getViewport());
|
||||
ShadowMap::prepareTime(transaction, engine, userTime);
|
||||
ShadowMap::prepareMaterialGlobals(transaction, view.getMaterialGlobals());
|
||||
ShadowMap::prepareShadowMapping(transaction,
|
||||
getWrapExponentEVSM(vsmShadowOptions, shadowMap.getShadowOptions()),
|
||||
getMaxMomentEVSM(vsmShadowOptions));
|
||||
vsmShadowOptions.highPrecision);
|
||||
shadowMap.commit(transaction, engine, driver);
|
||||
|
||||
// updatePrimitivesLod must be run before RenderPass::appendCommands.
|
||||
FView::updatePrimitivesLod(scene->getRenderableData(), engine, cameraInfo, entry.range);
|
||||
FView::updatePrimitivesLod(scene->getRenderableData(),
|
||||
engine, cameraInfo, entry.range);
|
||||
|
||||
// generate and sort the commands for rendering the shadow map
|
||||
|
||||
@@ -429,10 +430,10 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
entry.executor = pass.getExecutor();
|
||||
|
||||
if (!view.hasVSM()) {
|
||||
auto const& options = shadowMap.getShadowOptions();
|
||||
auto const* options = shadowMap.getShadowOptions();
|
||||
PolygonOffset const polygonOffset = { // handle reversed Z
|
||||
.slope = -options.polygonOffsetSlope,
|
||||
.constant = -options.polygonOffsetConstant
|
||||
.slope = -options->polygonOffsetSlope,
|
||||
.constant = -options->polygonOffsetConstant
|
||||
};
|
||||
entry.executor.overridePolygonOffset(&polygonOffset);
|
||||
}
|
||||
@@ -464,10 +465,10 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
auto const& entry = *first;
|
||||
|
||||
const uint8_t layer = entry.shadowMap->getLayer();
|
||||
const auto& options = entry.shadowMap->getShadowOptions();
|
||||
const auto* options = entry.shadowMap->getShadowOptions();
|
||||
const auto msaaSamples = textureRequirements.msaaSamples;
|
||||
const bool blur = entry.shadowMap->hasVisibleShadows() &&
|
||||
view.hasVSM() && options.vsm.blurWidth > 0.0f;
|
||||
view.hasVSM() && options->vsm.blurWidth > 0.0f;
|
||||
|
||||
auto last = first;
|
||||
// loop over each shadow pass to find its layer range
|
||||
@@ -507,9 +508,10 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
|
||||
renderTargetDesc.attachments.color[0] = data.output;
|
||||
renderTargetDesc.attachments.depth = depth;
|
||||
renderTargetDesc.clearFlags = TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH;
|
||||
renderTargetDesc.clearFlags =
|
||||
TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH;
|
||||
// we need to clear the shadow map with the max EVSM moments
|
||||
renderTargetDesc.clearColor = textureRequirements.clearColor;
|
||||
renderTargetDesc.clearColor = vsmClearColor;
|
||||
renderTargetDesc.samples = msaaSamples;
|
||||
|
||||
if (UTILS_UNLIKELY(blur)) {
|
||||
@@ -528,14 +530,16 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
.attachments = {
|
||||
.color = { data.tempBlurSrc },
|
||||
.depth = depth },
|
||||
.clearColor = textureRequirements.clearColor,
|
||||
.clearColor = vsmClearColor,
|
||||
.samples = msaaSamples,
|
||||
.clearFlags = TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH
|
||||
.clearFlags = TargetBufferFlags::COLOR
|
||||
| TargetBufferFlags::DEPTH
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// the shadowmap layer
|
||||
data.output = builder.write(data.output, FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
|
||||
data.output = builder.write(data.output,
|
||||
FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
|
||||
renderTargetDesc.attachments.depth = data.output;
|
||||
renderTargetDesc.clearFlags = TargetBufferFlags::DEPTH;
|
||||
}
|
||||
@@ -590,27 +594,27 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
// the whole layer. Blurring should happen per shadowmap, not for the whole
|
||||
// layer.
|
||||
|
||||
// FIXME: this Gaussian blur is not precise enough for EVSM
|
||||
|
||||
const float blurWidth = options.vsm.blurWidth;
|
||||
const float blurWidth = options->vsm.blurWidth;
|
||||
if (blurWidth > 0.0f) {
|
||||
const float sigma = (blurWidth + 1.0f) / 6.0f;
|
||||
size_t kernelWidth = size_t(std::ceil((blurWidth - 5.0f) / 4.0f));
|
||||
size_t kernelWidth = std::ceil((blurWidth - 5.0f) / 4.0f);
|
||||
kernelWidth = kernelWidth * 4 + 5;
|
||||
ppm.gaussianBlurPass(fg,
|
||||
shadowPass->tempBlurSrc,
|
||||
shadowPass->output,
|
||||
false, kernelWidth, sigma);
|
||||
}
|
||||
}
|
||||
|
||||
// If the shadow texture has more than one level, mipmapping was requested, either directly
|
||||
// or indirectly via anisotropic filtering.
|
||||
// So generate the mipmaps for each layer
|
||||
if (UTILS_UNLIKELY(textureRequirements.levels > 1)) {
|
||||
auto& ppm = engine.getPostProcessManager();
|
||||
for (size_t level = 0; level < textureRequirements.levels - 1; level++) {
|
||||
ppm.vsmMipmapPass(fg, prepareShadowPass->shadows, layer, level, textureRequirements.clearColor);
|
||||
// FIXME: mipmapping here is broken because it'll access texels from adjacent
|
||||
// shadow maps.
|
||||
|
||||
// If the shadow texture has more than one level, mipmapping was requested, either directly
|
||||
// or indirectly via anisotropic filtering.
|
||||
// So generate the mipmaps for each layer
|
||||
if (textureRequirements.levels > 1) {
|
||||
for (size_t level = 0; level < textureRequirements.levels - 1; level++) {
|
||||
ppm.vsmMipmapPass(fg, prepareShadowPass->shadows, layer, level, vsmClearColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -622,8 +626,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
FView& view, CameraInfo cameraInfo, FScene::RenderableSoa& renderableData,
|
||||
FScene::LightSoa const& lightData, ShadowMap::SceneInfo sceneInfo) noexcept {
|
||||
|
||||
FScene const* const scene = view.getScene();
|
||||
auto const& vsmShadowOptions = view.getVsmShadowOptions();
|
||||
FScene* scene = view.getScene();
|
||||
auto& lcm = engine.getLightManager();
|
||||
|
||||
FLightManager::Instance const directionalLight = lightData.elementAt<FScene::LIGHT_INSTANCE>(0);
|
||||
@@ -647,7 +650,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
};
|
||||
|
||||
bool hasVisibleShadows = false;
|
||||
utils::Slice<ShadowMap> const cascadedShadowMaps = getCascadedShadowMap();
|
||||
utils::Slice<ShadowMap> cascadedShadowMaps = getCascadedShadowMap();
|
||||
if (!cascadedShadowMaps.empty()) {
|
||||
// Even if we have more than one cascade, we cull directional shadow casters against the
|
||||
// entire camera frustum, as if we only had a single cascade.
|
||||
@@ -681,7 +684,6 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
ShadowTechnique shadowTechnique{};
|
||||
uint32_t directionalShadowsMask = 0;
|
||||
uint32_t cascadeHasVisibleShadows = 0;
|
||||
float4 wsSplitPositionUniform = { -std::numeric_limits<float>::infinity() };
|
||||
|
||||
if (hasVisibleShadows) {
|
||||
uint32_t const cascadeCount = cascadedShadowMaps.size();
|
||||
@@ -705,8 +707,11 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
// (which is the near plane, and doesn't need to be communicated to the shaders).
|
||||
static_assert(CONFIG_MAX_SHADOW_CASCADES <= 5,
|
||||
"At most, a float4 can fit 4 split positions for 5 shadow cascades");
|
||||
float4 wsSplitPositionUniform{ -std::numeric_limits<float>::infinity() };
|
||||
std::copy(splits.begin() + 1, splits.end(), &wsSplitPositionUniform[0]);
|
||||
|
||||
mShadowMappingUniforms.cascadeSplits = wsSplitPositionUniform;
|
||||
|
||||
// When computing the required bias we need a half-texel size, so we multiply by 0.5 here.
|
||||
// note: normalBias is set to zero for VSM
|
||||
const float normalBias = shadowMapInfo.vsm ? 0.0f : 0.5f * lcm.getShadowNormalBias(0);
|
||||
@@ -731,8 +736,6 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
lightData, 0, cameraInfo, shadowMapInfo, sceneInfo,
|
||||
canUseDepthClamp);
|
||||
|
||||
mCascadesShaderParameters[i] = shaderParameters;
|
||||
|
||||
if (shadowMap.hasVisibleShadows()) {
|
||||
const size_t shadowIndex = shadowMap.getShadowIndex();
|
||||
assert_invariant(shadowIndex == i);
|
||||
@@ -747,7 +750,6 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
s.shadows[shadowIndex].scissorNormalized = shaderParameters.scissorNormalized;
|
||||
s.shadows[shadowIndex].normalBias = wsTexelSize * normalBias;
|
||||
s.shadows[shadowIndex].elvsm = options.vsm.elvsm;
|
||||
s.shadows[shadowIndex].vsmExponent = getWrapExponentEVSM(vsmShadowOptions, options);
|
||||
s.shadows[shadowIndex].bulbRadiusLs =
|
||||
mSoftShadowOptions.penumbraScale * options.shadowBulbRadius / length(wsTexelSize);
|
||||
|
||||
@@ -775,14 +777,10 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
cascades |= uint32_t(cascadedShadowMaps.size());
|
||||
cascades |= cascadeHasVisibleShadows << 8u;
|
||||
|
||||
mShadowMappingUniforms = {
|
||||
.cascadeSplits = wsSplitPositionUniform,
|
||||
.ssContactShadowDistance = screenSpaceShadowDistance,
|
||||
.directionalShadows = directionalShadowsMask,
|
||||
.cascades = cascades,
|
||||
.atlasResolution = { mTextureAtlasRequirements.size, 1.0f / float(mTextureAtlasRequirements.size) }
|
||||
};
|
||||
|
||||
mShadowMappingUniforms.directionalShadows = directionalShadowsMask;
|
||||
mShadowMappingUniforms.ssContactShadowDistance = screenSpaceShadowDistance;
|
||||
mShadowMappingUniforms.cascades = cascades;
|
||||
|
||||
return shadowTechnique;
|
||||
}
|
||||
|
||||
@@ -803,8 +801,10 @@ void ShadowMapManager::updateSpotVisibilityMasks(
|
||||
const bool visSpotShadowRenderable = v.castShadows && inVisibleLayer &&
|
||||
(!v.culling || (mask & VISIBLE_DYN_SHADOW_RENDERABLE));
|
||||
|
||||
visibleMask[i] &= ~Culler::result_type(VISIBLE_DYN_SHADOW_RENDERABLE);
|
||||
visibleMask[i] |= Culler::result_type(visSpotShadowRenderable << VISIBLE_DYN_SHADOW_RENDERABLE_BIT);
|
||||
using Type = Culler::result_type;
|
||||
|
||||
visibleMask[i] &= ~Type(VISIBLE_DYN_SHADOW_RENDERABLE);
|
||||
visibleMask[i] |= Type(visSpotShadowRenderable << VISIBLE_DYN_SHADOW_RENDERABLE_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -813,14 +813,13 @@ void ShadowMapManager::prepareSpotShadowMap(ShadowMap& shadowMap, FEngine& engin
|
||||
FScene::LightSoa const& lightData, ShadowMap::SceneInfo const& sceneInfo) noexcept {
|
||||
|
||||
const size_t lightIndex = shadowMap.getLightIndex();
|
||||
FLightManager::ShadowOptions const& options = shadowMap.getShadowOptions();
|
||||
auto const& vsmShadowOptions = view.getVsmShadowOptions();
|
||||
FLightManager::ShadowOptions const* const options = shadowMap.getShadowOptions();
|
||||
|
||||
// update the shadow map frustum/camera
|
||||
const ShadowMap::ShadowMapInfo shadowMapInfo{
|
||||
.atlasDimension = mTextureAtlasRequirements.size,
|
||||
.textureDimension = uint16_t(options.mapSize),
|
||||
.shadowDimension = uint16_t(options.mapSize - 2u),
|
||||
.textureDimension = uint16_t(options->mapSize),
|
||||
.shadowDimension = uint16_t(options->mapSize - 2u),
|
||||
.textureSpaceFlipped = engine.getBackend() == Backend::METAL ||
|
||||
engine.getBackend() == Backend::VULKAN ||
|
||||
engine.getBackend() == Backend::WEBGPU,
|
||||
@@ -835,7 +834,7 @@ void ShadowMapManager::prepareSpotShadowMap(ShadowMap& shadowMap, FEngine& engin
|
||||
const size_t shadowIndex = shadowMap.getShadowIndex();
|
||||
const float2 wsTexelSizeAtOneMeter = shaderParameters.texelSizeAtOneMeterWs;
|
||||
// note: normalBias is set to zero for VSM
|
||||
const float normalBias = shadowMapInfo.vsm ? 0.0f : options.normalBias;
|
||||
const float normalBias = shadowMapInfo.vsm ? 0.0f : options->normalBias;
|
||||
|
||||
auto& s = mShadowUb.edit();
|
||||
const double n = shadowMap.getCamera().getNear();
|
||||
@@ -846,10 +845,9 @@ void ShadowMapManager::prepareSpotShadowMap(ShadowMap& shadowMap, FEngine& engin
|
||||
s.shadows[shadowIndex].normalBias = normalBias * wsTexelSizeAtOneMeter;
|
||||
s.shadows[shadowIndex].lightFromWorldZ = shaderParameters.lightFromWorldZ;
|
||||
s.shadows[shadowIndex].nearOverFarMinusNear = float(n / (f - n));
|
||||
s.shadows[shadowIndex].elvsm = options.vsm.elvsm;
|
||||
s.shadows[shadowIndex].vsmExponent = getWrapExponentEVSM(vsmShadowOptions, options);
|
||||
s.shadows[shadowIndex].elvsm = options->vsm.elvsm;
|
||||
s.shadows[shadowIndex].bulbRadiusLs =
|
||||
mSoftShadowOptions.penumbraScale * options.shadowBulbRadius
|
||||
mSoftShadowOptions.penumbraScale * options->shadowBulbRadius
|
||||
/ length(wsTexelSizeAtOneMeter);
|
||||
|
||||
}
|
||||
@@ -907,14 +905,13 @@ void ShadowMapManager::preparePointShadowMap(ShadowMap& shadowMap,
|
||||
|
||||
const uint8_t face = shadowMap.getFace();
|
||||
const size_t lightIndex = shadowMap.getLightIndex();
|
||||
FLightManager::ShadowOptions const& options = shadowMap.getShadowOptions();
|
||||
auto const& vsmShadowOptions = view.getVsmShadowOptions();
|
||||
FLightManager::ShadowOptions const* const options = shadowMap.getShadowOptions();
|
||||
|
||||
// update the shadow map frustum/camera
|
||||
const ShadowMap::ShadowMapInfo shadowMapInfo{
|
||||
.atlasDimension = mTextureAtlasRequirements.size,
|
||||
.textureDimension = uint16_t(options.mapSize),
|
||||
.shadowDimension = uint16_t(options.mapSize), // point-lights don't have a border
|
||||
.textureDimension = uint16_t(options->mapSize),
|
||||
.shadowDimension = uint16_t(options->mapSize), // point-lights don't have a border
|
||||
.textureSpaceFlipped = engine.getBackend() == Backend::METAL ||
|
||||
engine.getBackend() == Backend::VULKAN ||
|
||||
engine.getBackend() == Backend::WEBGPU,
|
||||
@@ -929,7 +926,7 @@ void ShadowMapManager::preparePointShadowMap(ShadowMap& shadowMap,
|
||||
const size_t shadowIndex = shadowMap.getShadowIndex();
|
||||
const float2 wsTexelSizeAtOneMeter = shaderParameters.texelSizeAtOneMeterWs;
|
||||
// note: normalBias is set to zero for VSM
|
||||
const float normalBias = shadowMapInfo.vsm ? 0.0f : options.normalBias;
|
||||
const float normalBias = shadowMapInfo.vsm ? 0.0f : options->normalBias;
|
||||
|
||||
auto& s = mShadowUb.edit();
|
||||
const double n = shadowMap.getCamera().getNear();
|
||||
@@ -940,10 +937,9 @@ void ShadowMapManager::preparePointShadowMap(ShadowMap& shadowMap,
|
||||
s.shadows[shadowIndex].normalBias = normalBias * wsTexelSizeAtOneMeter;
|
||||
s.shadows[shadowIndex].lightFromWorldZ = shaderParameters.lightFromWorldZ;
|
||||
s.shadows[shadowIndex].nearOverFarMinusNear = float(n / (f - n));
|
||||
s.shadows[shadowIndex].elvsm = options.vsm.elvsm;
|
||||
s.shadows[shadowIndex].vsmExponent = getWrapExponentEVSM(vsmShadowOptions, options);
|
||||
s.shadows[shadowIndex].elvsm = options->vsm.elvsm;
|
||||
s.shadows[shadowIndex].bulbRadiusLs =
|
||||
mSoftShadowOptions.penumbraScale * options.shadowBulbRadius
|
||||
mSoftShadowOptions.penumbraScale * options->shadowBulbRadius
|
||||
/ length(wsTexelSizeAtOneMeter);
|
||||
}
|
||||
}
|
||||
@@ -999,7 +995,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateSpotShadowMaps(FEngine
|
||||
lightData.data<FScene::SHADOW_INFO>());
|
||||
|
||||
ShadowTechnique shadowTechnique{};
|
||||
utils::Slice<const ShadowMap> const spotShadowMaps = getSpotShadowMaps();
|
||||
utils::Slice<const ShadowMap> spotShadowMaps = getSpotShadowMaps();
|
||||
if (!spotShadowMaps.empty()) {
|
||||
shadowTechnique |= ShadowTechnique::SHADOW_MAP;
|
||||
for (ShadowMap const& shadowMap : spotShadowMaps) {
|
||||
@@ -1039,14 +1035,14 @@ void ShadowMapManager::calculateTextureRequirements(FEngine& engine, FView& view
|
||||
for (ShadowMap const& shadowMap : getCascadedShadowMap()) {
|
||||
// Shadow map size should be the same for all cascades.
|
||||
auto const& options = shadowMap.getShadowOptions();
|
||||
maxDimension = std::max(maxDimension, options.mapSize);
|
||||
elvsm = elvsm || options.vsm.elvsm;
|
||||
maxDimension = std::max(maxDimension, options->mapSize);
|
||||
elvsm = elvsm || options->vsm.elvsm;
|
||||
}
|
||||
|
||||
for (ShadowMap const& shadowMap : getSpotShadowMaps()) {
|
||||
auto const& options = shadowMap.getShadowOptions();
|
||||
maxDimension = std::max(maxDimension, options.mapSize);
|
||||
elvsm = elvsm || options.vsm.elvsm;
|
||||
maxDimension = std::max(maxDimension, options->mapSize);
|
||||
elvsm = elvsm || options->vsm.elvsm;
|
||||
}
|
||||
|
||||
uint8_t layersNeeded = 0;
|
||||
@@ -1056,7 +1052,7 @@ void ShadowMapManager::calculateTextureRequirements(FEngine& engine, FView& view
|
||||
ShadowMap* pShadowMap) mutable {
|
||||
// Allocate shadowmap from our Atlas Allocator
|
||||
auto const& options = pShadowMap->getShadowOptions();
|
||||
auto allocation = allocator.allocate(options.mapSize);
|
||||
auto allocation = allocator.allocate(options->mapSize);
|
||||
assert_invariant(allocation.isValid());
|
||||
assert_invariant(!allocation.viewport.empty());
|
||||
pShadowMap->setAllocation(allocation.layer, allocation.viewport);
|
||||
@@ -1092,7 +1088,6 @@ void ShadowMapManager::calculateTextureRequirements(FEngine& engine, FView& view
|
||||
msaaSamples = 1;
|
||||
}
|
||||
|
||||
float4 clearColor{};
|
||||
TextureFormat format = TextureFormat::DEPTH16;
|
||||
if (view.hasVSM()) {
|
||||
if (vsmShadowOptions.highPrecision) {
|
||||
@@ -1100,9 +1095,6 @@ void ShadowMapManager::calculateTextureRequirements(FEngine& engine, FView& view
|
||||
} else {
|
||||
format = elvsm ? TextureFormat::RGBA16F : TextureFormat::RG16F;
|
||||
}
|
||||
const float maxMoment2 = getMaxMomentEVSM(vsmShadowOptions);
|
||||
const float maxMoment1 = std::sqrt(maxMoment2);
|
||||
clearColor = { maxMoment1, maxMoment2, 0, 0 };
|
||||
}
|
||||
|
||||
mSoftShadowOptions = view.getSoftShadowOptions();
|
||||
@@ -1124,8 +1116,7 @@ void ShadowMapManager::calculateTextureRequirements(FEngine& engine, FView& view
|
||||
layersNeeded,
|
||||
mipLevels,
|
||||
msaaSamples,
|
||||
format,
|
||||
clearColor
|
||||
format
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user