Compare commits
68 Commits
rc/1.67.0
...
GetMappedR
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c1167ddb8 | ||
|
|
c9b5bfc4a4 | ||
|
|
f07176c0a2 | ||
|
|
15db141c7a | ||
|
|
d4bbb7c591 | ||
|
|
92e620d2ad | ||
|
|
311104da97 | ||
|
|
3127632f96 | ||
|
|
59f611bfde | ||
|
|
2d556bdca2 | ||
|
|
65e7dba8b8 | ||
|
|
6550a63056 | ||
|
|
aa4e2c56b5 | ||
|
|
aa4f1910b8 | ||
|
|
223a4b18a8 | ||
|
|
57ef534acd | ||
|
|
719914fb84 | ||
|
|
080f958da3 | ||
|
|
7547aa3807 | ||
|
|
67a0c6e0e1 | ||
|
|
37cb842993 | ||
|
|
ad27d48fd3 | ||
|
|
3a503976c8 | ||
|
|
ce6fa82026 | ||
|
|
e8349ab5cc | ||
|
|
4773fc4647 | ||
|
|
d47a69a529 | ||
|
|
d3b74e96b4 | ||
|
|
209d3f7550 | ||
|
|
92e65cb3fd | ||
|
|
a4945939de | ||
|
|
086760b307 | ||
|
|
ce1b63ce38 | ||
|
|
b7b8983653 | ||
|
|
d52fb1f4fd | ||
|
|
901c87761b | ||
|
|
081fe6a434 | ||
|
|
b84c6ace8d | ||
|
|
2113e04aba | ||
|
|
84fdf36ed9 | ||
|
|
dc1de994e0 | ||
|
|
7c6479020e | ||
|
|
a5f949d30c | ||
|
|
6c867c692b | ||
|
|
bd735fbab8 | ||
|
|
b4c4ce9e17 | ||
|
|
138cc55d3b | ||
|
|
3d6db313bd | ||
|
|
faa565c3ff | ||
|
|
2a2caad1bf | ||
|
|
804ee87356 | ||
|
|
f5c9d973dc | ||
|
|
778cbe09d1 | ||
|
|
bf6c51bae6 | ||
|
|
cb3933b349 | ||
|
|
4e9d691d9d | ||
|
|
fe5f8547f7 | ||
|
|
3207c31721 | ||
|
|
236d650ed7 | ||
|
|
294b79b321 | ||
|
|
0dc392a760 | ||
|
|
06fa370491 | ||
|
|
6fdd7398b6 | ||
|
|
32a89a9017 | ||
|
|
ddb2d89e6c | ||
|
|
ea608d409e | ||
|
|
494e454f38 | ||
|
|
a9bbb0bf3b |
2
.github/workflows/presubmit.yml
vendored
@@ -179,7 +179,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/linux-prereq
|
||||
- name: Run build script
|
||||
run: ./build.sh -W debug test_filamat filament
|
||||
run: ./build.sh -W debug test_filamat filament gltf_viewer
|
||||
- name: Run test
|
||||
run: ./out/cmake-debug/libs/filamat/test_filamat --gtest_filter=MaterialCompiler.Wgsl*
|
||||
|
||||
|
||||
@@ -62,6 +62,11 @@ force a clean build by adding the `-c` flag in that case.
|
||||
To install the libraries and executables in `out/debug/` and `out/release/`, add the `-i` flag.
|
||||
The script offers more features described by executing `build.sh -h`.
|
||||
|
||||
For more specialized options, please also consider the following pages:
|
||||
- `-d`: [`matdbg`](https://google.github.io/filament/dup/matdbg.html)
|
||||
- `-t`: [`fgviewer`](https://google.github.io/filament/dup/fgviewer.html)
|
||||
- `-b` and `-y`: [ASAN/UBSAN builds](https://google.github.io/filament/notes/asan_ubsan.html)
|
||||
|
||||
### Filament-specific CMake Options
|
||||
|
||||
The following CMake options are boolean options specific to Filament:
|
||||
|
||||
@@ -58,6 +58,8 @@ option(FILAMENT_USE_ABSEIL_LOGGING "Use Abseil to log, may increase binary size"
|
||||
|
||||
option(FILAMENT_ENABLE_EXPERIMENTAL_GCC_SUPPORT "Enable GCC support (unsupported)" OFF)
|
||||
|
||||
option(FILAMENT_SUPPORTS_WEBP_TEXTURES "Enable webp texture support for Filament (builds libwebp when ON)" OFF)
|
||||
|
||||
# This is to disable GTAO for the short-term while we investigate a way to better manage size increases.
|
||||
# On the regular filament build (where size is of less concern), we enable GTAO by default.
|
||||
option(FILAMENT_DISABLE_GTAO "Disable GTAO" OFF)
|
||||
@@ -753,12 +755,61 @@ function(combine_static_libs TARGET OUTPUT DEPS)
|
||||
endfunction()
|
||||
|
||||
# ==================================================================================================
|
||||
# Configuration for CMAKE_CROSSCOMPILING.
|
||||
# Configuration importing/exporting prebuilt "/tools" executables
|
||||
# ==================================================================================================
|
||||
|
||||
# In certain cases, we want the ability to separate the build type/flags (release, debug) of tools
|
||||
# (matc, resgen, etc...) from filament. Cross compilation is one such case (e.g. building material
|
||||
# with matc running on host while building filament for Android) [1]. Another example is when client
|
||||
# wants to enable ASAN for filament but not the tool [2].
|
||||
#
|
||||
# Here are the varibles that are used to determine behavior is such flows:
|
||||
# - CMAKE_CROSSCOMPILING : Set by cmake to indicate the build's target platform is different
|
||||
# from the host platform.
|
||||
# - IMPORT_EXECUTABLES_DIR : This is the directory containing the /ImportExecutables-type.cmake
|
||||
# which are then included in other CMakeLists.txt to enable finding targets that have been
|
||||
# prebuilt. This is set by the client in their `cmake` invocation.
|
||||
# - IMPORT_EXECUTABLES : Path to a file to for 1) exporting the prebuilt targets or 2)
|
||||
# importing a previously exported targets. (Used internally and should not be set by client).
|
||||
# - FILAMENT_EXPORT_PREBUILT_EXECUTABLES_DIR : A path set by the client to indicate that
|
||||
# they wish to export the tools as prebuilts, and the corresponding targets will be recorded
|
||||
# in a cmake file (i.e IMPORT_EXECUTABLES). IMPORT_EXECUTABLES will be set relative to the the
|
||||
# given path. This will set FILAMENT_EXPORT_PREBUILT_EXECUTABLES=ON.
|
||||
# - FILAMENT_IMPORT_PREBUILT_EXECUTABLES_DIR : A path set by the client to indicate that they wish
|
||||
# to import the tools as prebuilts from a .cmake file. The location (IMPORT_EXECUTABLES) of the
|
||||
# file is relative to the path given. This will set FILAMENT_IMPORT_PREBUILT_EXECUTABLES=ON.
|
||||
# - IMPORT_PREBUILT_EXECUTABLES and EXPORT_PREBUILT_EXECUTABLES are internal booleans set to
|
||||
# ON based on the prescence of FILAMENT_IMPORT_PREBUILT_EXECUTABLES_DIR and
|
||||
# FILAMENT_EXPORT_PREBUILT_EXECUTABLES_DIR.
|
||||
#
|
||||
# In conclusion, for cases
|
||||
# - [1] (crosscompiling), the client must set IMPORT_EXECUTABLES_DIR for both when they are building
|
||||
# the tools and when they are building the target-platform filament.
|
||||
# - [2] (all other instances), the client must set FILAMENT_EXPORT_PREBUILT_EXECUTABLES_DIR in
|
||||
# the prebuilt exporting step, and set FILAMENT_IMPORT_PREBUILT_EXECUTABLES_DIR in the importing
|
||||
# step.
|
||||
|
||||
# TODO: Fold the cross compilation case into the more general FILAMENT_IMPORT/EXPORT variables.
|
||||
|
||||
if (FILAMENT_EXPORT_PREBUILT_EXECUTABLES_DIR)
|
||||
set(FILAMENT_EXPORT_PREBUILT_EXECUTABLES ON)
|
||||
set(IMPORT_EXECUTABLES_DIR ${FILAMENT_EXPORT_PREBUILT_EXECUTABLES_DIR})
|
||||
endif()
|
||||
|
||||
if (FILAMENT_IMPORT_PREBUILT_EXECUTABLES_DIR)
|
||||
set(FILAMENT_IMPORT_PREBUILT_EXECUTABLES ON)
|
||||
set(IMPORT_EXECUTABLES_DIR ${FILAMENT_IMPORT_PREBUILT_EXECUTABLES_DIR})
|
||||
endif()
|
||||
|
||||
if (WEBGL)
|
||||
set(IMPORT_EXECUTABLES ${FILAMENT}/${IMPORT_EXECUTABLES_DIR}/ImportExecutables-Release.cmake)
|
||||
else()
|
||||
set(IMPORT_EXECUTABLES ${FILAMENT}/${IMPORT_EXECUTABLES_DIR}/ImportExecutables-${CMAKE_BUILD_TYPE}.cmake)
|
||||
if (FILAMENT_EXPORT_PREBUILT_EXECUTABLES OR FILAMENT_IMPORT_PREBUILT_EXECUTABLES)
|
||||
set(IMPORT_EXECUTABLES_BUILD_TYPE Prebuilt)
|
||||
else()
|
||||
set(IMPORT_EXECUTABLES_BUILD_TYPE ${CMAKE_BUILD_TYPE})
|
||||
endif()
|
||||
set(IMPORT_EXECUTABLES ${FILAMENT}/${IMPORT_EXECUTABLES_DIR}/ImportExecutables-${IMPORT_EXECUTABLES_BUILD_TYPE}.cmake)
|
||||
endif()
|
||||
|
||||
# ==================================================================================================
|
||||
@@ -837,6 +888,7 @@ add_subdirectory(${EXTERNAL}/getopt)
|
||||
add_subdirectory(${EXTERNAL}/perfetto/tnt)
|
||||
add_subdirectory(${EXTERNAL}/zstd/tnt)
|
||||
|
||||
|
||||
# Note that this has to be placed after mikktspace in order for combine_static_libs to work.
|
||||
add_subdirectory(${LIBRARIES}/geometry)
|
||||
|
||||
@@ -859,6 +911,11 @@ if (FILAMENT_ENABLE_FGVIEWER)
|
||||
add_subdirectory(${LIBRARIES}/fgviewer)
|
||||
endif()
|
||||
|
||||
if (FILAMENT_SUPPORTS_WEBP_TEXTURES)
|
||||
add_subdirectory(${EXTERNAL}/libwebp/tnt)
|
||||
add_definitions(-DFILAMENT_SUPPORTS_WEBP_TEXTURES)
|
||||
endif()
|
||||
|
||||
if (FILAMENT_SUPPORTS_VULKAN)
|
||||
add_subdirectory(${LIBRARIES}/bluevk)
|
||||
add_subdirectory(${EXTERNAL}/vkmemalloc/tnt)
|
||||
@@ -917,6 +974,6 @@ if (IS_HOST_PLATFORM)
|
||||
endif()
|
||||
|
||||
# Generate exported executables for cross-compiled builds (Android, WebGL, and iOS)
|
||||
if (NOT CMAKE_CROSSCOMPILING)
|
||||
if ((NOT CMAKE_CROSSCOMPILING AND NOT FILAMENT_IMPORT_PREBUILT_EXECUTABLES) OR FILAMENT_EXPORT_PREBUILT_EXECUTABLES)
|
||||
export(TARGETS matc cmgen filamesh mipgen resgen uberz glslminifier FILE ${IMPORT_EXECUTABLES})
|
||||
endif()
|
||||
|
||||
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.66.2'
|
||||
implementation 'com.google.android.filament:filament-android:1.67.1'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,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.66.2'
|
||||
pod 'Filament', '~> 1.67.1'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -7,6 +7,19 @@ 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.68.0
|
||||
|
||||
- engine: add `View::getLastDynamicResolutionScale()` (b/457753622)
|
||||
- Metal: report GPU errors to the platform via `debugUpdateStat` (b/431665753).
|
||||
- materials: Make Material Instances' UBO descriptor use dynamic offsets. [⚠️ **Recompile Materials**]
|
||||
|
||||
## v1.67.1
|
||||
|
||||
- Metal: Add support for the `SwapChain::CONFIG_MSAA_4_SAMPLES` flag.
|
||||
- third_party: Optionally add libwebp to build
|
||||
- controlled by cmake flag FILAMENT_SUPPORTS_WEBP_TEXTURES, defaults to OFF
|
||||
- actual webp texture support for libs/gltfio coming in subsequent change
|
||||
|
||||
## v1.67.0
|
||||
|
||||
- materials: Add a new API getParameterTransformName that will return the value of the transformName field of a sampler
|
||||
|
||||
@@ -51,10 +51,6 @@ add_library(bluevk STATIC IMPORTED)
|
||||
set_target_properties(bluevk PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libbluevk.a)
|
||||
|
||||
add_library(vkshaders STATIC IMPORTED)
|
||||
set_target_properties(vkshaders PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libvkshaders.a)
|
||||
|
||||
if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
add_library(webgpu_dawn STATIC IMPORTED)
|
||||
set_target_properties(webgpu_dawn PROPERTIES IMPORTED_LOCATION
|
||||
@@ -165,7 +161,6 @@ target_link_libraries(filament-jni
|
||||
PRIVATE $<$<STREQUAL:${FILAMENT_ENABLE_MATDBG},ON>:matdbg>
|
||||
PRIVATE $<$<STREQUAL:${FILAMENT_ENABLE_MATDBG},ON>:filamat>
|
||||
PRIVATE $<$<STREQUAL:${FILAMENT_SUPPORTS_VULKAN},ON>:bluevk>
|
||||
PRIVATE $<$<STREQUAL:${FILAMENT_SUPPORTS_VULKAN},ON>:vkshaders>
|
||||
PRIVATE $<$<STREQUAL:${FILAMENT_SUPPORTS_VULKAN},ON>:smol-v>
|
||||
PRIVATE $<$<STREQUAL:${FILAMENT_SUPPORTS_WEBGPU},ON>:webgpu_dawn>
|
||||
)
|
||||
|
||||
@@ -147,6 +147,15 @@ Java_com_google_android_filament_View_nSetDynamicResolutionOptions(JNIEnv*, jcla
|
||||
view->setDynamicResolutionOptions(options);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_View_nGetLastDynamicResolutionScale(JNIEnv *env, jclass, jlong nativeView, jfloatArray out_) {
|
||||
jfloat* out = env->GetFloatArrayElements(out_, nullptr);
|
||||
View *view = (View *) nativeView;
|
||||
math::float2 result = view->getLastDynamicResolutionScale();
|
||||
std::copy_n(result.v, 2, out);
|
||||
env->ReleaseFloatArrayElements(out_, out, 0);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_View_nSetShadowType(JNIEnv*, jclass, jlong nativeView, jint type) {
|
||||
View* view = (View*) nativeView;
|
||||
|
||||
@@ -99,6 +99,15 @@ final class Asserts {
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull @Size(min = 2)
|
||||
static float[] assertFloat2(@Nullable float[] out) {
|
||||
if (out == null) out = new float[2];
|
||||
else if (out.length < 2) {
|
||||
throw new ArrayIndexOutOfBoundsException("Array length must be at least 2");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@NonNull @Size(min = 4)
|
||||
static float[] assertFloat4(@Nullable float[] out) {
|
||||
if (out == null) out = new float[4];
|
||||
|
||||
@@ -25,7 +25,6 @@ import java.util.EnumSet;
|
||||
|
||||
import static com.google.android.filament.Asserts.assertFloat3In;
|
||||
import static com.google.android.filament.Asserts.assertFloat4In;
|
||||
import static com.google.android.filament.Colors.LinearColor;
|
||||
|
||||
import com.google.android.filament.proguard.UsedByNative;
|
||||
|
||||
@@ -674,6 +673,21 @@ public class View {
|
||||
return mDynamicResolution;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last dynamic resolution scale factor used by this view. This value is updated
|
||||
* when Renderer::render(View*) is called
|
||||
* @param out A 2-float array where the value will be stored, or null in which case the array is
|
||||
* allocated.
|
||||
* @return A 2-float array containing the horizontal and the vertical scale factors
|
||||
* @see Renderer#render(View)
|
||||
*/
|
||||
@NonNull @Size(min = 2)
|
||||
public float[] getLastDynamicResolutionScale(@Nullable @Size(min = 2) float[] out) {
|
||||
out = Asserts.assertFloat2(out);
|
||||
nGetLastDynamicResolutionScale(getNativeObject(), out);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the rendering quality for this view (e.g. color precision).
|
||||
*
|
||||
@@ -1317,6 +1331,7 @@ public class View {
|
||||
private static native void nSetDithering(long nativeView, int dithering);
|
||||
private static native int nGetDithering(long nativeView);
|
||||
private static native void nSetDynamicResolutionOptions(long nativeView, boolean enabled, boolean homogeneousScaling, float minScale, float maxScale, float sharpness, int quality);
|
||||
private static native void nGetLastDynamicResolutionScale(long nativeView, float[] out);
|
||||
private static native void nSetRenderQuality(long nativeView, int hdrColorBufferQuality);
|
||||
private static native void nSetDynamicLightingOptions(long nativeView, float zLightNear, float zLightFar);
|
||||
private static native void nSetShadowType(long nativeView, int type);
|
||||
|
||||
@@ -40,7 +40,21 @@ fun loadTexture(engine: Engine, resources: Resources, resourceId: Int, type: Tex
|
||||
options.inPremultiplied = type == TextureType.COLOR
|
||||
|
||||
val bitmap = BitmapFactory.decodeResource(resources, resourceId, options)
|
||||
return buildTexture(engine, bitmap, type)
|
||||
}
|
||||
|
||||
fun loadTexture(engine: Engine, bytes: ByteArray, type: TextureType, offset: Int = 0, length: Int = bytes.size): Texture {
|
||||
val options = BitmapFactory.Options()
|
||||
// Color is the only type of texture we want to pre-multiply with the alpha channel
|
||||
// Pre-multiplication is the default behavior, so we need to turn it off here
|
||||
options.inPremultiplied = type == TextureType.COLOR
|
||||
|
||||
val bitmap = BitmapFactory.decodeByteArray(bytes, offset, length, options)
|
||||
return buildTexture(engine, bitmap, type)
|
||||
}
|
||||
|
||||
|
||||
private fun buildTexture(engine: Engine, bitmap: Bitmap, type: TextureType): Texture {
|
||||
val texture = Texture.Builder()
|
||||
.width(bitmap.width)
|
||||
.height(bitmap.height)
|
||||
|
||||
@@ -22,6 +22,7 @@ object Utils {
|
||||
/**
|
||||
* Initializes the utils JNI layer. Must be called before using any utils functionality.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun init() {
|
||||
// Load Filament first to ensure that the NioUtils Java class is available in the JNIEnv.
|
||||
Filament.init()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.66.2
|
||||
VERSION_NAME=1.67.1
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -17,12 +17,20 @@ filamentTools {
|
||||
}
|
||||
|
||||
// don't forget to update MainACtivity.kt when/if changing this.
|
||||
tasks.register('copyMesh', Copy) {
|
||||
tasks.register('copyDamagedHelmetGltf', Copy) {
|
||||
from file("../../../third_party/models/DamagedHelmet/DamagedHelmet.glb")
|
||||
into file("src/main/assets/models")
|
||||
rename {String fileName -> "helmet.glb"}
|
||||
}
|
||||
|
||||
// don't forget to update MainACtivity.kt when/if changing this.
|
||||
tasks.register('copyBusterGltf', Copy) {
|
||||
from "../../../third_party/models/BusterDrone"
|
||||
into "src/main/assets/models"
|
||||
}
|
||||
|
||||
preBuild.dependsOn copyMesh
|
||||
preBuild.dependsOn copyDamagedHelmetGltf
|
||||
preBuild.dependsOn copyBusterGltf
|
||||
|
||||
clean.doFirst {
|
||||
delete "src/main/assets"
|
||||
|
||||
12
android/samples/sample-material-instance-stress/.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
/.idea/libraries
|
||||
/.idea/caches
|
||||
/.idea/gradle.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
/src/main/assets
|
||||
.externalNativeBuild
|
||||
52
android/samples/sample-material-instance-stress/build.gradle
Normal file
@@ -0,0 +1,52 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'filament-tools-plugin'
|
||||
}
|
||||
|
||||
project.ext.isSample = true
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(versions.jdk)
|
||||
}
|
||||
|
||||
filamentTools {
|
||||
materialInputDir = project.layout.projectDirectory.dir("src/main/materials")
|
||||
materialOutputDir = project.layout.projectDirectory.dir("src/main/assets/materials")
|
||||
}
|
||||
|
||||
clean.doFirst {
|
||||
delete "src/main/assets"
|
||||
}
|
||||
android {
|
||||
namespace 'com.google.android.filament.materialinstancestress'
|
||||
|
||||
compileSdkVersion versions.compileSdk
|
||||
defaultConfig {
|
||||
applicationId "com.google.android.filament.materialinstancestress"
|
||||
minSdkVersion versions.minSdk
|
||||
targetSdkVersion versions.targetSdk
|
||||
}
|
||||
|
||||
// NOTE: This is a workaround required because the AGP task collectReleaseDependencies
|
||||
// is not configuration-cache friendly yet; this is only useful for Play publication
|
||||
dependenciesInfo {
|
||||
includeInApk = false
|
||||
}
|
||||
|
||||
// We use the .filamat extension for materials compiled with matc
|
||||
// Telling aapt to not compress them allows to load them efficiently
|
||||
aaptOptions {
|
||||
noCompress 'filamat', 'ktx'
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility versions.jdk
|
||||
targetCompatibility versions.jdk
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation deps.kotlin
|
||||
implementation project(':filament-android')
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:exported="true"
|
||||
android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,532 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.filament.materialinstancestress
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.app.Activity
|
||||
import android.opengl.Matrix
|
||||
import android.os.Bundle
|
||||
import android.view.Choreographer
|
||||
import android.view.Surface
|
||||
import android.view.SurfaceView
|
||||
import android.view.animation.LinearInterpolator
|
||||
|
||||
import com.google.android.filament.*
|
||||
import com.google.android.filament.RenderableManager.*
|
||||
import com.google.android.filament.VertexBuffer.*
|
||||
import com.google.android.filament.android.DisplayHelper
|
||||
import com.google.android.filament.android.FilamentHelper
|
||||
import com.google.android.filament.android.UiHelper
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.nio.channels.Channels
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
class MainActivity : Activity() {
|
||||
// Make sure to initialize Filament first
|
||||
// This loads the JNI library needed by most API calls
|
||||
companion object {
|
||||
init {
|
||||
Filament.init()
|
||||
}
|
||||
|
||||
private const val NUM_CUBES = 1000
|
||||
private const val GRID_SIZE = 10
|
||||
private const val SPACING = 2.5f
|
||||
}
|
||||
|
||||
// The View we want to render into
|
||||
private lateinit var surfaceView: SurfaceView
|
||||
// UiHelper is provided by Filament to manage SurfaceView and SurfaceTexture
|
||||
private lateinit var uiHelper: UiHelper
|
||||
// DisplayHelper is provided by Filament to manage the display
|
||||
private lateinit var displayHelper: DisplayHelper
|
||||
// Choreographer is used to schedule new frames
|
||||
private lateinit var choreographer: Choreographer
|
||||
|
||||
// Engine creates and destroys Filament resources
|
||||
// Each engine must be accessed from a single thread of your choosing
|
||||
// Resources cannot be shared across engines
|
||||
private lateinit var engine: Engine
|
||||
// A renderer instance is tied to a single surface (SurfaceView, TextureView, etc.)
|
||||
private lateinit var renderer: Renderer
|
||||
// A scene holds all the renderable, lights, etc. to be drawn
|
||||
private lateinit var scene: Scene
|
||||
// A view defines a viewport, a scene and a camera for rendering
|
||||
private lateinit var view: View
|
||||
// Should be pretty obvious :)
|
||||
private lateinit var camera: Camera
|
||||
|
||||
private lateinit var material: Material
|
||||
private lateinit var vertexBuffer: VertexBuffer
|
||||
private lateinit var indexBuffer: IndexBuffer
|
||||
|
||||
// Filament entity representing a renderable object
|
||||
@Entity private val renderables = IntArray(NUM_CUBES)
|
||||
private val materialInstances = arrayOfNulls<MaterialInstance>(NUM_CUBES)
|
||||
private val translationMatrices = Array(NUM_CUBES) { FloatArray(16) }
|
||||
|
||||
@Entity private var light = 0
|
||||
|
||||
// A swap chain is Filament's representation of a surface
|
||||
private var swapChain: SwapChain? = null
|
||||
|
||||
private var cameraRadius = 42.7f // Initial distance (calculated from original 15Y, 40Z)
|
||||
private var cameraTheta = 0.0f // Azimuth (horizontal) in radians
|
||||
private var cameraPhi = 0.358f // Elevation (vertical) in radians (from original 15Y, 40Z)
|
||||
|
||||
private var mLastX = 0.0f
|
||||
private var mLastY = 0.0f
|
||||
private val DRAG_SPEED = 0.005f
|
||||
|
||||
// Performs the rendering and schedules new frames
|
||||
private val frameScheduler = FrameCallback()
|
||||
|
||||
private val animator = ValueAnimator.ofFloat(0.0f, 360.0f)
|
||||
|
||||
private val animatorListener = object : ValueAnimator.AnimatorUpdateListener {
|
||||
// Pre-allocate matrix to avoid GC churn
|
||||
private val transformMatrix = FloatArray(16)
|
||||
|
||||
override fun onAnimationUpdate(a: ValueAnimator) {
|
||||
val tcm = engine.transformManager
|
||||
val time = a.animatedFraction // 0.0 to 1.0
|
||||
|
||||
// Calculate the sine wave scale factor.
|
||||
// sin(time * 2 * PI) oscillates between -1.0 and 1.0.
|
||||
// We multiply by 0.5f (so it's -0.5 to 0.5) and add 1.0f.
|
||||
// This makes the final 'scale' oscillate between 0.5 and 1.5.
|
||||
val scale = 1.0f + sin(time * 2.0 * Math.PI).toFloat() * 0.5f
|
||||
|
||||
val localScale = (scale - 0.5f) * 0.5f + 0.5f
|
||||
|
||||
for (i in 0 until NUM_CUBES) {
|
||||
val inst = tcm.getInstance(renderables[i])
|
||||
val materialInst = materialInstances[i] ?: continue
|
||||
|
||||
// Get the cube's base position from its stored matrix
|
||||
// The translation is in elements 12 (x), 13 (y), and 14 (z)
|
||||
val baseX = translationMatrices[i][12]
|
||||
val baseY = translationMatrices[i][13]
|
||||
val baseZ = translationMatrices[i][14]
|
||||
|
||||
// Set the transformMatrix to a new translation matrix,
|
||||
// scaled from the center (0,0,0)
|
||||
Matrix.setIdentityM(transformMatrix, 0)
|
||||
Matrix.translateM(transformMatrix, 0,
|
||||
baseX * scale,
|
||||
baseY * scale,
|
||||
baseZ * scale)
|
||||
Matrix.scaleM(transformMatrix, 0, localScale, localScale, localScale)
|
||||
|
||||
tcm.setTransform(inst, transformMatrix)
|
||||
|
||||
// Vary roughness
|
||||
val roughness = 0.5f + 0.5f * sin(time * 2.0 * Math.PI + i * 0.1).toFloat()
|
||||
materialInst.setParameter("roughness", roughness)
|
||||
|
||||
// Vary color (using linear RGB)
|
||||
val r = 0.5f + 0.5f * cos(time * 2.0 * Math.PI + i * 0.5).toFloat()
|
||||
val g = 0.5f + 0.5f * sin(time * 2.0 * Math.PI + i * 0.3).toFloat()
|
||||
val b = 0.5f + 0.5f * cos(time * 2.0 * Math.PI + i * 0.1).toFloat()
|
||||
materialInst.setParameter("baseColor", Colors.RgbType.LINEAR, r, g, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
surfaceView = SurfaceView(this)
|
||||
setContentView(surfaceView)
|
||||
|
||||
surfaceView.setOnTouchListener(touchListener)
|
||||
choreographer = Choreographer.getInstance()
|
||||
|
||||
displayHelper = DisplayHelper(this)
|
||||
|
||||
setupSurfaceView()
|
||||
setupFilament()
|
||||
setupView()
|
||||
setupScene()
|
||||
}
|
||||
|
||||
private fun setupSurfaceView() {
|
||||
uiHelper = UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK)
|
||||
uiHelper.renderCallback = SurfaceCallback()
|
||||
uiHelper.attachTo(surfaceView)
|
||||
}
|
||||
|
||||
private fun setupFilament() {
|
||||
engine = Engine.create()
|
||||
renderer = engine.createRenderer()
|
||||
scene = engine.createScene()
|
||||
view = engine.createView()
|
||||
camera = engine.createCamera(engine.entityManager.create())
|
||||
}
|
||||
|
||||
private fun setupView() {
|
||||
scene.skybox = Skybox.Builder().color(0.035f, 0.035f, 0.035f, 1.0f).build(engine)
|
||||
view.camera = camera
|
||||
view.scene = scene
|
||||
}
|
||||
|
||||
private fun setupScene() {
|
||||
loadMaterial()
|
||||
createMesh()
|
||||
|
||||
val tcm = engine.transformManager
|
||||
val center = (GRID_SIZE - 1) * 0.5f
|
||||
|
||||
for (i in 0 until NUM_CUBES) {
|
||||
// Calculate grid position
|
||||
val x = (i % GRID_SIZE) - center
|
||||
val y = ((i / GRID_SIZE) % GRID_SIZE) - center
|
||||
val z = (i / (GRID_SIZE * GRID_SIZE)) - center
|
||||
|
||||
val posX = x * SPACING
|
||||
val posY = y * SPACING
|
||||
val posZ = z * SPACING
|
||||
|
||||
// Create material instance for this cube
|
||||
materialInstances[i] = material.createInstance()
|
||||
|
||||
// Set initial parameters (will be overwritten by animator, but good practice)
|
||||
materialInstances[i]?.setParameter("baseColor", Colors.RgbType.SRGB, 1.0f, 0.85f, 0.57f)
|
||||
materialInstances[i]?.setParameter("metallic", 0.0f)
|
||||
materialInstances[i]?.setParameter("roughness", 0.3f)
|
||||
|
||||
// Create the renderable entity
|
||||
renderables[i] = EntityManager.get().create()
|
||||
|
||||
// Create the renderable component
|
||||
RenderableManager.Builder(1)
|
||||
.boundingBox(Box(0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f))
|
||||
.geometry(0, PrimitiveType.TRIANGLES, vertexBuffer, indexBuffer, 0, 6 * 6)
|
||||
.material(0, materialInstances[i]!!)
|
||||
.build(engine, renderables[i])
|
||||
|
||||
// Add to scene
|
||||
scene.addEntity(renderables[i])
|
||||
|
||||
// Set its initial transform
|
||||
// Store the base translation matrix
|
||||
Matrix.setIdentityM(translationMatrices[i], 0)
|
||||
Matrix.translateM(translationMatrices[i], 0, posX, posY, posZ)
|
||||
|
||||
// Get the transform component instance
|
||||
val inst = tcm.getInstance(renderables[i])
|
||||
tcm.setTransform(inst, translationMatrices[i])
|
||||
}
|
||||
|
||||
// We now need a light, let's create a directional light
|
||||
light = EntityManager.get().create()
|
||||
|
||||
val (r, g, b) = Colors.cct(5_500.0f)
|
||||
LightManager.Builder(LightManager.Type.DIRECTIONAL)
|
||||
.color(r, g, b)
|
||||
.intensity(110_000.0f)
|
||||
.direction(0.0f, -0.5f, -1.0f)
|
||||
.castShadows(true)
|
||||
.build(engine, light)
|
||||
|
||||
scene.addEntity(light)
|
||||
|
||||
camera.setExposure(16.0f, 1.0f / 125.0f, 100.0f)
|
||||
|
||||
updateCamera()
|
||||
|
||||
startAnimation()
|
||||
}
|
||||
|
||||
private fun loadMaterial() {
|
||||
readUncompressedAsset("materials/lit.filamat").let {
|
||||
material = Material.Builder().payload(it, it.remaining()).build(engine)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createMesh() {
|
||||
val floatSize = 4
|
||||
val shortSize = 2
|
||||
val vertexSize = 3 * floatSize + 4 * floatSize
|
||||
|
||||
// A vertex is a position + a tangent frame:
|
||||
// 3 floats for XYZ position, 4 floats for normal+tangents (quaternion)
|
||||
@Suppress("ArrayInDataClass")
|
||||
data class Vertex(val x: Float, val y: Float, val z: Float, val tangents: FloatArray)
|
||||
fun ByteBuffer.put(v: Vertex): ByteBuffer {
|
||||
putFloat(v.x)
|
||||
putFloat(v.y)
|
||||
putFloat(v.z)
|
||||
v.tangents.forEach { putFloat(it) }
|
||||
return this
|
||||
}
|
||||
|
||||
// 6 faces, 4 vertices per face
|
||||
val vertexCount = 6 * 4
|
||||
|
||||
// Create tangent frames, one per face
|
||||
val tfPX = FloatArray(4)
|
||||
val tfNX = FloatArray(4)
|
||||
val tfPY = FloatArray(4)
|
||||
val tfNY = FloatArray(4)
|
||||
val tfPZ = FloatArray(4)
|
||||
val tfNZ = FloatArray(4)
|
||||
|
||||
MathUtils.packTangentFrame( 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, tfPX)
|
||||
MathUtils.packTangentFrame( 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f, -1.0f, 0.0f, 0.0f, tfNX)
|
||||
MathUtils.packTangentFrame(-1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, tfPY)
|
||||
MathUtils.packTangentFrame(-1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, -1.0f, 0.0f, tfNY)
|
||||
MathUtils.packTangentFrame( 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, tfPZ)
|
||||
MathUtils.packTangentFrame( 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, tfNZ)
|
||||
|
||||
val vertexData = ByteBuffer.allocate(vertexCount * vertexSize)
|
||||
// It is important to respect the native byte order
|
||||
.order(ByteOrder.nativeOrder())
|
||||
// Face -Z
|
||||
.put(Vertex(-1.0f, -1.0f, -1.0f, tfNZ))
|
||||
.put(Vertex(-1.0f, 1.0f, -1.0f, tfNZ))
|
||||
.put(Vertex( 1.0f, 1.0f, -1.0f, tfNZ))
|
||||
.put(Vertex( 1.0f, -1.0f, -1.0f, tfNZ))
|
||||
// Face +X
|
||||
.put(Vertex( 1.0f, -1.0f, -1.0f, tfPX))
|
||||
.put(Vertex( 1.0f, 1.0f, -1.0f, tfPX))
|
||||
.put(Vertex( 1.0f, 1.0f, 1.0f, tfPX))
|
||||
.put(Vertex( 1.0f, -1.0f, 1.0f, tfPX))
|
||||
// Face +Z
|
||||
.put(Vertex(-1.0f, -1.0f, 1.0f, tfPZ))
|
||||
.put(Vertex( 1.0f, -1.0f, 1.0f, tfPZ))
|
||||
.put(Vertex( 1.0f, 1.0f, 1.0f, tfPZ))
|
||||
.put(Vertex(-1.0f, 1.0f, 1.0f, tfPZ))
|
||||
// Face -X
|
||||
.put(Vertex(-1.0f, -1.0f, 1.0f, tfNX))
|
||||
.put(Vertex(-1.0f, 1.0f, 1.0f, tfNX))
|
||||
.put(Vertex(-1.0f, 1.0f, -1.0f, tfNX))
|
||||
.put(Vertex(-1.0f, -1.0f, -1.0f, tfNX))
|
||||
// Face -Y
|
||||
.put(Vertex(-1.0f, -1.0f, 1.0f, tfNY))
|
||||
.put(Vertex(-1.0f, -1.0f, -1.0f, tfNY))
|
||||
.put(Vertex( 1.0f, -1.0f, -1.0f, tfNY))
|
||||
.put(Vertex( 1.0f, -1.0f, 1.0f, tfNY))
|
||||
// Face +Y
|
||||
.put(Vertex(-1.0f, 1.0f, -1.0f, tfPY))
|
||||
.put(Vertex(-1.0f, 1.0f, 1.0f, tfPY))
|
||||
.put(Vertex( 1.0f, 1.0f, 1.0f, tfPY))
|
||||
.put(Vertex( 1.0f, 1.0f, -1.0f, tfPY))
|
||||
// Make sure the cursor is pointing in the right place in the byte buffer
|
||||
.flip()
|
||||
|
||||
// Declare the layout of our mesh
|
||||
vertexBuffer = VertexBuffer.Builder()
|
||||
.bufferCount(1)
|
||||
.vertexCount(vertexCount)
|
||||
// Because we interleave position and color data we must specify offset and stride
|
||||
// We could use de-interleaved data by declaring two buffers and giving each
|
||||
// attribute a different buffer index
|
||||
.attribute(VertexAttribute.POSITION, 0, AttributeType.FLOAT3, 0, vertexSize)
|
||||
.attribute(VertexAttribute.TANGENTS, 0, AttributeType.FLOAT4, 3 * floatSize, vertexSize)
|
||||
.build(engine)
|
||||
|
||||
// Feed the vertex data to the mesh
|
||||
// We only set 1 buffer because the data is interleaved
|
||||
vertexBuffer.setBufferAt(engine, 0, vertexData)
|
||||
|
||||
// Create the indices
|
||||
val indexData = ByteBuffer.allocate(6 * 2 * 3 * shortSize)
|
||||
.order(ByteOrder.nativeOrder())
|
||||
repeat(6) {
|
||||
val i = (it * 4).toShort()
|
||||
indexData
|
||||
.putShort(i).putShort((i + 1).toShort()).putShort((i + 2).toShort())
|
||||
.putShort(i).putShort((i + 2).toShort()).putShort((i + 3).toShort())
|
||||
}
|
||||
indexData.flip()
|
||||
|
||||
// 6 faces, 2 triangles per face,
|
||||
indexBuffer = IndexBuffer.Builder()
|
||||
.indexCount(vertexCount * 2)
|
||||
.bufferType(IndexBuffer.Builder.IndexType.USHORT)
|
||||
.build(engine)
|
||||
indexBuffer.setBuffer(engine, indexData)
|
||||
}
|
||||
|
||||
private fun updateCamera() {
|
||||
// Calculate eye position from spherical coordinates
|
||||
val eyeY = cameraRadius * sin(cameraPhi)
|
||||
val horizontalRadius = cameraRadius * cos(cameraPhi)
|
||||
val eyeX = horizontalRadius * sin(cameraTheta)
|
||||
val eyeZ = horizontalRadius * cos(cameraTheta)
|
||||
|
||||
// Point the camera at the center (0,0,0)
|
||||
camera.lookAt(eyeX.toDouble(), eyeY.toDouble(), eyeZ.toDouble(), // eye
|
||||
0.0, 0.0, 0.0, // center
|
||||
0.0, 1.0, 0.0) // up
|
||||
}
|
||||
|
||||
private val touchListener = object : android.view.View.OnTouchListener {
|
||||
override fun onTouch(v: android.view.View?, event: android.view.MotionEvent?): Boolean {
|
||||
if (event == null) return false
|
||||
when (event.action) {
|
||||
android.view.MotionEvent.ACTION_DOWN -> {
|
||||
mLastX = event.x
|
||||
mLastY = event.y
|
||||
return true
|
||||
}
|
||||
android.view.MotionEvent.ACTION_MOVE -> {
|
||||
val dx = event.x - mLastX
|
||||
val dy = event.y - mLastY
|
||||
|
||||
cameraTheta -= dx * DRAG_SPEED
|
||||
cameraPhi += dy * DRAG_SPEED
|
||||
|
||||
// Clamp vertical angle to avoid flipping over
|
||||
cameraPhi = cameraPhi.coerceIn(-1.5f, 1.5f)
|
||||
|
||||
updateCamera()
|
||||
|
||||
mLastX = event.x
|
||||
mLastY = event.y
|
||||
return true
|
||||
}
|
||||
}
|
||||
return v?.onTouchEvent(event) ?: false
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAnimation() {
|
||||
animator.interpolator = LinearInterpolator()
|
||||
animator.duration = 6000
|
||||
animator.repeatMode = ValueAnimator.RESTART
|
||||
animator.repeatCount = ValueAnimator.INFINITE
|
||||
animator.addUpdateListener(animatorListener)
|
||||
animator.start()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
choreographer.postFrameCallback(frameScheduler)
|
||||
animator.start()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
choreographer.removeFrameCallback(frameScheduler)
|
||||
animator.cancel()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
// Stop the animation and any pending frame
|
||||
choreographer.removeFrameCallback(frameScheduler)
|
||||
animator.cancel();
|
||||
|
||||
// Always detach the surface before destroying the engine
|
||||
uiHelper.detach()
|
||||
|
||||
// Cleanup all resources
|
||||
engine.destroyEntity(light)
|
||||
|
||||
// Destroy all 1000 entities and material instances
|
||||
for (i in 0 until NUM_CUBES) {
|
||||
engine.destroyEntity(renderables[i])
|
||||
materialInstances[i]?.let { engine.destroyMaterialInstance(it) }
|
||||
}
|
||||
|
||||
engine.destroyRenderer(renderer)
|
||||
engine.destroyVertexBuffer(vertexBuffer)
|
||||
engine.destroyIndexBuffer(indexBuffer)
|
||||
engine.destroyMaterial(material)
|
||||
engine.destroyView(view)
|
||||
engine.destroyScene(scene)
|
||||
engine.destroyCameraComponent(camera.entity)
|
||||
|
||||
// Engine.destroyEntity() destroys Filament related resources only
|
||||
// (components), not the entity itself
|
||||
val entityManager = EntityManager.get()
|
||||
entityManager.destroy(light)
|
||||
for (entity in renderables) {
|
||||
entityManager.destroy(entity)
|
||||
}
|
||||
entityManager.destroy(camera.entity)
|
||||
|
||||
// Destroying the engine will free up any resource you may have forgotten
|
||||
// to destroy, but it's recommended to do the cleanup properly
|
||||
engine.destroy()
|
||||
}
|
||||
|
||||
inner class FrameCallback : Choreographer.FrameCallback {
|
||||
override fun doFrame(frameTimeNanos: Long) {
|
||||
// Schedule the next frame
|
||||
choreographer.postFrameCallback(this)
|
||||
|
||||
// This check guarantees that we have a swap chain
|
||||
if (uiHelper.isReadyToRender) {
|
||||
// If beginFrame() returns false you should skip the frame
|
||||
// This means you are sending frames too quickly to the GPU
|
||||
if (renderer.beginFrame(swapChain!!, frameTimeNanos)) {
|
||||
renderer.render(view)
|
||||
renderer.endFrame()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner class SurfaceCallback : UiHelper.RendererCallback {
|
||||
override fun onNativeWindowChanged(surface: Surface) {
|
||||
swapChain?.let { engine.destroySwapChain(it) }
|
||||
swapChain = engine.createSwapChain(surface)
|
||||
displayHelper.attach(renderer, surfaceView.display)
|
||||
}
|
||||
|
||||
override fun onDetachedFromSurface() {
|
||||
displayHelper.detach()
|
||||
swapChain?.let {
|
||||
engine.destroySwapChain(it)
|
||||
// Required to ensure we don't return before Filament is done executing the
|
||||
// destroySwapChain command, otherwise Android might destroy the Surface
|
||||
// too early
|
||||
engine.flushAndWait()
|
||||
swapChain = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResized(width: Int, height: Int) {
|
||||
val aspect = width.toDouble() / height.toDouble()
|
||||
camera.setProjection(45.0, aspect, 0.1, 100.0, Camera.Fov.VERTICAL)
|
||||
|
||||
view.viewport = Viewport(0, 0, width, height)
|
||||
|
||||
FilamentHelper.synchronizePendingFrames(engine)
|
||||
}
|
||||
}
|
||||
|
||||
private fun readUncompressedAsset(assetName: String): ByteBuffer {
|
||||
assets.openFd(assetName).use { fd ->
|
||||
val input = fd.createInputStream()
|
||||
val dst = ByteBuffer.allocate(fd.length.toInt())
|
||||
|
||||
val src = Channels.newChannel(input)
|
||||
src.read(dst)
|
||||
src.close()
|
||||
|
||||
return dst.apply { rewind() }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Simple lit material that defines 3 parameters:
|
||||
// - baseColor
|
||||
// - roughness
|
||||
// - metallic
|
||||
//
|
||||
// These parameters can be used by the application to change the appearance of the material.
|
||||
//
|
||||
// This source material must be compiled to a binary material using the matc tool.
|
||||
// The command used to compile this material is:
|
||||
// matc -p mobile -a opengl -o app/src/main/assets/lit.filamat app/src/materials/lit.mat
|
||||
//
|
||||
// See build.gradle for an example of how to compile materials automatically
|
||||
// Please refer to the documentation for more information about matc and the materials system.
|
||||
|
||||
material {
|
||||
name : lit,
|
||||
|
||||
// Dynamic lighting is enabled on this material
|
||||
shadingModel : lit,
|
||||
|
||||
// We don't need to declare a "requires" array, lit materials
|
||||
// always requires the "tangents" vertex attribute (the normal
|
||||
// is required for lighting, tangent/bitangent for normal mapping
|
||||
// and anisotropy)
|
||||
|
||||
// List of parameters exposed by this material
|
||||
parameters : [
|
||||
// The color must be passed in linear space, not sRGB
|
||||
{
|
||||
type : float3,
|
||||
name : baseColor
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : roughness
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : metallic
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
fragment {
|
||||
void material(inout MaterialInputs material) {
|
||||
prepareMaterial(material);
|
||||
|
||||
// Nothing fancy here, we simply copy the parameters
|
||||
material.baseColor.rgb = materialParams.baseColor;
|
||||
material.roughness = materialParams.roughness;
|
||||
material.metallic = materialParams.metallic;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="78.5885"
|
||||
android:endY="90.9159"
|
||||
android:startX="48.7653"
|
||||
android:startY="61.0927"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0"/>
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,171 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillColor="#26A69A"
|
||||
android:pathData="M0,0h108v108h-108z"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#3F51B5</color>
|
||||
<color name="colorPrimaryDark">#303F9F</color>
|
||||
<color name="colorAccent">#FF4081</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">Material Instance Stress</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,8 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
@@ -12,6 +12,7 @@ include ':samples:sample-image-based-lighting'
|
||||
include ':samples:sample-lit-cube'
|
||||
include ':samples:sample-live-wallpaper'
|
||||
include ':samples:sample-material-builder'
|
||||
include ':samples:sample-material-instance-stress'
|
||||
include ':samples:sample-multi-view'
|
||||
include ':samples:sample-page-curl'
|
||||
include ':samples:sample-stream-test'
|
||||
|
||||
71
build.sh
@@ -77,6 +77,10 @@ function print_help {
|
||||
echo " meant for building the samples."
|
||||
echo " -P"
|
||||
echo " Enable perfetto traces on Android. Disabled by default on the Release build, enabled otherwise."
|
||||
echo " -y build_type"
|
||||
echo " Build the filament dependent tools (matc, resgen) separately from the project. This will set"
|
||||
echo " the tools as prebuilts that filament target will then use to build. The built_type option"
|
||||
echo " (debug|release) is meant to indicate the type of build of the resulting prebuilts."
|
||||
echo ""
|
||||
echo "Build types:"
|
||||
echo " release"
|
||||
@@ -217,6 +221,11 @@ OSMESA_OPTION=""
|
||||
IOS_BUILD_SIMULATOR=false
|
||||
BUILD_UNIVERSAL_LIBRARIES=false
|
||||
|
||||
ISSUE_SPLIT_BUILD=false
|
||||
SPLIT_BUILD_TYPE=""
|
||||
PREBUILT_TOOLS_DIR=""
|
||||
IMPORT_EXECUTABLES_DIR_OPTION="-DIMPORT_EXECUTABLES_DIR=out"
|
||||
|
||||
BUILD_GENERATOR=Ninja
|
||||
BUILD_COMMAND=ninja
|
||||
BUILD_CUSTOM_TARGETS=
|
||||
@@ -242,6 +251,37 @@ function build_clean_aggressive {
|
||||
git clean -qfX android
|
||||
}
|
||||
|
||||
function build_tools_for_split_build {
|
||||
local build_type_arg=$1
|
||||
local lc_build_type=$(echo "${build_type_arg}" | tr '[:upper:]' '[:lower:]')
|
||||
PREBUILT_TOOLS_DIR="out/prebuilt-tools-${lc_build_type}"
|
||||
|
||||
echo "Building tools for split build (${lc_build_type}) in ${PREBUILT_TOOLS_DIR}..."
|
||||
mkdir -p "${PREBUILT_TOOLS_DIR}"
|
||||
|
||||
pushd "${PREBUILT_TOOLS_DIR}" > /dev/null
|
||||
|
||||
local lc_name=$(echo "${UNAME}" | tr '[:upper:]' '[:lower:]')
|
||||
local architectures=""
|
||||
if [[ "${lc_name}" == "darwin" ]]; then
|
||||
if [[ "${BUILD_UNIVERSAL_LIBRARIES}" == "true" ]]; then
|
||||
architectures="-DCMAKE_OSX_ARCHITECTURES=arm64;x86_64"
|
||||
fi
|
||||
fi
|
||||
|
||||
cmake \
|
||||
-G "${BUILD_GENERATOR}" \
|
||||
-DFILAMENT_EXPORT_PREBUILT_EXECUTABLES_DIR=${PREBUILT_TOOLS_DIR} \
|
||||
-DCMAKE_BUILD_TYPE="${build_type_arg}" \
|
||||
${WEBGPU_OPTION} \
|
||||
${architectures} \
|
||||
../..
|
||||
|
||||
${BUILD_COMMAND} ${WEB_HOST_TOOLS}
|
||||
|
||||
popd > /dev/null
|
||||
}
|
||||
|
||||
function build_desktop_target {
|
||||
local lc_target=$(echo "$1" | tr '[:upper:]' '[:lower:]')
|
||||
local build_targets=$2
|
||||
@@ -265,7 +305,7 @@ function build_desktop_target {
|
||||
if [[ ! -d "CMakeFiles" ]] || [[ "${ISSUE_CMAKE_ALWAYS}" == "true" ]]; then
|
||||
cmake \
|
||||
-G "${BUILD_GENERATOR}" \
|
||||
-DIMPORT_EXECUTABLES_DIR=out \
|
||||
${IMPORT_EXECUTABLES_DIR_OPTION} \
|
||||
-DCMAKE_BUILD_TYPE="$1" \
|
||||
-DCMAKE_INSTALL_PREFIX="../${lc_target}/filament" \
|
||||
${EGL_ON_LINUX_OPTION} \
|
||||
@@ -331,7 +371,7 @@ function build_webgl_with_target {
|
||||
source "${EMSDK}/emsdk_env.sh"
|
||||
cmake \
|
||||
-G "${BUILD_GENERATOR}" \
|
||||
-DIMPORT_EXECUTABLES_DIR=out \
|
||||
${IMPORT_EXECUTABLES_DIR_OPTION} \
|
||||
-DCMAKE_TOOLCHAIN_FILE="${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake" \
|
||||
-DCMAKE_BUILD_TYPE="$1" \
|
||||
-DCMAKE_INSTALL_PREFIX="../webgl-${lc_target}/filament" \
|
||||
@@ -404,7 +444,7 @@ function build_android_target {
|
||||
if [[ ! -d "CMakeFiles" ]] || [[ "${ISSUE_CMAKE_ALWAYS}" == "true" ]]; then
|
||||
cmake \
|
||||
-G "${BUILD_GENERATOR}" \
|
||||
-DIMPORT_EXECUTABLES_DIR=out \
|
||||
${IMPORT_EXECUTABLES_DIR_OPTION} \
|
||||
-DCMAKE_BUILD_TYPE="$1" \
|
||||
-DFILAMENT_NDK_VERSION="${FILAMENT_NDK_VERSION}" \
|
||||
-DCMAKE_INSTALL_PREFIX="../android-${lc_target}/filament" \
|
||||
@@ -638,7 +678,7 @@ function build_ios_target {
|
||||
if [[ ! -d "CMakeFiles" ]] || [[ "${ISSUE_CMAKE_ALWAYS}" == "true" ]]; then
|
||||
cmake \
|
||||
-G "${BUILD_GENERATOR}" \
|
||||
-DIMPORT_EXECUTABLES_DIR=out \
|
||||
${IMPORT_EXECUTABLES_DIR_OPTION} \
|
||||
-DCMAKE_BUILD_TYPE="$1" \
|
||||
-DCMAKE_INSTALL_PREFIX="../ios-${lc_target}/filament" \
|
||||
-DIOS_ARCH="${arch}" \
|
||||
@@ -810,7 +850,7 @@ function check_debug_release_build {
|
||||
|
||||
pushd "$(dirname "$0")" > /dev/null
|
||||
|
||||
while getopts ":hacCfgimp:q:uvWslwedtk:bVx:S:X:P" opt; do
|
||||
while getopts ":hacCfgimp:q:uvWslwedtk:bVx:S:X:Py:" opt; do
|
||||
case ${opt} in
|
||||
h)
|
||||
print_help
|
||||
@@ -979,6 +1019,20 @@ while getopts ":hacCfgimp:q:uvWslwedtk:bVx:S:X:P" opt; do
|
||||
;;
|
||||
X) OSMESA_OPTION="-DFILAMENT_OSMESA_PATH=${OPTARG}"
|
||||
;;
|
||||
y)
|
||||
ISSUE_SPLIT_BUILD=true
|
||||
SPLIT_BUILD_TYPE=${OPTARG}
|
||||
case $(echo "${SPLIT_BUILD_TYPE}" | tr '[:upper:]' '[:lower:]') in
|
||||
debug|release)
|
||||
;;
|
||||
*)
|
||||
echo "Unknown build type for -y: ${SPLIT_BUILD_TYPE}"
|
||||
echo "Build type must be one of [debug|release]"
|
||||
echo ""
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
\?)
|
||||
echo "Invalid option: -${OPTARG}" >&2
|
||||
echo ""
|
||||
@@ -1013,6 +1067,13 @@ done
|
||||
|
||||
validate_build_command
|
||||
|
||||
if [[ "${ISSUE_SPLIT_BUILD}" == "true" ]]; then
|
||||
# Capitalize first letter of SPLIT_BUILD_TYPE
|
||||
SPLIT_BUILD_TYPE_CAPITALIZED="$(echo ${SPLIT_BUILD_TYPE:0:1} | tr '[:lower:]' '[:upper:]')${SPLIT_BUILD_TYPE:1}"
|
||||
build_tools_for_split_build "${SPLIT_BUILD_TYPE_CAPITALIZED}"
|
||||
IMPORT_EXECUTABLES_DIR_OPTION="-DFILAMENT_IMPORT_PREBUILT_EXECUTABLES_DIR=${PREBUILT_TOOLS_DIR}"
|
||||
fi
|
||||
|
||||
if [[ "${ISSUE_CLEAN}" == "true" ]]; then
|
||||
build_clean
|
||||
fi
|
||||
|
||||
@@ -180,7 +180,7 @@ rm -r Vulkan-Headers-1.3.232 v1.3.232.zip
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../dup/filamat.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<a rel="next prefetch" href="../dup/fgviewer.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
@@ -194,7 +194,7 @@ rm -r Vulkan-Headers-1.3.232 v1.3.232.zip
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../dup/filamat.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<a rel="next prefetch" href="../dup/fgviewer.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
@@ -200,6 +200,12 @@ inside the Filament source tree.</p>
|
||||
force a clean build by adding the <code>-c</code> flag in that case.</p>
|
||||
<p>To install the libraries and executables in <code>out/debug/</code> and <code>out/release/</code>, add the <code>-i</code> flag.
|
||||
The script offers more features described by executing <code>build.sh -h</code>.</p>
|
||||
<p>For more specialized options, please also consider the following pages:</p>
|
||||
<ul>
|
||||
<li><code>-d</code>: <a href="https://google.github.io/filament/dup/matdbg.html"><code>matdbg</code></a></li>
|
||||
<li><code>-t</code>: <a href="https://google.github.io/filament/dup/fgviewer.html"><code>fgviewer</code></a></li>
|
||||
<li><code>-b</code> and <code>-y</code>: <a href="https://google.github.io/filament/notes/asan_ubsan.html">ASAN/UBSAN builds</a></li>
|
||||
</ul>
|
||||
<h3 id="filament-specific-cmake-options"><a class="header" href="#filament-specific-cmake-options">Filament-specific CMake Options</a></h3>
|
||||
<p>The following CMake options are boolean options specific to Filament:</p>
|
||||
<ul>
|
||||
|
||||
345
docs/dup/fgviewer.html
Normal file
@@ -0,0 +1,345 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" class="light sidebar-visible" dir="ltr">
|
||||
<head>
|
||||
<!-- Book generated using mdBook -->
|
||||
<meta charset="UTF-8">
|
||||
<title>fgviewer - Filament</title>
|
||||
|
||||
|
||||
<!-- Custom HTML head -->
|
||||
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
<link rel="shortcut icon" href="../favicon.png">
|
||||
<link rel="stylesheet" href="../css/variables.css">
|
||||
<link rel="stylesheet" href="../css/general.css">
|
||||
<link rel="stylesheet" href="../css/chrome.css">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
|
||||
<link rel="stylesheet" href="../fonts/fonts.css">
|
||||
|
||||
<!-- Highlight.js Stylesheets -->
|
||||
<link rel="stylesheet" href="../highlight.css">
|
||||
<link rel="stylesheet" href="../tomorrow-night.css">
|
||||
<link rel="stylesheet" href="../ayu-highlight.css">
|
||||
|
||||
<!-- Custom theme stylesheets -->
|
||||
|
||||
<!-- MathJax -->
|
||||
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
||||
|
||||
<!-- Provide site root to javascript -->
|
||||
<script>
|
||||
var path_to_root = "../";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
|
||||
</script>
|
||||
<!-- Start loading toc.js asap -->
|
||||
<script src="../toc.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="body-container">
|
||||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||||
<script>
|
||||
try {
|
||||
var theme = localStorage.getItem('mdbook-theme');
|
||||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||||
|
||||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||||
}
|
||||
|
||||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||||
}
|
||||
} catch (e) { }
|
||||
</script>
|
||||
|
||||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||||
<script>
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||||
const html = document.documentElement;
|
||||
html.classList.remove('light')
|
||||
html.classList.add(theme);
|
||||
html.classList.add("js");
|
||||
</script>
|
||||
|
||||
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
|
||||
|
||||
<!-- Hide / unhide sidebar before it is displayed -->
|
||||
<script>
|
||||
var sidebar = null;
|
||||
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
|
||||
if (document.body.clientWidth >= 1080) {
|
||||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||||
sidebar = sidebar || 'visible';
|
||||
} else {
|
||||
sidebar = 'hidden';
|
||||
}
|
||||
sidebar_toggle.checked = sidebar === 'visible';
|
||||
html.classList.remove('sidebar-visible');
|
||||
html.classList.add("sidebar-" + sidebar);
|
||||
</script>
|
||||
|
||||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||||
<div style="display:flex;align-items:center;justify-content:center">
|
||||
<img class="flogo" src="../images/filament_logo_small.png"></img>
|
||||
</div>
|
||||
<!-- populated by js -->
|
||||
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
|
||||
<noscript>
|
||||
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
|
||||
</noscript>
|
||||
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
|
||||
<div class="sidebar-resize-indicator"></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="page-wrapper" class="page-wrapper">
|
||||
|
||||
<div class="page">
|
||||
<div id="menu-bar-hover-placeholder"></div>
|
||||
<div id="menu-bar" class="menu-bar sticky">
|
||||
<div class="left-buttons">
|
||||
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</label>
|
||||
<!-- Filament: disable themes because the markdeep part does not look good for dark themes -->
|
||||
<!--
|
||||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
</ul>
|
||||
-->
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h1 class="menu-title">Filament</h1>
|
||||
|
||||
<div class="right-buttons">
|
||||
<a href="https://github.com/google/filament" title="Git repository" aria-label="Git repository">
|
||||
<i id="git-repository-button" class="fa fa-github"></i>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="search-wrapper" class="hidden">
|
||||
<form id="searchbar-outer" class="searchbar-outer">
|
||||
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||||
</form>
|
||||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||||
<div id="searchresults-header" class="searchresults-header"></div>
|
||||
<ul id="searchresults">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||||
<script>
|
||||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="content" class="content">
|
||||
<main>
|
||||
<h1 id="fgviewer"><a class="header" href="#fgviewer">fgviewer</a></h1>
|
||||
<ol>
|
||||
<li><a href="#capabilities">Capabilities</a></li>
|
||||
<li><a href="#setup-for-desktop">Setup for Desktop</a></li>
|
||||
<li><a href="#setup-for-android">Setup for Android</a></li>
|
||||
<li><a href="#debugger-usage">Debugger Usage</a></li>
|
||||
<li><a href="#architecture-overview">Architecture Overview</a></li>
|
||||
<li><a href="#c-server">C++ Server</a></li>
|
||||
<li><a href="#javascript-client">JavaScript Client</a></li>
|
||||
<li><a href="#http-requests">HTTP Requests</a></li>
|
||||
<li><a href="#wish-list">Wish List</a></li>
|
||||
</ol>
|
||||
<h2 id="capabilities"><a class="header" href="#capabilities">Capabilities</a></h2>
|
||||
<p>fgviewer is a library and web application for real-time visualization of the frame graph in Filament.
|
||||
It displays active passes and resource usage, providing insights into the rendering pipeline.</p>
|
||||
<h2 id="setup-for-desktop"><a class="header" href="#setup-for-desktop">Setup for Desktop</a></h2>
|
||||
<p>When using the easy build script, include the <code>-t</code> argument. For example:</p>
|
||||
<pre><code>./build.sh -ft debug gltf_viewer
|
||||
</code></pre>
|
||||
<p>The <code>t</code> enables a CMake option called <code>FILAMENT_ENABLE_FGVIEWER</code> and the <code>f</code> ensures that CMake gets
|
||||
re-run so that the option is honored.</p>
|
||||
<p>Next, set an environment variable as follows. In Windows, use <code>set</code> instead of <code>export</code>.</p>
|
||||
<pre><code>export FILAMENT_FGVIEWER_PORT=8050
|
||||
</code></pre>
|
||||
<p>Next, launch any app that links against a debug build of a Filament and point your web browser to
|
||||
http://localhost:8050. Skip ahead to <strong>Debugger Usage</strong>.</p>
|
||||
<h2 id="setup-for-android"><a class="header" href="#setup-for-android">Setup for Android</a></h2>
|
||||
<p>Rebuild Filament for Android after enabling a CMake option called <code>FILAMENT_ENABLE_FGVIEWER</code>. Note that
|
||||
CMake is invoked from several places for Android (both gradle and our easy build script), so one
|
||||
pragmatic and reliable way of doing this is to simply hack <code>CMakeLists.txt</code> and
|
||||
<code>filament-android/CMakeLists.txt</code> by unconditionally setting <code>FILAMENT_ENABLE_FGVIEWER</code> to <code>ON</code>.</p>
|
||||
<p>After rebuilding Filament with the option enabled, ensure that internet permissions are enabled in
|
||||
your app by adding the following into your manifest as a child of the <code><manifest></code> element.</p>
|
||||
<pre><code><uses-permission android:name="android.permission.INTERNET" />
|
||||
</code></pre>
|
||||
<p>Now launch your app as usual. The Filament Engine sets up a server that is hardcoded to listen to
|
||||
port <code>8085</code>. Next, you will need to forward your device's TCP port <code>8085</code> to your host port of choice.
|
||||
For example, to forward the fgviewer server on your device to port <code>8085</code> on your host machine, do the
|
||||
following:</p>
|
||||
<pre><code>adb forward tcp:8085 tcp:8085
|
||||
</code></pre>
|
||||
<p>This lets you go to http://localhost:8085 in Chrome on your host machine.</p>
|
||||
<h2 id="debugger-usage"><a class="header" href="#debugger-usage">Debugger Usage</a></h2>
|
||||
<p>After opening the fgviewer page in your browser, you can see the active views are on the left panel.
|
||||
Then you can select any of them to see the active passes and resources for that view.</p>
|
||||
<p align="center">
|
||||
<img width="600px" src=https://github.com/user-attachments/assets/2d31767f-fc25-4f17-8c14-528fe5c6b698>
|
||||
</p>
|
||||
<h2 id="architecture-overview"><a class="header" href="#architecture-overview">Architecture Overview</a></h2>
|
||||
<p align="center">
|
||||
<img width="450" src=https://github.com/user-attachments/assets/537ebb89-6ad0-4b93-bbeb-207d4fe9ec5a>
|
||||
</p>
|
||||
<p>The fgviewer library has two parts: a C++ server and a JavaScript client. The C++ server is
|
||||
responsible for instancing a <a href="https://github.com/civetweb/civetweb">civetweb</a> context that handles HTTP requests. The
|
||||
JavaScript client is a small web app that contains a view into an in-browser database of framegraphs.</p>
|
||||
<p>When a new connection is established, the client asks the server for a list of framegraphs
|
||||
in order to populate its in-browser database. If the connection is lost (e.g. if the app crashes),
|
||||
then the database stays intact and the web app is still functional.</p>
|
||||
<h2 id="c-server"><a class="header" href="#c-server">C++ Server</a></h2>
|
||||
<p>The civetweb server is wrapped by our <code>DebugServer</code> class, which provides a public interface consisting of
|
||||
a few methods invoked by the Filament engine.</p>
|
||||
<p>Since each view corresponds to a frame graph, the engine should notify <code>DebugServer</code> of any changes to the views
|
||||
on the engine side.</p>
|
||||
<ul>
|
||||
<li><strong>createView</strong> Notifies the debugger that a new view has been created.</li>
|
||||
<li><strong>updateView</strong> Notifies the debugger of updates to an existing view.</li>
|
||||
<li><strong>destroyView</strong> Notifies the debugger that a view is being removed.</li>
|
||||
</ul>
|
||||
<h2 id="javascript-client"><a class="header" href="#javascript-client">JavaScript Client</a></h2>
|
||||
<p>The web app is built using LitElement, a lightweight library for creating Web Components. Our goal is to keep the code simple and modern, avoiding frameworks like React or Angular.</p>
|
||||
<p>The app presents a view over a pseudo-database, which is essentially a global variable holding a dictionary that maps frame graph ids to objects following the JSON structure described below.</p>
|
||||
<h2 id="http-requests"><a class="header" href="#http-requests">HTTP requests</a></h2>
|
||||
<p>The server responds to the following GET requests by returning a JSON blob. The <code>{id}</code> in these
|
||||
requests is a concept specific to fgviewer (not Filament) which is an 8-digit hex string for identifying frame graphs.</p>
|
||||
<hr />
|
||||
<p><code>/api/framegraphs</code></p>
|
||||
<p>Returns an array containing all framegraphs in an app. Example:</p>
|
||||
<pre><code class="language-json">[{
|
||||
"fgid": "00000000",
|
||||
"viewName": "Main View",
|
||||
"passes": [{
|
||||
"name": "shadow pass",
|
||||
"reads": [],
|
||||
"writes": ["0"]
|
||||
}],
|
||||
"resources": [{
|
||||
"id": "0",
|
||||
"name": "shadowmap",
|
||||
"properties": [{"resolution": "256x256"}, {"is_subresource": "false"}]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"fgid": "00000001",
|
||||
"viewName": "UI view",
|
||||
...
|
||||
}]
|
||||
</code></pre>
|
||||
<hr />
|
||||
<p><code>/api/framegraph?fg={id}</code></p>
|
||||
<p>Returns a specific framegraph info. Example:</p>
|
||||
<pre><code class="language-json">{
|
||||
"fgid": "00000000",
|
||||
"viewName": "Main View",
|
||||
"passes": [{
|
||||
"name": "shadow pass",
|
||||
"reads": [],
|
||||
"writes": ["0"]
|
||||
}],
|
||||
"resources": [{
|
||||
"id": "0",
|
||||
"name": "shadowmap",
|
||||
"properties": [{"resolution": "256x256"}, {"is_subresource": "false"}]
|
||||
}]
|
||||
}
|
||||
</code></pre>
|
||||
<hr />
|
||||
<p><code>/api/status</code></p>
|
||||
<p>Returns one of the below:</p>
|
||||
<ul>
|
||||
<li><code>0</code>: first time connected</li>
|
||||
<li><code>1</code>: no-op</li>
|
||||
<li><code>{fgid}</code>: the corresponding frame graph has an update
|
||||
<ul>
|
||||
<li>Then the web view can request for the actual info using the previous api</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<p>If the request gets timeout, the web page show disconnected to the user.</p>
|
||||
<hr />
|
||||
<h2 id="wish-list"><a class="header" href="#wish-list">Wish List</a></h2>
|
||||
<ul>
|
||||
<li>Display the texture contents on the webview</li>
|
||||
</ul>
|
||||
|
||||
</main>
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
<a rel="prev" href="../dup/bluevk.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../dup/filamat.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
|
||||
<div style="clear: both"></div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
<a rel="prev" href="../dup/bluevk.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
<a rel="next prefetch" href="../dup/filamat.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
window.playground_copyable = true;
|
||||
</script>
|
||||
|
||||
|
||||
<script src="../elasticlunr.min.js"></script>
|
||||
<script src="../mark.min.js"></script>
|
||||
<script src="../searcher.js"></script>
|
||||
|
||||
<script src="../clipboard.min.js"></script>
|
||||
<script src="../highlight.js"></script>
|
||||
<script src="../book.js"></script>
|
||||
|
||||
<!-- Custom JS scripts -->
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -336,7 +336,7 @@ void material(inout MaterialInputs material) {
|
||||
|
||||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
<a rel="prev" href="../dup/bluevk.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<a rel="prev" href="../dup/fgviewer.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
@@ -350,7 +350,7 @@ void material(inout MaterialInputs material) {
|
||||
</div>
|
||||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
<a rel="prev" href="../dup/bluevk.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<a rel="prev" href="../dup/fgviewer.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.66.1'
|
||||
implementation 'com.google.android.filament:filament-android:1.67.1'
|
||||
}
|
||||
</code></pre>
|
||||
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
|
||||
@@ -196,7 +196,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.66.1'
|
||||
<pre><code class="language-shell">pod 'Filament', '~> 1.67.1'
|
||||
</code></pre>
|
||||
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
|
||||
<ul>
|
||||
|
||||
@@ -160,10 +160,15 @@
|
||||
<main>
|
||||
<h1 id="running-with-asanubsan"><a class="header" href="#running-with-asanubsan">Running with ASAN/UBSAN</a></h1>
|
||||
<h2 id="enabling"><a class="header" href="#enabling">Enabling</a></h2>
|
||||
<p>When building though build.sh, pass the <code>-b</code> flag. This sets the cmake variable
|
||||
<p>When building though <code>build.sh</code>, pass the <code>-b</code> flag. This sets the cmake variable
|
||||
<code>FILAMENT_ENABLE_ASAN_UBSAN=ON</code> which eventually passes <code>"-fsanitize=address -fsanitize=undefined"</code>
|
||||
to all compile and link operations.</p>
|
||||
<p>If building through CMake directly, or an IDE like CLion that doesn't use build.sh, instead pass
|
||||
<p>It might be desirable to pair the <code>-b</code> with <code>-y release</code>. The <code>-y</code> flag indicates that targets
|
||||
in <code>/tools</code> will be built separately from the filament target, and the filament build depends on
|
||||
the <code>/tools</code> targets as prebuilt binaries. This separation reduces ASAN/UBSAN build time
|
||||
considerably. This option assumes that the user is trying to catch sanitization issues for
|
||||
filament and not the tools that are used to build filament.</p>
|
||||
<p>If building through CMake directly, or an IDE like CLion that doesn't use <code>build.sh</code>, instead pass
|
||||
<code>-DFILAMENT_ENABLE_ASAN_UBSAN=ON</code> to cmake in order to get the same result.</p>
|
||||
<h2 id="getting-memory-leak-detection-on-mac"><a class="header" href="#getting-memory-leak-detection-on-mac">Getting memory leak detection on Mac</a></h2>
|
||||
<p>Memory leak detection isn't enabled by default on MacOS. There are two issues to address, first is
|
||||
|
||||
@@ -46,6 +46,9 @@
|
||||
"libs/matdbg/README.md": {
|
||||
"dest": "dup/matdbg.md"
|
||||
},
|
||||
"libs/fgviewer/README.md": {
|
||||
"dest": "dup/fgviewer.md"
|
||||
},
|
||||
"tools/normal-blending/README.md": {
|
||||
"dest": "dup/normal_blending.md"
|
||||
},
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
- [Libraries](./notes/libs.md)
|
||||
- [bluegl](./dup/bluegl.md)
|
||||
- [bluevk](./dup/bluevk.md)
|
||||
- [fgviewer](./dup/fgviewer.md)
|
||||
- [filamat](./dup/filamat.md)
|
||||
- [gltfio](./dup/gltfio.md)
|
||||
- [iblprefilter](./dup/iblprefilter.md)
|
||||
|
||||
@@ -2,11 +2,17 @@
|
||||
|
||||
## Enabling
|
||||
|
||||
When building though build.sh, pass the `-b` flag. This sets the cmake variable
|
||||
When building though `build.sh`, pass the `-b` flag. This sets the cmake variable
|
||||
`FILAMENT_ENABLE_ASAN_UBSAN=ON` which eventually passes `"-fsanitize=address -fsanitize=undefined"`
|
||||
to all compile and link operations.
|
||||
|
||||
If building through CMake directly, or an IDE like CLion that doesn't use build.sh, instead pass
|
||||
It might be desirable to pair the `-b` with `-y release`. The `-y` flag indicates that targets
|
||||
in `/tools` will be built separately from the filament target, and the filament build depends on
|
||||
the `/tools` targets as prebuilt binaries. This separation reduces ASAN/UBSAN build time
|
||||
considerably. This option assumes that the user is trying to catch sanitization issues for
|
||||
filament and not the tools that are used to build filament.
|
||||
|
||||
If building through CMake directly, or an IDE like CLion that doesn't use `build.sh`, instead pass
|
||||
`-DFILAMENT_ENABLE_ASAN_UBSAN=ON` to cmake in order to get the same result.
|
||||
|
||||
## Getting memory leak detection on Mac
|
||||
|
||||
@@ -139,6 +139,7 @@ set(SRCS
|
||||
src/details/SwapChain.cpp
|
||||
src/details/Sync.cpp
|
||||
src/details/Texture.cpp
|
||||
src/details/UboManager.cpp
|
||||
src/details/VertexBuffer.cpp
|
||||
src/details/View.cpp
|
||||
src/ds/PerViewDescriptorSetUtils.cpp
|
||||
@@ -218,6 +219,7 @@ set(PRIVATE_HDRS
|
||||
src/details/Stream.h
|
||||
src/details/SwapChain.h
|
||||
src/details/Texture.h
|
||||
src/details/UboManager.h
|
||||
src/details/VertexBuffer.h
|
||||
src/details/View.h
|
||||
src/downcast.h
|
||||
@@ -363,7 +365,7 @@ add_definitions(
|
||||
# Generate all .filamat: default material, skyboxes, and post-process
|
||||
# ==================================================================================================
|
||||
|
||||
if (CMAKE_CROSSCOMPILING)
|
||||
if (CMAKE_CROSSCOMPILING OR FILAMENT_IMPORT_PREBUILT_EXECUTABLES)
|
||||
include(${IMPORT_EXECUTABLES})
|
||||
endif()
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ Copy your platform's Makefile below into a `Makefile` inside the same directory.
|
||||
### Linux
|
||||
|
||||
```make
|
||||
FILAMENT_LIBS=-lfilament -lbackend -lbluegl -lbluevk -lfilabridge -lfilaflat -lutils -lgeometry -lsmol-v -lvkshaders -libl -labseil
|
||||
FILAMENT_LIBS=-lfilament -lbackend -lbluegl -lbluevk -lfilabridge -lfilaflat -lutils -lgeometry -lsmol-v -libl -labseil
|
||||
CC=clang++
|
||||
|
||||
main: main.o
|
||||
@@ -110,7 +110,7 @@ clean:
|
||||
### macOS
|
||||
|
||||
```make
|
||||
FILAMENT_LIBS=-lfilament -lbackend -lbluegl -lbluevk -lfilabridge -lfilaflat -lutils -lgeometry -lsmol-v -lvkshaders -libl -labseil
|
||||
FILAMENT_LIBS=-lfilament -lbackend -lbluegl -lbluevk -lfilabridge -lfilaflat -lutils -lgeometry -lsmol-v -libl -labseil
|
||||
FRAMEWORKS=-framework Cocoa -framework Metal -framework CoreVideo
|
||||
CC=clang++
|
||||
ARCH ?= $(shell uname -m)
|
||||
@@ -140,7 +140,7 @@ used to change the run-time library version.
|
||||
|
||||
```make
|
||||
FILAMENT_LIBS=filament.lib backend.lib bluegl.lib bluevk.lib filabridge.lib filaflat.lib \
|
||||
utils.lib geometry.lib smol-v.lib ibl.lib vkshaders.lib abseil.lib
|
||||
utils.lib geometry.lib smol-v.lib ibl.lib abseil.lib
|
||||
CC=cl.exe
|
||||
|
||||
main.exe: main.obj
|
||||
|
||||
@@ -104,8 +104,10 @@ if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3)
|
||||
list(APPEND SRCS src/VirtualMachineEnv.cpp)
|
||||
endif ()
|
||||
if (ANDROID)
|
||||
list(APPEND SRCS src/AndroidNdk.cpp)
|
||||
list(APPEND SRCS src/AndroidNativeWindow.cpp)
|
||||
list(APPEND SRCS src/AndroidSwapChainHelper.cpp)
|
||||
list(APPEND SRCS src/AndroidFrameCallback.cpp)
|
||||
list(APPEND SRCS src/opengl/platforms/ExternalStreamManagerAndroid.cpp)
|
||||
list(APPEND SRCS src/opengl/platforms/PlatformEGLAndroid.cpp)
|
||||
elseif (IOS)
|
||||
@@ -250,10 +252,21 @@ if (FILAMENT_SUPPORTS_VULKAN)
|
||||
src/vulkan/utils/Spirv.h
|
||||
src/vulkan/utils/StaticVector.h
|
||||
)
|
||||
if (LINUX OR WIN32)
|
||||
list(APPEND SRCS src/vulkan/platform/VulkanPlatformLinuxWindows.cpp)
|
||||
if (LINUX)
|
||||
list(APPEND SRCS
|
||||
src/vulkan/platform/VulkanPlatformLinux.cpp
|
||||
include/backend/platforms/VulkanPlatformLinux.h
|
||||
)
|
||||
elseif (WIN32)
|
||||
list(APPEND SRCS
|
||||
include/backend/platforms/VulkanPlatformWindows.h
|
||||
src/vulkan/platform/VulkanPlatformWindows.cpp
|
||||
)
|
||||
elseif (APPLE OR IOS)
|
||||
list(APPEND SRCS src/vulkan/platform/VulkanPlatformApple.mm)
|
||||
list(APPEND SRCS
|
||||
include/backend/platforms/VulkanPlatformApple.h
|
||||
src/vulkan/platform/VulkanPlatformApple.mm
|
||||
)
|
||||
elseif (ANDROID)
|
||||
list(APPEND SRCS
|
||||
include/backend/platforms/VulkanPlatformAndroid.h
|
||||
@@ -303,6 +316,8 @@ if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
src/webgpu/WebGPURenderPrimitive.h
|
||||
src/webgpu/WebGPURenderTarget.cpp
|
||||
src/webgpu/WebGPURenderTarget.h
|
||||
src/webgpu/WebGPUStagePool.cpp
|
||||
src/webgpu/WebGPUStagePool.h
|
||||
src/webgpu/WebGPUStrings.h
|
||||
src/webgpu/WebGPUSwapChain.cpp
|
||||
src/webgpu/WebGPUSwapChain.h
|
||||
@@ -369,46 +384,6 @@ endif()
|
||||
add_library(${TARGET}_headers INTERFACE)
|
||||
target_include_directories(${TARGET}_headers INTERFACE ${PUBLIC_HDR_DIR})
|
||||
|
||||
# ==================================================================================================
|
||||
# Build SPIRV snippets used by the Vulkan backend.
|
||||
# ==================================================================================================
|
||||
|
||||
set(VKSHADERS_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated/vkshaders")
|
||||
|
||||
file(MAKE_DIRECTORY ${VKSHADERS_DIR})
|
||||
|
||||
get_resgen_vars(${VKSHADERS_DIR} vkshaders)
|
||||
|
||||
set(VKSHADER_BINS)
|
||||
function(build_vkshader SOURCE TARGET_PATH)
|
||||
set(SOURCE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/src/vulkan/${SOURCE}")
|
||||
set(VKSHADER_BINS ${VKSHADER_BINS} ${TARGET_PATH} PARENT_SCOPE)
|
||||
add_custom_command(
|
||||
OUTPUT ${TARGET_PATH}
|
||||
COMMAND matc --raw -o ${TARGET_PATH} ${SOURCE_PATH}
|
||||
MAIN_DEPENDENCY ${SOURCE_PATH}
|
||||
DEPENDS matc ${SOURCE_PATH}
|
||||
COMMENT "Building SPIR-V")
|
||||
endfunction()
|
||||
build_vkshader("BlitDepth.vs" "BlitDepthVs.spv")
|
||||
build_vkshader("BlitDepth.fs" "BlitDepthFs.spv")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${RESGEN_OUTPUTS}
|
||||
COMMAND resgen ${RESGEN_FLAGS} ${VKSHADER_BINS}
|
||||
DEPENDS resgen ${VKSHADER_BINS}
|
||||
COMMENT "Aggregating compiled VK shaders")
|
||||
|
||||
if (DEFINED RESGEN_SOURCE_FLAGS)
|
||||
set_source_files_properties(${RESGEN_SOURCE} PROPERTIES COMPILE_FLAGS ${RESGEN_SOURCE_FLAGS})
|
||||
endif()
|
||||
|
||||
set(DUMMY_SRC "${VKSHADERS_DIR}/dummy.c")
|
||||
add_custom_command(OUTPUT ${DUMMY_SRC} COMMAND echo "//" > ${DUMMY_SRC})
|
||||
|
||||
add_library(vkshaders STATIC ${DUMMY_SRC} ${RESGEN_SOURCE})
|
||||
set_target_properties(vkshaders PROPERTIES FOLDER Filament/Generated)
|
||||
|
||||
# ==================================================================================================
|
||||
# Dependencies
|
||||
# ==================================================================================================
|
||||
@@ -442,7 +417,7 @@ if(FILAMENT_SUPPORTS_OPENGL AND NOT IOS AND NOT ANDROID AND NOT WEBGL)
|
||||
endif()
|
||||
|
||||
if (FILAMENT_SUPPORTS_VULKAN)
|
||||
target_link_libraries(${TARGET} PUBLIC bluevk vkmemalloc vkshaders smol-v)
|
||||
target_link_libraries(${TARGET} PUBLIC bluevk vkmemalloc smol-v)
|
||||
target_link_libraries(${TARGET} PRIVATE SPIRV-Headers)
|
||||
endif()
|
||||
|
||||
@@ -561,7 +536,7 @@ endif()
|
||||
# ==================================================================================================
|
||||
set(INSTALL_TYPE ARCHIVE)
|
||||
install(TARGETS ${TARGET} ${INSTALL_TYPE} DESTINATION lib/${DIST_DIR})
|
||||
install(TARGETS vkshaders ${INSTALL_TYPE} DESTINATION lib/${DIST_DIR})
|
||||
install(TARGETS ${INSTALL_TYPE} DESTINATION lib/${DIST_DIR})
|
||||
install(DIRECTORY ${PUBLIC_HDR_DIR}/backend DESTINATION include)
|
||||
|
||||
if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
@@ -592,6 +567,7 @@ if (APPLE OR LINUX)
|
||||
test/test_BufferUpdates.cpp
|
||||
test/test_Callbacks.cpp
|
||||
test/test_MemoryMappedBuffer.cpp
|
||||
test/test_MsaaSwapChain.cpp
|
||||
test/test_MRT.cpp
|
||||
test/test_PushConstants.cpp
|
||||
test/test_LoadImage.cpp
|
||||
|
||||
@@ -1681,7 +1681,7 @@ static_assert(sizeof(StencilState::StencilOperations) == 5u,
|
||||
static_assert(sizeof(StencilState) == 12u,
|
||||
"StencilState size not what was intended");
|
||||
|
||||
using FrameScheduledCallback = utils::Invocable<void(backend::PresentCallable)>;
|
||||
using FrameScheduledCallback = utils::Invocable<void(PresentCallable)>;
|
||||
|
||||
enum class Workaround : uint16_t {
|
||||
// The EASU pass must split because shader compiler flattens early-exit branch
|
||||
@@ -1707,7 +1707,11 @@ enum class Workaround : uint16_t {
|
||||
EMULATE_SRGB_SWAPCHAIN,
|
||||
};
|
||||
|
||||
using StereoscopicType = backend::Platform::StereoscopicType;
|
||||
using StereoscopicType = Platform::StereoscopicType;
|
||||
|
||||
using FrameTimestamps = Platform::FrameTimestamps;
|
||||
|
||||
using CompositorTiming = Platform::CompositorTiming;
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
|
||||
@@ -19,13 +19,17 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_PLATFORM_H
|
||||
#define TNT_FILAMENT_BACKEND_PLATFORM_H
|
||||
|
||||
#include <utils/CString.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Invocable.h>
|
||||
#include <utils/Mutex.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -96,6 +100,7 @@ public:
|
||||
using time_point_ns = int64_t;
|
||||
/** duration in nanosecond on the std::steady_clock */
|
||||
using duration_ns = int64_t;
|
||||
static constexpr time_point_ns INVALID = -1; //!< value not supported
|
||||
/**
|
||||
* The timestamp [ns] since epoch of the next time the compositor will begin composition.
|
||||
* This is effectively the deadline for when the compositor must receive a newly queued
|
||||
@@ -110,9 +115,27 @@ public:
|
||||
|
||||
/**
|
||||
* The time delta [ns] between the start of composition and the expected present time of
|
||||
* that composition. This can be used to estimate the latency of the actual present time.
|
||||
* that composition. This can be used to estimate the latency of the actual present time.
|
||||
*/
|
||||
duration_ns compositeToPresentLatency;
|
||||
|
||||
/**
|
||||
* The timestamp [ns] since epoch of the system's expected presentation time.
|
||||
* INVALID if not supported.
|
||||
*/
|
||||
time_point_ns expectedPresentTime;
|
||||
|
||||
/**
|
||||
* The timestamp [ns] since epoch of the current frame's start (i.e. vsync)
|
||||
* INVALID if not supported.
|
||||
*/
|
||||
time_point_ns frameTime;
|
||||
|
||||
/**
|
||||
* The timestamp [ns] since epoch of the current frame's deadline
|
||||
* INVALID if not supported.
|
||||
*/
|
||||
time_point_ns frameTimelineDeadline;
|
||||
};
|
||||
|
||||
struct FrameTimestamps {
|
||||
@@ -120,14 +143,62 @@ public:
|
||||
using time_point_ns = int64_t;
|
||||
static constexpr time_point_ns INVALID = -1; //!< value not supported
|
||||
static constexpr time_point_ns PENDING = -2; //!< value not yet available
|
||||
|
||||
/**
|
||||
* The time the application requested this frame be presented.
|
||||
* If the application does not request a presentation time explicitly,
|
||||
* this will correspond to buffer's queue time.
|
||||
*/
|
||||
time_point_ns requestedPresentTime;
|
||||
|
||||
/**
|
||||
* The time when all the application's rendering to the surface was completed.
|
||||
*/
|
||||
time_point_ns acquireTime;
|
||||
|
||||
/**
|
||||
* The time when the compositor selected this frame as the one to use for the next
|
||||
* composition. This is the earliest indication that the frame was submitted in time.
|
||||
*/
|
||||
time_point_ns latchTime;
|
||||
time_point_ns firstRefreshStartTime;
|
||||
time_point_ns lastRefreshStartTime;
|
||||
|
||||
/**
|
||||
* The first time at which the compositor began preparing composition for this frame.
|
||||
* Zero if composition was handled by the display and the compositor didn't do any
|
||||
* rendering.
|
||||
*/
|
||||
time_point_ns firstCompositionStartTime;
|
||||
|
||||
/**
|
||||
* The last time at which the compositor began preparing composition for this frame, for
|
||||
* frames composited more than once. Zero if composition was handled by the display and the
|
||||
* compositor didn't do any rendering.
|
||||
*/
|
||||
time_point_ns lastCompositionStartTime;
|
||||
|
||||
/**
|
||||
* The time at which the compositor's rendering work for this frame finished. This will be
|
||||
* INVALID if composition was handled by the display and the compositor didn't do any
|
||||
* rendering.
|
||||
*/
|
||||
time_point_ns gpuCompositionDoneTime;
|
||||
|
||||
/**
|
||||
* The time at which this frame started to scan out to the physical display.
|
||||
*/
|
||||
time_point_ns displayPresentTime;
|
||||
|
||||
/**
|
||||
* The time when the buffer became available for reuse as a buffer the client can target
|
||||
* without blocking. This is generally the point when all read commands of the buffer have
|
||||
* been submitted, but not necessarily completed.
|
||||
*/
|
||||
time_point_ns dequeueReadyTime;
|
||||
|
||||
/**
|
||||
* The time at which all reads for the purpose of display/composition were completed for
|
||||
* this frame.
|
||||
*/
|
||||
time_point_ns releaseTime;
|
||||
};
|
||||
|
||||
@@ -441,13 +512,22 @@ public:
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Debugging APIs
|
||||
|
||||
using DebugUpdateStatFunc = utils::Invocable<void(const char* UTILS_NONNULL key, uint64_t value)>;
|
||||
using DebugUpdateStatFunc = utils::Invocable<void(const char* UTILS_NONNULL key,
|
||||
uint64_t intValue, utils::CString stringValue)>;
|
||||
|
||||
/**
|
||||
* Sets the callback function that the backend can use to update backend-specific statistics
|
||||
* to aid with debugging. This callback is guaranteed to be called on the Filament driver
|
||||
* thread.
|
||||
*
|
||||
* The callback signature is (key, intValue, stringValue). Note that for any given call,
|
||||
* only one of the value parameters (intValue or stringValue) will be meaningful, depending on
|
||||
* the specific key.
|
||||
*
|
||||
* IMPORTANT_NOTE: because the callback is called on the driver thread, only quick, non-blocking
|
||||
* work should be done inside it. Furthermore, no graphics API calls (such as GL calls) should
|
||||
* be made, which could interfere with Filament's driver state.
|
||||
*
|
||||
* @param debugUpdateStat an Invocable that updates debug statistics
|
||||
*/
|
||||
void setDebugUpdateStatFunc(DebugUpdateStatFunc&& debugUpdateStat) noexcept;
|
||||
@@ -466,15 +546,32 @@ public:
|
||||
* This function is guaranteed to be called only on a single thread, the Filament driver
|
||||
* thread.
|
||||
*
|
||||
* @param key a null-terminated C-string with the key of the debug statistic
|
||||
* @param value the updated value of key
|
||||
* @param key a null-terminated C-string with the key of the debug statistic
|
||||
* @param intValue the updated integer value of key (the string value passed to the
|
||||
* callback will be empty)
|
||||
*/
|
||||
void debugUpdateStat(const char* UTILS_NONNULL key, uint64_t value);
|
||||
void debugUpdateStat(const char* UTILS_NONNULL key, uint64_t intValue);
|
||||
|
||||
/**
|
||||
* To track backend-specific statistics, the backend implementation can call the
|
||||
* application-provided callback function debugUpdateStatFunc to associate or update a value
|
||||
* with a given key. It is possible for this function to be called multiple times with the
|
||||
* same key, in which case newer values should overwrite older values.
|
||||
*
|
||||
* This function is guaranteed to be called only on a single thread, the Filament driver
|
||||
* thread.
|
||||
*
|
||||
* @param key a null-terminated C-string with the key of the debug statistic
|
||||
* @param stringValue the updated string value of key (the integer value passed to the
|
||||
* callback will be 0)
|
||||
*/
|
||||
void debugUpdateStat(const char* UTILS_NONNULL key, utils::CString stringValue);
|
||||
|
||||
private:
|
||||
InsertBlobFunc mInsertBlob;
|
||||
RetrieveBlobFunc mRetrieveBlob;
|
||||
DebugUpdateStatFunc mDebugUpdateStat;
|
||||
std::shared_ptr<InsertBlobFunc> mInsertBlob;
|
||||
std::shared_ptr<RetrieveBlobFunc> mRetrieveBlob;
|
||||
std::shared_ptr<DebugUpdateStatFunc> mDebugUpdateStat;
|
||||
mutable utils::Mutex mMutex;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
79
filament/backend/include/backend/platforms/AndroidNdk.h
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef FILAMENT_BACKEND_ANDROIDNDK_H
|
||||
#define FILAMENT_BACKEND_ANDROIDNDK_H
|
||||
|
||||
#include <android/native_window.h>
|
||||
#include <android/hardware_buffer.h>
|
||||
|
||||
#define FILAMENT_REQUIRES_API(x) __attribute__((__availability__(android,introduced=x)))
|
||||
|
||||
#define FILAMENT_USE_DLSYM(api) (__ANDROID_API__ < (api))
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class AndroidNdk {
|
||||
public:
|
||||
AndroidNdk();
|
||||
|
||||
#if FILAMENT_USE_DLSYM(26)
|
||||
|
||||
static void AHardwareBuffer_acquire(
|
||||
AHardwareBuffer* buffer) FILAMENT_REQUIRES_API(26) {
|
||||
ndk.AHardwareBuffer_acquire(buffer);
|
||||
}
|
||||
|
||||
static void AHardwareBuffer_release(
|
||||
AHardwareBuffer* buffer) FILAMENT_REQUIRES_API(26) {
|
||||
ndk.AHardwareBuffer_release(buffer);
|
||||
}
|
||||
|
||||
static void AHardwareBuffer_describe(
|
||||
AHardwareBuffer const* buffer, AHardwareBuffer_Desc* desc) FILAMENT_REQUIRES_API(26) {
|
||||
ndk.AHardwareBuffer_describe(buffer, desc);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void AHardwareBuffer_acquire(
|
||||
AHardwareBuffer* buffer) FILAMENT_REQUIRES_API(26) {
|
||||
::AHardwareBuffer_acquire(buffer);
|
||||
}
|
||||
static void AHardwareBuffer_release(
|
||||
AHardwareBuffer* buffer) FILAMENT_REQUIRES_API(26) {
|
||||
::AHardwareBuffer_release(buffer);
|
||||
}
|
||||
static void AHardwareBuffer_describe(
|
||||
AHardwareBuffer const* buffer, AHardwareBuffer_Desc* desc) FILAMENT_REQUIRES_API(26) {
|
||||
::AHardwareBuffer_describe(buffer, desc);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if FILAMENT_USE_DLSYM(26)
|
||||
static struct Ndk {
|
||||
void (*AHardwareBuffer_acquire)(AHardwareBuffer*);
|
||||
void (*AHardwareBuffer_release)(AHardwareBuffer*);
|
||||
void (*AHardwareBuffer_describe)(AHardwareBuffer const*, AHardwareBuffer_Desc*);
|
||||
} ndk;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // filament::backend
|
||||
|
||||
#endif //FILAMENT_BACKEND_ANDROIDNDK_H
|
||||
@@ -17,7 +17,7 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H
|
||||
#define TNT_FILAMENT_BACKEND_OPENGL_OPENGL_PLATFORM_EGL_ANDROID_H
|
||||
|
||||
#include "AndroidSwapChainHelper.h"
|
||||
#include "AndroidNdk.h"
|
||||
|
||||
#include <backend/AcquiredImage.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
@@ -30,6 +30,8 @@
|
||||
|
||||
#include <math/mat3.h>
|
||||
|
||||
#include "AndroidNativeWindow.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include <stddef.h>
|
||||
@@ -43,7 +45,7 @@ class ExternalStreamManagerAndroid;
|
||||
* A concrete implementation of OpenGLPlatform and subclass of PlatformEGL that supports
|
||||
* EGL on Android. It adds Android streaming functionality to PlatformEGL.
|
||||
*/
|
||||
class PlatformEGLAndroid : public PlatformEGL {
|
||||
class PlatformEGLAndroid : public PlatformEGL, public AndroidNdk {
|
||||
public:
|
||||
|
||||
PlatformEGLAndroid() noexcept;
|
||||
@@ -167,24 +169,19 @@ protected:
|
||||
SwapChain* drawSwapChain,
|
||||
SwapChain* readSwapChain) override;
|
||||
|
||||
struct SwapChainEGLAndroid : public SwapChainEGL {
|
||||
SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
|
||||
void* nativeWindow, uint64_t flags);
|
||||
SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
|
||||
uint32_t width, uint32_t height, uint64_t flags);
|
||||
void terminate(PlatformEGLAndroid& platform);
|
||||
bool setPresentFrameId(uint64_t frameId) const noexcept;
|
||||
uint64_t getFrameId(uint64_t frameId) const noexcept;
|
||||
private:
|
||||
AndroidSwapChainHelper mImpl{};
|
||||
};
|
||||
|
||||
private:
|
||||
struct SwapChainEGLAndroid;
|
||||
struct AndroidDetails;
|
||||
|
||||
// prevent derived classes' implementations to call through
|
||||
[[nodiscard]] SwapChain* createSwapChain(void* nativeWindow, uint64_t flags) override;
|
||||
[[nodiscard]] SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) override;
|
||||
void destroySwapChain(SwapChain* swapChain) noexcept override;
|
||||
|
||||
bool isProducerThrottlingControlSupported() const;
|
||||
|
||||
int32_t setProducerThrottlingEnabled(EGLNativeWindowType nativeWindow, bool enabled) const;
|
||||
|
||||
struct InitializeJvmForPerformanceManagerIfNeeded {
|
||||
InitializeJvmForPerformanceManagerIfNeeded();
|
||||
};
|
||||
@@ -195,18 +192,14 @@ private:
|
||||
|
||||
int mOSVersion;
|
||||
ExternalStreamManagerAndroid& mExternalStreamManager;
|
||||
AndroidDetails& mAndroidDetails;
|
||||
InitializeJvmForPerformanceManagerIfNeeded const mInitializeJvmForPerformanceManagerIfNeeded;
|
||||
utils::PerformanceHintManager mPerformanceHintManager;
|
||||
utils::PerformanceHintManager::Session mPerformanceHintSession;
|
||||
SwapChainEGLAndroid* mCurrentDrawSwapChain{};
|
||||
|
||||
using clock = std::chrono::high_resolution_clock;
|
||||
clock::time_point mStartTimeOfActualWork;
|
||||
|
||||
int32_t (*ANativeWindow_setProducerThrottlingEnabled)(ANativeWindow* window, bool enabled) = nullptr;
|
||||
int32_t (*ANativeWindow_isProducerThrottlingEnabled)(ANativeWindow* window, bool* outEnabled) = nullptr;
|
||||
SwapChainEGLAndroid* mCurrentDrawSwapChain{};
|
||||
bool mAssertNativeWindowIsValid = false;
|
||||
bool mHasProducerThrottlingControl = false;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -437,22 +437,19 @@ protected:
|
||||
std::shared_ptr<VulkanCmdFence> fenceStatus;
|
||||
};
|
||||
|
||||
virtual ExtensionSet getSwapchainInstanceExtensions() const;
|
||||
|
||||
using SurfaceBundle = std::tuple<VkSurfaceKHR, VkExtent2D>;
|
||||
virtual ExtensionSet getSwapchainInstanceExtensions() const = 0;
|
||||
virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) const noexcept;
|
||||
uint64_t flags) const noexcept = 0;
|
||||
|
||||
virtual VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept;
|
||||
|
||||
/**
|
||||
* Query if transient attachments are supported by the backend.
|
||||
*/
|
||||
bool isTransientAttachmentSupported() const noexcept;
|
||||
|
||||
private:
|
||||
// Platform dependent helper methods
|
||||
static ExtensionSet getSwapchainInstanceExtensionsImpl();
|
||||
|
||||
// Platform dependent helper methods
|
||||
static SurfaceBundle createVkSurfaceKHRImpl(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) noexcept;
|
||||
|
||||
friend struct VulkanPlatformPrivate;
|
||||
};
|
||||
|
||||
|
||||
@@ -17,14 +17,18 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H
|
||||
#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H
|
||||
|
||||
#include "AndroidNdk.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
|
||||
#include <android/hardware_buffer.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanPlatformAndroid : public VulkanPlatform {
|
||||
class VulkanPlatformAndroid : public VulkanPlatform, public AndroidNdk {
|
||||
public:
|
||||
ExternalImageHandle UTILS_PUBLIC createExternalImage(AHardwareBuffer const* buffer,
|
||||
bool sRGB) noexcept;
|
||||
@@ -36,13 +40,17 @@ public:
|
||||
TextureUsage usage; // Texture usage flags
|
||||
};
|
||||
|
||||
VulkanPlatformAndroid();
|
||||
|
||||
~VulkanPlatformAndroid() noexcept override;
|
||||
|
||||
ExternalImageDescAndroid UTILS_PUBLIC getExternalImageDesc(
|
||||
ExternalImageHandleRef externalImage) const noexcept;
|
||||
|
||||
virtual ExternalImageMetadata extractExternalImageMetadata(
|
||||
ExternalImageMetadata extractExternalImageMetadata(
|
||||
ExternalImageHandleRef image) const override;
|
||||
|
||||
virtual ImageData createVkImageFromExternal(ExternalImageHandleRef image) const override;
|
||||
ImageData createVkImageFromExternal(ExternalImageHandleRef image) const override;
|
||||
|
||||
/**
|
||||
* Converts a sync to an external file descriptor, if possible. Accepts an
|
||||
@@ -53,25 +61,49 @@ public:
|
||||
* @return `true` on success, `false` on failure. The default implementation
|
||||
* returns `false`.
|
||||
*/
|
||||
bool convertSyncToFd(Platform::Sync* sync, int* fd) const noexcept;
|
||||
bool convertSyncToFd(Sync* sync, int* fd) const noexcept;
|
||||
|
||||
int getOSVersion() const noexcept override;
|
||||
|
||||
void terminate() override;
|
||||
|
||||
Driver* createDriver(void* sharedContext,
|
||||
DriverConfig const& driverConfig) override;
|
||||
|
||||
|
||||
bool isCompositorTimingSupported() const noexcept override;
|
||||
|
||||
bool queryCompositorTiming(SwapChain const* swapchain,
|
||||
CompositorTiming* outCompositorTiming) const noexcept override;
|
||||
|
||||
bool setPresentFrameId(SwapChain const* swapchain, uint64_t frameId) noexcept override;
|
||||
|
||||
bool queryFrameTimestamps(SwapChain const* swapchain, uint64_t frameId,
|
||||
FrameTimestamps* outFrameTimestamps) const noexcept override;
|
||||
|
||||
|
||||
protected:
|
||||
virtual ExtensionSet getSwapchainInstanceExtensions() const override;
|
||||
ExtensionSet getSwapchainInstanceExtensions() const override;
|
||||
|
||||
using SurfaceBundle = VulkanPlatform::SurfaceBundle;
|
||||
virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
using SurfaceBundle = SurfaceBundle;
|
||||
SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) const noexcept override;
|
||||
|
||||
virtual VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept override;
|
||||
VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept override;
|
||||
|
||||
private:
|
||||
struct ExternalImageVulkanAndroid : public Platform::ExternalImage {
|
||||
struct AndroidDetails;
|
||||
|
||||
struct ExternalImageVulkanAndroid : public ExternalImage {
|
||||
AHardwareBuffer* aHardwareBuffer = nullptr;
|
||||
bool sRGB = false;
|
||||
|
||||
protected:
|
||||
~ExternalImageVulkanAndroid() override;
|
||||
};
|
||||
|
||||
AndroidDetails& mAndroidDetails;
|
||||
int mOSVersion{};
|
||||
};
|
||||
|
||||
}// namespace filament::backend
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMAPPLE_H
|
||||
#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMAPPLE_H
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanPlatformApple : public VulkanPlatform {
|
||||
protected:
|
||||
ExtensionSet getSwapchainInstanceExtensions() const override;
|
||||
SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) const noexcept override;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMAPPLE_H
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMLINUX_H
|
||||
#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMLINUX_H
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanPlatformLinux : public VulkanPlatform {
|
||||
protected:
|
||||
ExtensionSet getSwapchainInstanceExtensions() const override;
|
||||
SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) const noexcept override;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMLINUX_H
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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_BACKEND_PLATFORMS_VULKANPLATFORMWINDOWS_H
|
||||
#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMWINDOWS_H
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanPlatformWindows : public VulkanPlatform {
|
||||
protected:
|
||||
ExtensionSet getSwapchainInstanceExtensions() const override;
|
||||
SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) const noexcept override;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORMWINDOWS_H
|
||||
@@ -337,6 +337,7 @@ DECL_DRIVER_API_SYNCHRONOUS_N(int64_t, getStreamTimestamp, backend::StreamHandle
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(void, updateStreams, backend::DriverApi*, driver)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(backend::FenceStatus, getFenceStatus, backend::FenceHandle, fh)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(backend::FenceStatus, fenceWait, backend::FenceHandle, fh, uint64_t, timeout)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(void, fenceCancel, backend::FenceHandle, fh)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(void, getPlatformSync, backend::SyncHandle, sh,
|
||||
backend::CallbackHandler*, handler, backend::Platform::SyncCallback, cb, void*, userData)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isTextureFormatSupported, backend::TextureFormat, format)
|
||||
@@ -367,6 +368,9 @@ DECL_DRIVER_API_SYNCHRONOUS_N(backend::TimerQueryResult, getTimerQueryValue, bac
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isWorkaroundNeeded, backend::Workaround, workaround)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(backend::FeatureLevel, getFeatureLevel)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getUniformBufferOffsetAlignment)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isCompositorTimingSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, queryFrameTimestamps, backend::SwapChainHandle, swapChain, uint64_t, frameId, backend::FrameTimestamps*, outFrameTimestamps)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, queryCompositorTiming, backend::SwapChainHandle, swapChain, backend::CompositorTiming*, outCompositorTiming)
|
||||
|
||||
/*
|
||||
* Updating driver objects
|
||||
|
||||
@@ -169,10 +169,11 @@ public:
|
||||
* Destroy the object D at Handle<B> and frees Handle<B>
|
||||
* e.g.:
|
||||
* Handle<HwTexture> h = ...;
|
||||
* deallocate(h);
|
||||
* deallocate<GLTexture>(h);
|
||||
*/
|
||||
template<typename D>
|
||||
void deallocate(Handle<D>& handle) noexcept {
|
||||
template<typename D, typename B,
|
||||
typename = std::enable_if_t<std::is_base_of_v<B, D>, D>>
|
||||
void deallocate(Handle<B>& handle) noexcept {
|
||||
D const* d = handle_cast<const D*>(handle);
|
||||
deallocate(handle, d);
|
||||
}
|
||||
@@ -208,7 +209,7 @@ public:
|
||||
HandleBase::HandleId const index = (handle.getId() & HANDLE_INDEX_MASK);
|
||||
// if we've already handed out this handle index before, it's definitely a
|
||||
// use-after-free, otherwise it's probably just a corrupted handle
|
||||
if (index < mId) {
|
||||
if (index < mId.load(std::memory_order_relaxed)) {
|
||||
FILAMENT_CHECK_POSTCONDITION(p != nullptr)
|
||||
<< "use-after-free of heap Handle with id=" << handle.getId()
|
||||
<< ", tag=" << getHandleTag(handle.getId()).c_str_safe();
|
||||
|
||||
139
filament/backend/src/AndroidFrameCallback.cpp
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "AndroidFrameCallback.h"
|
||||
|
||||
#include <android/choreographer.h>
|
||||
#include <android/looper.h>
|
||||
|
||||
#include <private/utils/Tracing.h>
|
||||
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/JobSystem.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
using namespace utils;
|
||||
|
||||
AndroidFrameCallback::AndroidFrameCallback() = default;
|
||||
|
||||
AndroidFrameCallback::~AndroidFrameCallback() noexcept {
|
||||
assert_invariant(!mLooperThread.joinable());
|
||||
assert_invariant(mLooper == nullptr);
|
||||
}
|
||||
|
||||
void AndroidFrameCallback::init() {
|
||||
if (__builtin_available(android 33, *)) {
|
||||
FILAMENT_CHECK_PRECONDITION(!mLooperThread.joinable()) << "init() already called";
|
||||
|
||||
// start the looper thread for our choreographer callbacks
|
||||
mLooperThread = std::thread([this] {
|
||||
// create the looper for this thread
|
||||
mLooper = ALooper_prepare(0);
|
||||
// acquire a reference, so we can use it from our main thread
|
||||
ALooper_acquire(mLooper);
|
||||
// set thread name
|
||||
JobSystem::setThreadName("Filament Choreographer");
|
||||
// set priority
|
||||
JobSystem::setThreadPriority(JobSystem::Priority::DISPLAY);
|
||||
// start the choreographer callbacks
|
||||
if (__builtin_available(android 33, *)) {
|
||||
mChoreographer = AChoreographer_getInstance();
|
||||
// request our first callback for the next frame
|
||||
AChoreographer_postVsyncCallback(mChoreographer, &vsyncCallback, this);
|
||||
}
|
||||
// signal we're ready to run and choreographer and looper are initialized
|
||||
mInitBarrier.latch();
|
||||
// our main loop just sits there to handle events
|
||||
while (true) {
|
||||
int const result = ALooper_pollOnce(-1, nullptr, nullptr, nullptr);
|
||||
if (result == ALOOPER_POLL_ERROR || mExitRequested.
|
||||
load(std::memory_order_relaxed)) {
|
||||
return; // exit the loop
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// wait for the thread and looper to be created and ready to run
|
||||
mInitBarrier.await();
|
||||
}
|
||||
}
|
||||
|
||||
void AndroidFrameCallback::terminate() {
|
||||
if (__builtin_available(android 33, *)) {
|
||||
// the thread wouldn't be joinable if terminate() is called twice, or init() is not called.
|
||||
if (mLooperThread.joinable()) {
|
||||
// request exit
|
||||
mExitRequested.store(true, std::memory_order_relaxed);
|
||||
// wake the looper right away
|
||||
ALooper_wake(mLooper);
|
||||
// release our reference to the looper
|
||||
ALooper_release(mLooper);
|
||||
mLooper = nullptr;
|
||||
// and make sure to wait for the thread to terminate
|
||||
mLooperThread.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AndroidFrameCallback::isSupported() noexcept {
|
||||
if (__builtin_available(android 33, *)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
AndroidFrameCallback::Timeline AndroidFrameCallback::getPreferredTimeline() const noexcept {
|
||||
std::lock_guard const l(mLock);
|
||||
return mPreferredTimeline;
|
||||
}
|
||||
|
||||
void AndroidFrameCallback::vsyncCallback(const AChoreographerFrameCallbackData* callbackData) {
|
||||
if (__builtin_available(android 33, *)) {
|
||||
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
|
||||
|
||||
// request the next frame callback
|
||||
AChoreographer_postVsyncCallback(mChoreographer, &vsyncCallback, this);
|
||||
|
||||
int64_t const frameTime = AChoreographerFrameCallbackData_getFrameTimeNanos(callbackData);
|
||||
|
||||
size_t const preferredIndex =
|
||||
AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex(callbackData);
|
||||
|
||||
int64_t const expectedPresentTime =
|
||||
AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentationTimeNanos(
|
||||
callbackData, preferredIndex);
|
||||
|
||||
int64_t const frameTimelineDeadline =
|
||||
AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos(
|
||||
callbackData, preferredIndex);
|
||||
|
||||
std::lock_guard const l(mLock);
|
||||
mPreferredTimeline = {
|
||||
.frameTime = frameTime,
|
||||
.expectedPresentTime = expectedPresentTime,
|
||||
.frameTimelineDeadline = frameTimelineDeadline
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
82
filament/backend/src/AndroidFrameCallback.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_ANDROIDFRAMECALLBACK_H
|
||||
#define TNT_FILAMENT_BACKEND_ANDROIDFRAMECALLBACK_H
|
||||
|
||||
#include <android/choreographer.h>
|
||||
#include <android/looper.h>
|
||||
|
||||
#include <utils/CountDownLatch.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/*
|
||||
* AndroidFrameCallback uses a dedicated thread running a Looper and uses
|
||||
* Choreographer to receive the FrameCallbackData.
|
||||
*
|
||||
*/
|
||||
class AndroidFrameCallback {
|
||||
public:
|
||||
struct Timeline {
|
||||
using timepoint_ns = int64_t;
|
||||
static constexpr timepoint_ns INVALID = -1;
|
||||
timepoint_ns frameTime{ INVALID };
|
||||
timepoint_ns expectedPresentTime{ INVALID };
|
||||
timepoint_ns frameTimelineDeadline{ INVALID };
|
||||
};
|
||||
|
||||
AndroidFrameCallback();
|
||||
|
||||
~AndroidFrameCallback() noexcept;
|
||||
|
||||
AndroidFrameCallback(AndroidFrameCallback const&) = delete;
|
||||
AndroidFrameCallback& operator=(AndroidFrameCallback const&) = delete;
|
||||
|
||||
void init();
|
||||
void terminate();
|
||||
|
||||
static bool isSupported() noexcept;
|
||||
|
||||
Timeline getPreferredTimeline() const noexcept;
|
||||
|
||||
private:
|
||||
// looper
|
||||
std::thread mLooperThread;
|
||||
mutable utils::CountDownLatch mInitBarrier{ 1 };
|
||||
ALooper* mLooper = nullptr;
|
||||
std::atomic_bool mExitRequested{ false };
|
||||
|
||||
// choreographer
|
||||
AChoreographer* mChoreographer{};
|
||||
|
||||
void vsyncCallback(const AChoreographerFrameCallbackData* callbackData);
|
||||
static void vsyncCallback(const AChoreographerFrameCallbackData* callbackData, void* data) {
|
||||
static_cast<AndroidFrameCallback*>(data)->vsyncCallback(callbackData);
|
||||
}
|
||||
|
||||
mutable std::mutex mLock;
|
||||
Timeline mPreferredTimeline{};
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif //TNT_FILAMENT_BACKEND_ANDROIDFRAMECALLBACK_H
|
||||
@@ -16,22 +16,31 @@
|
||||
|
||||
#include "AndroidNativeWindow.h"
|
||||
|
||||
#include <android/api-level.h>
|
||||
#include <android/native_window.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Logger.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cerrno>
|
||||
#include <utility>
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
std::pair<int, bool> NativeWindow::isValid(ANativeWindow* const anw) noexcept {
|
||||
#if __ANDROID_API__ >= 26
|
||||
// libnativewindow.so is not available before API level 26, this means we can't call
|
||||
// any method above 25 (even protected by __builtin_available()).
|
||||
if (__builtin_available(android 28, *)) {
|
||||
// this a proxy for is_valid()
|
||||
auto const result = ANativeWindow_getBuffersDataSpace(anw);
|
||||
return { result, result >= 0 };
|
||||
}
|
||||
#endif
|
||||
|
||||
// fallback on using private APIs
|
||||
NativeWindow const* pWindow = reinterpret_cast<NativeWindow const*>(anw);
|
||||
if (UTILS_LIKELY(pWindow->query)) {
|
||||
@@ -75,4 +84,48 @@ int NativeWindow::getFrameTimestamps(ANativeWindow* anw,
|
||||
outDequeueReadyTime, outReleaseTime);
|
||||
}
|
||||
|
||||
AndroidProducerThrottling::AndroidProducerThrottling() {
|
||||
// note: we don't need to dlclose() mNativeWindowLib here, because the library will be cleaned
|
||||
// when the process ends and dlopen() are ref-counted. dlclose() NDK documentation documents
|
||||
// not to call dlclose().
|
||||
|
||||
// libnativewindow.so is not available before API level 26, this means we can't call
|
||||
// any method above 25 (even protected by __builtin_available()).
|
||||
|
||||
void* nativeWindowLibHandle = dlopen("libnativewindow.so", RTLD_LOCAL | RTLD_NOW);
|
||||
if (nativeWindowLibHandle) {
|
||||
mSetProducerThrottlingEnabled =
|
||||
(int32_t(*)(ANativeWindow*, bool)) dlsym(nativeWindowLibHandle,
|
||||
"ANativeWindow_setProducerThrottlingEnabled");
|
||||
|
||||
mIsProducerThrottlingEnabled =
|
||||
(int32_t(*)(ANativeWindow*, bool*)) dlsym(nativeWindowLibHandle,
|
||||
"ANativeWindow_isProducerThrottlingEnabled");
|
||||
|
||||
if (mSetProducerThrottlingEnabled && mIsProducerThrottlingEnabled) {
|
||||
LOG(INFO) << "Producer Throttling API available";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32_t AndroidProducerThrottling::setProducerThrottlingEnabled(
|
||||
ANativeWindow* window, bool enabled) const {
|
||||
if (mSetProducerThrottlingEnabled) {
|
||||
return mSetProducerThrottlingEnabled(window, enabled);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t AndroidProducerThrottling::isProducerThrottlingEnabled(
|
||||
ANativeWindow* window, bool* outEnabled) const {
|
||||
if (mIsProducerThrottlingEnabled) {
|
||||
return mIsProducerThrottlingEnabled(window, outEnabled);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool AndroidProducerThrottling::isSupported() const noexcept {
|
||||
return mSetProducerThrottlingEnabled && mIsProducerThrottlingEnabled;
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -37,7 +37,12 @@ struct NativeWindow {
|
||||
GET_COMPOSITOR_TIMING = 26,
|
||||
GET_FRAME_TIMESTAMPS = 27,
|
||||
};
|
||||
|
||||
#if defined(__LP64__)
|
||||
uint64_t pad[18];
|
||||
#else
|
||||
uint32_t pad[21];
|
||||
#endif
|
||||
int (*query)(ANativeWindow const*, int, int*);
|
||||
int (*perform)(ANativeWindow*, int, ...);
|
||||
|
||||
@@ -58,6 +63,17 @@ struct NativeWindow {
|
||||
int64_t* outReleaseTime);
|
||||
};
|
||||
|
||||
struct AndroidProducerThrottling {
|
||||
AndroidProducerThrottling();
|
||||
int32_t setProducerThrottlingEnabled(ANativeWindow* window, bool enabled) const;
|
||||
int32_t isProducerThrottlingEnabled(ANativeWindow* window, bool* outEnabled) const;
|
||||
bool isSupported() const noexcept;
|
||||
private:
|
||||
int32_t (*mSetProducerThrottlingEnabled)(ANativeWindow* window, bool enabled) = nullptr;
|
||||
int32_t (*mIsProducerThrottlingEnabled)(ANativeWindow* window, bool* outEnabled) = nullptr;
|
||||
};
|
||||
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif //FILAMENT_BACKEND_ANDROIDNATIVEWINDOW_H
|
||||
|
||||
60
filament/backend/src/AndroidNdk.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <backend/platforms/AndroidNdk.h>
|
||||
|
||||
#include <android/hardware_buffer.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
UTILS_UNUSED
|
||||
static std::once_flag sInitOnce{};
|
||||
|
||||
template <typename T>
|
||||
UTILS_UNUSED
|
||||
static void loadSymbol(void* handle, const char* symbol, T& pfn) {
|
||||
pfn = T(dlsym(handle, symbol));
|
||||
}
|
||||
|
||||
#if FILAMENT_USE_DLSYM(26)
|
||||
AndroidNdk::Ndk AndroidNdk::ndk{};
|
||||
#endif
|
||||
|
||||
AndroidNdk::AndroidNdk() {
|
||||
#if FILAMENT_USE_DLSYM(26)
|
||||
std::call_once(sInitOnce, [] {
|
||||
// note: we don't need to dlclose() mNativeWindowLib here, because the library will be cleaned
|
||||
// when the process ends and dlopen() are ref-counted. dlclose() NDK documentation documents
|
||||
// not to call dlclose().
|
||||
if (__builtin_available(android 26, *)) {
|
||||
void* h = dlopen("libnativewindow.so", RTLD_LOCAL | RTLD_NOW);
|
||||
if (h) {
|
||||
loadSymbol(h, "AHardwareBuffer_acquire", ndk.AHardwareBuffer_acquire);
|
||||
loadSymbol(h, "AHardwareBuffer_release", ndk.AHardwareBuffer_release);
|
||||
loadSymbol(h, "AHardwareBuffer_describe", ndk.AHardwareBuffer_describe);
|
||||
}
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
} // filament::backend
|
||||
@@ -31,31 +31,27 @@ AndroidSwapChainHelper::AndroidSwapChainHelper() = default;
|
||||
AndroidSwapChainHelper::~AndroidSwapChainHelper() noexcept = default;
|
||||
|
||||
bool AndroidSwapChainHelper::setPresentFrameId(
|
||||
ANativeWindow* anw, uint64_t frameId) const noexcept {
|
||||
ANativeWindow* anw, uint64_t const frameId) const noexcept {
|
||||
uint64_t sysFrameId{};
|
||||
int const status = NativeWindow::getNextFrameId(anw, &sysFrameId);
|
||||
if (status == 0) {
|
||||
std::lock_guard const lock(mLock);
|
||||
auto const pos = mFrameIdToSystemFrameId.find(frameId);
|
||||
if (pos != mFrameIdToSystemFrameId.end() && pos->second != sysFrameId) {
|
||||
if (pos && *pos != sysFrameId) {
|
||||
// we're trying to associate the same frame id to a different frame!
|
||||
return false;
|
||||
}
|
||||
// make space by destroying the oldest entry
|
||||
if (mFrameIdToSystemFrameId.size() >= MAX_HISTORY_SIZE) {
|
||||
mFrameIdToSystemFrameId.erase(mFrameIdToSystemFrameId.begin());
|
||||
}
|
||||
mFrameIdToSystemFrameId.insert(mFrameIdToSystemFrameId.end(), { frameId, sysFrameId });
|
||||
// oldest entry is removed
|
||||
mFrameIdToSystemFrameId.insert(frameId, sysFrameId);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t AndroidSwapChainHelper::getFrameId(uint64_t frameId) const noexcept {
|
||||
uint64_t AndroidSwapChainHelper::getFrameId(uint64_t const frameId) const noexcept {
|
||||
std::lock_guard const lock(mLock);
|
||||
auto pos = mFrameIdToSystemFrameId.find(frameId);
|
||||
if (pos != mFrameIdToSystemFrameId.end()) {
|
||||
return pos->second;
|
||||
if (auto const* const pos = mFrameIdToSystemFrameId.find(frameId)) {
|
||||
return *pos;
|
||||
}
|
||||
return std::numeric_limits<uint64_t>::max();
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#define TNT_FILAMENT_BACKEND_ANDROIDSWAPCHAINHELPER_H
|
||||
|
||||
#include <utils/Mutex.h>
|
||||
#include <utils/MonotonicRingMap.h>
|
||||
|
||||
#include <android/native_window.h>
|
||||
|
||||
@@ -39,7 +40,7 @@ struct AndroidSwapChainHelper {
|
||||
private:
|
||||
static constexpr size_t MAX_HISTORY_SIZE = 32;
|
||||
mutable utils::Mutex mLock; // very low-contention lock
|
||||
mutable std::map<uint64_t, uint64_t> mFrameIdToSystemFrameId{};
|
||||
mutable utils::MonotonicRingMap<MAX_HISTORY_SIZE, uint64_t, uint64_t> mFrameIdToSystemFrameId{};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -25,8 +25,11 @@ TextureFormat mapToFilamentFormat(unsigned int format, bool isSrgbTransfer) noex
|
||||
switch (format) {
|
||||
case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM:
|
||||
return TextureFormat::SRGB8;
|
||||
default:
|
||||
case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
|
||||
case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM:
|
||||
return TextureFormat::SRGB8_A8;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch (format) {
|
||||
|
||||
@@ -108,7 +108,7 @@ template <size_t P0, size_t P1, size_t P2>
|
||||
UTILS_NOINLINE
|
||||
void* HandleAllocator<P0, P1, P2>::handleToPointerSlow(HandleBase::HandleId id) const noexcept {
|
||||
auto& overflowMap = mOverflowMap;
|
||||
std::lock_guard lock(mLock);
|
||||
std::lock_guard const lock(mLock);
|
||||
auto pos = overflowMap.find(id);
|
||||
if (pos != overflowMap.end()) {
|
||||
return pos.value();
|
||||
@@ -119,14 +119,15 @@ void* HandleAllocator<P0, P1, P2>::handleToPointerSlow(HandleBase::HandleId id)
|
||||
template <size_t P0, size_t P1, size_t P2>
|
||||
HandleBase::HandleId HandleAllocator<P0, P1, P2>::allocateHandleSlow(size_t size) {
|
||||
void* p = ::malloc(size);
|
||||
std::unique_lock lock(mLock);
|
||||
|
||||
HandleBase::HandleId id = (++mId) | HANDLE_HEAP_FLAG;
|
||||
|
||||
FILAMENT_CHECK_POSTCONDITION(mId < HANDLE_HEAP_FLAG) <<
|
||||
auto const nextId = mId.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
FILAMENT_CHECK_POSTCONDITION(nextId < HANDLE_HEAP_FLAG) <<
|
||||
"No more Handle ids available! This can happen if HandleAllocator arena has been full"
|
||||
" for a while. Please increase FILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB";
|
||||
|
||||
HandleBase::HandleId id = nextId | HANDLE_HEAP_FLAG;
|
||||
|
||||
std::unique_lock lock(mLock);
|
||||
mOverflowMap.emplace(id, p);
|
||||
lock.unlock();
|
||||
|
||||
|
||||
@@ -139,42 +139,73 @@ bool Platform::queryFrameTimestamps(SwapChain const*, uint64_t, FrameTimestamps*
|
||||
}
|
||||
|
||||
void Platform::setBlobFunc(InsertBlobFunc&& insertBlob, RetrieveBlobFunc&& retrieveBlob) noexcept {
|
||||
mInsertBlob = std::move(insertBlob);
|
||||
mRetrieveBlob = std::move(retrieveBlob);
|
||||
std::lock_guard<decltype(mMutex)> lock(mMutex);
|
||||
mInsertBlob = std::make_shared<InsertBlobFunc>(std::move(insertBlob));
|
||||
mRetrieveBlob = std::make_shared<RetrieveBlobFunc>(std::move(retrieveBlob));
|
||||
}
|
||||
|
||||
bool Platform::hasInsertBlobFunc() const noexcept {
|
||||
std::lock_guard<decltype(mMutex)> lock(mMutex);
|
||||
return bool(mInsertBlob);
|
||||
}
|
||||
|
||||
bool Platform::hasRetrieveBlobFunc() const noexcept {
|
||||
std::lock_guard<decltype(mMutex)> lock(mMutex);
|
||||
return bool(mRetrieveBlob);
|
||||
}
|
||||
|
||||
void Platform::insertBlob(void const* key, size_t keySize, void const* value, size_t valueSize) {
|
||||
if (mInsertBlob) {
|
||||
mInsertBlob(key, keySize, value, valueSize);
|
||||
std::shared_ptr<InsertBlobFunc> callback;
|
||||
{
|
||||
std::unique_lock<decltype(mMutex)> lock(mMutex);
|
||||
callback = mInsertBlob;
|
||||
}
|
||||
if (callback) {
|
||||
(*callback)(key, keySize, value, valueSize);
|
||||
}
|
||||
}
|
||||
|
||||
size_t Platform::retrieveBlob(void const* key, size_t keySize, void* value, size_t valueSize) {
|
||||
if (mRetrieveBlob) {
|
||||
return mRetrieveBlob(key, keySize, value, valueSize);
|
||||
std::shared_ptr<RetrieveBlobFunc> callback;
|
||||
{
|
||||
std::unique_lock<decltype(mMutex)> lock(mMutex);
|
||||
callback = mRetrieveBlob;
|
||||
}
|
||||
if (callback) {
|
||||
return (*callback)(key, keySize, value, valueSize);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Platform::setDebugUpdateStatFunc(DebugUpdateStatFunc&& debugUpdateStat) noexcept {
|
||||
mDebugUpdateStat = std::move(debugUpdateStat);
|
||||
std::lock_guard<decltype(mMutex)> lock(mMutex);
|
||||
mDebugUpdateStat = std::make_shared<DebugUpdateStatFunc>(std::move(debugUpdateStat));
|
||||
}
|
||||
|
||||
bool Platform::hasDebugUpdateStatFunc() const noexcept {
|
||||
return bool(mDebugUpdateStat);
|
||||
std::lock_guard<decltype(mMutex)> lock(mMutex);
|
||||
return mDebugUpdateStat != nullptr;
|
||||
}
|
||||
|
||||
void Platform::debugUpdateStat(const char* key, uint64_t value) {
|
||||
if (mDebugUpdateStat) {
|
||||
mDebugUpdateStat(key, value);
|
||||
void Platform::debugUpdateStat(const char* key, uint64_t intValue) {
|
||||
std::shared_ptr<DebugUpdateStatFunc> callback;
|
||||
{
|
||||
std::unique_lock<decltype(mMutex)> lock(mMutex);
|
||||
callback = mDebugUpdateStat;
|
||||
}
|
||||
if (callback) {
|
||||
(*callback)(key, intValue, "");
|
||||
}
|
||||
}
|
||||
|
||||
void Platform::debugUpdateStat(const char* key, utils::CString stringValue) {
|
||||
std::shared_ptr<DebugUpdateStatFunc> callback;
|
||||
{
|
||||
std::unique_lock<decltype(mMutex)> lock(mMutex);
|
||||
callback = mDebugUpdateStat;
|
||||
}
|
||||
if (callback) {
|
||||
(*callback)(key, 0, stringValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,8 +67,12 @@
|
||||
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
|
||||
#if defined(__ANDROID__)
|
||||
#include "backend/platforms/VulkanPlatformAndroid.h"
|
||||
#else
|
||||
#include "backend/platforms/VulkanPlatform.h"
|
||||
#elif defined(__APPLE__)
|
||||
#include "backend/platforms/VulkanPlatformApple.h"
|
||||
#elif defined(__linux__)
|
||||
#include "backend/platforms/VulkanPlatformLinux.h"
|
||||
#elif defined(WIN32)
|
||||
#include "backend/platforms/VulkanPlatformWindows.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -117,8 +121,14 @@ Platform* PlatformFactory::create(Backend* backend) noexcept {
|
||||
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
|
||||
#if defined(__ANDROID__)
|
||||
return new VulkanPlatformAndroid();
|
||||
#elif defined(__APPLE__)
|
||||
return new VulkanPlatformApple();
|
||||
#elif defined(__linux__)
|
||||
return new VulkanPlatformLinux();
|
||||
#elif defined(WIN32)
|
||||
return new VulkanPlatformWindows();
|
||||
#else
|
||||
return new VulkanPlatform();
|
||||
return nullptr;
|
||||
#endif
|
||||
#else
|
||||
return nullptr;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#ifndef TNT_METALCONTEXT_H
|
||||
#define TNT_METALCONTEXT_H
|
||||
|
||||
#include "MetalErrorQueue.h"
|
||||
#include "MetalResourceTracker.h"
|
||||
#include "MetalShaderCompiler.h"
|
||||
#include "MetalState.h"
|
||||
@@ -129,6 +130,7 @@ struct MetalContext {
|
||||
id<MTLCommandBuffer> pendingCommandBuffer = nil;
|
||||
id<MTLRenderCommandEncoder> currentRenderPassEncoder = nil;
|
||||
uint32_t currentFrame = 0;
|
||||
MetalErrorQueue commandBufferErrors;
|
||||
|
||||
std::atomic<bool> memorylessLimitsReached = false;
|
||||
|
||||
|
||||
@@ -95,6 +95,38 @@ void initializeSupportedGpuFamilies(MetalContext* context) {
|
||||
}
|
||||
}
|
||||
|
||||
void logMTLCommandBufferError(MTLCommandBufferError error) {
|
||||
#define MTL_COMMAND_ERROR_CASE(ERR) \
|
||||
if (error == (ERR)) { \
|
||||
LOG(ERROR) << "Filament Metal error: " #ERR "."; \
|
||||
return; \
|
||||
}
|
||||
|
||||
#if !defined(FILAMENT_IOS)
|
||||
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorDeviceRemoved)
|
||||
#endif
|
||||
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorNone)
|
||||
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorInternal)
|
||||
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorTimeout)
|
||||
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorPageFault)
|
||||
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorAccessRevoked)
|
||||
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorNotPermitted)
|
||||
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorOutOfMemory)
|
||||
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorInvalidResource)
|
||||
|
||||
if (@available(macOS 11.0, *)) {
|
||||
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorMemoryless)
|
||||
}
|
||||
|
||||
if (@available(iOS 15.0, macOS 12.0, *)) {
|
||||
MTL_COMMAND_ERROR_CASE(MTLCommandBufferErrorStackOverflow)
|
||||
}
|
||||
|
||||
LOG(ERROR) << "Filament Metal unknown error.";
|
||||
|
||||
#undef MTL_COMMAND_ERROR_CASE
|
||||
}
|
||||
|
||||
id<MTLCommandBuffer> getPendingCommandBuffer(MetalContext* context) {
|
||||
if (context->pendingCommandBuffer) {
|
||||
return context->pendingCommandBuffer;
|
||||
@@ -120,8 +152,8 @@ id<MTLCommandBuffer> getPendingCommandBuffer(MetalContext* context) {
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(errorCode != MTLCommandBufferErrorNone)) {
|
||||
LOG(ERROR) << "Filament Metal command buffer errored with code: " << errorCode << " ("
|
||||
<< stringifyMTLCommandBufferError(errorCode) << ").";
|
||||
logMTLCommandBufferError(errorCode);
|
||||
context->commandBufferErrors.push(buffer.error);
|
||||
}
|
||||
}];
|
||||
FILAMENT_CHECK_POSTCONDITION(context->pendingCommandBuffer)
|
||||
|
||||
@@ -254,6 +254,18 @@ MetalDriver::~MetalDriver() noexcept {
|
||||
void MetalDriver::tick(int) {
|
||||
executeTickOps();
|
||||
executeDeferredOps();
|
||||
|
||||
// Notify platform of GPU errors.
|
||||
auto& platform = mPlatform;
|
||||
if (UTILS_UNLIKELY(!mContext->commandBufferErrors.isEmpty())) {
|
||||
mContext->commandBufferErrors.flush([&platform](NSError* error) {
|
||||
if (UTILS_VERY_UNLIKELY(!error)) {
|
||||
return;
|
||||
}
|
||||
const utils::CString errorString(error.localizedDescription.UTF8String);
|
||||
platform.debugUpdateStat("filament.metal.command_buffer_error", errorString);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void MetalDriver::beginFrame(int64_t monotonic_clock_ns,
|
||||
@@ -621,7 +633,9 @@ void MetalDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
||||
auto& sc = mContext->sampleCountLookup;
|
||||
samples = sc[std::min(MAX_SAMPLE_COUNT, samples)];
|
||||
|
||||
MetalRenderTarget::Attachment colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {{}};
|
||||
using AttachmentInfo = MetalRenderTarget::AttachmentInfo;
|
||||
|
||||
AttachmentInfo colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = { {} };
|
||||
for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
|
||||
if (none(targetBufferFlags & getTargetBufferFlagsAt(i))) {
|
||||
continue;
|
||||
@@ -635,7 +649,7 @@ void MetalDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
||||
colorAttachments[i] = { colorTexture, color[i].level, color[i].layer };
|
||||
}
|
||||
|
||||
MetalRenderTarget::Attachment depthAttachment = {};
|
||||
AttachmentInfo depthAttachment = {};
|
||||
if (any(targetBufferFlags & TargetBufferFlags::DEPTH)) {
|
||||
FILAMENT_CHECK_PRECONDITION(depth.handle)
|
||||
<< "The DEPTH flag was specified, but invalid depth handle provided.";
|
||||
@@ -645,7 +659,7 @@ void MetalDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
||||
depthAttachment = { depthTexture, depth.level, depth.layer };
|
||||
}
|
||||
|
||||
MetalRenderTarget::Attachment stencilAttachment = {};
|
||||
AttachmentInfo stencilAttachment = {};
|
||||
if (any(targetBufferFlags & TargetBufferFlags::STENCIL)) {
|
||||
FILAMENT_CHECK_PRECONDITION(stencil.handle)
|
||||
<< "The STENCIL flag was specified, but invalid stencil handle provided.";
|
||||
@@ -669,8 +683,6 @@ void MetalDriver::createFenceR(Handle<HwFence> fh, utils::ImmutableCString&& tag
|
||||
|
||||
void MetalDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags,
|
||||
utils::ImmutableCString&& tag) {
|
||||
// TODO: support MSAA swapchain
|
||||
|
||||
if (UTILS_UNLIKELY(flags & SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER)) {
|
||||
CVPixelBufferRef pixelBuffer = (CVPixelBufferRef) nativeWindow;
|
||||
construct_handle<MetalSwapChain>(sch, *mContext, mPlatform, pixelBuffer, flags);
|
||||
@@ -1039,19 +1051,27 @@ void MetalDriver::updateStreams(DriverApi* driver) {
|
||||
|
||||
void MetalDriver::destroyFence(Handle<HwFence> fh) {
|
||||
if (fh) {
|
||||
// note: it's invalid to call this during a fenceWait(fh) on another thread. For this
|
||||
// reason there is no point signaling the waiters. There should be no waiters.
|
||||
destruct_handle<MetalFence>(fh);
|
||||
}
|
||||
}
|
||||
|
||||
void MetalDriver::fenceCancel(FenceHandle const fh) {
|
||||
// Even though this is a synchronous call, the fence handle must be (and stay) valid
|
||||
assert_invariant(fh);
|
||||
auto* fence = handle_cast<MetalFence>(fh);
|
||||
fence->cancel();
|
||||
}
|
||||
|
||||
FenceStatus MetalDriver::getFenceStatus(Handle<HwFence> fh) {
|
||||
return fenceWait(fh, 0);
|
||||
}
|
||||
|
||||
FenceStatus MetalDriver::fenceWait(FenceHandle fh, uint64_t const timeout) {
|
||||
// Even though this is a synchronous call, the fence handle must be (and stay) valid
|
||||
assert_invariant(fh);
|
||||
auto* fence = handle_cast<MetalFence>(fh);
|
||||
if (!fence) {
|
||||
return FenceStatus::ERROR;
|
||||
}
|
||||
return fence->wait(timeout);
|
||||
}
|
||||
|
||||
@@ -1150,8 +1170,7 @@ bool MetalDriver::isSRGBSwapChainSupported() {
|
||||
}
|
||||
|
||||
bool MetalDriver::isMSAASwapChainSupported(uint32_t) {
|
||||
// TODO: support MSAA swapchain
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetalDriver::isProtectedContentSupported() {
|
||||
@@ -1475,6 +1494,20 @@ void MetalDriver::setRenderPrimitiveBuffer(Handle<HwRenderPrimitive> rph, Primit
|
||||
primitive->type = pt;
|
||||
}
|
||||
|
||||
bool MetalDriver::isCompositorTimingSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MetalDriver::queryCompositorTiming(backend::SwapChainHandle swapChain,
|
||||
CompositorTiming* outCompositorTiming) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MetalDriver::queryFrameTimestamps(SwapChainHandle swapChain, uint64_t frameId,
|
||||
FrameTimestamps* outFrameTimestamps) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void MetalDriver::makeCurrent(Handle<HwSwapChain> schDraw, Handle<HwSwapChain> schRead) {
|
||||
ASSERT_PRECONDITION_NON_FATAL(schDraw, "A draw SwapChain must be set.");
|
||||
auto* drawSwapChain = handle_cast<MetalSwapChain>(schDraw);
|
||||
@@ -1561,9 +1594,9 @@ void MetalDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y,
|
||||
|
||||
auto srcTarget = handle_cast<MetalRenderTarget>(src);
|
||||
// We always readPixels from the COLOR0 attachment.
|
||||
MetalRenderTarget::Attachment color = srcTarget->getDrawColorAttachment(0);
|
||||
MetalAttachment color = srcTarget->getDrawColorAttachment(0);
|
||||
id<MTLTexture> srcTexture = color.getTexture();
|
||||
size_t miplevel = color.level;
|
||||
size_t miplevel = color.getLevel();
|
||||
|
||||
// Clamp height and width to actual texture's height and width
|
||||
MTLSize srcTextureSize = MTLSizeMake(srcTexture.width >> miplevel, srcTexture.height >> miplevel, 1);
|
||||
@@ -1771,8 +1804,8 @@ void MetalDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
};
|
||||
|
||||
// We always blit from/to the COLOR0 attachment.
|
||||
MetalRenderTarget::Attachment const srcColorAttachment = srcTarget->getReadColorAttachment(0);
|
||||
MetalRenderTarget::Attachment const dstColorAttachment = dstTarget->getDrawColorAttachment(0);
|
||||
MetalAttachment const srcColorAttachment = srcTarget->getReadColorAttachment(0);
|
||||
MetalAttachment const dstColorAttachment = dstTarget->getDrawColorAttachment(0);
|
||||
|
||||
if (srcColorAttachment && dstColorAttachment) {
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
@@ -1784,13 +1817,13 @@ void MetalDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
args.filter = filter;
|
||||
args.source.region = srcTarget->getRegionFromClientRect(srcRect);
|
||||
args.source.texture = srcColorAttachment.getTexture();
|
||||
args.source.level = srcColorAttachment.level;
|
||||
args.source.slice = srcColorAttachment.layer;
|
||||
args.source.level = srcColorAttachment.getLevel();
|
||||
args.source.slice = srcColorAttachment.getLayer();
|
||||
|
||||
args.destination.region = dstTarget->getRegionFromClientRect(dstRect);
|
||||
args.destination.texture = dstColorAttachment.getTexture();
|
||||
args.destination.level = dstColorAttachment.level;
|
||||
args.destination.slice = dstColorAttachment.layer;
|
||||
args.destination.level = dstColorAttachment.getLevel();
|
||||
args.destination.slice = dstColorAttachment.getLayer();
|
||||
|
||||
mContext->blitter->blit(getPendingCommandBuffer(mContext), args, "blitDEPRECATED");
|
||||
}
|
||||
@@ -1826,24 +1859,28 @@ void MetalDriver::bindPipeline(PipelineState const& ps) {
|
||||
auto [fragment, vertex] = functions.getRasterFunctions();
|
||||
|
||||
// Pipeline state
|
||||
MetalRenderTarget* const rt = mContext->currentRenderTarget;
|
||||
MTLPixelFormat colorPixelFormat[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = { MTLPixelFormatInvalid };
|
||||
for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
|
||||
const auto& attachment = mContext->currentRenderTarget->getDrawColorAttachment(i);
|
||||
const auto& attachment = rt->getDrawColorAttachment(i);
|
||||
if (!attachment) {
|
||||
continue;
|
||||
}
|
||||
colorPixelFormat[i] = attachment.getPixelFormat();
|
||||
assert_invariant(attachment.getSampleCount() == rt->getSampleCount());
|
||||
}
|
||||
MTLPixelFormat depthPixelFormat = MTLPixelFormatInvalid;
|
||||
const auto& depthAttachment = mContext->currentRenderTarget->getDepthAttachment();
|
||||
const auto& depthAttachment = rt->getDepthAttachment();
|
||||
if (depthAttachment) {
|
||||
depthPixelFormat = depthAttachment.getPixelFormat();
|
||||
assert_invariant(depthAttachment.getSampleCount() == rt->getSampleCount());
|
||||
}
|
||||
MTLPixelFormat stencilPixelFormat = MTLPixelFormatInvalid;
|
||||
const auto& stencilAttachment = mContext->currentRenderTarget->getStencilAttachment();
|
||||
const auto& stencilAttachment = rt->getStencilAttachment();
|
||||
if (stencilAttachment) {
|
||||
stencilPixelFormat = stencilAttachment.getPixelFormat();
|
||||
assert_invariant(isMetalFormatStencil(stencilPixelFormat));
|
||||
assert_invariant(stencilAttachment.getSampleCount() == rt->getSampleCount());
|
||||
}
|
||||
MetalPipelineState const pipelineState {
|
||||
.vertexFunction = vertex,
|
||||
@@ -1861,7 +1898,7 @@ void MetalDriver::bindPipeline(PipelineState const& ps) {
|
||||
},
|
||||
.depthAttachmentPixelFormat = depthPixelFormat,
|
||||
.stencilAttachmentPixelFormat = stencilPixelFormat,
|
||||
.sampleCount = mContext->currentRenderTarget->getSamples(),
|
||||
.sampleCount = rt->getSampleCount(),
|
||||
.blendState = BlendState {
|
||||
.alphaBlendOperation = getMetalBlendOperation(rs.blendEquationAlpha),
|
||||
.rgbBlendOperation = getMetalBlendOperation(rs.blendEquationRGB),
|
||||
|
||||
@@ -451,38 +451,6 @@ inline MTLTextureSwizzleChannels getSwizzleChannels(TextureSwizzle r, TextureSwi
|
||||
getSwizzle(a));
|
||||
}
|
||||
|
||||
inline const char* stringifyMTLCommandBufferError(MTLCommandBufferError error) {
|
||||
#if !defined(FILAMENT_IOS)
|
||||
if (error == MTLCommandBufferErrorDeviceRemoved) {
|
||||
return "MTLCommandBufferErrorDeviceRemoved";
|
||||
}
|
||||
#endif
|
||||
switch (error) {
|
||||
case MTLCommandBufferErrorNone:
|
||||
return "MTLCommandBufferErrorNone";
|
||||
case MTLCommandBufferErrorInternal:
|
||||
return "MTLCommandBufferErrorInternal";
|
||||
case MTLCommandBufferErrorTimeout:
|
||||
return "MTLCommandBufferErrorTimeout";
|
||||
case MTLCommandBufferErrorPageFault:
|
||||
return "MTLCommandBufferErrorPageFault";
|
||||
case MTLCommandBufferErrorAccessRevoked:
|
||||
return "MTLCommandBufferErrorAccessRevoked";
|
||||
case MTLCommandBufferErrorNotPermitted:
|
||||
return "MTLCommandBufferErrorNotPermitted";
|
||||
case MTLCommandBufferErrorOutOfMemory:
|
||||
return "MTLCommandBufferErrorOutOfMemory";
|
||||
case MTLCommandBufferErrorInvalidResource:
|
||||
return "MTLCommandBufferErrorInvalidResource";
|
||||
case MTLCommandBufferErrorMemoryless:
|
||||
return "MTLCommandBufferErrorMemoryless";
|
||||
case MTLCommandBufferErrorStackOverflow:
|
||||
return "MTLCommandBufferErrorStackOverflow";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace backend
|
||||
} // namespace filament
|
||||
|
||||
|
||||
66
filament/backend/src/metal/MetalErrorQueue.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_DRIVER_METALERRORQUEUE_H
|
||||
#define TNT_FILAMENT_DRIVER_METALERRORQUEUE_H
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
class MetalErrorQueue {
|
||||
public:
|
||||
bool isEmpty() const {
|
||||
return !mHasErrors.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void push(NSError* error) {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mErrors.push_back(error);
|
||||
mHasErrors.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void flush(const std::function<void(NSError*)>& callback) {
|
||||
if (UTILS_LIKELY(isEmpty())) {
|
||||
return;
|
||||
}
|
||||
std::vector<NSError*> errors;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
std::swap(mErrors, errors);
|
||||
mHasErrors.store(false, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
for (const auto& error: errors) {
|
||||
callback(error);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<NSError*> mErrors;
|
||||
std::mutex mMutex;
|
||||
|
||||
// Optimization to avoid locking the mutex at each call to flush.
|
||||
std::atomic<bool> mHasErrors;
|
||||
};
|
||||
|
||||
|
||||
#endif // TNT_FILAMENT_DRIVER_METALERRORQUEUE_H
|
||||
@@ -49,6 +49,61 @@
|
||||
namespace filament {
|
||||
namespace backend {
|
||||
|
||||
class MetalAttachment {
|
||||
public:
|
||||
MetalAttachment() = default;
|
||||
MetalAttachment(id<MTLTexture> texture, uint8_t level = 0, uint16_t layer = 0)
|
||||
: mLevel(level),
|
||||
mLayer(layer),
|
||||
mTexture(texture) {
|
||||
assert_invariant(texture);
|
||||
}
|
||||
|
||||
explicit operator bool() const { return mTexture != nil; }
|
||||
|
||||
id<MTLTexture> getTexture() const { return mTexture; }
|
||||
|
||||
id<MTLTexture> getMsaaTexture() const { return mMsaaTexture; }
|
||||
|
||||
MTLPixelFormat getPixelFormat() const {
|
||||
return mTexture ? mTexture.pixelFormat : MTLPixelFormatInvalid;
|
||||
}
|
||||
|
||||
NSUInteger getSampleCount() const {
|
||||
if (mMsaaTexture) {
|
||||
return mMsaaTexture.sampleCount;
|
||||
}
|
||||
if (mTexture) {
|
||||
return mTexture.sampleCount;
|
||||
}
|
||||
return 1u;
|
||||
}
|
||||
|
||||
uint8_t getLevel() const { return mLevel; }
|
||||
uint16_t getLayer() const { return mLayer; }
|
||||
|
||||
MetalAttachment withMsaaTexture(id<MTLTexture> msaa) const {
|
||||
assert_invariant(mTexture != nil);
|
||||
assert_invariant(mTexture.sampleCount == 1u);
|
||||
MetalAttachment result = *this;
|
||||
result.mMsaaTexture = msaa;
|
||||
return result;
|
||||
}
|
||||
|
||||
static MetalAttachment invalidAttachment() { return MetalAttachment(); }
|
||||
|
||||
private:
|
||||
uint8_t mLevel = 0;
|
||||
uint16_t mLayer = 0;
|
||||
|
||||
// The main texture for this Attachment. May be single-sampled or MSAA.
|
||||
id<MTLTexture> mTexture = nil;
|
||||
|
||||
// The MSAA "sidecar" texture that will resolve into mTexture.
|
||||
// If this is non-nil, mTexture must be single-sampled.
|
||||
id<MTLTexture> mMsaaTexture = nil;
|
||||
};
|
||||
|
||||
class MetalSwapChain : public HwSwapChain {
|
||||
public:
|
||||
|
||||
@@ -66,14 +121,14 @@ public:
|
||||
|
||||
~MetalSwapChain();
|
||||
|
||||
// Acquires a texture that can be used to render into this SwapChain.
|
||||
// The texture source depends on the type of SwapChain:
|
||||
// Acquires a texture that can be used to render into this SwapChain and returns a
|
||||
// MetalAttachment. The texture source depends on the type of SwapChain:
|
||||
// - CAMetalLayer-backed: acquires the CAMetalDrawable and returns its texture.
|
||||
// - Headless: lazily creates and returns a headless texture.
|
||||
id<MTLTexture> acquireDrawable();
|
||||
|
||||
id<MTLTexture> acquireDepthTexture();
|
||||
id<MTLTexture> acquireStencilTexture();
|
||||
// May return an invalid attachemnt if a drawable cannot be acquired.
|
||||
MetalAttachment acquireDrawable();
|
||||
MetalAttachment acquireDepthTexture();
|
||||
MetalAttachment acquireStencilTexture();
|
||||
|
||||
void releaseDrawable();
|
||||
|
||||
@@ -88,6 +143,7 @@ public:
|
||||
|
||||
NSUInteger getSurfaceWidth() const;
|
||||
NSUInteger getSurfaceHeight() const;
|
||||
NSUInteger getSampleCount() const;
|
||||
|
||||
bool isPixelBuffer() const { return type == SwapChainType::CVPIXELBUFFERREF; }
|
||||
|
||||
@@ -106,13 +162,24 @@ private:
|
||||
void scheduleFrameScheduledCallback();
|
||||
void scheduleFrameCompletedCallback();
|
||||
|
||||
MetalAttachment acquireBaseDrawable();
|
||||
|
||||
id<MTLTexture> ensureDepthStencilTexture(uint32_t width, uint32_t height);
|
||||
id<MTLTexture> ensureMsaaColorTexture(MTLPixelFormat format, uint32_t width, uint32_t height,
|
||||
uint8_t samples);
|
||||
id<MTLTexture> ensureMsaaDepthStencilTexture(MTLPixelFormat format, uint32_t width,
|
||||
uint32_t height, uint8_t samples);
|
||||
|
||||
static MTLPixelFormat decideDepthStencilFormat(uint64_t flags);
|
||||
void ensureDepthStencilTexture();
|
||||
static id<MTLTexture> createMultisampledTexture(MetalContext const& context,
|
||||
MTLPixelFormat format, uint32_t width, uint32_t height, uint8_t samples);
|
||||
|
||||
MetalContext& context;
|
||||
PlatformMetal& platform;
|
||||
id<CAMetalDrawable> drawable = nil;
|
||||
id<MTLTexture> depthStencilTexture = nil;
|
||||
id<MTLTexture> msaaColor = nil;
|
||||
id<MTLTexture> msaaDepthStencil = nil;
|
||||
id<MTLTexture> headlessDrawable = nil;
|
||||
MTLPixelFormat depthStencilFormat = MTLPixelFormatInvalid;
|
||||
NSUInteger headlessWidth = 0;
|
||||
@@ -121,6 +188,7 @@ private:
|
||||
std::shared_ptr<std::mutex> layerDrawableMutex;
|
||||
MetalExternalImage externalImage;
|
||||
SwapChainType type;
|
||||
uint64_t flags;
|
||||
|
||||
int64_t abandonedUntilFrame = -1;
|
||||
|
||||
@@ -307,51 +375,15 @@ private:
|
||||
class MetalRenderTarget : public HwRenderTarget {
|
||||
public:
|
||||
|
||||
class Attachment {
|
||||
public:
|
||||
|
||||
friend class MetalRenderTarget;
|
||||
|
||||
Attachment() = default;
|
||||
Attachment(MetalTexture* metalTexture, uint8_t level = 0, uint16_t layer = 0) :
|
||||
level(level), layer(layer),
|
||||
texture(metalTexture->getMtlTextureForWrite()),
|
||||
metalTexture(metalTexture) { }
|
||||
|
||||
id<MTLTexture> getTexture() const {
|
||||
return texture;
|
||||
}
|
||||
|
||||
NSUInteger getSampleCount() const {
|
||||
return texture ? texture.sampleCount : 0u;
|
||||
}
|
||||
|
||||
MTLPixelFormat getPixelFormat() const {
|
||||
return texture ? texture.pixelFormat : MTLPixelFormatInvalid;
|
||||
}
|
||||
|
||||
explicit operator bool() const {
|
||||
return texture != nil;
|
||||
}
|
||||
|
||||
struct AttachmentInfo {
|
||||
MetalTexture* texture = nullptr;
|
||||
uint8_t level = 0;
|
||||
uint16_t layer = 0;
|
||||
|
||||
private:
|
||||
|
||||
id<MTLTexture> getMSAASidecarTexture() const {
|
||||
// This should only be called from render targets associated with a MetalTexture.
|
||||
assert_invariant(metalTexture);
|
||||
return metalTexture->msaaSidecar;
|
||||
}
|
||||
|
||||
id<MTLTexture> texture = nil;
|
||||
MetalTexture* metalTexture = nullptr;
|
||||
};
|
||||
|
||||
MetalRenderTarget(MetalContext* context, uint32_t width, uint32_t height, uint8_t samples,
|
||||
Attachment colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
|
||||
Attachment depthAttachment, Attachment stencilAttachment);
|
||||
AttachmentInfo colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
|
||||
AttachmentInfo depthAttachment, AttachmentInfo stencilAttachment);
|
||||
explicit MetalRenderTarget(MetalContext* context)
|
||||
: HwRenderTarget(0, 0), context(context), defaultRenderTarget(true) {}
|
||||
|
||||
@@ -387,12 +419,14 @@ public:
|
||||
|
||||
math::uint2 getAttachmentSize() noexcept;
|
||||
|
||||
uint8_t getSamples() const { return samples; }
|
||||
MetalAttachment getDrawColorAttachment(size_t index);
|
||||
MetalAttachment getReadColorAttachment(size_t index);
|
||||
MetalAttachment getDepthAttachment();
|
||||
MetalAttachment getStencilAttachment();
|
||||
|
||||
Attachment getDrawColorAttachment(size_t index);
|
||||
Attachment getReadColorAttachment(size_t index);
|
||||
Attachment getDepthAttachment();
|
||||
Attachment getStencilAttachment();
|
||||
// Returns the number of samples that should be used in the pipeline state that renders to this
|
||||
// render target. Takes into account "automagic" resolve and MSAA SwapChains.
|
||||
NSUInteger getSampleCount() const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -402,12 +436,12 @@ private:
|
||||
|
||||
MetalContext* context;
|
||||
bool defaultRenderTarget = false;
|
||||
uint8_t samples = 1;
|
||||
|
||||
Attachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {};
|
||||
Attachment depth = {};
|
||||
Attachment stencil = {};
|
||||
MetalAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {};
|
||||
MetalAttachment depth = {};
|
||||
MetalAttachment stencil = {};
|
||||
math::uint2 attachmentSize = {};
|
||||
uint8_t samples = 1;
|
||||
};
|
||||
|
||||
// MetalFence is used to implement both Fences and Syncs.
|
||||
@@ -429,6 +463,8 @@ public:
|
||||
API_AVAILABLE(ios(12.0))
|
||||
void onSignal(MetalFenceSignalBlock block);
|
||||
|
||||
void cancel();
|
||||
|
||||
private:
|
||||
|
||||
MetalContext& context;
|
||||
|
||||
@@ -116,7 +116,8 @@ MetalSwapChain::MetalSwapChain(
|
||||
depthStencilFormat(decideDepthStencilFormat(flags)),
|
||||
layer(nativeWindow),
|
||||
layerDrawableMutex(std::make_shared<std::mutex>()),
|
||||
type(SwapChainType::CAMETALLAYER) {
|
||||
type(SwapChainType::CAMETALLAYER),
|
||||
flags(flags) {
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION([nativeWindow isKindOfClass:[CAMetalLayer class]])
|
||||
<< "nativeWindow pointer of class "
|
||||
@@ -147,7 +148,8 @@ MetalSwapChain::MetalSwapChain(MetalContext& context, PlatformMetal& platform, i
|
||||
depthStencilFormat(decideDepthStencilFormat(flags)),
|
||||
headlessWidth(width),
|
||||
headlessHeight(height),
|
||||
type(SwapChainType::HEADLESS) {}
|
||||
type(SwapChainType::HEADLESS),
|
||||
flags(flags) {}
|
||||
|
||||
MetalSwapChain::MetalSwapChain(MetalContext& context, PlatformMetal& platform,
|
||||
CVPixelBufferRef pixelBuffer, uint64_t flags)
|
||||
@@ -155,7 +157,8 @@ MetalSwapChain::MetalSwapChain(MetalContext& context, PlatformMetal& platform,
|
||||
platform(platform),
|
||||
depthStencilFormat(decideDepthStencilFormat(flags)),
|
||||
externalImage(MetalExternalImage::createFromImage(context, pixelBuffer)),
|
||||
type(SwapChainType::CVPIXELBUFFERREF) {
|
||||
type(SwapChainType::CVPIXELBUFFERREF),
|
||||
flags(flags) {
|
||||
assert_invariant(flags & SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER);
|
||||
MetalExternalImage::assertWritableImage(pixelBuffer);
|
||||
assert_invariant(externalImage.isValid());
|
||||
@@ -167,6 +170,27 @@ MTLPixelFormat MetalSwapChain::decideDepthStencilFormat(uint64_t flags) {
|
||||
: MTLPixelFormatDepth32Float;
|
||||
}
|
||||
|
||||
id<MTLTexture> MetalSwapChain::createMultisampledTexture(MetalContext const& context,
|
||||
MTLPixelFormat format, uint32_t width, uint32_t height, uint8_t samples) {
|
||||
MTLTextureDescriptor* descriptor =
|
||||
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:format
|
||||
width:width
|
||||
height:height
|
||||
mipmapped:NO];
|
||||
descriptor.textureType = MTLTextureType2DMultisample;
|
||||
descriptor.sampleCount = samples;
|
||||
descriptor.usage = MTLTextureUsageRenderTarget;
|
||||
descriptor.resourceOptions = MTLResourceStorageModePrivate;
|
||||
|
||||
if (context.supportsMemorylessRenderTargets) {
|
||||
if (@available(macOS 11.0, *)) {
|
||||
descriptor.resourceOptions = MTLResourceStorageModeMemoryless;
|
||||
}
|
||||
}
|
||||
|
||||
return [context.device newTextureWithDescriptor:descriptor];
|
||||
}
|
||||
|
||||
MetalSwapChain::~MetalSwapChain() {
|
||||
}
|
||||
|
||||
@@ -190,63 +214,16 @@ NSUInteger MetalSwapChain::getSurfaceHeight() const {
|
||||
return (NSUInteger) layer.drawableSize.height;
|
||||
}
|
||||
|
||||
bool MetalSwapChain::isAbandoned() const {
|
||||
return context.currentFrame < abandonedUntilFrame;
|
||||
|
||||
NSUInteger MetalSwapChain::getSampleCount() const {
|
||||
if (flags & flags & SwapChain::CONFIG_MSAA_4_SAMPLES) {
|
||||
return 4u;
|
||||
}
|
||||
return 1u;
|
||||
}
|
||||
|
||||
id<MTLTexture> MetalSwapChain::acquireDrawable() {
|
||||
if (drawable) {
|
||||
return drawable.texture;
|
||||
}
|
||||
|
||||
if (isHeadless()) {
|
||||
if (headlessDrawable) {
|
||||
return headlessDrawable;
|
||||
}
|
||||
// For headless surfaces we construct a "fake" drawable, which is simply a renderable
|
||||
// texture.
|
||||
MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor new];
|
||||
textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;
|
||||
textureDescriptor.width = headlessWidth;
|
||||
textureDescriptor.height = headlessHeight;
|
||||
// Specify MTLTextureUsageShaderRead so the headless surface can be blitted from.
|
||||
textureDescriptor.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
|
||||
#if defined(FILAMENT_IOS)
|
||||
textureDescriptor.storageMode = MTLStorageModeShared;
|
||||
#else
|
||||
textureDescriptor.storageMode = MTLStorageModeManaged;
|
||||
#endif
|
||||
headlessDrawable = [context.device newTextureWithDescriptor:textureDescriptor];
|
||||
return headlessDrawable;
|
||||
}
|
||||
|
||||
if (isPixelBuffer()) {
|
||||
return externalImage.getMtlTexture();
|
||||
}
|
||||
|
||||
assert_invariant(isCaMetalLayer());
|
||||
|
||||
// CAMetalLayer's drawable pool is not thread safe. Use a mutex when
|
||||
// calling -nextDrawable, or when releasing the last known reference
|
||||
// to any CAMetalDrawable returned from a previous -nextDrawable.
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*layerDrawableMutex);
|
||||
drawable = [layer nextDrawable];
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(drawable == nil)) {
|
||||
switch (platform.getDrawableFailureBehavior()) {
|
||||
case PlatformMetal::DrawableFailureBehavior::PANIC:
|
||||
FILAMENT_CHECK_POSTCONDITION(drawable != nil) << "Could not obtain drawable.";
|
||||
break;
|
||||
|
||||
case PlatformMetal::DrawableFailureBehavior::ABORT_FRAME:
|
||||
abandonedUntilFrame = context.currentFrame + 1;
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
return drawable.texture;
|
||||
bool MetalSwapChain::isAbandoned() const {
|
||||
return context.currentFrame < abandonedUntilFrame;
|
||||
}
|
||||
|
||||
void MetalSwapChain::releaseDrawable() {
|
||||
@@ -256,32 +233,45 @@ void MetalSwapChain::releaseDrawable() {
|
||||
}
|
||||
}
|
||||
|
||||
id<MTLTexture> MetalSwapChain::acquireDepthTexture() {
|
||||
ensureDepthStencilTexture();
|
||||
assert_invariant(depthStencilTexture);
|
||||
return depthStencilTexture;
|
||||
}
|
||||
|
||||
id<MTLTexture> MetalSwapChain::acquireStencilTexture() {
|
||||
if (!isMetalFormatStencil(depthStencilFormat)) {
|
||||
return nil;
|
||||
MetalAttachment MetalSwapChain::acquireDrawable() {
|
||||
MetalAttachment attachment = acquireBaseDrawable();
|
||||
if (auto texture = attachment.getTexture();
|
||||
texture && flags & SwapChain::CONFIG_MSAA_4_SAMPLES) {
|
||||
return attachment.withMsaaTexture(
|
||||
ensureMsaaColorTexture(texture.pixelFormat, texture.width, texture.height, 4u));
|
||||
}
|
||||
ensureDepthStencilTexture();
|
||||
assert_invariant(depthStencilTexture);
|
||||
return depthStencilTexture;
|
||||
return attachment;
|
||||
}
|
||||
|
||||
void MetalSwapChain::ensureDepthStencilTexture() {
|
||||
NSUInteger width = getSurfaceWidth();
|
||||
NSUInteger height = getSurfaceHeight();
|
||||
if (UTILS_LIKELY(depthStencilTexture)) {
|
||||
// If the surface size has changed, we'll need to allocate a new depth/stencil texture.
|
||||
if (UTILS_UNLIKELY(
|
||||
depthStencilTexture.width != width || depthStencilTexture.height != height)) {
|
||||
depthStencilTexture = nil;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
MetalAttachment MetalSwapChain::acquireDepthTexture() {
|
||||
MetalAttachment attachment =
|
||||
MetalAttachment(ensureDepthStencilTexture(getSurfaceWidth(), getSurfaceHeight()));
|
||||
if (auto texture = attachment.getTexture();
|
||||
texture && flags & SwapChain::CONFIG_MSAA_4_SAMPLES) {
|
||||
return attachment.withMsaaTexture(ensureMsaaDepthStencilTexture(texture.pixelFormat,
|
||||
texture.width, texture.height, 4u));
|
||||
}
|
||||
return attachment;
|
||||
}
|
||||
|
||||
MetalAttachment MetalSwapChain::acquireStencilTexture() {
|
||||
if (!(flags & SwapChain::CONFIG_HAS_STENCIL_BUFFER)) {
|
||||
return MetalAttachment::invalidAttachment();
|
||||
}
|
||||
MetalAttachment attachment =
|
||||
MetalAttachment(ensureDepthStencilTexture(getSurfaceWidth(), getSurfaceHeight()));
|
||||
if (auto texture = attachment.getTexture();
|
||||
texture && flags & SwapChain::CONFIG_MSAA_4_SAMPLES) {
|
||||
return attachment.withMsaaTexture(ensureMsaaDepthStencilTexture(texture.pixelFormat,
|
||||
texture.width, texture.height, 4u));
|
||||
}
|
||||
return attachment;
|
||||
}
|
||||
|
||||
id<MTLTexture> MetalSwapChain::ensureDepthStencilTexture(uint32_t width, uint32_t height) {
|
||||
if (UTILS_LIKELY(depthStencilTexture && depthStencilTexture.width == width &&
|
||||
depthStencilTexture.height == height)) {
|
||||
return depthStencilTexture;
|
||||
}
|
||||
MTLTextureDescriptor* descriptor =
|
||||
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:depthStencilFormat
|
||||
@@ -290,7 +280,26 @@ void MetalSwapChain::ensureDepthStencilTexture() {
|
||||
mipmapped:NO];
|
||||
descriptor.usage = MTLTextureUsageRenderTarget;
|
||||
descriptor.resourceOptions = MTLResourceStorageModePrivate;
|
||||
depthStencilTexture = [context.device newTextureWithDescriptor:descriptor];
|
||||
return depthStencilTexture = [context.device newTextureWithDescriptor:descriptor];
|
||||
}
|
||||
|
||||
id<MTLTexture> MetalSwapChain::ensureMsaaColorTexture(MTLPixelFormat format, uint32_t width, uint32_t height,
|
||||
uint8_t samples) {
|
||||
if (UTILS_LIKELY(msaaColor && msaaColor.pixelFormat == format && msaaColor.width == width &&
|
||||
msaaColor.height == height && msaaColor.sampleCount == samples)) {
|
||||
return msaaColor;
|
||||
}
|
||||
return msaaColor = createMultisampledTexture(context, format, width, height, samples);
|
||||
}
|
||||
|
||||
id<MTLTexture> MetalSwapChain::ensureMsaaDepthStencilTexture(MTLPixelFormat format, uint32_t width,
|
||||
uint32_t height, uint8_t samples) {
|
||||
if (UTILS_LIKELY(msaaDepthStencil && msaaDepthStencil.pixelFormat == format &&
|
||||
msaaDepthStencil.width == width && msaaDepthStencil.height == height &&
|
||||
msaaDepthStencil.sampleCount == samples)) {
|
||||
return msaaDepthStencil;
|
||||
}
|
||||
return msaaDepthStencil = createMultisampledTexture(context, format, width, height, samples);
|
||||
}
|
||||
|
||||
void MetalSwapChain::setFrameScheduledCallback(
|
||||
@@ -454,6 +463,62 @@ void MetalSwapChain::scheduleFrameCompletedCallback() {
|
||||
}];
|
||||
}
|
||||
|
||||
MetalAttachment MetalSwapChain::acquireBaseDrawable() {
|
||||
if (drawable) {
|
||||
return MetalAttachment(drawable.texture);
|
||||
}
|
||||
|
||||
if (isHeadless()) {
|
||||
if (headlessDrawable) {
|
||||
return MetalAttachment(headlessDrawable);
|
||||
}
|
||||
// For headless surfaces we construct a "fake" drawable, which is simply a renderable
|
||||
// texture.
|
||||
MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor new];
|
||||
textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;
|
||||
textureDescriptor.width = headlessWidth;
|
||||
textureDescriptor.height = headlessHeight;
|
||||
// Specify MTLTextureUsageShaderRead so the headless surface can be blitted from.
|
||||
textureDescriptor.usage = MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
|
||||
#if defined(FILAMENT_IOS)
|
||||
textureDescriptor.storageMode = MTLStorageModeShared;
|
||||
#else
|
||||
textureDescriptor.storageMode = MTLStorageModeManaged;
|
||||
#endif
|
||||
headlessDrawable = [context.device newTextureWithDescriptor:textureDescriptor];
|
||||
return MetalAttachment(headlessDrawable);
|
||||
}
|
||||
|
||||
if (isPixelBuffer()) {
|
||||
return MetalAttachment(externalImage.getMtlTexture());
|
||||
}
|
||||
|
||||
assert_invariant(isCaMetalLayer());
|
||||
|
||||
// CAMetalLayer's drawable pool is not thread safe. Use a mutex when
|
||||
// calling -nextDrawable, or when releasing the last known reference
|
||||
// to any CAMetalDrawable returned from a previous -nextDrawable.
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*layerDrawableMutex);
|
||||
drawable = [layer nextDrawable];
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(drawable == nil)) {
|
||||
switch (platform.getDrawableFailureBehavior()) {
|
||||
case PlatformMetal::DrawableFailureBehavior::PANIC:
|
||||
FILAMENT_CHECK_POSTCONDITION(drawable != nil) << "Could not obtain drawable.";
|
||||
break;
|
||||
|
||||
case PlatformMetal::DrawableFailureBehavior::ABORT_FRAME:
|
||||
abandonedUntilFrame = context.currentFrame + 1;
|
||||
return MetalAttachment::invalidAttachment();
|
||||
}
|
||||
}
|
||||
|
||||
return MetalAttachment(drawable.texture);
|
||||
}
|
||||
|
||||
|
||||
MetalBufferObject::MetalBufferObject(MetalContext& context, BufferObjectBinding bindingType,
|
||||
BufferUsage usage, uint32_t byteCount)
|
||||
: HwBufferObject(byteCount), buffer(context, bindingType, usage, byteCount) {}
|
||||
@@ -970,90 +1035,108 @@ void MetalTexture::loadWithBlit(uint32_t level, uint32_t slice, MTLRegion region
|
||||
}
|
||||
|
||||
MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint32_t height,
|
||||
uint8_t samples, Attachment colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
|
||||
Attachment depthAttachment, Attachment stencilAttachment) :
|
||||
HwRenderTarget(width, height), context(context), samples(samples) {
|
||||
math::uint2 tmin = {std::numeric_limits<uint32_t>::max()};
|
||||
UTILS_UNUSED_IN_RELEASE math::uint2 tmax = {0};
|
||||
uint8_t samples, AttachmentInfo colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
|
||||
AttachmentInfo depthAttachment, AttachmentInfo stencilAttachment)
|
||||
: HwRenderTarget(width, height),
|
||||
context(context),
|
||||
samples(samples) {
|
||||
math::uint2 tmin = { std::numeric_limits<uint32_t>::max() };
|
||||
UTILS_UNUSED_IN_RELEASE math::uint2 tmax = { 0 };
|
||||
UTILS_UNUSED_IN_RELEASE size_t attachmentCount = 0;
|
||||
|
||||
for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
|
||||
if (!colorAttachments[i]) {
|
||||
MetalTexture* const texture = colorAttachments[i].texture;
|
||||
const auto& level = colorAttachments[i].level;
|
||||
const auto& layer = colorAttachments[i].layer;
|
||||
if (!texture) {
|
||||
continue;
|
||||
}
|
||||
color[i] = colorAttachments[i];
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(color[i].getSampleCount() <= samples)
|
||||
FILAMENT_CHECK_PRECONDITION(texture->samples <= samples)
|
||||
<< "MetalRenderTarget was initialized with a MSAA COLOR" << i
|
||||
<< " texture, but sample count is " << samples << ".";
|
||||
|
||||
auto t = color[i].metalTexture;
|
||||
const auto twidth = std::max(1u, t->width >> color[i].level);
|
||||
const auto theight = std::max(1u, t->height >> color[i].level);
|
||||
const auto twidth = std::max(1u, texture->width >> level);
|
||||
const auto theight = std::max(1u, texture->height >> level);
|
||||
tmin = { std::min(tmin.x, twidth), std::min(tmin.y, theight) };
|
||||
tmax = { std::max(tmax.x, twidth), std::max(tmax.y, theight) };
|
||||
attachmentCount++;
|
||||
|
||||
id<MTLTexture> mtlTexture = texture->getMtlTextureForWrite();
|
||||
color[i] = MetalAttachment(mtlTexture, level, layer);
|
||||
|
||||
// If we were given a single-sampled texture but the samples parameter is > 1, we create
|
||||
// a multisampled sidecar texture and do a resolve automatically.
|
||||
if (samples > 1 && color[i].getSampleCount() == 1) {
|
||||
auto& sidecar = color[i].metalTexture->msaaSidecar;
|
||||
if (samples > 1 && texture->samples == 1) {
|
||||
auto& sidecar = texture->msaaSidecar;
|
||||
if (!sidecar) {
|
||||
sidecar = createMultisampledTexture(color[i].getPixelFormat(),
|
||||
color[i].metalTexture->width, color[i].metalTexture->height, samples);
|
||||
sidecar = createMultisampledTexture(mtlTexture.pixelFormat, texture->width,
|
||||
texture->height, samples);
|
||||
}
|
||||
color[i] = MetalAttachment(mtlTexture, level, layer).withMsaaTexture(sidecar);
|
||||
}
|
||||
}
|
||||
|
||||
if (depthAttachment) {
|
||||
depth = depthAttachment;
|
||||
if (depthAttachment.texture) {
|
||||
MetalTexture* const texture = depthAttachment.texture;
|
||||
const auto& level = depthAttachment.level;
|
||||
const auto& layer = depthAttachment.layer;
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(depth.getSampleCount() <= samples)
|
||||
FILAMENT_CHECK_PRECONDITION(texture->samples <= samples)
|
||||
<< "MetalRenderTarget was initialized with a MSAA DEPTH texture, but sample count "
|
||||
"is "
|
||||
<< samples << ".";
|
||||
|
||||
auto t = depth.metalTexture;
|
||||
const auto twidth = std::max(1u, t->width >> depth.level);
|
||||
const auto theight = std::max(1u, t->height >> depth.level);
|
||||
const auto twidth = std::max(1u, texture->width >> level);
|
||||
const auto theight = std::max(1u, texture->height >> level);
|
||||
tmin = { math::min(tmin.x, twidth), math::min(tmin.y, theight) };
|
||||
tmax = { math::max(tmax.x, twidth), math::max(tmax.y, theight) };
|
||||
attachmentCount++;
|
||||
|
||||
id<MTLTexture> mtlTexture = texture->getMtlTextureForWrite();
|
||||
depth = MetalAttachment(mtlTexture, level, layer);
|
||||
|
||||
// If we were given a single-sampled texture but the samples parameter is > 1, we create
|
||||
// a multisampled sidecar texture and do a resolve automatically.
|
||||
if (samples > 1 && depth.getSampleCount() == 1) {
|
||||
auto& sidecar = depth.metalTexture->msaaSidecar;
|
||||
if (samples > 1 && texture->samples == 1) {
|
||||
auto& sidecar = texture->msaaSidecar;
|
||||
if (!sidecar) {
|
||||
sidecar = createMultisampledTexture(depth.getPixelFormat(),
|
||||
depth.metalTexture->width, depth.metalTexture->height, samples);
|
||||
sidecar = createMultisampledTexture(mtlTexture.pixelFormat, texture->width,
|
||||
texture->height, samples);
|
||||
}
|
||||
depth = MetalAttachment(mtlTexture, level, layer).withMsaaTexture(sidecar);
|
||||
}
|
||||
}
|
||||
|
||||
if (stencilAttachment) {
|
||||
stencil = stencilAttachment;
|
||||
if (stencilAttachment.texture) {
|
||||
// stencil = stencilAttachment;
|
||||
MetalTexture* const texture = stencilAttachment.texture;
|
||||
const auto& level = stencilAttachment.level;
|
||||
const auto& layer = stencilAttachment.layer;
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(stencil.getSampleCount() <= samples)
|
||||
FILAMENT_CHECK_PRECONDITION(texture->samples <= samples)
|
||||
<< "MetalRenderTarget was initialized with a MSAA STENCIL texture, but sample "
|
||||
"count is "
|
||||
<< samples << ".";
|
||||
|
||||
auto t = stencil.metalTexture;
|
||||
const auto twidth = std::max(1u, t->width >> stencil.level);
|
||||
const auto theight = std::max(1u, t->height >> stencil.level);
|
||||
const auto twidth = std::max(1u, texture->width >> level);
|
||||
const auto theight = std::max(1u, texture->height >> level);
|
||||
tmin = { math::min(tmin.x, twidth), math::min(tmin.y, theight) };
|
||||
tmax = { math::max(tmax.x, twidth), math::max(tmax.y, theight) };
|
||||
attachmentCount++;
|
||||
|
||||
id<MTLTexture> mtlTexture = texture->getMtlTextureForWrite();
|
||||
stencil = MetalAttachment(mtlTexture, level, layer);
|
||||
|
||||
// If we were given a single-sampled texture but the samples parameter is > 1, we create
|
||||
// a multisampled sidecar texture and do a resolve automatically.
|
||||
if (samples > 1 && stencil.getSampleCount() == 1) {
|
||||
auto& sidecar = stencil.metalTexture->msaaSidecar;
|
||||
if (samples > 1 && texture->samples == 1) {
|
||||
auto& sidecar = texture->msaaSidecar;
|
||||
if (!sidecar) {
|
||||
sidecar = createMultisampledTexture(stencil.getPixelFormat(),
|
||||
stencil.metalTexture->width, stencil.metalTexture->height, samples);
|
||||
sidecar = createMultisampledTexture(mtlTexture.pixelFormat, texture->width,
|
||||
texture->height, samples);
|
||||
}
|
||||
stencil = MetalAttachment(mtlTexture, level, layer).withMsaaTexture(sidecar);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1076,105 +1159,105 @@ void MetalRenderTarget::setUpRenderPassAttachments(MTLRenderPassDescriptor* desc
|
||||
const auto discardFlags = params.flags.discardEnd;
|
||||
|
||||
for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
|
||||
Attachment attachment = getDrawColorAttachment(i);
|
||||
MetalAttachment attachment = getDrawColorAttachment(i);
|
||||
if (!attachment) {
|
||||
continue;
|
||||
}
|
||||
|
||||
descriptor.colorAttachments[i].texture = attachment.getTexture();
|
||||
descriptor.colorAttachments[i].level = attachment.level;
|
||||
descriptor.colorAttachments[i].slice = attachment.layer;
|
||||
descriptor.colorAttachments[i].level = attachment.getLevel();
|
||||
descriptor.colorAttachments[i].slice = attachment.getLayer();
|
||||
descriptor.colorAttachments[i].loadAction = getLoadAction(params, getTargetBufferFlagsAt(i));
|
||||
descriptor.colorAttachments[i].storeAction = getStoreAction(params,
|
||||
getTargetBufferFlagsAt(i));
|
||||
descriptor.colorAttachments[i].clearColor = MTLClearColorMake(
|
||||
params.clearColor.r, params.clearColor.g, params.clearColor.b, params.clearColor.a);
|
||||
|
||||
const bool automaticResolve = samples > 1 && attachment.getSampleCount() == 1;
|
||||
if (automaticResolve) {
|
||||
if (attachment.getMsaaTexture()) {
|
||||
// Check that the loadAction is valid for MSAA targets: either DontCare or Clear.
|
||||
// We're rendering into our temporary MSAA texture and doing an automatic resolve.
|
||||
// We should not be attempting to load anything into the MSAA texture.
|
||||
assert_invariant(descriptor.colorAttachments[i].loadAction != MTLLoadActionLoad);
|
||||
assert_invariant(!defaultRenderTarget);
|
||||
// This might happen if the user is rendering multiple views into a MSAA SwapChain,
|
||||
// which is not supported. In that case, ignore the load action.
|
||||
if (descriptor.colorAttachments[i].loadAction == MTLLoadActionLoad) {
|
||||
descriptor.colorAttachments[i].loadAction = MTLLoadActionDontCare;
|
||||
}
|
||||
|
||||
id<MTLTexture> sidecar = attachment.getMSAASidecarTexture();
|
||||
assert_invariant(sidecar);
|
||||
|
||||
descriptor.colorAttachments[i].texture = sidecar;
|
||||
descriptor.colorAttachments[i].texture = attachment.getMsaaTexture();
|
||||
descriptor.colorAttachments[i].level = 0;
|
||||
descriptor.colorAttachments[i].slice = 0;
|
||||
const bool discard = any(discardFlags & getTargetBufferFlagsAt(i));
|
||||
if (!discard) {
|
||||
descriptor.colorAttachments[i].resolveTexture = attachment.texture;
|
||||
descriptor.colorAttachments[i].resolveLevel = attachment.level;
|
||||
descriptor.colorAttachments[i].resolveSlice = attachment.layer;
|
||||
descriptor.colorAttachments[i].resolveTexture = attachment.getTexture();
|
||||
descriptor.colorAttachments[i].resolveLevel = attachment.getLevel();
|
||||
descriptor.colorAttachments[i].resolveSlice = attachment.getLayer();
|
||||
descriptor.colorAttachments[i].storeAction = MTLStoreActionMultisampleResolve;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Attachment depthAttachment = getDepthAttachment();
|
||||
MetalAttachment depthAttachment = getDepthAttachment();
|
||||
if (depthAttachment) {
|
||||
descriptor.depthAttachment.texture = depthAttachment.getTexture();
|
||||
descriptor.depthAttachment.level = depthAttachment.level;
|
||||
descriptor.depthAttachment.slice = depthAttachment.layer;
|
||||
descriptor.depthAttachment.level = depthAttachment.getLevel();
|
||||
descriptor.depthAttachment.slice = depthAttachment.getLayer();
|
||||
descriptor.depthAttachment.loadAction = getLoadAction(params, TargetBufferFlags::DEPTH);
|
||||
descriptor.depthAttachment.storeAction = getStoreAction(params, TargetBufferFlags::DEPTH);
|
||||
descriptor.depthAttachment.clearDepth = params.clearDepth;
|
||||
}
|
||||
|
||||
const bool depthAutomaticResolve = samples > 1 && depthAttachment.getSampleCount() == 1;
|
||||
if (depthAutomaticResolve) {
|
||||
if (depthAttachment.getMsaaTexture()) {
|
||||
// Check that the loadAction is valid for MSAA targets: either DontCare or Clear.
|
||||
// We're rendering into our temporary MSAA texture and doing an automatic resolve.
|
||||
// We should not be attempting to load anything into the MSAA texture.
|
||||
assert_invariant(descriptor.depthAttachment.loadAction != MTLLoadActionLoad);
|
||||
assert_invariant(!defaultRenderTarget);
|
||||
// This might happen if the user is rendering multiple views into a MSAA SwapChain,
|
||||
// which is not supported. In that case, ignore the load action.
|
||||
if (descriptor.depthAttachment.loadAction == MTLLoadActionLoad) {
|
||||
descriptor.depthAttachment.loadAction = MTLLoadActionDontCare;
|
||||
}
|
||||
|
||||
id<MTLTexture> sidecar = depthAttachment.getMSAASidecarTexture();
|
||||
assert_invariant(sidecar);
|
||||
|
||||
descriptor.depthAttachment.texture = sidecar;
|
||||
descriptor.depthAttachment.texture = depthAttachment.getMsaaTexture();
|
||||
descriptor.depthAttachment.level = 0;
|
||||
descriptor.depthAttachment.slice = 0;
|
||||
const bool discard = any(discardFlags & TargetBufferFlags::DEPTH);
|
||||
if (!discard) {
|
||||
assert_invariant(context->supportsAutoDepthResolve);
|
||||
descriptor.depthAttachment.resolveTexture = depthAttachment.getTexture();
|
||||
descriptor.depthAttachment.resolveLevel = depthAttachment.level;
|
||||
descriptor.depthAttachment.resolveSlice = depthAttachment.layer;
|
||||
descriptor.depthAttachment.resolveLevel = depthAttachment.getLevel();
|
||||
descriptor.depthAttachment.resolveSlice = depthAttachment.getLayer();
|
||||
descriptor.depthAttachment.storeAction = MTLStoreActionMultisampleResolve;
|
||||
}
|
||||
}
|
||||
|
||||
Attachment stencilAttachment = getStencilAttachment();
|
||||
MetalAttachment stencilAttachment = getStencilAttachment();
|
||||
if (stencilAttachment) {
|
||||
descriptor.stencilAttachment.texture = stencilAttachment.getTexture();
|
||||
descriptor.stencilAttachment.level = stencilAttachment.level;
|
||||
descriptor.stencilAttachment.slice = stencilAttachment.layer;
|
||||
descriptor.stencilAttachment.level = stencilAttachment.getLevel();
|
||||
descriptor.stencilAttachment.slice = stencilAttachment.getLayer();
|
||||
descriptor.stencilAttachment.loadAction = getLoadAction(params, TargetBufferFlags::STENCIL);
|
||||
descriptor.stencilAttachment.storeAction = getStoreAction(params, TargetBufferFlags::STENCIL);
|
||||
descriptor.stencilAttachment.clearStencil = params.clearStencil;
|
||||
}
|
||||
|
||||
const bool stencilAutomaticResolve = samples > 1 && stencilAttachment.getSampleCount() == 1;
|
||||
if (stencilAutomaticResolve) {
|
||||
if (stencilAttachment.getMsaaTexture()) {
|
||||
// Check that the loadAction is valid for MSAA targets: either DontCare or Clear.
|
||||
// We're rendering into our temporary MSAA texture and doing an automatic resolve.
|
||||
// We should not be attempting to load anything into the MSAA texture.
|
||||
assert_invariant(descriptor.stencilAttachment.loadAction != MTLLoadActionLoad);
|
||||
assert_invariant(!defaultRenderTarget);
|
||||
// This might happen if the user is rendering multiple views into a MSAA SwapChain,
|
||||
// which is not supported. In that case, ignore the load action.
|
||||
if (descriptor.stencilAttachment.loadAction == MTLLoadActionLoad) {
|
||||
descriptor.stencilAttachment.loadAction = MTLLoadActionDontCare;
|
||||
}
|
||||
|
||||
id<MTLTexture> sidecar = stencilAttachment.getMSAASidecarTexture();
|
||||
assert_invariant(sidecar);
|
||||
|
||||
descriptor.stencilAttachment.texture = sidecar;
|
||||
descriptor.stencilAttachment.texture = stencilAttachment.getMsaaTexture();
|
||||
descriptor.stencilAttachment.level = 0;
|
||||
descriptor.stencilAttachment.slice = 0;
|
||||
const bool discard = any(discardFlags & TargetBufferFlags::STENCIL);
|
||||
if (!discard) {
|
||||
assert_invariant(context->supportsAutoDepthResolve);
|
||||
descriptor.stencilAttachment.resolveTexture = stencilAttachment.getTexture();
|
||||
descriptor.stencilAttachment.resolveLevel = stencilAttachment.level;
|
||||
descriptor.stencilAttachment.resolveSlice = stencilAttachment.layer;
|
||||
descriptor.stencilAttachment.resolveLevel = stencilAttachment.getLevel();
|
||||
descriptor.stencilAttachment.resolveSlice = stencilAttachment.getLayer();
|
||||
descriptor.stencilAttachment.storeAction = MTLStoreActionMultisampleResolve;
|
||||
if (@available(iOS 12.0, *)) {
|
||||
descriptor.stencilAttachment.stencilResolveFilter = MTLMultisampleStencilResolveFilterSample0;
|
||||
@@ -1189,44 +1272,46 @@ bool MetalRenderTarget::involvesAbandonedSwapChain() const noexcept {
|
||||
return (draw && draw->isAbandoned()) || (read && read->isAbandoned());
|
||||
}
|
||||
|
||||
MetalRenderTarget::Attachment MetalRenderTarget::getDrawColorAttachment(size_t index) {
|
||||
MetalAttachment MetalRenderTarget::getDrawColorAttachment(size_t index) {
|
||||
assert_invariant(index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
|
||||
Attachment result = color[index];
|
||||
if (index == 0 && defaultRenderTarget) {
|
||||
assert_invariant(context->currentDrawSwapChain);
|
||||
result.texture = context->currentDrawSwapChain->acquireDrawable();
|
||||
// acquireDrawable may fail to acquire the drawable, in which case result.texture will be
|
||||
// nil, and an invalid attachment
|
||||
return context->currentDrawSwapChain->acquireDrawable();
|
||||
}
|
||||
return result;
|
||||
return color[index];
|
||||
}
|
||||
|
||||
MetalRenderTarget::Attachment MetalRenderTarget::getReadColorAttachment(size_t index) {
|
||||
MetalAttachment MetalRenderTarget::getReadColorAttachment(size_t index) {
|
||||
assert_invariant(index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
|
||||
Attachment result = color[index];
|
||||
if (index == 0 && defaultRenderTarget) {
|
||||
assert_invariant(context->currentReadSwapChain);
|
||||
result.texture = context->currentReadSwapChain->acquireDrawable();
|
||||
// acquireDrawable may fail to acquire the drawable, in which case result.texture will be
|
||||
// nil, and an invalid attachment
|
||||
return context->currentReadSwapChain->acquireDrawable();
|
||||
}
|
||||
return result;
|
||||
return color[index];
|
||||
}
|
||||
|
||||
MetalRenderTarget::Attachment MetalRenderTarget::getDepthAttachment() {
|
||||
Attachment result = depth;
|
||||
MetalAttachment MetalRenderTarget::getDepthAttachment() {
|
||||
if (defaultRenderTarget) {
|
||||
result.texture = context->currentDrawSwapChain->acquireDepthTexture();
|
||||
assert_invariant(context->currentDrawSwapChain);
|
||||
return context->currentDrawSwapChain->acquireDepthTexture();
|
||||
}
|
||||
return result;
|
||||
return depth;
|
||||
}
|
||||
|
||||
MetalRenderTarget::Attachment MetalRenderTarget::getStencilAttachment() {
|
||||
Attachment result = stencil;
|
||||
MetalAttachment MetalRenderTarget::getStencilAttachment() {
|
||||
if (defaultRenderTarget) {
|
||||
result.texture = context->currentDrawSwapChain->acquireStencilTexture();
|
||||
assert_invariant(context->currentDrawSwapChain);
|
||||
return context->currentDrawSwapChain->acquireStencilTexture();
|
||||
}
|
||||
return result;
|
||||
return stencil;
|
||||
}
|
||||
|
||||
NSUInteger MetalRenderTarget::getSampleCount() const {
|
||||
if (defaultRenderTarget) {
|
||||
assert_invariant(context->currentDrawSwapChain);
|
||||
return context->currentDrawSwapChain->getSampleCount();
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
MTLLoadAction MetalRenderTarget::getLoadAction(const RenderPassParams& params,
|
||||
@@ -1316,14 +1401,20 @@ FenceStatus MetalFence::wait(uint64_t timeoutNs) {
|
||||
state->cv.wait(guard);
|
||||
} else if (timeoutNs == 0 ||
|
||||
state->cv.wait_for(guard, ns(timeoutNs)) == std::cv_status::timeout) {
|
||||
return FenceStatus::TIMEOUT_EXPIRED;
|
||||
return state->status;
|
||||
}
|
||||
}
|
||||
return FenceStatus::CONDITION_SATISFIED;
|
||||
return state->status;
|
||||
}
|
||||
return FenceStatus::ERROR;
|
||||
}
|
||||
|
||||
void MetalFence::cancel() {
|
||||
std::unique_lock guard(state->mutex);
|
||||
state->status = FenceStatus::ERROR;
|
||||
state->cv.notify_all();
|
||||
}
|
||||
|
||||
MetalDescriptorSetLayout::MetalDescriptorSetLayout(DescriptorSetLayout&& l) noexcept
|
||||
: mLayout(std::move(l)) {
|
||||
size_t dynamicBindings = 0;
|
||||
|
||||
@@ -166,6 +166,9 @@ void NoopDriver::getPlatformSync(Handle<HwSync> sh, CallbackHandler* handler,
|
||||
void NoopDriver::destroyFence(Handle<HwFence> fh) {
|
||||
}
|
||||
|
||||
void NoopDriver::fenceCancel(FenceHandle fh) {
|
||||
}
|
||||
|
||||
FenceStatus NoopDriver::getFenceStatus(Handle<HwFence> fh) {
|
||||
return FenceStatus::CONDITION_SATISFIED;
|
||||
}
|
||||
@@ -450,4 +453,18 @@ void NoopDriver::copyToMemoryMappedBuffer(MemoryMappedBufferHandle mmbh, size_t
|
||||
BufferDescriptor&& data) {
|
||||
}
|
||||
|
||||
bool NoopDriver::isCompositorTimingSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NoopDriver::queryCompositorTiming(backend::SwapChainHandle swapChain,
|
||||
backend::CompositorTiming* outCompositorTiming) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NoopDriver::queryFrameTimestamps(SwapChainHandle swapChain, uint64_t frameId,
|
||||
FrameTimestamps* outFrameTimestamps) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -303,13 +303,10 @@ void GLDescriptorSet::bind(
|
||||
offset += offsets[dynamicOffsetIndex++];
|
||||
}
|
||||
if (arg.bo) {
|
||||
auto buffer = static_cast<char const*>(arg.bo->gl.buffer) + offset;
|
||||
p.updateUniforms(bindingPoint, arg.bo->gl.id, buffer, arg.bo->age);
|
||||
p.updateUniforms(bindingPoint, arg.bo->gl.id, arg.bo->gl.buffer, arg.bo->age, offset);
|
||||
}
|
||||
} else if constexpr (std::is_same_v<T, Sampler>) {
|
||||
GLuint const unit = p.getTextureUnit(set, binding);
|
||||
|
||||
|
||||
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);
|
||||
|
||||
@@ -807,8 +807,9 @@ void OpenGLDriver::textureStorage(GLTexture* t,
|
||||
for (GLint face = 0 ; face < 6 ; face++) {
|
||||
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
|
||||
level, GLint(t->gl.internalFormat),
|
||||
GLsizei(width), GLsizei(height), 0,
|
||||
format, type, nullptr);
|
||||
std::max(GLsizei(1), GLsizei(width >> level)),
|
||||
std::max(GLsizei(1), GLsizei(height >> level)),
|
||||
0, format, type, nullptr);
|
||||
}
|
||||
} else {
|
||||
glTexImage2D(t->gl.target, level, GLint(t->gl.internalFormat),
|
||||
@@ -1787,7 +1788,7 @@ void OpenGLDriver::createFenceR(Handle<HwFence> fh, ImmutableCString&& tag) {
|
||||
|
||||
mHandleAllocator.associateTagToHandle(fh.getId(), std::move(tag));
|
||||
|
||||
GLFence* f = handle_cast<GLFence*>(fh);
|
||||
GLFence* const f = handle_cast<GLFence*>(fh);
|
||||
assert_invariant(f->state);
|
||||
|
||||
bool const platformCanCreateFence = mPlatform.canCreateFence();
|
||||
@@ -1803,8 +1804,9 @@ void OpenGLDriver::createFenceR(Handle<HwFence> fh, ImmutableCString&& tag) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This is the case where we need to use OpenGL fences
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
// This is the case where we need to use OpenGL fences, as soon as we return, the user
|
||||
// is allowed to destroy the fence, so we need to keep a reference to the internal state.
|
||||
std::weak_ptr<GLFence::State> const weak = f->state;
|
||||
whenGpuCommandsComplete([weak] {
|
||||
if (auto const state = weak.lock()) {
|
||||
@@ -2287,74 +2289,84 @@ mat3f OpenGLDriver::getStreamTransformMatrix(Handle<HwStream> sh) {
|
||||
|
||||
void OpenGLDriver::destroyFence(Handle<HwFence> fh) {
|
||||
if (fh) {
|
||||
GLFence const* f = handle_cast<GLFence*>(fh);
|
||||
GLFence const* const f = handle_cast<GLFence*>(fh);
|
||||
if (mPlatform.canCreateFence() || mContext.isES2()) {
|
||||
mPlatform.destroyFence(f->fence);
|
||||
}
|
||||
// note: it's invalid to call this during a fenceWait(fh) on another thread. For this
|
||||
// reason there is no point signaling the waiters. There should be no waiters.
|
||||
destruct(fh, f);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDriver::fenceCancel(FenceHandle fh) {
|
||||
// Even though this is a synchronous call, the fence handle must be (and stay) valid
|
||||
assert_invariant(fh);
|
||||
GLFence const* const f = handle_cast<GLFence*>(fh);
|
||||
assert_invariant(f->state);
|
||||
|
||||
std::lock_guard const lock(f->state->lock);
|
||||
f->state->status = FenceStatus::ERROR;
|
||||
f->state->cond.notify_all();
|
||||
}
|
||||
|
||||
FenceStatus OpenGLDriver::getFenceStatus(Handle<HwFence> fh) {
|
||||
return fenceWait(fh, 0);
|
||||
}
|
||||
|
||||
FenceStatus OpenGLDriver::fenceWait(FenceHandle fh, uint64_t const timeout) {
|
||||
if (fh) {
|
||||
// we have to take into account that the STL's wait_for() actually works with
|
||||
// time_points relative to steady_clock::now() internally.
|
||||
using namespace std::chrono;
|
||||
auto const now = steady_clock::now();
|
||||
steady_clock::time_point until = steady_clock::time_point::max();
|
||||
if (now <= steady_clock::time_point::max() - nanoseconds(timeout)) {
|
||||
until = now + nanoseconds(timeout);
|
||||
}
|
||||
// Even though this is a synchronous call, the fence handle must be (and stay) valid
|
||||
assert_invariant(fh);
|
||||
GLFence const* const f = handle_cast<GLFence*>(fh);
|
||||
assert_invariant(f->state);
|
||||
|
||||
GLFence const* f = handle_cast<GLFence*>(fh);
|
||||
assert_invariant(f->state);
|
||||
|
||||
bool const platformCanCreateFence = mPlatform.canCreateFence();
|
||||
|
||||
// immediately acquire a reference on our state, so that things don't go south if
|
||||
// the HwFence gets destroyed (on the main thread) while we wait.
|
||||
std::shared_ptr const state{ f->state };
|
||||
|
||||
if (mContext.isES2() || platformCanCreateFence) {
|
||||
if (platformCanCreateFence) {
|
||||
std::unique_lock lock(state->lock);
|
||||
if (f->fence == nullptr) {
|
||||
// we've been called before the fence was created asynchronously,
|
||||
// so we need to wait for that, before using the real fence.
|
||||
// By construction, "f" can't be destroyed while we wait, because its
|
||||
// construction call is in the queue and a destroy call will have to come later.
|
||||
state->cond.wait_until(lock, until, [f] {
|
||||
return f->fence != nullptr;
|
||||
});
|
||||
if (f->fence == nullptr) {
|
||||
// the only possible choice here is that we timed out
|
||||
assert_invariant(state->status == FenceStatus::TIMEOUT_EXPIRED);
|
||||
return FenceStatus::TIMEOUT_EXPIRED;
|
||||
}
|
||||
}
|
||||
lock.unlock();
|
||||
// here we know that we have the platform fence
|
||||
assert_invariant(f->fence);
|
||||
return mPlatform.waitFence(f->fence, timeout);
|
||||
}
|
||||
// platform doesn't support fences -- nothing we can do.
|
||||
return FenceStatus::ERROR;
|
||||
}
|
||||
|
||||
// This is the case where we need to use OpenGL fences
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
std::unique_lock lock(state->lock);
|
||||
state->cond.wait_until(lock, until, [&state] {
|
||||
return state->status != FenceStatus::TIMEOUT_EXPIRED;
|
||||
});
|
||||
return state->status;
|
||||
#endif
|
||||
// we have to take into account that the STL's wait_for() actually works with
|
||||
// time_points relative to steady_clock::now() internally.
|
||||
using namespace std::chrono;
|
||||
auto const now = steady_clock::now();
|
||||
steady_clock::time_point until = steady_clock::time_point::max();
|
||||
if (now <= steady_clock::time_point::max() - nanoseconds(timeout)) {
|
||||
until = now + nanoseconds(timeout);
|
||||
}
|
||||
return FenceStatus::ERROR;
|
||||
|
||||
// we don't need to acquire a reference to f->state here because `f` already has one, and
|
||||
// `f` is not supposed to become invalid while we wait.
|
||||
|
||||
bool const platformCanCreateFence = mPlatform.canCreateFence();
|
||||
if (mContext.isES2() || platformCanCreateFence) {
|
||||
if (platformCanCreateFence) {
|
||||
std::unique_lock lock(f->state->lock);
|
||||
if (f->fence == nullptr) {
|
||||
// we've been called before the fence was created asynchronously,
|
||||
// so we need to wait for that, before using the real fence.
|
||||
// By construction, "f" can't be destroyed while we wait, because its
|
||||
// construction call is in the queue and a destroy call will have to come later.
|
||||
f->state->cond.wait_until(lock, until, [f] {
|
||||
return f->fence != nullptr;
|
||||
});
|
||||
if (f->fence == nullptr) {
|
||||
// the only possible choice here is that we timed out
|
||||
assert_invariant(f->state->status == FenceStatus::TIMEOUT_EXPIRED);
|
||||
return FenceStatus::TIMEOUT_EXPIRED;
|
||||
}
|
||||
}
|
||||
lock.unlock();
|
||||
// here we know that we have the platform fence
|
||||
assert_invariant(f->fence);
|
||||
return mPlatform.waitFence(f->fence, timeout);
|
||||
}
|
||||
// platform doesn't support fences -- nothing we can do.
|
||||
return FenceStatus::ERROR;
|
||||
}
|
||||
|
||||
// This is the case where we need to use OpenGL fences
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
std::unique_lock lock(f->state->lock);
|
||||
f->state->cond.wait_until(lock, until, [f] {
|
||||
return f->state->status != FenceStatus::TIMEOUT_EXPIRED;
|
||||
});
|
||||
return f->state->status;
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::getPlatformSync(Handle<HwSync> sh, CallbackHandler* handler,
|
||||
@@ -2698,6 +2710,39 @@ void OpenGLDriver::commit(Handle<HwSwapChain> sch) {
|
||||
#endif
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isCompositorTimingSupported() {
|
||||
// this is a synchronous call
|
||||
return mPlatform.isCompositorTimingSupported();
|
||||
}
|
||||
|
||||
bool OpenGLDriver::queryCompositorTiming(SwapChainHandle swapChain,
|
||||
CompositorTiming* outCompositorTiming) {
|
||||
// this is a synchronous call
|
||||
if (!swapChain) {
|
||||
return false;
|
||||
}
|
||||
GLSwapChain const* const sc = handle_cast<GLSwapChain*>(swapChain);
|
||||
if (!sc) {
|
||||
// can happen if the SwapChainHandle is not initialized yet (still in CommandStream)
|
||||
return false;
|
||||
}
|
||||
return mPlatform.queryCompositorTiming(sc->swapChain, outCompositorTiming);
|
||||
}
|
||||
|
||||
bool OpenGLDriver::queryFrameTimestamps(SwapChainHandle swapChain, uint64_t const frameId,
|
||||
FrameTimestamps* outFrameTimestamps) {
|
||||
// this is a synchronous call
|
||||
if (!swapChain) {
|
||||
return false;
|
||||
}
|
||||
GLSwapChain const* const sc = handle_cast<GLSwapChain*>(swapChain);
|
||||
if (!sc) {
|
||||
// can happen if the SwapChainHandle is not initialized yet (still in CommandStream)
|
||||
return false;
|
||||
}
|
||||
return mPlatform.queryFrameTimestamps(sc->swapChain, frameId, outFrameTimestamps);
|
||||
}
|
||||
|
||||
void OpenGLDriver::makeCurrent(Handle<HwSwapChain> schDraw, Handle<HwSwapChain> schRead) {
|
||||
DEBUG_MARKER()
|
||||
|
||||
|
||||
@@ -262,20 +262,25 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
|
||||
}
|
||||
|
||||
void OpenGLProgram::updateUniforms(
|
||||
uint32_t index, GLuint id, void const* buffer, uint16_t age) const noexcept {
|
||||
uint32_t const index, GLuint const id, void const* buffer,
|
||||
uint16_t const age, uint32_t const offset) const noexcept {
|
||||
assert_invariant(mUniformsRecords);
|
||||
assert_invariant(buffer);
|
||||
|
||||
// only update the uniforms if the UBO has changed since last time we updated
|
||||
UniformsRecord const& records = mUniformsRecords[index];
|
||||
if (records.id == id && records.age == age) {
|
||||
if (records.id == id && records.age == age && records.offset == offset) {
|
||||
return;
|
||||
}
|
||||
records.id = id;
|
||||
records.age = age;
|
||||
records.offset = offset;
|
||||
|
||||
assert_invariant(records.uniforms.size() == records.locations.size());
|
||||
|
||||
// apply the offset to the buffer
|
||||
buffer = static_cast<char const*>(buffer) + offset;
|
||||
|
||||
for (size_t i = 0, c = records.uniforms.size(); i < c; i++) {
|
||||
Program::Uniform const& u = records.uniforms[i];
|
||||
GLint const loc = records.locations[i];
|
||||
|
||||
@@ -88,7 +88,7 @@ public:
|
||||
}
|
||||
|
||||
// For ES2 only
|
||||
void updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age) const noexcept;
|
||||
void updateUniforms(uint32_t index, GLuint id, void const* buffer, uint16_t age, uint32_t offset) const noexcept;
|
||||
void setRec709ColorSpace(bool rec709) const noexcept;
|
||||
|
||||
PushConstantBundle getPushConstants() {
|
||||
@@ -123,6 +123,7 @@ private:
|
||||
LocationInfo locations;
|
||||
mutable GLuint id = 0;
|
||||
mutable uint16_t age = std::numeric_limits<uint16_t>::max();
|
||||
mutable uint32_t offset = 0;
|
||||
};
|
||||
UniformsRecord const* mUniformsRecords = nullptr;
|
||||
GLint mRec709Location : 24; // 4 bytes
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <private/backend/VirtualMachineEnv.h>
|
||||
|
||||
#include "AndroidNativeWindow.h"
|
||||
#include "AndroidFrameCallback.h"
|
||||
#include "AndroidSwapChainHelper.h"
|
||||
#include "ExternalStreamManagerAndroid.h"
|
||||
|
||||
@@ -58,7 +59,6 @@
|
||||
#include <new>
|
||||
#include <string_view>
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
@@ -98,6 +98,25 @@ using namespace glext;
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
struct PlatformEGLAndroid::SwapChainEGLAndroid : public SwapChainEGL {
|
||||
SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
|
||||
void* nativeWindow, uint64_t flags);
|
||||
SwapChainEGLAndroid(PlatformEGLAndroid const& platform,
|
||||
uint32_t width, uint32_t height, uint64_t flags);
|
||||
void terminate(PlatformEGLAndroid& platform);
|
||||
bool setPresentFrameId(uint64_t frameId) const noexcept;
|
||||
uint64_t getFrameId(uint64_t frameId) const noexcept;
|
||||
private:
|
||||
AndroidSwapChainHelper mImpl{};
|
||||
};
|
||||
|
||||
struct PlatformEGLAndroid::AndroidDetails {
|
||||
AndroidProducerThrottling producerThrottling;
|
||||
AndroidFrameCallback androidFrameCallback;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
PlatformEGLAndroid::InitializeJvmForPerformanceManagerIfNeeded::InitializeJvmForPerformanceManagerIfNeeded() {
|
||||
// PerformanceHintManager() needs the calling thread to be a Java thread; so we need
|
||||
// to attach this thread to the JVM before we initialize PerformanceHintManager.
|
||||
@@ -111,38 +130,20 @@ PlatformEGLAndroid::InitializeJvmForPerformanceManagerIfNeeded::InitializeJvmFor
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
PlatformEGLAndroid::PlatformEGLAndroid() noexcept
|
||||
: mExternalStreamManager(ExternalStreamManagerAndroid::create()) {
|
||||
: mExternalStreamManager(ExternalStreamManagerAndroid::create()),
|
||||
mAndroidDetails(*(new(std::nothrow) AndroidDetails{})) {
|
||||
mOSVersion = android_get_device_api_level();
|
||||
if (mOSVersion < 0) {
|
||||
mOSVersion = __ANDROID_API_FUTURE__;
|
||||
}
|
||||
|
||||
// TODO: remove this code one the new NDK is available
|
||||
void* nativeWindowLibHandle = dlopen("libnativewindow.so", RTLD_LOCAL | RTLD_NOW);
|
||||
if (nativeWindowLibHandle) {
|
||||
ANativeWindow_setProducerThrottlingEnabled =
|
||||
(int32_t(*)(ANativeWindow*, bool))dlsym(nativeWindowLibHandle,
|
||||
"ANativeWindow_setProducerThrottlingEnabled");
|
||||
|
||||
ANativeWindow_isProducerThrottlingEnabled =
|
||||
(int32_t(*)(ANativeWindow*, bool*))dlsym(nativeWindowLibHandle,
|
||||
"ANativeWindow_isProducerThrottlingEnabled");
|
||||
|
||||
if (ANativeWindow_setProducerThrottlingEnabled &&
|
||||
ANativeWindow_isProducerThrottlingEnabled) {
|
||||
mHasProducerThrottlingControl = true;
|
||||
LOG(INFO) << "Producer Throttling API available";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PlatformEGLAndroid::~PlatformEGLAndroid() noexcept {
|
||||
// note: we don't need to dlclose() mNativeWindowLib here, because the library will be cleaned
|
||||
// when the process ends and dlopen() are ref-counted. dlclose() NDK documentation documents
|
||||
// not to call dlclose().
|
||||
delete &mAndroidDetails;
|
||||
}
|
||||
|
||||
void PlatformEGLAndroid::terminate() noexcept {
|
||||
mAndroidDetails.androidFrameCallback.terminate();
|
||||
ExternalStreamManagerAndroid::destroy(&mExternalStreamManager);
|
||||
PlatformEGL::terminate();
|
||||
}
|
||||
@@ -260,6 +261,8 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
|
||||
|
||||
mAssertNativeWindowIsValid = driverConfig.assertNativeWindowIsValid;
|
||||
|
||||
mAndroidDetails.androidFrameCallback.init();
|
||||
|
||||
return driver;
|
||||
}
|
||||
|
||||
@@ -281,8 +284,14 @@ bool PlatformEGLAndroid::queryCompositorTiming(SwapChain const* swapchain,
|
||||
return false;
|
||||
}
|
||||
|
||||
AndroidFrameCallback::Timeline const preferredTimeline{
|
||||
mAndroidDetails.androidFrameCallback.getPreferredTimeline() };
|
||||
outCompositorTiming->frameTime = preferredTimeline.frameTime;
|
||||
outCompositorTiming->expectedPresentTime = preferredTimeline.expectedPresentTime;
|
||||
outCompositorTiming->frameTimelineDeadline = preferredTimeline.frameTimelineDeadline;
|
||||
|
||||
if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) {
|
||||
EGLSurface sur = static_cast<SwapChainEGL const *>(swapchain)->sur;
|
||||
EGLSurface const sur = static_cast<SwapChainEGL const *>(swapchain)->sur;
|
||||
if (sur == EGL_NO_SURFACE) {
|
||||
return false;
|
||||
}
|
||||
@@ -365,8 +374,8 @@ bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64
|
||||
outFrameTimestamps->requestedPresentTime = values[0];
|
||||
outFrameTimestamps->acquireTime = values[1];
|
||||
outFrameTimestamps->latchTime = values[2];
|
||||
outFrameTimestamps->firstRefreshStartTime = values[3];
|
||||
outFrameTimestamps->lastRefreshStartTime = values[4];
|
||||
outFrameTimestamps->firstCompositionStartTime = values[3];
|
||||
outFrameTimestamps->lastCompositionStartTime = values[4];
|
||||
outFrameTimestamps->gpuCompositionDoneTime = values[5];
|
||||
outFrameTimestamps->displayPresentTime = values[6];
|
||||
outFrameTimestamps->dequeueReadyTime = values[7];
|
||||
@@ -380,8 +389,8 @@ bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64
|
||||
&outFrameTimestamps->requestedPresentTime,
|
||||
&outFrameTimestamps->acquireTime,
|
||||
&outFrameTimestamps->latchTime,
|
||||
&outFrameTimestamps->firstRefreshStartTime,
|
||||
&outFrameTimestamps->lastRefreshStartTime,
|
||||
&outFrameTimestamps->firstCompositionStartTime,
|
||||
&outFrameTimestamps->lastCompositionStartTime,
|
||||
&outFrameTimestamps->gpuCompositionDoneTime,
|
||||
&outFrameTimestamps->displayPresentTime,
|
||||
&outFrameTimestamps->dequeueReadyTime,
|
||||
@@ -419,12 +428,13 @@ Platform::ExternalImageHandle PlatformEGLAndroid::createExternalImage(
|
||||
auto const hardwareBuffer = const_cast<AHardwareBuffer*>(buffer);
|
||||
AHardwareBuffer_acquire(hardwareBuffer);
|
||||
p->aHardwareBuffer = hardwareBuffer;
|
||||
p->sRGB = sRGB;
|
||||
AHardwareBuffer_Desc hardwareBufferDescription = {};
|
||||
AHardwareBuffer_describe(hardwareBuffer, &hardwareBufferDescription);
|
||||
p->height = hardwareBufferDescription.height;
|
||||
p->width = hardwareBufferDescription.width;
|
||||
auto const textureFormat = mapToFilamentFormat(hardwareBufferDescription.format, sRGB);
|
||||
// Only set sRGB as true if the filament format requires it, otherwise the eglCreateImage might fail.
|
||||
p->sRGB = textureFormat == TextureFormat::SRGB8 || textureFormat == TextureFormat::SRGB8_A8;
|
||||
p->format = textureFormat;
|
||||
p->usage = mapToFilamentUsage(hardwareBufferDescription.usage, textureFormat);
|
||||
return ExternalImageHandle{ p };
|
||||
@@ -567,7 +577,15 @@ void PlatformEGLAndroid::destroyStream(Stream* stream) noexcept {
|
||||
}
|
||||
|
||||
Platform::Sync* PlatformEGLAndroid::createSync() noexcept {
|
||||
auto const sync = eglCreateSyncKHR(getEglDisplay(), EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
|
||||
EGLSyncKHR sync = EGL_NO_SYNC_KHR;
|
||||
if (UTILS_LIKELY(ext.egl.ANDROID_native_fence_sync)) {
|
||||
sync = eglCreateSyncKHR(getEglDisplay(), EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
|
||||
if (sync == EGL_NO_SYNC_KHR) {
|
||||
LOG(ERROR) << "Failed to create sync: " << eglGetError();
|
||||
}
|
||||
} else {
|
||||
LOG(WARNING) << "Native fences not supported on this device.";
|
||||
}
|
||||
return new(std::nothrow) SyncEGLAndroid{ .sync = sync };
|
||||
}
|
||||
|
||||
@@ -575,10 +593,16 @@ bool PlatformEGLAndroid::convertSyncToFd(Sync* sync, int* fd) noexcept {
|
||||
assert_invariant(sync && fd);
|
||||
|
||||
if (UTILS_UNLIKELY(!ext.egl.ANDROID_native_fence_sync)) {
|
||||
LOG(WARNING) << "Native fences not supported, cannot convert to fd.";
|
||||
return false;
|
||||
}
|
||||
|
||||
SyncEGLAndroid const& eglSync = static_cast<SyncEGLAndroid&>(*sync);
|
||||
if (eglSync.sync == EGL_NO_SYNC_KHR) {
|
||||
LOG(ERROR) << "Invalid fence, cannot convert to fd.";
|
||||
return false;
|
||||
}
|
||||
|
||||
*fd = eglDupNativeFenceFDANDROID(getEglDisplay(), eglSync.sync);
|
||||
// In the case where there was no native FD, -1 is returned. Return false
|
||||
// to indicate there was an error in this case.
|
||||
@@ -591,8 +615,12 @@ bool PlatformEGLAndroid::convertSyncToFd(Sync* sync, int* fd) noexcept {
|
||||
|
||||
void PlatformEGLAndroid::destroySync(Sync* sync) noexcept {
|
||||
assert_invariant(sync);
|
||||
SyncEGLAndroid const& eglSync = static_cast<SyncEGLAndroid&>(*sync);
|
||||
eglDestroySyncKHR(getEglDisplay(), eglSync.sync);
|
||||
if (UTILS_LIKELY(ext.egl.ANDROID_native_fence_sync)) {
|
||||
SyncEGLAndroid const& eglSync = static_cast<SyncEGLAndroid&>(*sync);
|
||||
if (eglSync.sync != EGL_NO_SYNC_KHR) {
|
||||
eglDestroySyncKHR(getEglDisplay(), eglSync.sync);
|
||||
}
|
||||
}
|
||||
delete sync;
|
||||
}
|
||||
|
||||
@@ -666,6 +694,16 @@ AcquiredImage PlatformEGLAndroid::transformAcquiredImage(AcquiredImage const sou
|
||||
return { eglImage, patchedCallback, closure, source.handler };
|
||||
}
|
||||
|
||||
|
||||
bool PlatformEGLAndroid::isProducerThrottlingControlSupported() const {
|
||||
return mAndroidDetails.producerThrottling.isSupported();
|
||||
}
|
||||
|
||||
int32_t PlatformEGLAndroid::setProducerThrottlingEnabled(
|
||||
EGLNativeWindowType const nativeWindow, bool const enabled) const {
|
||||
return mAndroidDetails.producerThrottling.setProducerThrottlingEnabled(nativeWindow, enabled);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// PlatformEGLAndroid::SwapChainEGLAndroid
|
||||
|
||||
@@ -673,9 +711,9 @@ PlatformEGLAndroid::SwapChainEGLAndroid::SwapChainEGLAndroid(PlatformEGLAndroid
|
||||
void* nativeWindow, uint64_t const flags)
|
||||
: SwapChainEGL(platform, nativeWindow, flags) {
|
||||
|
||||
if (nativeWindow && platform.mHasProducerThrottlingControl) {
|
||||
if (nativeWindow && platform.isProducerThrottlingControlSupported()) {
|
||||
// Disable Producer Throttling when supported. This allows eglSwapBuffers() to not stall
|
||||
int32_t const result = platform.ANativeWindow_setProducerThrottlingEnabled(
|
||||
int32_t const result = platform.setProducerThrottlingEnabled(
|
||||
EGLNativeWindowType(nativeWindow), false);
|
||||
if (UTILS_UNLIKELY(result < 0)) {
|
||||
LOG(WARNING) << "ANativeWindow_setProducerThrottlingEnabled(false) failed: " << result;
|
||||
@@ -702,6 +740,10 @@ void PlatformEGLAndroid::SwapChainEGLAndroid::terminate(PlatformEGLAndroid& plat
|
||||
}
|
||||
|
||||
bool PlatformEGLAndroid::SwapChainEGLAndroid::setPresentFrameId(uint64_t frameId) const noexcept {
|
||||
if (!nativeWindow) {
|
||||
// nativeWindow is null in the headless case
|
||||
return false;
|
||||
}
|
||||
return mImpl.setPresentFrameId(nativeWindow, frameId);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#define LIBRARY_GLX "libGL.so.1"
|
||||
#define LIBRARY_X11 "libX11.so.6"
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
#version 320 es
|
||||
precision mediump float;
|
||||
|
||||
precision lowp sampler2DMS;
|
||||
|
||||
layout(set = 0, binding = 0, std140) uniform ParamsBlock {
|
||||
int sampleCount;
|
||||
float inverseSampleCount;
|
||||
} params;
|
||||
|
||||
layout(set = 1, binding = 0) uniform sampler2DMS tex;
|
||||
|
||||
void main() {
|
||||
float depth = 0.0;
|
||||
for (int sampleIndex = 0; sampleIndex < params.sampleCount; sampleIndex++) {
|
||||
depth += texelFetch(tex, ivec2(gl_FragCoord.xy), sampleIndex).r;
|
||||
}
|
||||
gl_FragDepth = depth * params.inverseSampleCount;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
#version 320 es
|
||||
|
||||
layout(location = 0) in vec4 pos;
|
||||
|
||||
void main() {
|
||||
gl_Position = pos;
|
||||
}
|
||||
@@ -32,27 +32,31 @@ namespace filament::backend {
|
||||
FenceStatus VulkanCmdFence::wait(VkDevice device, uint64_t const timeout,
|
||||
std::chrono::steady_clock::time_point const until) {
|
||||
|
||||
{
|
||||
std::shared_lock l(mLock);
|
||||
// this lock MUST be held for READ when calling vkWaitForFences()
|
||||
std::shared_lock rl(mLock);
|
||||
|
||||
// If the vulkan fence has not been submitted yet, we need to wait for that before we
|
||||
// can use vkWaitForFences()
|
||||
if (mStatus == VK_INCOMPLETE) {
|
||||
bool const success = mCond.wait_until(l, until, [this] {
|
||||
// Internally we use the VK_INCOMPLETE status to mean "not yet submitted".
|
||||
// When this fence gets submitted, its status changes to VK_NOT_READY.
|
||||
return mStatus != VK_INCOMPLETE;
|
||||
});
|
||||
if (!success) {
|
||||
// !success indicates a timeout
|
||||
return FenceStatus::TIMEOUT_EXPIRED;
|
||||
}
|
||||
// If the vulkan fence has not been submitted yet, we need to wait for that before we
|
||||
// can use vkWaitForFences()
|
||||
if (mStatus == VK_INCOMPLETE) {
|
||||
bool const success = mCond.wait_until(rl, until, [this] {
|
||||
// Internally we use the VK_INCOMPLETE status to mean "not yet submitted".
|
||||
// When this fence gets submitted, its status changes to VK_NOT_READY.
|
||||
return mStatus != VK_INCOMPLETE || mCanceled;
|
||||
});
|
||||
if (!success) {
|
||||
// !success indicates a timeout or cancel
|
||||
return mCanceled ? FenceStatus::ERROR : FenceStatus::TIMEOUT_EXPIRED;
|
||||
}
|
||||
}
|
||||
|
||||
// The fence could have already signaled, avoid calling into vkWaitForFences()
|
||||
if (mStatus == VK_SUCCESS) {
|
||||
return FenceStatus::CONDITION_SATISFIED;
|
||||
}
|
||||
// The fence could have already signaled, avoid calling into vkWaitForFences()
|
||||
if (mStatus == VK_SUCCESS) {
|
||||
return FenceStatus::CONDITION_SATISFIED;
|
||||
}
|
||||
|
||||
// Or it could have been canceled, return immediately
|
||||
if (mCanceled) {
|
||||
return FenceStatus::ERROR;
|
||||
}
|
||||
|
||||
// If we're here, we know that vkQueueSubmit has been called (because it sets the status
|
||||
@@ -62,13 +66,14 @@ FenceStatus VulkanCmdFence::wait(VkDevice device, uint64_t const timeout,
|
||||
// place simultaneously. vkResetFence is only called once it knows the fence has signaled,
|
||||
// which guaranties that vkResetFence won't have to wait too long, just enough for
|
||||
// all the vkWaitForFences() to return.
|
||||
VkResult status = vkWaitForFences(device, 1, &mFence, VK_TRUE, timeout);
|
||||
VkResult const status = vkWaitForFences(device, 1, &mFence, VK_TRUE, timeout);
|
||||
if (status == VK_TIMEOUT) {
|
||||
return FenceStatus::TIMEOUT_EXPIRED;
|
||||
}
|
||||
|
||||
if (status == VK_SUCCESS) {
|
||||
std::lock_guard const l(mLock);
|
||||
rl.unlock();
|
||||
std::lock_guard const wl(mLock);
|
||||
mStatus = status;
|
||||
return FenceStatus::CONDITION_SATISFIED;
|
||||
}
|
||||
|
||||
@@ -56,9 +56,16 @@ struct VulkanCmdFence {
|
||||
FenceStatus wait(VkDevice device, uint64_t timeout,
|
||||
std::chrono::steady_clock::time_point until);
|
||||
|
||||
void cancel() {
|
||||
std::lock_guard const l(mLock);
|
||||
mCanceled = true;
|
||||
mCond.notify_all();
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_mutex mLock; // NOLINT(*-include-cleaner)
|
||||
std::condition_variable_any mCond;
|
||||
bool mCanceled = false;
|
||||
// Internally we use the VK_INCOMPLETE status to mean "not yet submitted". When this fence
|
||||
// gets submitted, its status changes to VK_NOT_READY. Finally, when the GPU actually
|
||||
// finishes executing the command buffer, the status changes to VK_SUCCESS.
|
||||
@@ -70,31 +77,41 @@ struct VulkanFence : public HwFence, fvkmemory::ThreadSafeResource {
|
||||
VulkanFence() {}
|
||||
|
||||
void setFence(std::shared_ptr<VulkanCmdFence> fence) {
|
||||
std::lock_guard lock(mState->lock);
|
||||
mState->sharedFence = std::move(fence);
|
||||
mState->cond.notify_all();
|
||||
std::lock_guard const l(lock);
|
||||
sharedFence = std::move(fence);
|
||||
cond.notify_all();
|
||||
}
|
||||
|
||||
std::shared_ptr<VulkanCmdFence>& getSharedFence() {
|
||||
std::lock_guard lock(mState->lock);
|
||||
return mState->sharedFence;
|
||||
std::lock_guard const l(lock);
|
||||
return sharedFence;
|
||||
}
|
||||
|
||||
std::shared_ptr<VulkanCmdFence> wait(std::chrono::steady_clock::time_point const until) {
|
||||
std::pair<std::shared_ptr<VulkanCmdFence>, bool>
|
||||
wait(std::chrono::steady_clock::time_point const until) {
|
||||
// hold a reference so that our state doesn't disappear while we wait
|
||||
std::shared_ptr state{ mState };
|
||||
std::unique_lock lock(state->lock);
|
||||
state->cond.wait_until(lock, until, [&state] { return bool(state->sharedFence); });
|
||||
std::unique_lock l(lock);
|
||||
cond.wait_until(l, until, [this] {
|
||||
return bool(sharedFence) || canceled;
|
||||
});
|
||||
// here mSharedFence will be null if we timed out
|
||||
return state->sharedFence;
|
||||
return { sharedFence, canceled };
|
||||
}
|
||||
|
||||
void cancel() const {
|
||||
std::lock_guard const l(lock);
|
||||
if (sharedFence) {
|
||||
sharedFence->cancel();
|
||||
}
|
||||
canceled = true;
|
||||
cond.notify_all();
|
||||
}
|
||||
|
||||
private:
|
||||
struct State {
|
||||
std::mutex lock;
|
||||
std::condition_variable cond;
|
||||
std::shared_ptr<VulkanCmdFence> sharedFence;
|
||||
};
|
||||
std::shared_ptr<State> mState{ std::make_shared<State>() };
|
||||
mutable std::mutex lock;
|
||||
mutable std::condition_variable cond;
|
||||
mutable bool canceled = false;
|
||||
std::shared_ptr<VulkanCmdFence> sharedFence;
|
||||
};
|
||||
|
||||
struct VulkanSync : fvkmemory::ThreadSafeResource, public HwSync {
|
||||
@@ -116,12 +133,12 @@ struct VulkanTimerQuery : public HwTimerQuery, fvkmemory::ThreadSafeResource {
|
||||
mStoppingQueryIndex(stoppingIndex) {}
|
||||
|
||||
void setFence(std::shared_ptr<VulkanCmdFence> fence) noexcept {
|
||||
std::unique_lock const lock(mFenceMutex);
|
||||
std::lock_guard const lock(mFenceMutex);
|
||||
mFence = std::move(fence);
|
||||
}
|
||||
|
||||
bool isCompleted() noexcept {
|
||||
std::unique_lock const lock(mFenceMutex);
|
||||
std::lock_guard const lock(mFenceMutex);
|
||||
// QueryValue is a synchronous call and might occur before beginTimerQuery has written
|
||||
// anything into the command buffer, which is an error according to the validation layer
|
||||
// that ships in the Android NDK. Even when AVAILABILITY_BIT is set, validation seems to
|
||||
|
||||
@@ -214,4 +214,10 @@ constexpr static const int FVK_MAX_PIPELINE_AGE = FVK_MAX_COMMAND_BUFFERS;
|
||||
// destroying any unused pipeline object.
|
||||
static_assert(FVK_MAX_PIPELINE_AGE >= FVK_MAX_COMMAND_BUFFERS);
|
||||
|
||||
// Indicates if the backend must be setup to allow doing a RenderDoc capture.
|
||||
//
|
||||
// If this is true, the features not supported by RenderDoc must be disabled, otherwise
|
||||
// when using RenderDoc the application will crash or will fail to do a capture.
|
||||
constexpr static const int FVK_RENDERDOC_CAPTURE_MODE = false;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -154,6 +154,10 @@ public:
|
||||
return mStagingBufferBypassEnabled;
|
||||
}
|
||||
|
||||
inline bool pipelineCreationFeedbackSupported() const noexcept {
|
||||
return mPipelineCreationFeedbackSupported;
|
||||
}
|
||||
|
||||
private:
|
||||
VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
|
||||
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
|
||||
@@ -181,6 +185,7 @@ private:
|
||||
bool mProtectedMemorySupported = false;
|
||||
bool mIsUnifiedMemoryArchitecture = false;
|
||||
bool mStagingBufferBypassEnabled = false;
|
||||
bool mPipelineCreationFeedbackSupported = false;
|
||||
|
||||
fvkutils::VkFormatList mDepthStencilFormats;
|
||||
fvkutils::VkFormatList mBlittableDepthStencilFormats;
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
#endif
|
||||
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
@@ -230,7 +231,7 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext& context,
|
||||
mPlatform->getGraphicsQueueFamilyIndex(), mPlatform->getProtectedGraphicsQueue(),
|
||||
mPlatform->getProtectedGraphicsQueueFamilyIndex(), mContext, &mSemaphoreManager),
|
||||
mPipelineLayoutCache(mPlatform->getDevice()),
|
||||
mPipelineCache(mPlatform->getDevice()),
|
||||
mPipelineCache(mPlatform->getDevice(), mContext),
|
||||
mStagePool(mAllocator, &mResourceManager, &mCommands, &mContext.getPhysicalDeviceLimits()),
|
||||
mBufferCache(mContext, mResourceManager, mAllocator),
|
||||
mFramebufferCache(mPlatform->getDevice()),
|
||||
@@ -419,6 +420,10 @@ void VulkanDriver::beginFrame(int64_t monotonic_clock_ns,
|
||||
int64_t refreshIntervalNs, uint32_t frameId) {
|
||||
FVK_PROFILE_MARKER(PROFILE_NAME_BEGINFRAME);
|
||||
|
||||
if (mCurrentSwapChain) { // This should be guaranteed
|
||||
mPlatform->setPresentFrameId(mCurrentSwapChain->swapChain, frameId);
|
||||
}
|
||||
|
||||
// Check if any command have finished and reset all its used resources. The resources
|
||||
// wont be destroyed but their reference count will decreased if the command is already
|
||||
// completed.
|
||||
@@ -915,10 +920,14 @@ void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
|
||||
FVK_LOGW << "protected swapchain requested, but Platform does not support it";
|
||||
}
|
||||
}
|
||||
|
||||
auto swapChain = resource_ptr<VulkanSwapChain>::make(&mResourceManager, sch, mPlatform,
|
||||
mContext, &mResourceManager, mAllocator, &mCommands, mStagePool, nativeWindow, flags);
|
||||
swapChain.inc();
|
||||
mResourceManager.associateHandle(sch.getId(), std::move(tag));
|
||||
|
||||
std::unique_lock<std::mutex> lock(mTiming.lock);
|
||||
mTiming.nativeSwapchains.emplace(sch.getId(), swapChain->swapChain);
|
||||
}
|
||||
|
||||
void VulkanDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t width,
|
||||
@@ -1093,6 +1102,9 @@ void VulkanDriver::destroySwapChain(Handle<HwSwapChain> sch) {
|
||||
mCurrentSwapChain = {};
|
||||
}
|
||||
swapChain.dec();
|
||||
|
||||
std::unique_lock<std::mutex> lock(mTiming.lock);
|
||||
mTiming.nativeSwapchains.erase(sch.getId());
|
||||
}
|
||||
|
||||
void VulkanDriver::destroyStream(Handle<HwStream> sh) {
|
||||
@@ -1155,8 +1167,19 @@ void VulkanDriver::updateStreams(CommandStream* driver) {
|
||||
}
|
||||
|
||||
void VulkanDriver::destroyFence(Handle<HwFence> fh) {
|
||||
if (fh) {
|
||||
auto fence = resource_ptr<VulkanFence>::cast(&mResourceManager, fh);
|
||||
// note: it's invalid to call this during a fenceWait(fh) on another thread. For this
|
||||
// reason there is no point signaling the waiters. There should be no waiters.
|
||||
fence.dec();
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanDriver::fenceCancel(FenceHandle const fh) {
|
||||
// Even though this is a synchronous call, the fence handle must be (and stay) valid
|
||||
assert_invariant(fh);
|
||||
auto fence = resource_ptr<VulkanFence>::cast(&mResourceManager, fh);
|
||||
fence.dec();
|
||||
fence->cancel();
|
||||
}
|
||||
|
||||
FenceStatus VulkanDriver::getFenceStatus(Handle<HwFence> const fh) {
|
||||
@@ -1164,6 +1187,9 @@ FenceStatus VulkanDriver::getFenceStatus(Handle<HwFence> const fh) {
|
||||
}
|
||||
|
||||
FenceStatus VulkanDriver::fenceWait(FenceHandle const fh, uint64_t const timeout) {
|
||||
// Even though this is a synchronous call, the fence handle must be (and stay) valid
|
||||
assert_invariant(fh);
|
||||
|
||||
auto fence = resource_ptr<VulkanFence>::cast(&mResourceManager, fh);
|
||||
|
||||
// we have to take into account that the STL's wait_for() actually works with
|
||||
@@ -1180,9 +1206,9 @@ FenceStatus VulkanDriver::fenceWait(FenceHandle const fh, uint64_t const timeout
|
||||
until = now + nanoseconds(timeout);
|
||||
}
|
||||
|
||||
std::shared_ptr const cmdfence = fence->wait(until);
|
||||
if (!cmdfence) {
|
||||
return FenceStatus::TIMEOUT_EXPIRED;
|
||||
auto const [cmdfence, canceled] = fence->wait(until);
|
||||
if (!cmdfence || canceled) {
|
||||
return canceled ? FenceStatus::ERROR : FenceStatus::TIMEOUT_EXPIRED;
|
||||
}
|
||||
|
||||
// now we are holding a reference to our VulkanCmdFence, so we know it can't
|
||||
@@ -1759,6 +1785,43 @@ void VulkanDriver::nextSubpass(int) {
|
||||
}
|
||||
}
|
||||
|
||||
bool VulkanDriver::isCompositorTimingSupported() {
|
||||
return mPlatform->isCompositorTimingSupported();
|
||||
}
|
||||
|
||||
bool VulkanDriver::queryCompositorTiming(Handle<HwSwapChain> const swapChain,
|
||||
CompositorTiming* outCompositorTiming) {
|
||||
// this is a synchronous call
|
||||
if (!swapChain) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HandleId const id = swapChain.getId();
|
||||
std::unique_lock<std::mutex> lock(mTiming.lock);
|
||||
auto& swapchains = mTiming.nativeSwapchains;
|
||||
if (auto itr = swapchains.find(id); itr != swapchains.end()) {
|
||||
lock.unlock();
|
||||
return mPlatform->queryCompositorTiming(itr->second, outCompositorTiming);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VulkanDriver::queryFrameTimestamps(Handle<HwSwapChain> const swapChain, uint64_t const frameId,
|
||||
FrameTimestamps* outFrameTimestamps) {
|
||||
// this is a synchronous call
|
||||
if (!swapChain) {
|
||||
return false;
|
||||
}
|
||||
HandleId const id = swapChain.getId();
|
||||
std::unique_lock<std::mutex> lock(mTiming.lock);
|
||||
auto& swapchains = mTiming.nativeSwapchains;
|
||||
if (auto itr = swapchains.find(id); itr != swapchains.end()) {
|
||||
lock.unlock();
|
||||
return mPlatform->queryFrameTimestamps(itr->second, frameId, outFrameTimestamps);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void VulkanDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain> readSch) {
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
|
||||
|
||||
@@ -158,6 +158,15 @@ private:
|
||||
VulkanQueryManager mQueryManager;
|
||||
VulkanExternalImageManager mExternalImageManager;
|
||||
|
||||
// This maps a VulkanSwapchain to a native swapchain. VulkanSwapchain should have a copy of the
|
||||
// Platform::Swapchain pointer, but queryFrameTimestamps() and queryCompositorTiming() are
|
||||
// synchronous calls, making access to VulkanSwapchain unsafe (this difference vs other backends
|
||||
// is due to the ref-counting of vulkan resources).
|
||||
struct {
|
||||
std::mutex lock;
|
||||
std::unordered_map<HandleId, Platform::SwapChain*> nativeSwapchains;
|
||||
} mTiming;
|
||||
|
||||
// This is necessary for us to write to push constants after binding a pipeline.
|
||||
using DescriptorSetLayoutHandleList = std::array<resource_ptr<VulkanDescriptorSetLayout>,
|
||||
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||
|
||||