Compare commits
151
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
031cd302dd | ||
|
|
87351097ad | ||
|
|
694766682f | ||
|
|
c946ebd1e6 | ||
|
|
25a8291101 | ||
|
|
60c689688d | ||
|
|
763bc1f34a | ||
|
|
ef07638eef | ||
|
|
0aa0efe159 | ||
|
|
ef7bcd1e19 | ||
|
|
702ceda82a | ||
|
|
66b78074de | ||
|
|
8d440cea17 | ||
|
|
3ab8e4d725 | ||
|
|
97f20afdd7 | ||
|
|
42989e76d7 | ||
|
|
ad45cc9092 | ||
|
|
04669f6ab9 | ||
|
|
c3c0dde82f | ||
|
|
ecd5b681d0 | ||
|
|
66081e6cc1 | ||
|
|
aa6e94a128 | ||
|
|
098be2e115 | ||
|
|
17caf6cae9 | ||
|
|
26952631a3 | ||
|
|
fc7b6447b7 | ||
|
|
6c0db37919 | ||
|
|
69f78dbcbe | ||
|
|
c0389ac54c | ||
|
|
c0db909c13 | ||
|
|
46e4e966b9 | ||
|
|
288b59a348 | ||
|
|
1c7293db8d | ||
|
|
6006b47c44 | ||
|
|
6bb29f6e01 | ||
|
|
f1b160db04 | ||
|
|
3bb52f083b | ||
|
|
88337ab358 | ||
|
|
0935fe3fe3 | ||
|
|
96ed19549e | ||
|
|
945e9a2cb5 | ||
|
|
4d703e3807 | ||
|
|
f537f62adf | ||
|
|
7840404132 | ||
|
|
afad361cac | ||
|
|
58b23c290c | ||
|
|
12a73137d7 | ||
|
|
7a136eec5d | ||
|
|
e9bd9ab3a6 | ||
|
|
083bff62e3 | ||
|
|
f713316541 | ||
|
|
bcfdf2f70d | ||
|
|
018d6f877f | ||
|
|
1e4172b820 | ||
|
|
6b6827b70d | ||
|
|
96cccc83c6 | ||
|
|
2468a3a854 | ||
|
|
f68825f2ed | ||
|
|
2a12f71f96 | ||
|
|
74b64a5451 | ||
|
|
7d01e0349b | ||
|
|
80014bf2b1 | ||
|
|
adf3421f4a | ||
|
|
51c65ccfdc | ||
|
|
5d37d08cf8 | ||
|
|
eb18d75b2e | ||
|
|
549c582287 | ||
|
|
6c05029a9f | ||
|
|
b2278986dd | ||
|
|
98a2b8f159 | ||
|
|
0ba891fb14 | ||
|
|
042cd670aa | ||
|
|
95c7e4d02b | ||
|
|
d302525674 | ||
|
|
3dbb7298f8 | ||
|
|
0ed71ab53b | ||
|
|
b1491ae5b1 | ||
|
|
03b8dc8027 | ||
|
|
e3568cd89f | ||
|
|
b35e24daa7 | ||
|
|
f506b27a31 | ||
|
|
9452d5be1d | ||
|
|
ea3f449a08 | ||
|
|
dc9510fe25 | ||
|
|
731dd761d9 | ||
|
|
6726ccb2fb | ||
|
|
5fce0f9ecf | ||
|
|
ad03fc4118 | ||
|
|
3f77ff8815 | ||
|
|
26b5fa1e38 | ||
|
|
24286e6016 | ||
|
|
626577fe3d | ||
|
|
176915b59c | ||
|
|
0b60933c2a | ||
|
|
0e31d6936a | ||
|
|
78d9c43ef1 | ||
|
|
ce253a9563 | ||
|
|
18c3c36727 | ||
|
|
4e6f6a4579 | ||
|
|
84487f4d8d | ||
|
|
45f5a07989 | ||
|
|
0061883dbf | ||
|
|
47e3be11f0 | ||
|
|
420f4000c2 | ||
|
|
4582ce8769 | ||
|
|
3beb39f30b | ||
|
|
b72d6c058a | ||
|
|
0c8f367b29 | ||
|
|
50045edb4f | ||
|
|
f538d1aa43 | ||
|
|
4bd8d2a3b7 | ||
|
|
9fa952d968 | ||
|
|
1edad92d73 | ||
|
|
52282baeff | ||
|
|
85ab9d211d | ||
|
|
6e99a4dcfc | ||
|
|
35c91827af | ||
|
|
5c11b237f3 | ||
|
|
432b5d0427 | ||
|
|
b8b1fadfa1 | ||
|
|
74f5d8a066 | ||
|
|
09aeeccfa7 | ||
|
|
094abdc194 | ||
|
|
f4b97028b0 | ||
|
|
e14cbf2163 | ||
|
|
032cbab7b0 | ||
|
|
e306856c4f | ||
|
|
c1f164190b | ||
|
|
8030a078ff | ||
|
|
11adcbed25 | ||
|
|
c2b10859c9 | ||
|
|
e0e2c76d2f | ||
|
|
b0238c1560 | ||
|
|
5ffb52a17d | ||
|
|
6b34e72418 | ||
|
|
d43c632e55 | ||
|
|
d55dfdebde | ||
|
|
4b12876ebc | ||
|
|
3e12449b8f | ||
|
|
fdc3f302d9 | ||
|
|
07642fec83 | ||
|
|
4ce93232c3 | ||
|
|
ccea370cd9 | ||
|
|
8f6885689b | ||
|
|
64b15fafe2 | ||
|
|
d891acb8c0 | ||
|
|
de0bc718be | ||
|
|
283a06e200 | ||
|
|
3cfb2f8339 | ||
|
|
21164885e8 | ||
|
|
0d745c4fea |
@@ -4,6 +4,8 @@ about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
⚠️ **Issues not using this template will be systematically closed.**
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
@@ -18,8 +20,8 @@ A clear and concise description of what you expected to happen.
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Logs**
|
||||
If applicable, copy logs from your console here. Please *do not*
|
||||
use screenshots of logs, copy them as text.
|
||||
If applicable, copy **full** logs from your console here. Please *do not*
|
||||
use screenshots of logs, copy them as text, use gist or attach an *uncompressed* file.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
|
||||
+9
-9
@@ -5,7 +5,7 @@
|
||||
To build Filament, you must first install the following tools:
|
||||
|
||||
- CMake 3.19 (or more recent)
|
||||
- clang 7.0 (or more recent)
|
||||
- clang 14.0 (or more recent)
|
||||
- [ninja 1.10](https://github.com/ninja-build/ninja/wiki/Pre-built-Ninja-packages) (or more recent)
|
||||
|
||||
Additional dependencies may be required for your operating system. Please refer to the appropriate
|
||||
@@ -78,7 +78,7 @@ To turn an option on or off:
|
||||
|
||||
```
|
||||
$ cd <cmake-build-directory>
|
||||
$ cmake . -DOPTION=ON # Relace OPTION with the option name, set to ON / OFF
|
||||
$ cmake . -DOPTION=ON # Replace OPTION with the option name, set to ON / OFF
|
||||
```
|
||||
|
||||
Options can also be set with the CMake GUI.
|
||||
@@ -87,10 +87,10 @@ Options can also be set with the CMake GUI.
|
||||
|
||||
Make sure you've installed the following dependencies:
|
||||
|
||||
- `clang-7` or higher
|
||||
- `clang-14` or higher
|
||||
- `libglu1-mesa-dev`
|
||||
- `libc++-7-dev` (`libcxx-devel` and `libcxx-static` on Fedora) or higher
|
||||
- `libc++abi-7-dev` (`libcxxabi-static` on Fedora) or higher
|
||||
- `libc++-14-dev` (`libcxx-devel` and `libcxx-static` on Fedora) or higher
|
||||
- `libc++abi-14-dev` (`libcxxabi-static` on Fedora) or higher
|
||||
- `ninja-build`
|
||||
- `libxi-dev`
|
||||
- `libxcomposite-dev` (`libXcomposite-devel` on Fedora)
|
||||
@@ -114,7 +114,7 @@ Your Linux distribution might default to `gcc` instead of `clang`, if that's the
|
||||
```
|
||||
$ mkdir out/cmake-release
|
||||
$ cd out/cmake-release
|
||||
# Or use a specific version of clang, for instance /usr/bin/clang-7
|
||||
# Or use a specific version of clang, for instance /usr/bin/clang-14
|
||||
$ CC=/usr/bin/clang CXX=/usr/bin/clang++ CXXFLAGS=-stdlib=libc++ \
|
||||
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../release/filament ../..
|
||||
```
|
||||
@@ -124,8 +124,8 @@ solution is to use `update-alternatives` to both change the default compiler, an
|
||||
specific version of clang:
|
||||
|
||||
```
|
||||
$ update-alternatives --install /usr/bin/clang clang /usr/bin/clang-7 100
|
||||
$ update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-7 100
|
||||
$ update-alternatives --install /usr/bin/clang clang /usr/bin/clang-14 100
|
||||
$ update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-14 100
|
||||
$ update-alternatives --install /usr/bin/cc cc /usr/bin/clang 100
|
||||
$ update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 100
|
||||
```
|
||||
@@ -181,7 +181,7 @@ Install the following components:
|
||||
- [Python 3.7](https://www.python.org/ftp/python/3.7.0/python-3.7.0.exe)
|
||||
- [CMake 3.14 or later](https://github.com/Kitware/CMake/releases/download/v3.14.7/cmake-3.14.7-win64-x64.msi)
|
||||
|
||||
The latest Windows SDK can also by installed by opening Visual Studio and selecting _Get Tools and
|
||||
The latest Windows SDK can also be installed by opening Visual Studio and selecting _Get Tools and
|
||||
Features..._ under the _Tools_ menu.
|
||||
|
||||
By default, Windows treats the file system as case insensitive. Please do not enable case
|
||||
|
||||
@@ -372,6 +372,12 @@ if (ANDROID OR IOS OR WEBGL)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Turn off exceptions on iOS debug as well. This fixes an availability error we see when using
|
||||
# std::visit, which is not supported on iOS 11.0 when exceptions are enabled.
|
||||
if (IOS)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-exceptions")
|
||||
endif()
|
||||
|
||||
# With WebGL, we disable RTTI even for debug builds because we pass emscripten::val back and forth
|
||||
# between C++ and JavaScript in order to efficiently access typed arrays, which are unbound.
|
||||
# NOTE: This is not documented in emscripten so we should consider a different approach.
|
||||
|
||||
@@ -7,3 +7,9 @@ for next branch cut* header.
|
||||
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||
|
||||
## Release notes for next branch cut
|
||||
|
||||
- Fix possible NPE when updating fog options from Java/Kotlin
|
||||
- The `emissive` property was not applied properly to `MASKED` materials, and could cause
|
||||
dark fringes to appear (recompile materials)
|
||||
- Allow glTF materials with transmission/volume extensions to choose their alpha mode
|
||||
instead of forcing `MASKED`
|
||||
|
||||
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.38.0'
|
||||
implementation 'com.google.android.filament:filament-android:1.42.0'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -40,6 +40,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
||||
| Artifact | Description |
|
||||
| ------------- | ------------- |
|
||||
| [](https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android) | The Filament rendering engine itself. |
|
||||
| [](https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-android-debug) | Debug version of `filament-android`. |
|
||||
| [](https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/gltfio-android) | A glTF 2.0 loader for Filament, depends on `filament-android`. |
|
||||
| [](https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filament-utils-android) | KTX loading, Kotlin math, and camera utilities, depends on `gltfio-android`. |
|
||||
| [](https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android) | A runtime material builder/compiler. This library is large but contains a full shader compiler/validator/optimizer and supports both OpenGL and Vulkan. |
|
||||
@@ -50,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:
|
||||
|
||||
```
|
||||
pod 'Filament', '~> 1.38.0'
|
||||
pod 'Filament', '~> 1.42.0'
|
||||
```
|
||||
|
||||
### Snapshots
|
||||
|
||||
+7
-3
@@ -9,11 +9,15 @@ Before starting, ensure that each of these branches is up-to-date with origin:
|
||||
- rc/$RELEASE
|
||||
- main
|
||||
|
||||
## 0. Make sure the rc/$RELEASE branch has the correct version.
|
||||
## 0. Check versions.
|
||||
|
||||
It should have the version corresponding to its name, $RELEASE.
|
||||
Make sure the rc/$RELEASE branch has the correct Filament version. It should have the version
|
||||
corresponding to its name, $RELEASE.
|
||||
|
||||
## 1. Bump versions on main to $RELEASE.
|
||||
Make sure `MATERIAL_VERSION` has been bumped to a new version if this is a MAJOR or MINOR release
|
||||
(first two version numbers).
|
||||
|
||||
## 1. Bump Filament versions on main to $RELEASE.
|
||||
|
||||
Checkout main and run the following command to bump Filament's version to $RELEASE:
|
||||
|
||||
|
||||
@@ -7,6 +7,58 @@ 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.42.1
|
||||
|
||||
## v1.42.0
|
||||
|
||||
- engine: add preliminary support for instanced stereoscopic rendering [⚠️ **Recompile materials**]
|
||||
|
||||
## v1.41.0
|
||||
|
||||
- backend: fix #6997 : picking can fail on Adreno [⚠️ **New Material Version**]
|
||||
- backend: A partial workaround for PowerVR devices (#5118, b/190221124) [⚠️ **Recompile Materials**]
|
||||
|
||||
## v1.40.5
|
||||
|
||||
- backend: Disable timer queries on all Mali GPUs (fixes b/233754398)
|
||||
- engine: Add a way to query the validity of most filament objects (see `Engine::isValid`)
|
||||
- opengl: fix b/290388359 : possible crash when shutting down the engine
|
||||
- engine: Improve precision of frame time measurement when using emulated TimerQueries
|
||||
- backend: Improve frame pacing on Android and Vulkan.
|
||||
- backend: workaround b/291140208 (gltf_viewer crashes on Nexus 6P)
|
||||
- engine: support `setDepthFunc` for `MaterialInstance`
|
||||
- web: Added setDepthFunc()/getDepthFunc() to MaterialInstance
|
||||
- android: Added setDepthFunc()/getDepthFunc() to MaterialInstance
|
||||
|
||||
## v1.40.4
|
||||
|
||||
- gltfio: fix crash when compute morph target without material
|
||||
- matc: fix buggy `variant-filter` flag
|
||||
- web: Added missing setMat3Parameter()/setMat4Parameter() to MaterialInstance
|
||||
- opengl: fix b/290670707 : crash when using the blob cache
|
||||
- engine: fix a crash with `Material::compile()` when a callback is specified
|
||||
|
||||
## v1.40.3
|
||||
|
||||
## v1.40.2
|
||||
|
||||
- rendering: dynamic resolution would not work with a translucent render target and quality > low
|
||||
- Java/Kotlin: user callbacks were not invoked on successful texture upload
|
||||
|
||||
## v1.40.1
|
||||
|
||||
## v1.40.0
|
||||
|
||||
- matc: fix VSM high precision option on mobile [⚠️ **Recompile materials**]
|
||||
- vulkan: support sRGB swap chain
|
||||
- Add new `getMaxAutomaticInstances()` API on `Engine` to get max supported automatic instances.
|
||||
- UiHelper: fix jank when a `TextureView` is resized (fixes b\282220665)
|
||||
- backend: parallel shader compilation support. This breaks and improves the recent `Material::compile` API.
|
||||
|
||||
## v1.39.0
|
||||
|
||||
- matc: workaround a bug in spirv-tools causing vsm to fail [⚠️ **Recompile materials**]
|
||||
|
||||
## v1.38.0
|
||||
|
||||
- engine: a new feature to set a transform on the global-scale fog [⚠️ **Recompile materials**]
|
||||
|
||||
+1
-1
@@ -135,7 +135,7 @@ gradlew -Pcom.google.android.filament.dist-dir=..\out\android-release\filament a
|
||||
If you're only interested in building SDK, you may skip samples build by passing a `com.google.android.filament.skip-samples` flag:
|
||||
|
||||
```
|
||||
gradlew -Pcom.google.android.filament.dist-dir=..\out\android-release\filament assembleRelease -Pfilament_skip_samples
|
||||
gradlew -Pcom.google.android.filament.dist-dir=..\out\android-release\filament assembleRelease -Pcom.google.android.filament.skip-samples
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -83,12 +83,12 @@ buildscript {
|
||||
'minSdk': 19,
|
||||
'targetSdk': 33,
|
||||
'compileSdk': 33,
|
||||
'kotlin': '1.8.20',
|
||||
'kotlin_coroutines': '1.6.4',
|
||||
'buildTools': '33.0.2',
|
||||
'kotlin': '1.9.0',
|
||||
'kotlin_coroutines': '1.7.2',
|
||||
'buildTools': '34.0.0',
|
||||
'ndk': '25.1.8937393',
|
||||
'androidx_core': '1.10.0',
|
||||
'androidx_annotations': '1.3.0'
|
||||
'androidx_core': '1.10.1',
|
||||
'androidx_annotations': '1.6.0'
|
||||
]
|
||||
|
||||
ext.deps = [
|
||||
@@ -104,7 +104,7 @@ buildscript {
|
||||
]
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.0.0'
|
||||
classpath 'com.android.tools.build:gradle:8.1.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ buildscript {
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "io.github.gradle-nexus.publish-plugin" version "1.1.0"
|
||||
id "io.github.gradle-nexus.publish-plugin" version "1.3.0"
|
||||
}
|
||||
|
||||
// See https://github.com/gradle-nexus/publish-plugin
|
||||
|
||||
@@ -278,6 +278,112 @@ Java_com_google_android_filament_Engine_nDestroyEntity(JNIEnv*, jclass,
|
||||
engine->destroy(entity);
|
||||
}
|
||||
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nIsValidRenderer(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jlong nativeRenderer) {
|
||||
Engine* engine = (Engine *)nativeEngine;
|
||||
return (jboolean)engine->isValid((Renderer*)nativeRenderer);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nIsValidView(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jlong nativeView) {
|
||||
Engine* engine = (Engine *)nativeEngine;
|
||||
return (jboolean)engine->isValid((View*)nativeView);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nIsValidScene(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jlong nativeScene) {
|
||||
Engine* engine = (Engine *)nativeEngine;
|
||||
return (jboolean)engine->isValid((Scene*)nativeScene);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nIsValidFence(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jlong nativeFence) {
|
||||
Engine* engine = (Engine *)nativeEngine;
|
||||
return (jboolean)engine->isValid((Fence*)nativeFence);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nIsValidStream(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jlong nativeStream) {
|
||||
Engine* engine = (Engine *)nativeEngine;
|
||||
return (jboolean)engine->isValid((Stream*)nativeStream);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nIsValidIndexBuffer(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jlong nativeIndexBuffer) {
|
||||
Engine* engine = (Engine *)nativeEngine;
|
||||
return (jboolean)engine->isValid((IndexBuffer*)nativeIndexBuffer);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nIsValidVertexBuffer(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jlong nativeVertexBuffer) {
|
||||
Engine* engine = (Engine *)nativeEngine;
|
||||
return (jboolean)engine->isValid((VertexBuffer*)nativeVertexBuffer);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nIsValidSkinningBuffer(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jlong nativeSkinningBuffer) {
|
||||
Engine* engine = (Engine *)nativeEngine;
|
||||
return (jboolean)engine->isValid((SkinningBuffer*)nativeSkinningBuffer);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nIsValidIndirectLight(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jlong nativeIndirectLight) {
|
||||
Engine* engine = (Engine *)nativeEngine;
|
||||
return (jboolean)engine->isValid((IndirectLight*)nativeIndirectLight);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nIsValidMaterial(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jlong nativeMaterial) {
|
||||
Engine* engine = (Engine *)nativeEngine;
|
||||
return (jboolean)engine->isValid((Material*)nativeMaterial);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nIsValidSkybox(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jlong nativeSkybox) {
|
||||
Engine* engine = (Engine *)nativeEngine;
|
||||
return (jboolean)engine->isValid((Skybox*)nativeSkybox);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nIsValidColorGrading(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jlong nativeColorGrading) {
|
||||
Engine* engine = (Engine *)nativeEngine;
|
||||
return (jboolean)engine->isValid((ColorGrading*)nativeColorGrading);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nIsValidTexture(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jlong nativeTexture) {
|
||||
Engine* engine = (Engine *)nativeEngine;
|
||||
return (jboolean)engine->isValid((Texture*)nativeTexture);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nIsValidRenderTarget(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jlong nativeTarget) {
|
||||
Engine* engine = (Engine *)nativeEngine;
|
||||
return (jboolean)engine->isValid((RenderTarget*)nativeTarget);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Engine_nIsValidSwapChain(JNIEnv*, jclass,
|
||||
jlong nativeEngine, jlong nativeSwapChain) {
|
||||
Engine* engine = (Engine *)nativeEngine;
|
||||
return (jboolean)engine->isValid((SwapChain*)nativeSwapChain);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_Engine_nFlushAndWait(JNIEnv*, jclass,
|
||||
jlong nativeEngine) {
|
||||
|
||||
@@ -246,17 +246,21 @@ Java_com_google_android_filament_MaterialInstance_nSetFloatParameterArray(JNIEnv
|
||||
env->ReleaseStringUTFChars(name_, name);
|
||||
}
|
||||
|
||||
// defined in TextureSampler.cpp
|
||||
namespace filament::JniUtils {
|
||||
TextureSampler from_long(jlong params) noexcept;
|
||||
} // TextureSamplerJniUtils
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_MaterialInstance_nSetParameterTexture(
|
||||
JNIEnv *env, jclass, jlong nativeMaterialInstance, jstring name_,
|
||||
jlong nativeTexture, jint sampler_) {
|
||||
jlong nativeTexture, jlong sampler_) {
|
||||
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
|
||||
Texture* texture = (Texture*) nativeTexture;
|
||||
TextureSampler& sampler = reinterpret_cast<TextureSampler&>(sampler_);
|
||||
|
||||
const char *name = env->GetStringUTFChars(name_, 0);
|
||||
instance->setParameter(name, texture, sampler);
|
||||
instance->setParameter(name, texture, JniUtils::from_long(sampler_));
|
||||
env->ReleaseStringUTFChars(name_, name);
|
||||
}
|
||||
|
||||
@@ -357,6 +361,14 @@ Java_com_google_android_filament_MaterialInstance_nSetDepthCulling(JNIEnv*,
|
||||
instance->setDepthCulling(enable);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_MaterialInstance_nSetDepthFunc(JNIEnv*,
|
||||
jclass, jlong nativeMaterialInstance, jlong function) {
|
||||
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
|
||||
instance->setDepthFunc(static_cast<MaterialInstance::DepthFunc>(function));
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_MaterialInstance_nSetStencilCompareFunction(JNIEnv*, jclass,
|
||||
@@ -524,3 +536,11 @@ Java_com_google_android_filament_MaterialInstance_nIsDepthCullingEnabled(JNIEnv*
|
||||
MaterialInstance* instance = (MaterialInstance*)nativeMaterialInstance;
|
||||
return instance->isDepthCullingEnabled();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_MaterialInstance_nGetDepthFunc(JNIEnv* env, jclass clazz,
|
||||
jlong nativeMaterialInstance) {
|
||||
MaterialInstance* instance = (MaterialInstance*)nativeMaterialInstance;
|
||||
return (jint)instance->getDepthFunc();
|
||||
}
|
||||
|
||||
@@ -27,11 +27,10 @@ extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_SwapChain_nSetFrameCompletedCallback(JNIEnv* env, jclass,
|
||||
jlong nativeSwapChain, jobject handler, jobject runnable) {
|
||||
SwapChain* swapChain = (SwapChain*) nativeSwapChain;
|
||||
auto *callback = JniCallback::make(env, handler, runnable);
|
||||
swapChain->setFrameCompletedCallback([](void* user) {
|
||||
JniCallback* callback = (JniCallback*)user;
|
||||
auto* callback = JniCallback::make(env, handler, runnable);
|
||||
swapChain->setFrameCompletedCallback(nullptr, [callback](SwapChain* swapChain) {
|
||||
JniCallback::postToJavaAndDestroy(callback);
|
||||
}, callback);
|
||||
});
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
|
||||
@@ -511,9 +511,7 @@ public:
|
||||
|
||||
private:
|
||||
void* mData = nullptr;
|
||||
jobject mBitmap = nullptr;
|
||||
jobject mHandler = nullptr;
|
||||
jobject mCallback = nullptr;
|
||||
jobject mBitmap{};
|
||||
AndroidBitmapInfo mInfo{};
|
||||
};
|
||||
|
||||
|
||||
@@ -18,142 +18,139 @@
|
||||
|
||||
#include <filament/TextureSampler.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
|
||||
using namespace filament;
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nCreateSampler(JNIEnv *env, jclass type, jint min,
|
||||
namespace filament::JniUtils {
|
||||
|
||||
jlong to_long(TextureSampler const& sampler) noexcept {
|
||||
return jlong(utils::bit_cast<uint32_t>(sampler.getSamplerParams()));
|
||||
}
|
||||
|
||||
TextureSampler from_long(jlong params) noexcept {
|
||||
return TextureSampler{
|
||||
utils::bit_cast<backend::SamplerParams>(
|
||||
static_cast<uint32_t>(params))};
|
||||
}
|
||||
|
||||
} // namespace filament::JniUtils
|
||||
|
||||
using namespace JniUtils;
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nCreateSampler(JNIEnv *, jclass, jint min,
|
||||
jint max, jint s, jint t, jint r) {
|
||||
return TextureSampler(static_cast<TextureSampler::MinFilter>(min),
|
||||
static_cast<TextureSampler::MagFilter>(max), static_cast<TextureSampler::WrapMode>(s),
|
||||
static_cast<TextureSampler::WrapMode>(t),
|
||||
static_cast<TextureSampler::WrapMode>(r)).getSamplerParams().u;
|
||||
TextureSampler sampler(static_cast<TextureSampler::MinFilter>(min),
|
||||
static_cast<TextureSampler::MagFilter>(max),
|
||||
static_cast<TextureSampler::WrapMode>(s),
|
||||
static_cast<TextureSampler::WrapMode>(t),
|
||||
static_cast<TextureSampler::WrapMode>(r));
|
||||
return to_long(sampler);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nCreateCompareSampler(JNIEnv *env, jclass type,
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nCreateCompareSampler(JNIEnv *, jclass,
|
||||
jint mode, jint function) {
|
||||
return TextureSampler(static_cast<TextureSampler::CompareMode>(mode),
|
||||
static_cast<TextureSampler::CompareFunc>(function)).getSamplerParams().u;
|
||||
TextureSampler sampler(static_cast<TextureSampler::CompareMode>(mode),
|
||||
static_cast<TextureSampler::CompareFunc>(function));
|
||||
return to_long(sampler);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nGetMinFilter(JNIEnv *env, jclass type,
|
||||
jint sampler_) {
|
||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
||||
return static_cast<jint>(sampler.getMinFilter());
|
||||
Java_com_google_android_filament_TextureSampler_nGetMinFilter(JNIEnv *, jclass, jlong sampler) {
|
||||
return static_cast<jint>(from_long(sampler).getMinFilter());
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nSetMinFilter(JNIEnv *env, jclass type,
|
||||
jint sampler_, jint filter) {
|
||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nSetMinFilter(JNIEnv *, jclass, jlong sampler_, jint filter) {
|
||||
TextureSampler sampler{from_long(sampler_)};
|
||||
sampler.setMinFilter(static_cast<TextureSampler::MinFilter>(filter));
|
||||
return sampler.getSamplerParams().u;
|
||||
return to_long(sampler);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nGetMagFilter(JNIEnv *env, jclass type,
|
||||
jint sampler_) {
|
||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
||||
return static_cast<jint>(sampler.getMagFilter());
|
||||
Java_com_google_android_filament_TextureSampler_nGetMagFilter(JNIEnv *, jclass, jlong sampler) {
|
||||
return static_cast<jint>(from_long(sampler).getMagFilter());
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nSetMagFilter(JNIEnv *env, jclass type,
|
||||
jint sampler_, jint filter) {
|
||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nSetMagFilter(JNIEnv *, jclass, jlong sampler_, jint filter) {
|
||||
TextureSampler sampler{from_long(sampler_)};
|
||||
sampler.setMagFilter(static_cast<TextureSampler::MagFilter>(filter));
|
||||
return sampler.getSamplerParams().u;
|
||||
return to_long(sampler);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nGetWrapModeS(JNIEnv *env, jclass type,
|
||||
jint sampler_) {
|
||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
||||
return static_cast<jint>(sampler.getWrapModeS());
|
||||
Java_com_google_android_filament_TextureSampler_nGetWrapModeS(JNIEnv *, jclass, jlong sampler) {
|
||||
return static_cast<jint>(from_long(sampler).getWrapModeS());
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nSetWrapModeS(JNIEnv *env, jclass type,
|
||||
jint sampler_, jint mode) {
|
||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nSetWrapModeS(JNIEnv *, jclass, jlong sampler_, jint mode) {
|
||||
TextureSampler sampler{from_long(sampler_)};
|
||||
sampler.setWrapModeS(static_cast<TextureSampler::WrapMode>(mode));
|
||||
return sampler.getSamplerParams().u;
|
||||
return to_long(sampler);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nGetWrapModeT(JNIEnv *env, jclass type,
|
||||
jint sampler_) {
|
||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
||||
return static_cast<jint>(sampler.getWrapModeT());
|
||||
Java_com_google_android_filament_TextureSampler_nGetWrapModeT(JNIEnv *, jclass, jlong sampler) {
|
||||
return static_cast<jint>(from_long(sampler).getWrapModeT());
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nSetWrapModeT(JNIEnv *env, jclass type,
|
||||
jint sampler_, jint mode) {
|
||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nSetWrapModeT(JNIEnv *, jclass, jlong sampler_, jint mode) {
|
||||
TextureSampler sampler{from_long(sampler_)};
|
||||
sampler.setWrapModeT(static_cast<TextureSampler::WrapMode>(mode));
|
||||
return sampler.getSamplerParams().u;
|
||||
return to_long(sampler);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nGetWrapModeR(JNIEnv *env, jclass type,
|
||||
jint sampler_) {
|
||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
||||
return static_cast<jint>(sampler.getWrapModeR());
|
||||
Java_com_google_android_filament_TextureSampler_nGetWrapModeR(JNIEnv *, jclass, jlong sampler) {
|
||||
return static_cast<jint>(from_long(sampler).getWrapModeR());
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nSetWrapModeR(JNIEnv *env, jclass type,
|
||||
jint sampler_, jint mode) {
|
||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nSetWrapModeR(JNIEnv *, jclass, jlong sampler_, jint mode) {
|
||||
TextureSampler sampler{from_long(sampler_)};
|
||||
sampler.setWrapModeR(static_cast<TextureSampler::WrapMode>(mode));
|
||||
return sampler.getSamplerParams().u;
|
||||
return to_long(sampler);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nGetCompareMode(JNIEnv *env, jclass type,
|
||||
jint sampler_) {
|
||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
||||
return static_cast<jint>(sampler.getCompareMode());
|
||||
Java_com_google_android_filament_TextureSampler_nGetCompareMode(JNIEnv *, jclass, jlong sampler) {
|
||||
return static_cast<jint>(from_long(sampler).getCompareMode());
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nSetCompareMode(JNIEnv *env, jclass type,
|
||||
jint sampler_, jint mode) {
|
||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nSetCompareMode(JNIEnv *, jclass, jlong sampler_, jint mode) {
|
||||
TextureSampler sampler{from_long(sampler_)};
|
||||
sampler.setCompareMode(static_cast<TextureSampler::CompareMode>(mode),
|
||||
sampler.getCompareFunc());
|
||||
return sampler.getSamplerParams().u;
|
||||
return to_long(sampler);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nGetCompareFunction(JNIEnv *env, jclass type,
|
||||
jint sampler_) {
|
||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
||||
return static_cast<jint>(sampler.getCompareFunc());
|
||||
Java_com_google_android_filament_TextureSampler_nGetCompareFunction(JNIEnv *, jclass, jlong sampler) {
|
||||
return static_cast<jint>(from_long(sampler).getCompareFunc());
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nSetCompareFunction(JNIEnv *env, jclass type,
|
||||
jint sampler_, jint function) {
|
||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nSetCompareFunction(JNIEnv *, jclass, jlong sampler_, jint function) {
|
||||
TextureSampler sampler{from_long(sampler_)};
|
||||
sampler.setCompareMode(sampler.getCompareMode(),
|
||||
static_cast<TextureSampler::CompareFunc>(function));
|
||||
return sampler.getSamplerParams().u;
|
||||
return to_long(sampler);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jfloat JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nGetAnisotropy(JNIEnv *env, jclass type,
|
||||
jint sampler_) {
|
||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
||||
return sampler.getAnisotropy();
|
||||
Java_com_google_android_filament_TextureSampler_nGetAnisotropy(JNIEnv *, jclass, jlong sampler) {
|
||||
return from_long(sampler).getAnisotropy();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nSetAnisotropy(JNIEnv *env, jclass type,
|
||||
jint sampler_, jfloat anisotropy) {
|
||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_TextureSampler_nSetAnisotropy(JNIEnv *, jclass, jlong sampler_, jfloat anisotropy) {
|
||||
TextureSampler sampler{from_long(sampler_)};
|
||||
sampler.setAnisotropy(anisotropy);
|
||||
return sampler.getSamplerParams().u;
|
||||
return to_long(sampler);
|
||||
}
|
||||
|
||||
@@ -449,6 +449,141 @@ public class Engine {
|
||||
swapChain.clearNativeObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the object is valid.
|
||||
* @param object Object to check for validity
|
||||
* @return returns true if the specified object is valid.
|
||||
*/
|
||||
public boolean isValidRenderer(@NonNull Renderer object) {
|
||||
return nIsValidRenderer(getNativeObject(), object.getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the object is valid.
|
||||
* @param object Object to check for validity
|
||||
* @return returns true if the specified object is valid.
|
||||
*/
|
||||
public boolean isValidView(@NonNull View object) {
|
||||
return nIsValidView(getNativeObject(), object.getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the object is valid.
|
||||
* @param object Object to check for validity
|
||||
* @return returns true if the specified object is valid.
|
||||
*/
|
||||
public boolean isValidScene(@NonNull Scene object) {
|
||||
return nIsValidScene(getNativeObject(), object.getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the object is valid.
|
||||
* @param object Object to check for validity
|
||||
* @return returns true if the specified object is valid.
|
||||
*/
|
||||
public boolean isValidFence(@NonNull Fence object) {
|
||||
return nIsValidFence(getNativeObject(), object.getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the object is valid.
|
||||
* @param object Object to check for validity
|
||||
* @return returns true if the specified object is valid.
|
||||
*/
|
||||
public boolean isValidStream(@NonNull Stream object) {
|
||||
return nIsValidStream(getNativeObject(), object.getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the object is valid.
|
||||
* @param object Object to check for validity
|
||||
* @return returns true if the specified object is valid.
|
||||
*/
|
||||
public boolean isValidIndexBuffer(@NonNull IndexBuffer object) {
|
||||
return nIsValidIndexBuffer(getNativeObject(), object.getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the object is valid.
|
||||
* @param object Object to check for validity
|
||||
* @return returns true if the specified object is valid.
|
||||
*/
|
||||
public boolean isValidVertexBuffer(@NonNull VertexBuffer object) {
|
||||
return nIsValidVertexBuffer(getNativeObject(), object.getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the object is valid.
|
||||
* @param object Object to check for validity
|
||||
* @return returns true if the specified object is valid.
|
||||
*/
|
||||
public boolean isValidSkinningBuffer(@NonNull SkinningBuffer object) {
|
||||
return nIsValidSkinningBuffer(getNativeObject(), object.getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the object is valid.
|
||||
* @param object Object to check for validity
|
||||
* @return returns true if the specified object is valid.
|
||||
*/
|
||||
public boolean isValidIndirectLight(@NonNull IndirectLight object) {
|
||||
return nIsValidIndirectLight(getNativeObject(), object.getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the object is valid.
|
||||
* @param object Object to check for validity
|
||||
* @return returns true if the specified object is valid.
|
||||
*/
|
||||
public boolean isValidMaterial(@NonNull Material object) {
|
||||
return nIsValidMaterial(getNativeObject(), object.getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the object is valid.
|
||||
* @param object Object to check for validity
|
||||
* @return returns true if the specified object is valid.
|
||||
*/
|
||||
public boolean isValidSkybox(@NonNull Skybox object) {
|
||||
return nIsValidSkybox(getNativeObject(), object.getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the object is valid.
|
||||
* @param object Object to check for validity
|
||||
* @return returns true if the specified object is valid.
|
||||
*/
|
||||
public boolean isValidColorGrading(@NonNull ColorGrading object) {
|
||||
return nIsValidColorGrading(getNativeObject(), object.getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the object is valid.
|
||||
* @param object Object to check for validity
|
||||
* @return returns true if the specified object is valid.
|
||||
*/
|
||||
public boolean isValidTexture(@NonNull Texture object) {
|
||||
return nIsValidTexture(getNativeObject(), object.getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the object is valid.
|
||||
* @param object Object to check for validity
|
||||
* @return returns true if the specified object is valid.
|
||||
*/
|
||||
public boolean isValidRenderTarget(@NonNull RenderTarget object) {
|
||||
return nIsValidRenderTarget(getNativeObject(), object.getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the object is valid.
|
||||
* @param object Object to check for validity
|
||||
* @return returns true if the specified object is valid.
|
||||
*/
|
||||
public boolean isValidSwapChain(@NonNull SwapChain object) {
|
||||
return nIsValidSwapChain(getNativeObject(), object.getNativeObject());
|
||||
}
|
||||
|
||||
// View
|
||||
|
||||
/**
|
||||
@@ -785,17 +920,17 @@ public class Engine {
|
||||
private static native long nCreateSwapChain(long nativeEngine, Object nativeWindow, long flags);
|
||||
private static native long nCreateSwapChainHeadless(long nativeEngine, int width, int height, long flags);
|
||||
private static native long nCreateSwapChainFromRawPointer(long nativeEngine, long pointer, long flags);
|
||||
private static native boolean nDestroySwapChain(long nativeEngine, long nativeSwapChain);
|
||||
private static native long nCreateView(long nativeEngine);
|
||||
private static native boolean nDestroyView(long nativeEngine, long nativeView);
|
||||
private static native long nCreateRenderer(long nativeEngine);
|
||||
private static native boolean nDestroyRenderer(long nativeEngine, long nativeRenderer);
|
||||
private static native long nCreateCamera(long nativeEngine, int entity);
|
||||
private static native long nGetCameraComponent(long nativeEngine, int entity);
|
||||
private static native void nDestroyCameraComponent(long nativeEngine, int entity);
|
||||
private static native long nCreateScene(long nativeEngine);
|
||||
private static native boolean nDestroyScene(long nativeEngine, long nativeScene);
|
||||
private static native long nCreateFence(long nativeEngine);
|
||||
|
||||
private static native boolean nDestroyRenderer(long nativeEngine, long nativeRenderer);
|
||||
private static native boolean nDestroyView(long nativeEngine, long nativeView);
|
||||
private static native boolean nDestroyScene(long nativeEngine, long nativeScene);
|
||||
private static native boolean nDestroyFence(long nativeEngine, long nativeFence);
|
||||
private static native boolean nDestroyStream(long nativeEngine, long nativeStream);
|
||||
private static native boolean nDestroyIndexBuffer(long nativeEngine, long nativeIndexBuffer);
|
||||
@@ -808,6 +943,22 @@ public class Engine {
|
||||
private static native boolean nDestroyColorGrading(long nativeEngine, long nativeColorGrading);
|
||||
private static native boolean nDestroyTexture(long nativeEngine, long nativeTexture);
|
||||
private static native boolean nDestroyRenderTarget(long nativeEngine, long nativeTarget);
|
||||
private static native boolean nDestroySwapChain(long nativeEngine, long nativeSwapChain);
|
||||
private static native boolean nIsValidRenderer(long nativeEngine, long nativeRenderer);
|
||||
private static native boolean nIsValidView(long nativeEngine, long nativeView);
|
||||
private static native boolean nIsValidScene(long nativeEngine, long nativeScene);
|
||||
private static native boolean nIsValidFence(long nativeEngine, long nativeFence);
|
||||
private static native boolean nIsValidStream(long nativeEngine, long nativeStream);
|
||||
private static native boolean nIsValidIndexBuffer(long nativeEngine, long nativeIndexBuffer);
|
||||
private static native boolean nIsValidVertexBuffer(long nativeEngine, long nativeVertexBuffer);
|
||||
private static native boolean nIsValidSkinningBuffer(long nativeEngine, long nativeSkinningBuffer);
|
||||
private static native boolean nIsValidIndirectLight(long nativeEngine, long nativeIndirectLight);
|
||||
private static native boolean nIsValidMaterial(long nativeEngine, long nativeMaterial);
|
||||
private static native boolean nIsValidSkybox(long nativeEngine, long nativeSkybox);
|
||||
private static native boolean nIsValidColorGrading(long nativeEngine, long nativeColorGrading);
|
||||
private static native boolean nIsValidTexture(long nativeEngine, long nativeTexture);
|
||||
private static native boolean nIsValidRenderTarget(long nativeEngine, long nativeTarget);
|
||||
private static native boolean nIsValidSwapChain(long nativeEngine, long nativeSwapChain);
|
||||
private static native void nDestroyEntity(long nativeEngine, int entity);
|
||||
private static native void nFlushAndWait(long nativeEngine);
|
||||
private static native long nGetTransformManager(long nativeEngine);
|
||||
|
||||
+19
-1
@@ -625,6 +625,15 @@ public class MaterialInstance {
|
||||
nSetDepthCulling(getNativeObject(), enable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the depth comparison function (default is {@link TextureSampler.CompareFunction#GE}).
|
||||
*
|
||||
* @param func the depth comparison function
|
||||
*/
|
||||
public void setDepthFunc(TextureSampler.CompareFunction func) {
|
||||
nSetDepthFunc(getNativeObject(), func.ordinal());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether depth culling is enabled.
|
||||
*/
|
||||
@@ -632,6 +641,13 @@ public class MaterialInstance {
|
||||
return nIsDepthCullingEnabled(getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the depth comparison function.
|
||||
*/
|
||||
public TextureSampler.CompareFunction getDepthFunc() {
|
||||
return TextureSampler.EnumCache.sCompareFunctionValues[nGetDepthFunc(getNativeObject())];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the stencil comparison function (default is {@link TextureSampler.CompareFunction#ALWAYS}).
|
||||
*
|
||||
@@ -884,7 +900,7 @@ public class MaterialInstance {
|
||||
@IntRange(from = 0) int offset, @IntRange(from = 1) int count);
|
||||
|
||||
private static native void nSetParameterTexture(long nativeMaterialInstance,
|
||||
@NonNull String name, long nativeTexture, int sampler);
|
||||
@NonNull String name, long nativeTexture, long sampler);
|
||||
|
||||
private static native void nSetScissor(long nativeMaterialInstance,
|
||||
@IntRange(from = 0) int left, @IntRange(from = 0) int bottom,
|
||||
@@ -908,6 +924,7 @@ public class MaterialInstance {
|
||||
private static native void nSetDepthWrite(long nativeMaterialInstance, boolean enable);
|
||||
private static native void nSetStencilWrite(long nativeMaterialInstance, boolean enable);
|
||||
private static native void nSetDepthCulling(long nativeMaterialInstance, boolean enable);
|
||||
private static native void nSetDepthFunc(long nativeMaterialInstance, long function);
|
||||
|
||||
private static native void nSetStencilCompareFunction(long nativeMaterialInstance,
|
||||
long function, long face);
|
||||
@@ -939,4 +956,5 @@ public class MaterialInstance {
|
||||
private static native boolean nIsDepthWriteEnabled(long nativeMaterialInstance);
|
||||
private static native boolean nIsStencilWriteEnabled(long nativeMaterialInstance);
|
||||
private static native boolean nIsDepthCullingEnabled(long nativeMaterialInstance);
|
||||
private static native int nGetDepthFunc(long nativeMaterialInstance);
|
||||
}
|
||||
|
||||
@@ -81,8 +81,6 @@ public class RenderTarget {
|
||||
/**
|
||||
* Sets a texture to a given attachment point.
|
||||
*
|
||||
* <p>All RenderTargets must have a non-null <code>COLOR</code> attachment.</p>
|
||||
*
|
||||
* @param attachment The attachment point of the texture.
|
||||
* @param texture The associated texture object.
|
||||
* @return A reference to this Builder for chaining calls.
|
||||
|
||||
@@ -137,10 +137,6 @@ public class SwapChain {
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The FrameCompletedCallback is guaranteed to be called on the main Filament thread.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Warning: Only Filament's Metal backend supports frame callbacks. Other backends ignore the
|
||||
* callback (which will never be called) and proceed normally.
|
||||
* </p>
|
||||
|
||||
+19
-19
@@ -126,7 +126,7 @@ public class TextureSampler {
|
||||
NEVER
|
||||
}
|
||||
|
||||
int mSampler = 0; // bit field used by native
|
||||
long mSampler = 0; // bit field used by native
|
||||
|
||||
/**
|
||||
* Initializes the <code>TextureSampler</code> with default values.
|
||||
@@ -342,26 +342,26 @@ public class TextureSampler {
|
||||
}
|
||||
}
|
||||
|
||||
private static native int nCreateSampler(int min, int max, int s, int t, int r);
|
||||
private static native int nCreateCompareSampler(int mode, int function);
|
||||
private static native long nCreateSampler(int min, int max, int s, int t, int r);
|
||||
private static native long nCreateCompareSampler(int mode, int function);
|
||||
|
||||
private static native int nGetMinFilter(int sampler);
|
||||
private static native int nSetMinFilter(int sampler, int filter);
|
||||
private static native int nGetMagFilter(int sampler);
|
||||
private static native int nSetMagFilter(int sampler, int filter);
|
||||
private static native int nGetMinFilter(long sampler);
|
||||
private static native long nSetMinFilter(long sampler, int filter);
|
||||
private static native int nGetMagFilter(long sampler);
|
||||
private static native long nSetMagFilter(long sampler, int filter);
|
||||
|
||||
private static native int nGetWrapModeS(int sampler);
|
||||
private static native int nSetWrapModeS(int sampler, int mode);
|
||||
private static native int nGetWrapModeT(int sampler);
|
||||
private static native int nSetWrapModeT(int sampler, int mode);
|
||||
private static native int nGetWrapModeR(int sampler);
|
||||
private static native int nSetWrapModeR(int sampler, int mode);
|
||||
private static native int nGetWrapModeS(long sampler);
|
||||
private static native long nSetWrapModeS(long sampler, int mode);
|
||||
private static native int nGetWrapModeT(long sampler);
|
||||
private static native long nSetWrapModeT(long sampler, int mode);
|
||||
private static native int nGetWrapModeR(long sampler);
|
||||
private static native long nSetWrapModeR(long sampler, int mode);
|
||||
|
||||
private static native int nGetCompareMode(int sampler);
|
||||
private static native int nSetCompareMode(int sampler, int mode);
|
||||
private static native int nGetCompareFunction(int sampler);
|
||||
private static native int nSetCompareFunction(int sampler, int function);
|
||||
private static native int nGetCompareMode(long sampler);
|
||||
private static native long nSetCompareMode(long sampler, int mode);
|
||||
private static native int nGetCompareFunction(long sampler);
|
||||
private static native long nSetCompareFunction(long sampler, int function);
|
||||
|
||||
private static native float nGetAnisotropy(int sampler);
|
||||
private static native int nSetAnisotropy(int sampler, float anisotropy);
|
||||
private static native float nGetAnisotropy(long sampler);
|
||||
private static native long nSetAnisotropy(long sampler, float anisotropy);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ 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;
|
||||
|
||||
/**
|
||||
* Encompasses all the state needed for rendering a {@link Scene}.
|
||||
*
|
||||
@@ -965,7 +967,8 @@ public class View {
|
||||
options.heightFalloff, options.cutOffDistance,
|
||||
options.color[0], options.color[1], options.color[2],
|
||||
options.density, options.inScatteringStart, options.inScatteringSize,
|
||||
options.fogColorFromIbl, options.skyColor.getNativeObject(),
|
||||
options.fogColorFromIbl,
|
||||
options.skyColor == null ? 0 : options.skyColor.getNativeObject(),
|
||||
options.enabled);
|
||||
}
|
||||
|
||||
@@ -1095,10 +1098,29 @@ public class View {
|
||||
nPick(getNativeObject(), x, y, handler, internalCallback);
|
||||
}
|
||||
|
||||
@UsedByNative("View.cpp")
|
||||
private static class InternalOnPickCallback implements Runnable {
|
||||
private final OnPickCallback mUserCallback;
|
||||
private final PickingQueryResult mPickingQueryResult = new PickingQueryResult();
|
||||
|
||||
@UsedByNative("View.cpp")
|
||||
@Entity
|
||||
int mRenderable;
|
||||
|
||||
@UsedByNative("View.cpp")
|
||||
float mDepth;
|
||||
|
||||
@UsedByNative("View.cpp")
|
||||
float mFragCoordsX;
|
||||
@UsedByNative("View.cpp")
|
||||
float mFragCoordsY;
|
||||
@UsedByNative("View.cpp")
|
||||
float mFragCoordsZ;
|
||||
|
||||
public InternalOnPickCallback(OnPickCallback mUserCallback) {
|
||||
this.mUserCallback = mUserCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mPickingQueryResult.renderable = mRenderable;
|
||||
@@ -1108,13 +1130,6 @@ public class View {
|
||||
mPickingQueryResult.fragCoords[2] = mFragCoordsZ;
|
||||
mUserCallback.onPick(mPickingQueryResult);
|
||||
}
|
||||
private final OnPickCallback mUserCallback;
|
||||
private final PickingQueryResult mPickingQueryResult = new PickingQueryResult();
|
||||
@Entity int mRenderable;
|
||||
float mDepth;
|
||||
float mFragCoordsX;
|
||||
float mFragCoordsY;
|
||||
float mFragCoordsZ;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1377,13 +1392,13 @@ public class View {
|
||||
/**
|
||||
* resolution of vertical axis (2^levels to 2048)
|
||||
*/
|
||||
public int resolution = 360;
|
||||
public int resolution = 384;
|
||||
/**
|
||||
* bloom x/y aspect-ratio (1/32 to 32)
|
||||
*/
|
||||
public float anamorphism = 1.0f;
|
||||
/**
|
||||
* number of blur levels (3 to 11)
|
||||
* number of blur levels (1 to 11)
|
||||
*/
|
||||
public int levels = 6;
|
||||
/**
|
||||
@@ -1971,4 +1986,11 @@ public class View {
|
||||
*/
|
||||
public float penumbraRatioScale = 1.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for stereoscopic (multi-eye) rendering.
|
||||
*/
|
||||
public static class StereoscopicOptions {
|
||||
public boolean enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
+24
-6
@@ -22,17 +22,35 @@ import com.google.android.filament.Fence;
|
||||
public class FilamentHelper {
|
||||
|
||||
/**
|
||||
* Wait for all pending frames to be processed before returning. This is to
|
||||
* avoid a race between the surface being resized before pending frames are
|
||||
* rendered into it. This is typically called from {@link UiHelper.RendererCallback#onResized},
|
||||
* {@link android.view.SurfaceHolder.Callback#surfaceChanged} or
|
||||
* {@link android.view.TextureView.SurfaceTextureListener#onSurfaceTextureSizeChanged}.
|
||||
* Wait for all pending frames to be processed before returning. This is to avoid a race
|
||||
* between the surface being resized before pending frames are rendered into it.
|
||||
* <p>
|
||||
* For {@link android.view.TextureView} this must be called before the texture's size is
|
||||
* reconfigured, which unfortunately is done by the Android framework before
|
||||
* {@link UiHelper} listeners are invoked. Therefore <code>synchronizePendingFrames</code>
|
||||
* cannot be called from
|
||||
* {@link android.view.TextureView.SurfaceTextureListener#onSurfaceTextureSizeChanged}; instead
|
||||
* a subclass of {@link android.view.TextureView} must be used in order to call it from
|
||||
* {@link android.view.TextureView#onSizeChanged}:
|
||||
* </p>
|
||||
* <pre>
|
||||
* public class MyTextureView extends TextureView {
|
||||
* private Engine engine;
|
||||
* protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
* FilamentHelper.synchronizePendingFrames(engine);
|
||||
* super.onSizeChanged(w, h, oldw, oldh);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Otherwise, this is typically called from {@link UiHelper.RendererCallback#onResized},
|
||||
* {@link android.view.SurfaceHolder.Callback#surfaceChanged}.
|
||||
*
|
||||
* @param engine Filament engine to synchronize
|
||||
*
|
||||
* @see UiHelper.RendererCallback#onResized
|
||||
* @see android.view.SurfaceHolder.Callback#surfaceChanged
|
||||
* @see android.view.TextureView.SurfaceTextureListener#onSurfaceTextureSizeChanged
|
||||
* @see android.view.TextureView#onSizeChanged
|
||||
*/
|
||||
static public void synchronizePendingFrames(Engine engine) {
|
||||
Fence fence = engine.createFence();
|
||||
|
||||
+12
@@ -244,6 +244,10 @@ public class UiHelper {
|
||||
}
|
||||
mSurface = surface;
|
||||
}
|
||||
|
||||
public Surface getSurface() {
|
||||
return mSurface;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -489,6 +493,14 @@ public class UiHelper {
|
||||
} else {
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
// We must recreate the SwapChain to guarantee that it sees the new size.
|
||||
// More precisely, for an EGL client, the EGLSurface must be recreated. For
|
||||
// a Vulkan client, the SwapChain must be recreated. Calling
|
||||
// onNativeWindowChanged() will accomplish that.
|
||||
// This requirement comes from SurfaceTexture.setDefaultBufferSize()
|
||||
// documentation.
|
||||
TextureViewHandler textureViewHandler = (TextureViewHandler) mRenderSurface;
|
||||
mRenderCallback.onNativeWindowChanged(textureViewHandler.getSurface());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+4
-4
@@ -191,7 +191,7 @@ class ModelViewer(
|
||||
asset = assetLoader.createAsset(buffer)
|
||||
asset?.let { asset ->
|
||||
resourceLoader.asyncBeginLoad(asset)
|
||||
animator = asset.getInstance().animator
|
||||
animator = asset.instance.animator
|
||||
asset.releaseSourceData()
|
||||
}
|
||||
}
|
||||
@@ -214,7 +214,7 @@ class ModelViewer(
|
||||
resourceLoader.addResourceData(uri, resourceBuffer)
|
||||
}
|
||||
resourceLoader.asyncBeginLoad(asset)
|
||||
animator = asset.getInstance().animator
|
||||
animator = asset.instance.animator
|
||||
asset.releaseSourceData()
|
||||
}
|
||||
}
|
||||
@@ -311,7 +311,7 @@ class ModelViewer(
|
||||
var count = 0
|
||||
val popRenderables = { count = asset.popRenderables(readyRenderables); count != 0 }
|
||||
while (popRenderables()) {
|
||||
for (i in 0..count - 1) {
|
||||
for (i in 0 until count) {
|
||||
val ri = rcm.getInstance(readyRenderables[i])
|
||||
rcm.setScreenSpaceContactShadows(ri, true)
|
||||
}
|
||||
@@ -371,7 +371,7 @@ class ModelViewer(
|
||||
resourceLoader.addResourceData(uri, buffer)
|
||||
}
|
||||
resourceLoader.asyncBeginLoad(asset)
|
||||
animator = asset.getInstance().animator
|
||||
animator = asset.instance.animator
|
||||
asset.releaseSourceData()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.38.0
|
||||
VERSION_NAME=1.42.0
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
+26
@@ -28,6 +28,7 @@ import com.google.android.filament.Fence
|
||||
import com.google.android.filament.IndirectLight
|
||||
import com.google.android.filament.Skybox
|
||||
import com.google.android.filament.View
|
||||
import com.google.android.filament.View.OnPickCallback
|
||||
import com.google.android.filament.utils.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -56,7 +57,9 @@ class MainActivity : Activity() {
|
||||
private lateinit var modelViewer: ModelViewer
|
||||
private lateinit var titlebarHint: TextView
|
||||
private val doubleTapListener = DoubleTapListener()
|
||||
private val singleTapListener = SingleTapListener()
|
||||
private lateinit var doubleTapDetector: GestureDetector
|
||||
private lateinit var singleTapDetector: GestureDetector
|
||||
private var remoteServer: RemoteServer? = null
|
||||
private var statusToast: Toast? = null
|
||||
private var statusText: String? = null
|
||||
@@ -77,6 +80,7 @@ class MainActivity : Activity() {
|
||||
choreographer = Choreographer.getInstance()
|
||||
|
||||
doubleTapDetector = GestureDetector(applicationContext, doubleTapListener)
|
||||
singleTapDetector = GestureDetector(applicationContext, singleTapListener)
|
||||
|
||||
modelViewer = ModelViewer(surfaceView)
|
||||
viewerContent.view = modelViewer.view
|
||||
@@ -88,6 +92,7 @@ class MainActivity : Activity() {
|
||||
surfaceView.setOnTouchListener { _, event ->
|
||||
modelViewer.onTouchEvent(event)
|
||||
doubleTapDetector.onTouchEvent(event)
|
||||
singleTapDetector.onTouchEvent(event)
|
||||
true
|
||||
}
|
||||
|
||||
@@ -229,6 +234,7 @@ class MainActivity : Activity() {
|
||||
modelViewer.scene.skybox = sky
|
||||
modelViewer.scene.indirectLight = ibl
|
||||
viewerContent.indirectLight = ibl
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -337,6 +343,11 @@ class MainActivity : Activity() {
|
||||
remoteServer?.close()
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
super.onBackPressed()
|
||||
finish()
|
||||
}
|
||||
|
||||
fun loadModelData(message: RemoteServer.ReceivedMessage) {
|
||||
Log.i(TAG, "Downloaded model ${message.label} (${message.buffer.capacity()} bytes)")
|
||||
clearStatusText()
|
||||
@@ -425,4 +436,19 @@ class MainActivity : Activity() {
|
||||
return super.onDoubleTap(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Just for testing purposes
|
||||
inner class SingleTapListener : GestureDetector.SimpleOnGestureListener() {
|
||||
override fun onSingleTapUp(event: MotionEvent): Boolean {
|
||||
modelViewer.view.pick(
|
||||
event.x.toInt(),
|
||||
surfaceView.height - event.y.toInt(),
|
||||
surfaceView.handler, {
|
||||
val name = modelViewer.asset!!.getName(it.renderable)
|
||||
Log.v("Filament", "Picked ${it.renderable}: " + name)
|
||||
},
|
||||
)
|
||||
return super.onSingleTapUp(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -118,9 +118,10 @@ class MainActivity : Activity() {
|
||||
}
|
||||
|
||||
private fun setupView() {
|
||||
val ssaoOptions = view.ambientOcclusionOptions
|
||||
ssaoOptions.enabled = true
|
||||
view.ambientOcclusionOptions = ssaoOptions
|
||||
// ambient occlusion is the cheapest effect that adds a lot of quality
|
||||
view.ambientOcclusionOptions = view.ambientOcclusionOptions.apply {
|
||||
enabled = true
|
||||
}
|
||||
|
||||
// NOTE: Try to disable post-processing (tone-mapping, etc.) to see the difference
|
||||
// view.isPostProcessingEnabled = false
|
||||
|
||||
+2
-2
@@ -231,7 +231,7 @@ class MainActivity : Activity() {
|
||||
animator.repeatCount = ValueAnimator.INFINITE
|
||||
animator.addUpdateListener { a ->
|
||||
val v = (a.animatedValue as Float)
|
||||
camera.lookAt(cos(v) * 4.5, 1.5, sin(v) * 4.5, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)
|
||||
camera.lookAt(cos(v) * 5.5, 1.5, sin(v) * 5.5, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)
|
||||
}
|
||||
animator.start()
|
||||
}
|
||||
@@ -253,7 +253,7 @@ class MainActivity : Activity() {
|
||||
|
||||
// Stop the animation and any pending frame
|
||||
choreographer.removeFrameCallback(frameScheduler)
|
||||
animator.cancel();
|
||||
animator.cancel()
|
||||
|
||||
// Always detach the surface before destroying the engine
|
||||
uiHelper.detach()
|
||||
|
||||
+3
-3
@@ -140,9 +140,9 @@ class MainActivity : Activity() {
|
||||
camera = engine.createCamera(engine.entityManager.create())
|
||||
|
||||
// clear the swapchain with transparent pixels
|
||||
val options = renderer.clearOptions
|
||||
options.clear = true
|
||||
renderer.clearOptions = options
|
||||
renderer.clearOptions = renderer.clearOptions.apply {
|
||||
clear = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupView() {
|
||||
|
||||
@@ -90,8 +90,8 @@ function print_help {
|
||||
echo " Desktop matc target, release build:"
|
||||
echo " \$ ./$self_name release matc"
|
||||
echo ""
|
||||
echo " Build gltf_viewer then immediately run it with no arguments:"
|
||||
echo " \$ ./$self_name release run_gltf_viewer"
|
||||
echo " Build gltf_viewer:"
|
||||
echo " \$ ./$self_name release gltf_viewer"
|
||||
echo ""
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
set -e
|
||||
|
||||
case "$(uname -s)" in
|
||||
Darwin*) IS_DARWIN=1;;
|
||||
*) ;;
|
||||
esac
|
||||
|
||||
function print_help {
|
||||
local SELF_NAME
|
||||
SELF_NAME=$(basename "$0")
|
||||
@@ -45,7 +50,11 @@ function replace {
|
||||
FIND_STR="${1//\{\{VERSION\}\}/${VERSION_REGEX}}"
|
||||
REPLACE_STR="${1//\{\{VERSION\}\}/${NEW_VERSION}}"
|
||||
local FILE_NAME="$2"
|
||||
sed -i '' -E "s/${FIND_STR}/${REPLACE_STR}/" "${FILE_NAME}"
|
||||
if [ IS_DARWIN ]; then
|
||||
sed -i '' -E "s/${FIND_STR}/${REPLACE_STR}/" "${FILE_NAME}"
|
||||
else
|
||||
sed -i -E "s/${FIND_STR}/${REPLACE_STR}/" "${FILE_NAME}"
|
||||
fi
|
||||
}
|
||||
|
||||
# The following are the canonical locations where the Filament version number is referenced.
|
||||
|
||||
@@ -1139,7 +1139,8 @@ Type
|
||||
: array of `string`
|
||||
|
||||
Value
|
||||
: Each entry must be any of `dynamicLighting`, `directionalLighting`, `shadowReceiver`,`skinning` or `ssr`.
|
||||
: Each entry must be any of `dynamicLighting`, `directionalLighting`, `shadowReceiver`,
|
||||
`skinning`, `ssr`, or `stereo`.
|
||||
|
||||
Description
|
||||
: Used to specify a list of shader variants that the application guarantees will never be
|
||||
@@ -1158,6 +1159,7 @@ Description of the variants:
|
||||
- `fog`, used when global fog is applied to the scene
|
||||
- `vsm`, used when VSM shadows are enabled and the object is a shadow receiver
|
||||
- `ssr`, used when screen-space reflections are enabled in the View
|
||||
- `stereo`, used when stereoscopic rendering is enabled in the View
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
|
||||
material {
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -27,10 +27,10 @@ set(SRCS
|
||||
src/BackendUtils.cpp
|
||||
src/BlobCacheKey.cpp
|
||||
src/Callable.cpp
|
||||
src/CallbackHandler.cpp
|
||||
src/CircularBuffer.cpp
|
||||
src/CommandBufferQueue.cpp
|
||||
src/CommandStream.cpp
|
||||
src/CompilerThreadPool.cpp
|
||||
src/Driver.cpp
|
||||
src/Handle.cpp
|
||||
src/HandleAllocator.cpp
|
||||
@@ -55,6 +55,7 @@ set(PRIVATE_HDRS
|
||||
include/private/backend/PlatformFactory.h
|
||||
include/private/backend/SamplerGroup.h
|
||||
src/CommandStreamDispatcher.h
|
||||
src/CompilerThreadPool.h
|
||||
src/DataReshaper.h
|
||||
src/DriverBase.h
|
||||
)
|
||||
@@ -66,6 +67,8 @@ set(PRIVATE_HDRS
|
||||
if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3 AND NOT FILAMENT_USE_SWIFTSHADER)
|
||||
list(APPEND SRCS
|
||||
include/backend/platforms/OpenGLPlatform.h
|
||||
src/opengl/CallbackManager.h
|
||||
src/opengl/CallbackManager.cpp
|
||||
src/opengl/gl_headers.cpp
|
||||
src/opengl/gl_headers.h
|
||||
src/opengl/GLUtils.cpp
|
||||
@@ -82,6 +85,8 @@ if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3 AND NOT FILAMEN
|
||||
src/opengl/OpenGLPlatform.cpp
|
||||
src/opengl/OpenGLTimerQuery.cpp
|
||||
src/opengl/OpenGLTimerQuery.h
|
||||
src/opengl/ShaderCompilerService.cpp
|
||||
src/opengl/ShaderCompilerService.h
|
||||
)
|
||||
if (EGL)
|
||||
list(APPEND SRCS src/opengl/platforms/PlatformEGL.cpp)
|
||||
@@ -172,8 +177,6 @@ if (FILAMENT_SUPPORTS_VULKAN)
|
||||
src/vulkan/VulkanConstants.h
|
||||
src/vulkan/VulkanContext.cpp
|
||||
src/vulkan/VulkanContext.h
|
||||
src/vulkan/VulkanDisposer.cpp
|
||||
src/vulkan/VulkanDisposer.h
|
||||
src/vulkan/VulkanDriver.cpp
|
||||
src/vulkan/VulkanDriver.h
|
||||
src/vulkan/VulkanDriverFactory.h
|
||||
@@ -193,6 +196,11 @@ if (FILAMENT_SUPPORTS_VULKAN)
|
||||
src/vulkan/VulkanStagePool.h
|
||||
src/vulkan/VulkanSwapChain.cpp
|
||||
src/vulkan/VulkanSwapChain.h
|
||||
src/vulkan/VulkanReadPixels.cpp
|
||||
src/vulkan/VulkanReadPixels.h
|
||||
src/vulkan/VulkanResourceAllocator.h
|
||||
src/vulkan/VulkanResources.cpp
|
||||
src/vulkan/VulkanResources.h
|
||||
src/vulkan/VulkanTexture.cpp
|
||||
src/vulkan/VulkanTexture.h
|
||||
src/vulkan/VulkanUtility.cpp
|
||||
|
||||
@@ -66,7 +66,7 @@ public:
|
||||
virtual void post(void* user, Callback callback) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~CallbackHandler();
|
||||
virtual ~CallbackHandler() = default;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -296,6 +296,14 @@ enum class Precision : uint8_t {
|
||||
DEFAULT
|
||||
};
|
||||
|
||||
/**
|
||||
* Shader compiler priority queue
|
||||
*/
|
||||
enum class CompilerPriorityQueue : uint8_t {
|
||||
HIGH,
|
||||
LOW
|
||||
};
|
||||
|
||||
//! Texture sampler type
|
||||
enum class SamplerType : uint8_t {
|
||||
SAMPLER_2D, //!< 2D texture
|
||||
@@ -788,32 +796,53 @@ enum class SamplerCompareFunc : uint8_t {
|
||||
|
||||
//! Sampler parameters
|
||||
struct SamplerParams { // NOLINT
|
||||
union {
|
||||
struct {
|
||||
SamplerMagFilter filterMag : 1; //!< magnification filter (NEAREST)
|
||||
SamplerMinFilter filterMin : 3; //!< minification filter (NEAREST)
|
||||
SamplerWrapMode wrapS : 2; //!< s-coordinate wrap mode (CLAMP_TO_EDGE)
|
||||
SamplerWrapMode wrapT : 2; //!< t-coordinate wrap mode (CLAMP_TO_EDGE)
|
||||
SamplerMagFilter filterMag : 1; //!< magnification filter (NEAREST)
|
||||
SamplerMinFilter filterMin : 3; //!< minification filter (NEAREST)
|
||||
SamplerWrapMode wrapS : 2; //!< s-coordinate wrap mode (CLAMP_TO_EDGE)
|
||||
SamplerWrapMode wrapT : 2; //!< t-coordinate wrap mode (CLAMP_TO_EDGE)
|
||||
|
||||
SamplerWrapMode wrapR : 2; //!< r-coordinate wrap mode (CLAMP_TO_EDGE)
|
||||
uint8_t anisotropyLog2 : 3; //!< anisotropy level (0)
|
||||
SamplerCompareMode compareMode : 1; //!< sampler compare mode (NONE)
|
||||
uint8_t padding0 : 2; //!< reserved. must be 0.
|
||||
SamplerWrapMode wrapR : 2; //!< r-coordinate wrap mode (CLAMP_TO_EDGE)
|
||||
uint8_t anisotropyLog2 : 3; //!< anisotropy level (0)
|
||||
SamplerCompareMode compareMode : 1; //!< sampler compare mode (NONE)
|
||||
uint8_t padding0 : 2; //!< reserved. must be 0.
|
||||
|
||||
SamplerCompareFunc compareFunc : 3; //!< sampler comparison function (LE)
|
||||
uint8_t padding1 : 5; //!< reserved. must be 0.
|
||||
SamplerCompareFunc compareFunc : 3; //!< sampler comparison function (LE)
|
||||
uint8_t padding1 : 5; //!< reserved. must be 0.
|
||||
uint8_t padding2 : 8; //!< reserved. must be 0.
|
||||
|
||||
uint8_t padding2 : 8; //!< reserved. must be 0.
|
||||
};
|
||||
uint32_t u;
|
||||
struct Hasher {
|
||||
size_t operator()(SamplerParams p) const noexcept {
|
||||
// we don't use std::hash<> here, so we don't have to include <functional>
|
||||
return *reinterpret_cast<uint64_t const*>(reinterpret_cast<char const*>(&p));
|
||||
}
|
||||
};
|
||||
|
||||
struct EqualTo {
|
||||
bool operator()(SamplerParams lhs, SamplerParams rhs) const noexcept {
|
||||
auto* pLhs = reinterpret_cast<uint64_t const*>(reinterpret_cast<char const*>(&lhs));
|
||||
auto* pRhs = reinterpret_cast<uint64_t const*>(reinterpret_cast<char const*>(&rhs));
|
||||
return *pLhs == *pRhs;
|
||||
}
|
||||
};
|
||||
|
||||
struct LessThan {
|
||||
bool operator()(SamplerParams lhs, SamplerParams rhs) const noexcept {
|
||||
auto* pLhs = reinterpret_cast<uint64_t const*>(reinterpret_cast<char const*>(&lhs));
|
||||
auto* pRhs = reinterpret_cast<uint64_t const*>(reinterpret_cast<char const*>(&rhs));
|
||||
return *pLhs == *pRhs;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
friend inline bool operator < (SamplerParams lhs, SamplerParams rhs) {
|
||||
return lhs.u < rhs.u;
|
||||
friend inline bool operator < (SamplerParams lhs, SamplerParams rhs) noexcept {
|
||||
return SamplerParams::LessThan{}(lhs, rhs);
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(SamplerParams) == sizeof(uint32_t), "SamplerParams must be 32 bits");
|
||||
// The limitation to 64-bits max comes from how we store a SamplerParams in our JNI code
|
||||
// see android/.../TextureSampler.cpp
|
||||
static_assert(sizeof(SamplerParams) <= sizeof(uint64_t),
|
||||
"SamplerParams must be no more than 64 bits");
|
||||
|
||||
//! blending equation function
|
||||
enum class BlendEquation : uint8_t {
|
||||
@@ -1118,8 +1147,6 @@ static_assert(sizeof(StencilState) == 12u,
|
||||
|
||||
using FrameScheduledCallback = void(*)(PresentCallable callable, void* user);
|
||||
|
||||
using FrameCompletedCallback = void(*)(void* user);
|
||||
|
||||
enum class Workaround : uint16_t {
|
||||
// The EASU pass must split because shader compiler flattens early-exit branch
|
||||
SPLIT_EASU,
|
||||
@@ -1130,7 +1157,14 @@ enum class Workaround : uint16_t {
|
||||
ADRENO_UNIFORM_ARRAY_CRASH,
|
||||
// Workaround a Metal pipeline compilation error with the message:
|
||||
// "Could not statically determine the target of a texture". See light_indirect.fs
|
||||
A8X_STATIC_TEXTURE_TARGET_ERROR
|
||||
A8X_STATIC_TEXTURE_TARGET_ERROR,
|
||||
// Adreno drivers sometimes aren't able to blit into a layer of a texture array.
|
||||
DISABLE_BLIT_INTO_TEXTURE_ARRAY,
|
||||
// Multiple workarounds needed for PowerVR GPUs
|
||||
POWER_VR_SHADER_WORKAROUNDS,
|
||||
// The driver has some threads pinned, and we can't easily know on which core, it can hurt
|
||||
// performance more if we end-up pinned on the same one.
|
||||
DISABLE_THREAD_AFFINITY
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -39,7 +39,6 @@ struct HwRenderTarget;
|
||||
struct HwSamplerGroup;
|
||||
struct HwStream;
|
||||
struct HwSwapChain;
|
||||
struct HwSync;
|
||||
struct HwTexture;
|
||||
struct HwTimerQuery;
|
||||
struct HwVertexBuffer;
|
||||
@@ -126,7 +125,6 @@ using RenderTargetHandle = Handle<HwRenderTarget>;
|
||||
using SamplerGroupHandle = Handle<HwSamplerGroup>;
|
||||
using StreamHandle = Handle<HwStream>;
|
||||
using SwapChainHandle = Handle<HwSwapChain>;
|
||||
using SyncHandle = Handle<HwSync>;
|
||||
using TextureHandle = Handle<HwTexture>;
|
||||
using TimerQueryHandle = Handle<HwTimerQuery>;
|
||||
using VertexBufferHandle = Handle<HwVertexBuffer>;
|
||||
|
||||
@@ -71,6 +71,8 @@ public:
|
||||
|
||||
~Program() noexcept;
|
||||
|
||||
Program& priorityQueue(CompilerPriorityQueue priorityQueue) noexcept;
|
||||
|
||||
// sets the material name and variant for diagnostic purposes only
|
||||
Program& diagnostics(utils::CString const& name,
|
||||
utils::Invocable<utils::io::ostream&(utils::io::ostream& out)>&& logger);
|
||||
@@ -138,6 +140,8 @@ public:
|
||||
|
||||
uint64_t getCacheId() const noexcept { return mCacheId; }
|
||||
|
||||
CompilerPriorityQueue getPriorityQueue() const noexcept { return mPriorityQueue; }
|
||||
|
||||
private:
|
||||
friend utils::io::ostream& operator<<(utils::io::ostream& out, const Program& builder);
|
||||
|
||||
@@ -150,6 +154,7 @@ private:
|
||||
utils::FixedCapacityVector<SpecializationConstant> mSpecializationConstants;
|
||||
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> mAttributes;
|
||||
std::array<UniformInfo, Program::UNIFORM_BINDING_COUNT> mBindingUniformInfo;
|
||||
CompilerPriorityQueue mPriorityQueue = CompilerPriorityQueue::HIGH;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -267,6 +267,33 @@ public:
|
||||
* @return Transformed image.
|
||||
*/
|
||||
virtual AcquiredImage transformAcquiredImage(AcquiredImage source) noexcept;
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns true if additional OpenGL contexts can be created. Default: false.
|
||||
* @return true if additional OpenGL contexts can be created.
|
||||
* @see createContext
|
||||
*/
|
||||
virtual bool isExtraContextSupported() const noexcept;
|
||||
|
||||
/**
|
||||
* Creates an OpenGL context with the same configuration than the main context and makes it
|
||||
* current to the current thread. Must not be called from the main driver thread.
|
||||
* createContext() is only supported if isExtraContextSupported() returns true.
|
||||
* These additional contexts will be automatically terminated in terminate.
|
||||
*
|
||||
* @param shared whether the new context is shared with the main context.
|
||||
* @see isExtraContextSupported()
|
||||
* @see terminate()
|
||||
*/
|
||||
virtual void createContext(bool shared);
|
||||
|
||||
/**
|
||||
* Detach and destroy the current context if any and releases all resources associated to
|
||||
* this thread.
|
||||
*/
|
||||
virtual void releaseContext() noexcept;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -50,6 +50,9 @@ protected:
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// OpenGLPlatform Interface
|
||||
|
||||
bool isExtraContextSupported() const noexcept override;
|
||||
void createContext(bool shared) override;
|
||||
|
||||
void terminate() noexcept override;
|
||||
|
||||
SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override;
|
||||
|
||||
@@ -47,6 +47,9 @@ public:
|
||||
|
||||
uint32_t createDefaultRenderTarget() noexcept override;
|
||||
|
||||
bool isExtraContextSupported() const noexcept override;
|
||||
void createContext(bool shared) override;
|
||||
|
||||
SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override;
|
||||
SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override;
|
||||
void destroySwapChain(SwapChain* swapChain) noexcept override;
|
||||
|
||||
@@ -38,6 +38,9 @@ class PlatformEGL : public OpenGLPlatform {
|
||||
public:
|
||||
|
||||
PlatformEGL() noexcept;
|
||||
bool isExtraContextSupported() const noexcept override;
|
||||
void createContext(bool shared) override;
|
||||
void releaseContext() noexcept override;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -124,6 +127,8 @@ protected:
|
||||
EGLSurface mCurrentReadSurface = EGL_NO_SURFACE;
|
||||
EGLSurface mEGLDummySurface = EGL_NO_SURFACE;
|
||||
EGLConfig mEGLConfig = EGL_NO_CONFIG_KHR;
|
||||
Config mContextAttribs;
|
||||
std::vector<EGLContext> mAdditionalContexts;
|
||||
|
||||
// supported extensions detected at runtime
|
||||
struct {
|
||||
@@ -135,11 +140,13 @@ protected:
|
||||
bool KHR_create_context = false;
|
||||
bool KHR_gl_colorspace = false;
|
||||
bool KHR_no_config_context = false;
|
||||
bool KHR_surfaceless_context = false;
|
||||
} egl;
|
||||
} ext;
|
||||
|
||||
private:
|
||||
void initializeGlExtensions() noexcept;
|
||||
|
||||
private:
|
||||
EGLConfig findSwapChainConfig(uint64_t flags) const;
|
||||
};
|
||||
|
||||
|
||||
@@ -23,9 +23,10 @@
|
||||
#include "utils/unwindows.h"
|
||||
|
||||
#include <backend/platforms/OpenGLPlatform.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
@@ -46,6 +47,9 @@ protected:
|
||||
|
||||
void terminate() noexcept override;
|
||||
|
||||
bool isExtraContextSupported() const noexcept override;
|
||||
void createContext(bool shared) override;
|
||||
|
||||
SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override;
|
||||
SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override;
|
||||
void destroySwapChain(SwapChain* swapChain) noexcept override;
|
||||
@@ -57,6 +61,8 @@ protected:
|
||||
HWND mHWnd = NULL;
|
||||
HDC mWhdc = NULL;
|
||||
PIXELFORMATDESCRIPTOR mPfd = {};
|
||||
std::vector<HGLRC> mAdditionalContexts;
|
||||
std::vector<int> mAttribs;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -87,6 +87,38 @@ public:
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
// ---------- Platform Customization options ----------
|
||||
/**
|
||||
* The client preference can be stored within the struct. We allow for two specification of
|
||||
* preference:
|
||||
* 1) A substring to match against `VkPhysicalDeviceProperties.deviceName`.
|
||||
* 2) Index of the device in the list as returned by vkEnumeratePhysicalDevices.
|
||||
*/
|
||||
struct GPUPreference {
|
||||
std::string deviceName;
|
||||
int8_t index = -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Client can provide a preference over the GPU to use in the vulkan instance
|
||||
* @return `GPUPreference` struct that indicates the client's preference
|
||||
*/
|
||||
virtual GPUPreference getPreferredGPU() noexcept {
|
||||
return {};
|
||||
}
|
||||
// -------- End platform customization options --------
|
||||
// ----------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns whether the platform supports sRGB swapchain. This is true by default, and the client
|
||||
* needs to override this method to specify otherwise.
|
||||
* @return Whether the platform supports sRGB swapchain.
|
||||
*/
|
||||
virtual bool isSRGBSwapChainSupported() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the images handles and format of the memory backing the swapchain. This should be called
|
||||
* after createSwapChain() or after recreateIfResized().
|
||||
|
||||
@@ -73,14 +73,32 @@ public:
|
||||
// a cost here (writing and reading the stack at each iteration), in the end it's
|
||||
// probably better to pay the cost at just one location.
|
||||
intptr_t next;
|
||||
driver.mCurrentExecutingCommand = this;
|
||||
mExecute(driver, this, &next);
|
||||
return reinterpret_cast<CommandBase*>(reinterpret_cast<intptr_t>(this) + next);
|
||||
}
|
||||
|
||||
inline void captureCallstack() noexcept {
|
||||
auto c = utils::CallStack::unwind(4);
|
||||
size_t i = 0;
|
||||
for (; i < c.getFrameCount() && i < 16; i++) {
|
||||
mCallstack[i] = c[i];
|
||||
}
|
||||
for (; i < 16; i++) {
|
||||
mCallstack[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void printCallstack() noexcept {
|
||||
auto c = utils::CallStack(mCallstack);
|
||||
utils::slog.d << c << utils::io::endl;
|
||||
}
|
||||
|
||||
inline ~CommandBase() noexcept = default;
|
||||
|
||||
private:
|
||||
Execute mExecute;
|
||||
std::array<intptr_t, 16> mCallstack = {0};
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
@@ -218,6 +236,7 @@ public:
|
||||
using Cmd = COMMAND_TYPE(methodName); \
|
||||
void* const p = allocateCommand(CommandBase::align(sizeof(Cmd))); \
|
||||
new(p) Cmd(mDispatcher.methodName##_, APPLY(std::move, params)); \
|
||||
((Cmd*)p)->captureCallstack(); \
|
||||
DEBUG_COMMAND_END(methodName, false); \
|
||||
}
|
||||
|
||||
@@ -237,6 +256,7 @@ public:
|
||||
using Cmd = COMMAND_TYPE(methodName##R); \
|
||||
void* const p = allocateCommand(CommandBase::align(sizeof(Cmd))); \
|
||||
new(p) Cmd(mDispatcher.methodName##_, RetType(result), APPLY(std::move, params)); \
|
||||
((Cmd*)p)->captureCallstack(); \
|
||||
DEBUG_COMMAND_END(methodName, false); \
|
||||
return result; \
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ template<typename T>
|
||||
class ConcreteDispatcher;
|
||||
class Dispatcher;
|
||||
class CommandStream;
|
||||
class CommandBase;
|
||||
|
||||
class Driver {
|
||||
public:
|
||||
@@ -83,6 +84,8 @@ public:
|
||||
virtual void debugCommandEnd(CommandStream* cmds,
|
||||
bool synchronous, const char* methodName) noexcept = 0;
|
||||
|
||||
CommandBase* mCurrentExecutingCommand = nullptr;
|
||||
|
||||
/*
|
||||
* Asynchronous calls here only to provide a type to CommandStream. They must be non-virtual
|
||||
* so that calling the concrete implementation won't go through a vtable.
|
||||
|
||||
@@ -142,7 +142,8 @@ DECL_DRIVER_API_N(setFrameScheduledCallback,
|
||||
|
||||
DECL_DRIVER_API_N(setFrameCompletedCallback,
|
||||
backend::SwapChainHandle, sch,
|
||||
backend::FrameCompletedCallback, callback,
|
||||
backend::CallbackHandler*, handler,
|
||||
backend::CallbackHandler::Callback, callback,
|
||||
void*, user)
|
||||
|
||||
DECL_DRIVER_API_N(setPresentationTime,
|
||||
@@ -245,8 +246,6 @@ DECL_DRIVER_API_R_N(backend::RenderTargetHandle, createRenderTarget,
|
||||
|
||||
DECL_DRIVER_API_R_0(backend::FenceHandle, createFence)
|
||||
|
||||
DECL_DRIVER_API_R_0(backend::SyncHandle, createSync)
|
||||
|
||||
DECL_DRIVER_API_R_N(backend::SwapChainHandle, createSwapChain,
|
||||
void*, nativeWindow,
|
||||
uint64_t, flags)
|
||||
@@ -275,7 +274,7 @@ DECL_DRIVER_API_N(destroyRenderTarget, backend::RenderTargetHandle, rth)
|
||||
DECL_DRIVER_API_N(destroySwapChain, backend::SwapChainHandle, sch)
|
||||
DECL_DRIVER_API_N(destroyStream, backend::StreamHandle, sh)
|
||||
DECL_DRIVER_API_N(destroyTimerQuery, backend::TimerQueryHandle, sh)
|
||||
DECL_DRIVER_API_N(destroySync, backend::SyncHandle, sh)
|
||||
DECL_DRIVER_API_N(destroyFence, backend::FenceHandle, fh)
|
||||
|
||||
/*
|
||||
* Synchronous APIs
|
||||
@@ -289,8 +288,7 @@ DECL_DRIVER_API_SYNCHRONOUS_N(void, setAcquiredImage, backend::StreamHandle, str
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(void, setStreamDimensions, backend::StreamHandle, stream, uint32_t, width, uint32_t, height)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(int64_t, getStreamTimestamp, backend::StreamHandle, stream)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(void, updateStreams, backend::DriverApi*, driver)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(void, destroyFence, backend::FenceHandle, fh)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(backend::FenceStatus, wait, backend::FenceHandle, fh, uint64_t, timeout)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(backend::FenceStatus, getFenceStatus, backend::FenceHandle, fh)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isTextureFormatSupported, backend::TextureFormat, format)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isTextureSwizzleSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isTextureFormatMipmappable, backend::TextureFormat, format)
|
||||
@@ -300,13 +298,14 @@ DECL_DRIVER_API_SYNCHRONOUS_0(bool, isFrameBufferFetchMultiSampleSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isFrameTimeSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isAutoDepthResolveSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isSRGBSwapChainSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isStereoSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isParallelShaderCompileSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(uint8_t, getMaxDrawBuffers)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getMaxUniformBufferSize)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(math::float2, getClipSpaceParams)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, canGenerateMipmaps)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(void, setupExternalImage, void*, image)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, getTimerQueryValue, backend::TimerQueryHandle, query, uint64_t*, elapsedTime)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(backend::SyncStatus, getSyncStatus, backend::SyncHandle, sh)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isWorkaroundNeeded, backend::Workaround, workaround)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(backend::FeatureLevel, getFeatureLevel)
|
||||
|
||||
@@ -389,6 +388,7 @@ DECL_DRIVER_API_N(endTimerQuery,
|
||||
backend::TimerQueryHandle, query)
|
||||
|
||||
DECL_DRIVER_API_N(compilePrograms,
|
||||
backend::CompilerPriorityQueue, priority,
|
||||
backend::CallbackHandler*, handler,
|
||||
backend::CallbackHandler::Callback, callback,
|
||||
void*, user)
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "CompilerThreadPool.h"
|
||||
|
||||
#include <utils/Systrace.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
using namespace utils;
|
||||
|
||||
ProgramToken::~ProgramToken() = default;
|
||||
|
||||
CompilerThreadPool::CompilerThreadPool() noexcept = default;
|
||||
|
||||
CompilerThreadPool::~CompilerThreadPool() noexcept {
|
||||
assert_invariant(mCompilerThreads.empty());
|
||||
assert_invariant(mQueues[0].empty());
|
||||
assert_invariant(mQueues[1].empty());
|
||||
}
|
||||
|
||||
void CompilerThreadPool::init(uint32_t threadCount,
|
||||
ThreadSetup&& threadSetup, ThreadCleanup&& threadCleanup) noexcept {
|
||||
auto setup = std::make_shared<ThreadSetup>(std::move(threadSetup));
|
||||
auto cleanup = std::make_shared<ThreadCleanup>(std::move(threadCleanup));
|
||||
|
||||
for (size_t i = 0; i < threadCount; i++) {
|
||||
mCompilerThreads.emplace_back([this, setup, cleanup]() {
|
||||
SYSTRACE_CONTEXT();
|
||||
|
||||
(*setup)();
|
||||
|
||||
// process jobs from the queue until we're asked to exit
|
||||
while (!mExitRequested) {
|
||||
std::unique_lock lock(mQueueLock);
|
||||
mQueueCondition.wait(lock, [this]() {
|
||||
return mExitRequested ||
|
||||
(!std::all_of( std::begin(mQueues), std::end(mQueues),
|
||||
[](auto&& q) { return q.empty(); }));
|
||||
});
|
||||
|
||||
SYSTRACE_VALUE32("CompilerThreadPool Jobs",
|
||||
mQueues[0].size() + mQueues[1].size());
|
||||
|
||||
if (UTILS_LIKELY(!mExitRequested)) {
|
||||
Job job;
|
||||
// use the first queue that's not empty
|
||||
auto& queue = [this]() -> auto& {
|
||||
for (auto& q: mQueues) {
|
||||
if (!q.empty()) {
|
||||
return q;
|
||||
}
|
||||
}
|
||||
return mQueues[0]; // we should never end-up here.
|
||||
}();
|
||||
assert_invariant(!queue.empty());
|
||||
std::swap(job, queue.front().second);
|
||||
queue.pop_front();
|
||||
|
||||
// execute the job without holding any locks
|
||||
lock.unlock();
|
||||
job();
|
||||
}
|
||||
}
|
||||
|
||||
(*cleanup)();
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
auto CompilerThreadPool::find(program_token_t const& token) -> std::pair<Queue&, Queue::iterator> {
|
||||
for (auto&& q: mQueues) {
|
||||
auto pos = std::find_if(q.begin(), q.end(), [&token](auto&& item) {
|
||||
return item.first == token;
|
||||
});
|
||||
if (pos != q.end()) {
|
||||
return { q, pos };
|
||||
}
|
||||
}
|
||||
// this can happen if the program is being processed right now
|
||||
return { mQueues[0], mQueues[0].end() };
|
||||
}
|
||||
|
||||
auto CompilerThreadPool::dequeue(program_token_t const& token) -> Job {
|
||||
std::unique_lock const lock(mQueueLock);
|
||||
Job job;
|
||||
auto&& [q, pos] = find(token);
|
||||
if (pos != q.end()) {
|
||||
std::swap(job, pos->second);
|
||||
q.erase(pos);
|
||||
}
|
||||
return job;
|
||||
}
|
||||
|
||||
void CompilerThreadPool::queue(CompilerPriorityQueue priorityQueue,
|
||||
program_token_t const& token, Job&& job) {
|
||||
std::unique_lock const lock(mQueueLock);
|
||||
mQueues[size_t(priorityQueue)].emplace_back(token, std::move(job));
|
||||
mQueueCondition.notify_one();
|
||||
}
|
||||
|
||||
void CompilerThreadPool::terminate() noexcept {
|
||||
std::unique_lock lock(mQueueLock);
|
||||
mExitRequested = true;
|
||||
mQueueCondition.notify_all();
|
||||
lock.unlock();
|
||||
|
||||
for (auto& thread: mCompilerThreads) {
|
||||
if (thread.joinable()) {
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
mCompilerThreads.clear();
|
||||
|
||||
// Clear all the queues, dropping the remaining jobs. This relies on the jobs being cancelable.
|
||||
for (auto&& q : mQueues) {
|
||||
q.clear();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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_COMPILERTHREADPOOL_H
|
||||
#define TNT_FILAMENT_BACKEND_COMPILERTHREADPOOL_H
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/Invocable.h>
|
||||
#include <utils/Mutex.h>
|
||||
#include <utils/Condition.h>
|
||||
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
struct ProgramToken {
|
||||
virtual ~ProgramToken();
|
||||
};
|
||||
|
||||
using program_token_t = std::shared_ptr<ProgramToken>;
|
||||
|
||||
class Platform;
|
||||
|
||||
class CompilerThreadPool {
|
||||
public:
|
||||
CompilerThreadPool() noexcept;
|
||||
~CompilerThreadPool() noexcept;
|
||||
using Job = utils::Invocable<void()>;
|
||||
using ThreadSetup = utils::Invocable<void()>;
|
||||
using ThreadCleanup = utils::Invocable<void()>;
|
||||
void init(uint32_t threadCount,
|
||||
ThreadSetup&& threadSetup, ThreadCleanup&& threadCleanup) noexcept;
|
||||
void terminate() noexcept;
|
||||
void queue(CompilerPriorityQueue priorityQueue, program_token_t const& token, Job&& job);
|
||||
Job dequeue(program_token_t const& token);
|
||||
|
||||
private:
|
||||
using Queue = std::deque<std::pair<program_token_t, Job>>;
|
||||
std::vector<std::thread> mCompilerThreads;
|
||||
bool mExitRequested{ false };
|
||||
utils::Mutex mQueueLock;
|
||||
utils::Condition mQueueCondition;
|
||||
std::array<Queue, 2> mQueues;
|
||||
// lock must be held for methods below
|
||||
std::pair<Queue&, Queue::iterator> find(program_token_t const& token);
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_COMPILERTHREADPOOL_H
|
||||
|
||||
@@ -63,6 +63,8 @@ DriverBase::DriverBase() noexcept {
|
||||
}
|
||||
|
||||
DriverBase::~DriverBase() noexcept {
|
||||
assert_invariant(mCallbacks.empty());
|
||||
assert_invariant(mServiceThreadCallbackQueue.empty());
|
||||
if constexpr (UTILS_HAS_THREADING) {
|
||||
// quit our service thread
|
||||
std::unique_lock<std::mutex> lock(mServiceThreadLock);
|
||||
|
||||
@@ -135,9 +135,6 @@ struct HwFence : public HwBase {
|
||||
Platform::Fence* fence = nullptr;
|
||||
};
|
||||
|
||||
struct HwSync : public HwBase {
|
||||
};
|
||||
|
||||
struct HwSwapChain : public HwBase {
|
||||
Platform::SwapChain* swapChain = nullptr;
|
||||
};
|
||||
@@ -168,13 +165,6 @@ public:
|
||||
|
||||
void purge() noexcept final;
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Privates
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
protected:
|
||||
class CallbackDataDetails;
|
||||
|
||||
// Helpers...
|
||||
struct CallbackData {
|
||||
CallbackData(CallbackData const &) = delete;
|
||||
@@ -205,6 +195,13 @@ protected:
|
||||
|
||||
void scheduleCallback(CallbackHandler* handler, void* user, CallbackHandler::Callback callback);
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Privates
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
protected:
|
||||
class CallbackDataDetails;
|
||||
|
||||
inline void scheduleDestroy(BufferDescriptor&& buffer) noexcept {
|
||||
if (buffer.hasCallback()) {
|
||||
scheduleDestroySlow(std::move(buffer));
|
||||
|
||||
@@ -67,7 +67,6 @@ template io::ostream& operator<<(io::ostream& out, const Handle<HwFence>& h) noe
|
||||
template io::ostream& operator<<(io::ostream& out, const Handle<HwSwapChain>& h) noexcept;
|
||||
template io::ostream& operator<<(io::ostream& out, const Handle<HwStream>& h) noexcept;
|
||||
template io::ostream& operator<<(io::ostream& out, const Handle<HwTimerQuery>& h) noexcept;
|
||||
template io::ostream& operator<<(io::ostream& out, const Handle<HwSync>& h) noexcept;
|
||||
template io::ostream& operator<<(io::ostream& out, const Handle<HwBufferObject>& h) noexcept;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -39,6 +39,11 @@ Program& Program::operator=(Program&& rhs) noexcept {
|
||||
|
||||
Program::~Program() noexcept = default;
|
||||
|
||||
Program& Program::priorityQueue(CompilerPriorityQueue priorityQueue) noexcept {
|
||||
mPriorityQueue = priorityQueue;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Program& Program::diagnostics(CString const& name,
|
||||
Invocable<io::ostream&(io::ostream&)>&& logger) {
|
||||
mName = name;
|
||||
|
||||
@@ -176,9 +176,9 @@ void MetalDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
|
||||
}
|
||||
|
||||
void MetalDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
|
||||
FrameCompletedCallback callback, void* user) {
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||
auto* swapChain = handle_cast<MetalSwapChain>(sch);
|
||||
swapChain->setFrameCompletedCallback(callback, user);
|
||||
swapChain->setFrameCompletedCallback(handler, callback, user);
|
||||
}
|
||||
|
||||
void MetalDriver::execute(std::function<void(void)> const& fn) noexcept {
|
||||
@@ -380,11 +380,6 @@ void MetalDriver::createFenceR(Handle<HwFence> fh, int dummy) {
|
||||
fence->encode();
|
||||
}
|
||||
|
||||
void MetalDriver::createSyncR(Handle<HwSync> sh, int) {
|
||||
auto* fence = handle_cast<MetalFence>(sh);
|
||||
fence->encode();
|
||||
}
|
||||
|
||||
void MetalDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
|
||||
if (UTILS_UNLIKELY(flags & SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER)) {
|
||||
CVPixelBufferRef pixelBuffer = (CVPixelBufferRef) nativeWindow;
|
||||
@@ -454,12 +449,6 @@ Handle<HwFence> MetalDriver::createFenceS() noexcept {
|
||||
return alloc_and_construct_handle<MetalFence, HwFence>(*mContext);
|
||||
}
|
||||
|
||||
Handle<HwSync> MetalDriver::createSyncS() noexcept {
|
||||
// The handle must be constructed here, as a synchronous call to getSyncStatus might happen
|
||||
// before createSyncR is executed.
|
||||
return alloc_and_construct_handle<MetalFence, HwSync>(*mContext);
|
||||
}
|
||||
|
||||
Handle<HwSwapChain> MetalDriver::createSwapChainS() noexcept {
|
||||
return alloc_handle<MetalSwapChain>();
|
||||
}
|
||||
@@ -567,13 +556,6 @@ void MetalDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
}
|
||||
}
|
||||
|
||||
void MetalDriver::destroySync(Handle<HwSync> sh) {
|
||||
if (sh) {
|
||||
destruct_handle<MetalFence>(sh);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MetalDriver::terminate() {
|
||||
// finish() will flush the pending command buffer and will ensure all GPU work has finished.
|
||||
// This must be done before calling bufferPool->reset() to ensure no buffers are in flight.
|
||||
@@ -625,12 +607,12 @@ void MetalDriver::destroyFence(Handle<HwFence> fh) {
|
||||
}
|
||||
}
|
||||
|
||||
FenceStatus MetalDriver::wait(Handle<HwFence> fh, uint64_t timeout) {
|
||||
FenceStatus MetalDriver::getFenceStatus(Handle<HwFence> fh) {
|
||||
auto* fence = handle_cast<MetalFence>(fh);
|
||||
if (!fence) {
|
||||
return FenceStatus::ERROR;
|
||||
}
|
||||
return fence->wait(timeout);
|
||||
return fence->wait(0);
|
||||
}
|
||||
|
||||
bool MetalDriver::isTextureFormatSupported(TextureFormat format) {
|
||||
@@ -714,6 +696,14 @@ bool MetalDriver::isSRGBSwapChainSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MetalDriver::isStereoSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetalDriver::isParallelShaderCompileSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MetalDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
switch (workaround) {
|
||||
case Workaround::SPLIT_EASU:
|
||||
@@ -724,6 +714,10 @@ bool MetalDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
return false;
|
||||
case Workaround::A8X_STATIC_TEXTURE_TARGET_ERROR:
|
||||
return mContext->bugs.a8xStaticTextureTargetError;
|
||||
case Workaround::DISABLE_BLIT_INTO_TEXTURE_ARRAY:
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -839,17 +833,6 @@ bool MetalDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsed
|
||||
return mContext->timerQueryImpl->getQueryResult(tq, elapsedTime);
|
||||
}
|
||||
|
||||
SyncStatus MetalDriver::getSyncStatus(Handle<HwSync> sh) {
|
||||
auto* fence = handle_cast<MetalFence>(sh);
|
||||
FenceStatus status = fence->wait(0);
|
||||
if (status == FenceStatus::TIMEOUT_EXPIRED) {
|
||||
return SyncStatus::NOT_SIGNALED;
|
||||
} else if (status == FenceStatus::CONDITION_SATISFIED) {
|
||||
return SyncStatus::SIGNALED;
|
||||
}
|
||||
return SyncStatus::ERROR;
|
||||
}
|
||||
|
||||
void MetalDriver::generateMipmaps(Handle<HwTexture> th) {
|
||||
ASSERT_PRECONDITION(!isInRenderPass(mContext),
|
||||
"generateMipmaps must be called outside of a render pass.");
|
||||
@@ -973,9 +956,11 @@ void MetalDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh, BufferDescripto
|
||||
scheduleDestroy(std::move(data));
|
||||
}
|
||||
|
||||
void MetalDriver::compilePrograms(CallbackHandler* handler,
|
||||
CallbackHandler::Callback callback, void* user) {
|
||||
scheduleCallback(handler, user, callback);
|
||||
void MetalDriver::compilePrograms(CompilerPriorityQueue priority,
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||
if (callback) {
|
||||
scheduleCallback(handler, user, callback);
|
||||
}
|
||||
}
|
||||
|
||||
void MetalDriver::beginRenderPass(Handle<HwRenderTarget> rth,
|
||||
|
||||
@@ -70,7 +70,8 @@ public:
|
||||
void releaseDrawable();
|
||||
|
||||
void setFrameScheduledCallback(FrameScheduledCallback callback, void* user);
|
||||
void setFrameCompletedCallback(FrameCompletedCallback callback, void* user);
|
||||
void setFrameCompletedCallback(CallbackHandler* handler,
|
||||
CallbackHandler::Callback callback, void* user);
|
||||
|
||||
// For CAMetalLayer-backed SwapChains, presents the drawable or schedules a
|
||||
// FrameScheduledCallback.
|
||||
@@ -112,8 +113,11 @@ private:
|
||||
FrameScheduledCallback frameScheduledCallback = nullptr;
|
||||
void* frameScheduledUserData = nullptr;
|
||||
|
||||
FrameCompletedCallback frameCompletedCallback = nullptr;
|
||||
void* frameCompletedUserData = nullptr;
|
||||
struct {
|
||||
CallbackHandler* handler = nullptr;
|
||||
CallbackHandler::Callback callback = {};
|
||||
void* user = nullptr;
|
||||
} frameCompleted;
|
||||
};
|
||||
|
||||
class MetalBufferObject : public HwBufferObject {
|
||||
@@ -446,9 +450,7 @@ private:
|
||||
};
|
||||
|
||||
// MetalFence is used to implement both Fences and Syncs.
|
||||
// There's no diamond problem, because HwBase (superclass of HwFence and HwSync) is empty.
|
||||
static_assert(std::is_empty_v<HwBase>);
|
||||
class MetalFence : public HwFence, public HwSync {
|
||||
class MetalFence : public HwFence {
|
||||
public:
|
||||
|
||||
// MetalFence is special, as it gets constructed on the Filament thread. We must delay inserting
|
||||
|
||||
@@ -194,13 +194,15 @@ void MetalSwapChain::setFrameScheduledCallback(FrameScheduledCallback callback,
|
||||
frameScheduledUserData = user;
|
||||
}
|
||||
|
||||
void MetalSwapChain::setFrameCompletedCallback(FrameCompletedCallback callback, void* user) {
|
||||
frameCompletedCallback = callback;
|
||||
frameCompletedUserData = user;
|
||||
void MetalSwapChain::setFrameCompletedCallback(CallbackHandler* handler,
|
||||
CallbackHandler::Callback callback, void* user) {
|
||||
frameCompleted.handler = handler;
|
||||
frameCompleted.callback = callback;
|
||||
frameCompleted.user = user;
|
||||
}
|
||||
|
||||
void MetalSwapChain::present() {
|
||||
if (frameCompletedCallback) {
|
||||
if (frameCompleted.callback) {
|
||||
scheduleFrameCompletedCallback();
|
||||
}
|
||||
if (drawable) {
|
||||
@@ -244,30 +246,17 @@ void MetalSwapChain::scheduleFrameScheduledCallback() {
|
||||
}
|
||||
|
||||
void MetalSwapChain::scheduleFrameCompletedCallback() {
|
||||
if (!frameCompletedCallback) {
|
||||
if (!frameCompleted.callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
FrameCompletedCallback callback = frameCompletedCallback;
|
||||
void* userData = frameCompletedUserData;
|
||||
[getPendingCommandBuffer(&context) addCompletedHandler:^(id<MTLCommandBuffer> cb) {
|
||||
struct CallbackData {
|
||||
void* userData;
|
||||
FrameCompletedCallback callback;
|
||||
};
|
||||
CallbackData* data = new CallbackData();
|
||||
data->userData = userData;
|
||||
data->callback = callback;
|
||||
CallbackHandler* handler = frameCompleted.handler;
|
||||
void* user = frameCompleted.user;
|
||||
CallbackHandler::Callback callback = frameCompleted.callback;
|
||||
|
||||
// Instantiate a BufferDescriptor with a callback for the sole purpose of passing it to
|
||||
// scheduleDestroy. This forces the BufferDescriptor callback (and thus the
|
||||
// FrameCompletedCallback) to be called on the user thread.
|
||||
BufferDescriptor b(nullptr, 0u, [](void* buffer, size_t size, void* user) {
|
||||
CallbackData* data = (CallbackData*) user;
|
||||
data->callback(data->userData);
|
||||
free(data);
|
||||
}, data);
|
||||
context.driver->scheduleDestroy(std::move(b));
|
||||
MetalDriver* driver = context.driver;
|
||||
[getPendingCommandBuffer(&context) addCompletedHandler:^(id<MTLCommandBuffer> cb) {
|
||||
driver->scheduleCallback(handler, user, callback);
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace filament {
|
||||
namespace backend {
|
||||
|
||||
inline bool operator==(const SamplerParams& lhs, const SamplerParams& rhs) {
|
||||
return lhs.u == rhs.u;
|
||||
return SamplerParams::EqualTo{}(lhs, rhs);
|
||||
}
|
||||
|
||||
// Rasterization Bindings
|
||||
|
||||
@@ -58,7 +58,7 @@ void NoopDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
|
||||
}
|
||||
|
||||
void NoopDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
|
||||
FrameCompletedCallback callback, void* user) {
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||
|
||||
}
|
||||
|
||||
@@ -107,9 +107,6 @@ void NoopDriver::destroyStream(Handle<HwStream> sh) {
|
||||
void NoopDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
}
|
||||
|
||||
void NoopDriver::destroySync(Handle<HwSync> fh) {
|
||||
}
|
||||
|
||||
Handle<HwStream> NoopDriver::createStreamNative(void* nativeStream) {
|
||||
return {};
|
||||
}
|
||||
@@ -135,7 +132,7 @@ void NoopDriver::updateStreams(CommandStream* driver) {
|
||||
void NoopDriver::destroyFence(Handle<HwFence> fh) {
|
||||
}
|
||||
|
||||
FenceStatus NoopDriver::wait(Handle<HwFence> fh, uint64_t timeout) {
|
||||
FenceStatus NoopDriver::getFenceStatus(Handle<HwFence> fh) {
|
||||
return FenceStatus::CONDITION_SATISFIED;
|
||||
}
|
||||
|
||||
@@ -177,6 +174,14 @@ bool NoopDriver::isSRGBSwapChainSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NoopDriver::isStereoSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NoopDriver::isParallelShaderCompileSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NoopDriver::isWorkaroundNeeded(Workaround) {
|
||||
return false;
|
||||
}
|
||||
@@ -236,10 +241,6 @@ bool NoopDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsedT
|
||||
return false;
|
||||
}
|
||||
|
||||
SyncStatus NoopDriver::getSyncStatus(Handle<HwSync> sh) {
|
||||
return SyncStatus::SIGNALED;
|
||||
}
|
||||
|
||||
void NoopDriver::setExternalImage(Handle<HwTexture> th, void* image) {
|
||||
}
|
||||
|
||||
@@ -260,9 +261,11 @@ void NoopDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
|
||||
scheduleDestroy(std::move(data));
|
||||
}
|
||||
|
||||
void NoopDriver::compilePrograms(CallbackHandler* handler,
|
||||
CallbackHandler::Callback callback, void* user) {
|
||||
scheduleCallback(handler, user, callback);
|
||||
void NoopDriver::compilePrograms(CompilerPriorityQueue priority,
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||
if (callback) {
|
||||
scheduleCallback(handler, user, callback);
|
||||
}
|
||||
}
|
||||
|
||||
void NoopDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassParams& params) {
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "CallbackManager.h"
|
||||
|
||||
#include "DriverBase.h"
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
CallbackManager::CallbackManager(DriverBase& driver) noexcept
|
||||
: mDriver(driver), mCallbacks(1) {
|
||||
}
|
||||
|
||||
CallbackManager::~CallbackManager() noexcept = default;
|
||||
|
||||
void CallbackManager::terminate() noexcept {
|
||||
for (auto&& item: mCallbacks) {
|
||||
if (item.func) {
|
||||
mDriver.scheduleCallback(
|
||||
item.handler, item.user, item.func);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CallbackManager::Handle CallbackManager::get() const noexcept {
|
||||
Container::const_iterator const curr = getCurrent();
|
||||
curr->count.fetch_add(1);
|
||||
return curr;
|
||||
}
|
||||
|
||||
void CallbackManager::put(Handle& curr) noexcept {
|
||||
if (curr->count.fetch_sub(1) == 1) {
|
||||
if (curr->func) {
|
||||
mDriver.scheduleCallback(
|
||||
curr->handler, curr->user, curr->func);
|
||||
destroySlot(curr);
|
||||
}
|
||||
}
|
||||
curr = {};
|
||||
}
|
||||
|
||||
void CallbackManager::setCallback(
|
||||
CallbackHandler* handler, CallbackHandler::Callback func, void* user) {
|
||||
assert_invariant(func);
|
||||
Container::iterator const curr = allocateNewSlot();
|
||||
curr->handler = handler;
|
||||
curr->func = func;
|
||||
curr->user = user;
|
||||
if (curr->count == 0) {
|
||||
mDriver.scheduleCallback(
|
||||
curr->handler, curr->user, curr->func);
|
||||
destroySlot(curr);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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_OPENGL_CALLBACKMANAGER_H
|
||||
#define TNT_FILAMENT_BACKEND_OPENGL_CALLBACKMANAGER_H
|
||||
|
||||
#include <backend/CallbackHandler.h>
|
||||
|
||||
#include <utils/Mutex.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <list>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class DriverBase;
|
||||
class CallbackHandler;
|
||||
|
||||
/*
|
||||
* CallbackManager schedules user callbacks once all previous conditions are met.
|
||||
* A "Condition" is created by calling "get" and is met by calling "put". These
|
||||
* are typically called from different threads.
|
||||
* The callback is specified with "setCallback", which atomically creates a new set of
|
||||
* conditions to be met.
|
||||
*/
|
||||
class CallbackManager {
|
||||
struct Callback {
|
||||
mutable std::atomic_int count{};
|
||||
CallbackHandler* handler = nullptr;
|
||||
CallbackHandler::Callback func = {};
|
||||
void* user = nullptr;
|
||||
};
|
||||
|
||||
using Container = std::list<Callback>;
|
||||
|
||||
public:
|
||||
using Handle = Container::const_iterator;
|
||||
|
||||
explicit CallbackManager(DriverBase& driver) noexcept;
|
||||
|
||||
~CallbackManager() noexcept;
|
||||
|
||||
// Calls all the pending callbacks regardless of remaining conditions to be met. This is to
|
||||
// avoid leaking resources for instance. It also doesn't matter if the conditions are met
|
||||
// because we're shutting down.
|
||||
void terminate() noexcept;
|
||||
|
||||
// creates a condition and get a handle for it
|
||||
Handle get() const noexcept;
|
||||
|
||||
// Announces the specified condition is met. If a callback was specified and all conditions
|
||||
// prior to setting the callback are met, the callback is scheduled.
|
||||
void put(Handle& curr) noexcept;
|
||||
|
||||
// Sets a callback to be called when all previously created (get) conditions are met (put).
|
||||
// If there were no conditions created, or they're all already met, the callback is scheduled
|
||||
// immediately.
|
||||
void setCallback(CallbackHandler* handler, CallbackHandler::Callback func, void* user);
|
||||
|
||||
private:
|
||||
Container::const_iterator getCurrent() const noexcept {
|
||||
std::lock_guard const lock(mLock);
|
||||
return --mCallbacks.end();
|
||||
}
|
||||
|
||||
Container::iterator allocateNewSlot() noexcept {
|
||||
std::lock_guard const lock(mLock);
|
||||
auto curr = --mCallbacks.end();
|
||||
mCallbacks.emplace_back();
|
||||
return curr;
|
||||
}
|
||||
void destroySlot(Container::const_iterator curr) noexcept {
|
||||
std::lock_guard const lock(mLock);
|
||||
mCallbacks.erase(curr);
|
||||
}
|
||||
|
||||
DriverBase& mDriver;
|
||||
mutable utils::Mutex mLock;
|
||||
Container mCallbacks;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_OPENGL_CALLBACKMANAGER_H
|
||||
@@ -41,6 +41,7 @@ GLuint OpenGLBlobCache::retrieve(BlobCacheKey* outKey, Platform& platform,
|
||||
|
||||
GLuint programId = 0;
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
BlobCacheKey key{ program.getCacheId(), program.getSpecializationConstants() };
|
||||
|
||||
// FIXME: use a static buffer to avoid systematic allocation
|
||||
@@ -77,12 +78,14 @@ GLuint OpenGLBlobCache::retrieve(BlobCacheKey* outKey, Platform& platform,
|
||||
using std::swap;
|
||||
swap(*outKey, key);
|
||||
}
|
||||
#endif
|
||||
|
||||
return programId;
|
||||
}
|
||||
|
||||
void OpenGLBlobCache::insert(Platform& platform,
|
||||
BlobCacheKey const& key, GLuint program) noexcept {
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
SYSTRACE_CALL();
|
||||
if (platform.hasBlobFunc()) {
|
||||
SYSTRACE_CONTEXT();
|
||||
@@ -102,6 +105,21 @@ void OpenGLBlobCache::insert(Platform& platform,
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLBlobCache::insert(Platform& platform, BlobCacheKey const& key,
|
||||
GLenum format, void* data, GLsizei programBinarySize) noexcept {
|
||||
SYSTRACE_CALL();
|
||||
if (platform.hasBlobFunc()) {
|
||||
if (programBinarySize) {
|
||||
size_t const size = sizeof(Blob) + programBinarySize;
|
||||
std::unique_ptr<Blob, decltype(&::free)> blob{ (Blob*)malloc(size), &::free };
|
||||
blob->format = format;
|
||||
memcpy(blob->data, data, programBinarySize);
|
||||
platform.insertBlob(key.data(), key.size(), blob.get(), size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -34,6 +34,9 @@ public:
|
||||
static void insert(Platform& platform,
|
||||
BlobCacheKey const& key, GLuint program) noexcept;
|
||||
|
||||
static void insert(Platform& platform, BlobCacheKey const& key,
|
||||
GLenum format, void* data, GLsizei programBinarySize) noexcept;
|
||||
|
||||
private:
|
||||
struct Blob;
|
||||
};
|
||||
|
||||
@@ -49,6 +49,7 @@ bool OpenGLContext::queryOpenGLVersion(GLint* major, GLint* minor) noexcept {
|
||||
}
|
||||
|
||||
OpenGLContext::OpenGLContext() noexcept {
|
||||
|
||||
state.vao.p = &mDefaultVAO;
|
||||
|
||||
// These queries work with all GL/GLES versions!
|
||||
@@ -61,257 +62,74 @@ OpenGLContext::OpenGLContext() noexcept {
|
||||
"[" << state.version << "], [" << state.shader << "]" << io::endl;
|
||||
|
||||
/*
|
||||
* Figure out GL / GLES version and available features
|
||||
* Figure out GL / GLES version, extensions and capabilities we need to
|
||||
* determine the feature level
|
||||
*/
|
||||
|
||||
queryOpenGLVersion(&state.major, &state.minor);
|
||||
|
||||
glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &gets.max_renderbuffer_size);
|
||||
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &gets.max_texture_image_units);
|
||||
glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &gets.max_combined_texture_image_units);
|
||||
OpenGLContext::initExtensions(&ext, state.major, state.minor);
|
||||
|
||||
if (state.major > 2) { // this check works for both GL and GLES, but is intended for GLES
|
||||
OpenGLContext::initProcs(&procs, ext, state.major, state.minor);
|
||||
|
||||
OpenGLContext::initBugs(&bugs, ext, state.major, state.minor,
|
||||
state.vendor, state.renderer, state.version, state.shader);
|
||||
|
||||
glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &gets.max_renderbuffer_size);
|
||||
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &gets.max_texture_image_units);
|
||||
glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &gets.max_combined_texture_image_units);
|
||||
|
||||
mFeatureLevel = OpenGLContext::resolveFeatureLevel(state.major, state.minor, ext, gets, bugs);
|
||||
|
||||
#ifdef BACKEND_OPENGL_VERSION_GLES
|
||||
mShaderModel = ShaderModel::MOBILE;
|
||||
#else
|
||||
mShaderModel = ShaderModel::DESKTOP;
|
||||
#endif
|
||||
|
||||
#ifdef BACKEND_OPENGL_VERSION_GLES
|
||||
if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_2) {
|
||||
features.multisample_texture = true;
|
||||
}
|
||||
#else
|
||||
if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1) {
|
||||
features.multisample_texture = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1) {
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &gets.max_uniform_block_size);
|
||||
glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &gets.max_uniform_buffer_bindings);
|
||||
glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &gets.uniform_buffer_offset_alignment);
|
||||
glGetIntegerv(GL_MAX_SAMPLES, &gets.max_samples);
|
||||
glGetIntegerv(GL_MAX_DRAW_BUFFERS, &gets.max_draw_buffers);
|
||||
glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE,
|
||||
&gets.max_uniform_block_size);
|
||||
glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS,
|
||||
&gets.max_uniform_buffer_bindings);
|
||||
glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT,
|
||||
&gets.uniform_buffer_offset_alignment);
|
||||
glGetIntegerv(GL_MAX_SAMPLES,
|
||||
&gets.max_samples);
|
||||
glGetIntegerv(GL_MAX_DRAW_BUFFERS,
|
||||
&gets.max_draw_buffers);
|
||||
glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,
|
||||
&gets.max_transform_feedback_separate_attribs);
|
||||
#ifdef GL_EXT_texture_filter_anisotropic
|
||||
if (ext.EXT_texture_filter_anisotropic) {
|
||||
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gets.max_anisotropy);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
#endif
|
||||
}
|
||||
#ifdef BACKEND_OPENGL_VERSION_GLES
|
||||
else {
|
||||
gets.max_uniform_block_size = 0;
|
||||
gets.max_uniform_buffer_bindings = 0;
|
||||
gets.uniform_buffer_offset_alignment = 0;
|
||||
gets.max_samples = 1;
|
||||
gets.max_draw_buffers = 1;
|
||||
gets.max_transform_feedback_separate_attribs = 0;
|
||||
}
|
||||
|
||||
constexpr auto const caps3 = FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_3];
|
||||
constexpr GLint MAX_VERTEX_SAMPLER_COUNT = caps3.MAX_VERTEX_SAMPLER_COUNT;
|
||||
constexpr GLint MAX_FRAGMENT_SAMPLER_COUNT = caps3.MAX_FRAGMENT_SAMPLER_COUNT;
|
||||
|
||||
// default procs that can be overridden based on runtime version
|
||||
#ifdef BACKEND_OPENGL_LEVEL_GLES30
|
||||
procs.genVertexArrays = glGenVertexArrays;
|
||||
procs.bindVertexArray = glBindVertexArray;
|
||||
procs.deleteVertexArrays = glDeleteVertexArrays;
|
||||
|
||||
// these are core in GL and GLES 3.x
|
||||
procs.genQueries = glGenQueries;
|
||||
procs.deleteQueries = glDeleteQueries;
|
||||
procs.beginQuery = glBeginQuery;
|
||||
procs.endQuery = glEndQuery;
|
||||
procs.getQueryObjectuiv = glGetQueryObjectuiv;
|
||||
# ifdef BACKEND_OPENGL_VERSION_GL
|
||||
procs.getQueryObjectui64v = glGetQueryObjectui64v; // only core in GL
|
||||
# elif defined(GL_EXT_disjoint_timer_query)
|
||||
procs.getQueryObjectui64v = glGetQueryObjectui64vEXT;
|
||||
# endif // BACKEND_OPENGL_VERSION_GL
|
||||
|
||||
// core in ES 3.0 and GL 4.3
|
||||
procs.invalidateFramebuffer = glInvalidateFramebuffer;
|
||||
#endif // BACKEND_OPENGL_LEVEL_GLES30
|
||||
|
||||
#ifdef BACKEND_OPENGL_VERSION_GLES
|
||||
initExtensionsGLES();
|
||||
if (state.major == 3) {
|
||||
// Runtime OpenGL version is ES 3.x
|
||||
assert_invariant(gets.max_texture_image_units >= 16);
|
||||
assert_invariant(gets.max_combined_texture_image_units >= 32);
|
||||
if (state.minor >= 1) {
|
||||
features.multisample_texture = true;
|
||||
// figure out our feature level
|
||||
if (ext.EXT_texture_cube_map_array) {
|
||||
mFeatureLevel = FeatureLevel::FEATURE_LEVEL_2;
|
||||
if (gets.max_texture_image_units >= MAX_FRAGMENT_SAMPLER_COUNT &&
|
||||
gets.max_combined_texture_image_units >=
|
||||
(MAX_FRAGMENT_SAMPLER_COUNT + MAX_VERTEX_SAMPLER_COUNT)) {
|
||||
mFeatureLevel = FeatureLevel::FEATURE_LEVEL_3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifndef IOS // IOS is guaranteed to have ES3.x
|
||||
else if (UTILS_UNLIKELY(state.major == 2)) {
|
||||
// Runtime OpenGL version is ES 2.x
|
||||
|
||||
#if defined(BACKEND_OPENGL_LEVEL_GLES30)
|
||||
// mandatory extensions (all supported by Mali-400 and Adreno 304)
|
||||
assert_invariant(ext.OES_depth_texture);
|
||||
assert_invariant(ext.OES_depth24);
|
||||
assert_invariant(ext.OES_packed_depth_stencil);
|
||||
assert_invariant(ext.OES_rgb8_rgba8);
|
||||
assert_invariant(ext.OES_standard_derivatives);
|
||||
assert_invariant(ext.OES_texture_npot);
|
||||
#endif
|
||||
|
||||
if (UTILS_LIKELY(ext.OES_vertex_array_object)) {
|
||||
procs.genVertexArrays = glGenVertexArraysOES;
|
||||
procs.bindVertexArray = glBindVertexArrayOES;
|
||||
procs.deleteVertexArrays = glDeleteVertexArraysOES;
|
||||
} else {
|
||||
// if we don't have OES_vertex_array_object, just don't do anything with real VAOs,
|
||||
// we'll just rebind everything each time. Most Mali-400 support this extension, but
|
||||
// a few don't.
|
||||
procs.genVertexArrays = +[](GLsizei, GLuint*) {};
|
||||
procs.bindVertexArray = +[](GLuint) {};
|
||||
procs.deleteVertexArrays = +[](GLsizei, GLuint const*) {};
|
||||
// we activate this workaround path, which does the reset of array buffer
|
||||
bugs.vao_doesnt_store_element_array_buffer_binding = true;
|
||||
}
|
||||
|
||||
// EXT_disjoint_timer_query is optional -- pointers will be null if not available
|
||||
procs.genQueries = glGenQueriesEXT;
|
||||
procs.deleteQueries = glDeleteQueriesEXT;
|
||||
procs.beginQuery = glBeginQueryEXT;
|
||||
procs.endQuery = glEndQueryEXT;
|
||||
procs.getQueryObjectuiv = glGetQueryObjectuivEXT;
|
||||
procs.getQueryObjectui64v = glGetQueryObjectui64vEXT;
|
||||
|
||||
procs.invalidateFramebuffer = glDiscardFramebufferEXT;
|
||||
|
||||
mFeatureLevel = FeatureLevel::FEATURE_LEVEL_0;
|
||||
}
|
||||
#endif // IOS
|
||||
#else
|
||||
initExtensionsGL();
|
||||
if (state.major == 4) {
|
||||
assert_invariant(state.minor >= 1);
|
||||
mShaderModel = ShaderModel::DESKTOP;
|
||||
if (state.minor >= 3) {
|
||||
// cubemap arrays are available as of OpenGL 4.0
|
||||
mFeatureLevel = FeatureLevel::FEATURE_LEVEL_2;
|
||||
// figure out our feature level
|
||||
if (gets.max_texture_image_units >= MAX_FRAGMENT_SAMPLER_COUNT &&
|
||||
gets.max_combined_texture_image_units >=
|
||||
(MAX_FRAGMENT_SAMPLER_COUNT + MAX_VERTEX_SAMPLER_COUNT)) {
|
||||
mFeatureLevel = FeatureLevel::FEATURE_LEVEL_3;
|
||||
}
|
||||
}
|
||||
features.multisample_texture = true;
|
||||
}
|
||||
// feedback loops are allowed on GL desktop as long as writes are disabled
|
||||
bugs.allow_read_only_ancillary_feedback_loop = true;
|
||||
assert_invariant(gets.max_texture_image_units >= 16);
|
||||
assert_invariant(gets.max_combined_texture_image_units >= 32);
|
||||
#endif
|
||||
|
||||
#ifdef GL_EXT_texture_filter_anisotropic
|
||||
if (ext.EXT_texture_filter_anisotropic) {
|
||||
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gets.max_anisotropy);
|
||||
gets.max_anisotropy = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Figure out which driver bugs we need to workaround
|
||||
*/
|
||||
|
||||
const bool isAngle = strstr(state.renderer, "ANGLE");
|
||||
if (!isAngle) {
|
||||
if (strstr(state.renderer, "Adreno")) {
|
||||
// Qualcomm GPU
|
||||
bugs.invalidate_end_only_if_invalidate_start = true;
|
||||
|
||||
// On Adreno (As of 3/20) timer query seem to return the CPU time, not the GPU time.
|
||||
bugs.dont_use_timer_query = true;
|
||||
|
||||
// Blits to texture arrays are failing
|
||||
// This bug continues to reproduce, though at times we've seen it appear to "go away".
|
||||
// The standalone sample app that was written to show this problem still reproduces.
|
||||
// The working hypthesis is that some other state affects this behavior.
|
||||
bugs.disable_sidecar_blit_into_texture_array = true;
|
||||
|
||||
// early exit condition is flattened in EASU code
|
||||
bugs.split_easu = true;
|
||||
|
||||
// initialize the non used uniform array for adreno drivers.
|
||||
bugs.enable_initialize_non_used_uniform_array = true;
|
||||
|
||||
int maj, min, driverMajor, driverMinor;
|
||||
int const c = sscanf(state.version, "OpenGL ES %d.%d V@%d.%d", // NOLINT(cert-err34-c)
|
||||
&maj, &min, &driverMajor, &driverMinor);
|
||||
if (c == 4) {
|
||||
// Workarounds based on version here.
|
||||
// notes:
|
||||
// bugs.invalidate_end_only_if_invalidate_start
|
||||
// - appeared at least in
|
||||
// "OpenGL ES 3.2 V@0490.0 (GIT@85da404, I46ff5fc46f, 1606794520) (Date:11/30/20)"
|
||||
// - wasn't present in
|
||||
// "OpenGL ES 3.2 V@0490.0 (GIT@0905e9f, Ia11ce2d146, 1599072951) (Date:09/02/20)"
|
||||
// - has been confirmed fixed in V@570.1 by Qualcomm
|
||||
if (driverMajor < 490 || driverMajor > 570 ||
|
||||
(driverMajor == 570 && driverMinor >= 1)) {
|
||||
bugs.invalidate_end_only_if_invalidate_start = false;
|
||||
}
|
||||
}
|
||||
|
||||
// qualcomm seems to have no problem with this (which is good for us)
|
||||
bugs.allow_read_only_ancillary_feedback_loop = true;
|
||||
} else if (strstr(state.renderer, "Mali")) {
|
||||
// ARM GPU
|
||||
bugs.vao_doesnt_store_element_array_buffer_binding = true;
|
||||
if (strstr(state.renderer, "Mali-T")) {
|
||||
bugs.disable_glFlush = true;
|
||||
bugs.disable_shared_context_draws = true;
|
||||
bugs.texture_external_needs_rebind = true;
|
||||
// We have not verified that timer queries work on Mali-T, so we disable to be safe.
|
||||
bugs.dont_use_timer_query = true;
|
||||
}
|
||||
if (strstr(state.renderer, "Mali-G")) {
|
||||
// assume we don't have working timer queries
|
||||
bugs.dont_use_timer_query = true;
|
||||
|
||||
int maj, min, driverVersion, driverRevision, driverPatch;
|
||||
int const c = sscanf(state.version, "OpenGL ES %d.%d v%d.r%dp%d", // NOLINT(cert-err34-c)
|
||||
&maj, &min, &driverVersion, &driverRevision, &driverPatch);
|
||||
if (c == 5) {
|
||||
// Workarounds based on version here.
|
||||
// notes:
|
||||
// bugs.dont_use_timer_query : on some Mali-Gxx drivers timer query seems
|
||||
// to cause memory corruptions in some cases on some devices (see b/233754398).
|
||||
// - appeared at least in
|
||||
// "OpenGL ES 3.2 v1.r26p0-01eac0"
|
||||
// - wasn't present in
|
||||
// "OpenGL ES 3.2 v1.r32p1-00pxl1"
|
||||
if (driverVersion >= 2 || (driverVersion == 1 && driverRevision >= 32)) {
|
||||
bugs.dont_use_timer_query = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Mali seems to have no problem with this (which is good for us)
|
||||
bugs.allow_read_only_ancillary_feedback_loop = true;
|
||||
} else if (strstr(state.renderer, "Intel")) {
|
||||
// Intel GPU
|
||||
bugs.vao_doesnt_store_element_array_buffer_binding = true;
|
||||
} else if (strstr(state.renderer, "PowerVR")) {
|
||||
// PowerVR GPU
|
||||
} else if (strstr(state.renderer, "Apple")) {
|
||||
// Apple GPU
|
||||
} else if (strstr(state.renderer, "Tegra") ||
|
||||
strstr(state.renderer, "GeForce") ||
|
||||
strstr(state.renderer, "NV")) {
|
||||
// NVIDIA GPU
|
||||
} else if (strstr(state.renderer, "Vivante")) {
|
||||
// Vivante GPU
|
||||
} else if (strstr(state.renderer, "AMD") ||
|
||||
strstr(state.renderer, "ATI")) {
|
||||
// AMD/ATI GPU
|
||||
} else if (strstr(state.renderer, "Mozilla")) {
|
||||
bugs.disable_invalidate_framebuffer = true;
|
||||
}
|
||||
} else {
|
||||
// When running under ANGLE, it's a different set of workaround that we need.
|
||||
if (strstr(state.renderer, "Adreno")) {
|
||||
// Qualcomm GPU
|
||||
// early exit condition is flattened in EASU code
|
||||
// (that should be regardless of ANGLE, but we should double-check)
|
||||
bugs.split_easu = true;
|
||||
}
|
||||
// TODO: see if we could use `bugs.allow_read_only_ancillary_feedback_loop = true`
|
||||
}
|
||||
|
||||
slog.v << "Feature level: " << +mFeatureLevel << '\n';
|
||||
slog.v << "Active workarounds: " << '\n';
|
||||
@@ -337,14 +155,14 @@ OpenGLContext::OpenGLContext() noexcept {
|
||||
#endif
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
assert_invariant(state.major <= 2 || gets.max_draw_buffers >= 4); // minspec
|
||||
assert_invariant(mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0 || gets.max_draw_buffers >= 4); // minspec
|
||||
#endif
|
||||
|
||||
setDefaultState();
|
||||
|
||||
#ifdef GL_EXT_texture_filter_anisotropic
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
if (state.major > 2 && ext.EXT_texture_filter_anisotropic) {
|
||||
if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1 && ext.EXT_texture_filter_anisotropic) {
|
||||
// make sure we don't have any error flag
|
||||
while (glGetError() != GL_NO_ERROR) { }
|
||||
|
||||
@@ -444,11 +262,293 @@ void OpenGLContext::setDefaultState() noexcept {
|
||||
glClipControlEXT(GL_LOWER_LEFT_EXT, GL_ZERO_TO_ONE_EXT);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (ext.EXT_clip_cull_distance) {
|
||||
glEnable(GL_CLIP_DISTANCE0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OpenGLContext::initProcs(Procs* procs,
|
||||
Extensions const& ext, GLint major, GLint) noexcept {
|
||||
(void)ext;
|
||||
(void)major;
|
||||
|
||||
// default procs that can be overridden based on runtime version
|
||||
#ifdef BACKEND_OPENGL_LEVEL_GLES30
|
||||
procs->genVertexArrays = glGenVertexArrays;
|
||||
procs->bindVertexArray = glBindVertexArray;
|
||||
procs->deleteVertexArrays = glDeleteVertexArrays;
|
||||
|
||||
// these are core in GL and GLES 3.x
|
||||
procs->genQueries = glGenQueries;
|
||||
procs->deleteQueries = glDeleteQueries;
|
||||
procs->beginQuery = glBeginQuery;
|
||||
procs->endQuery = glEndQuery;
|
||||
procs->getQueryObjectuiv = glGetQueryObjectuiv;
|
||||
# ifdef BACKEND_OPENGL_VERSION_GL
|
||||
procs->getQueryObjectui64v = glGetQueryObjectui64v; // only core in GL
|
||||
# elif defined(GL_EXT_disjoint_timer_query)
|
||||
procs->getQueryObjectui64v = glGetQueryObjectui64vEXT;
|
||||
# endif // BACKEND_OPENGL_VERSION_GL
|
||||
|
||||
// core in ES 3.0 and GL 4.3
|
||||
procs->invalidateFramebuffer = glInvalidateFramebuffer;
|
||||
#endif // BACKEND_OPENGL_LEVEL_GLES30
|
||||
|
||||
// no-op if not supported
|
||||
procs->maxShaderCompilerThreadsKHR = +[](GLuint) {};
|
||||
|
||||
#ifdef BACKEND_OPENGL_VERSION_GLES
|
||||
# ifndef IOS // IOS is guaranteed to have ES3.x
|
||||
if (UTILS_UNLIKELY(major == 2)) {
|
||||
// Runtime OpenGL version is ES 2.x
|
||||
if (UTILS_LIKELY(ext.OES_vertex_array_object)) {
|
||||
procs->genVertexArrays = glGenVertexArraysOES;
|
||||
procs->bindVertexArray = glBindVertexArrayOES;
|
||||
procs->deleteVertexArrays = glDeleteVertexArraysOES;
|
||||
} else {
|
||||
// if we don't have OES_vertex_array_object, just don't do anything with real VAOs,
|
||||
// we'll just rebind everything each time. Most Mali-400 support this extension, but
|
||||
// a few don't.
|
||||
procs->genVertexArrays = +[](GLsizei, GLuint*) {};
|
||||
procs->bindVertexArray = +[](GLuint) {};
|
||||
procs->deleteVertexArrays = +[](GLsizei, GLuint const*) {};
|
||||
}
|
||||
|
||||
// EXT_disjoint_timer_query is optional -- pointers will be null if not available
|
||||
procs->genQueries = glGenQueriesEXT;
|
||||
procs->deleteQueries = glDeleteQueriesEXT;
|
||||
procs->beginQuery = glBeginQueryEXT;
|
||||
procs->endQuery = glEndQueryEXT;
|
||||
procs->getQueryObjectuiv = glGetQueryObjectuivEXT;
|
||||
procs->getQueryObjectui64v = glGetQueryObjectui64vEXT;
|
||||
|
||||
procs->invalidateFramebuffer = glDiscardFramebufferEXT;
|
||||
|
||||
procs->maxShaderCompilerThreadsKHR = glMaxShaderCompilerThreadsKHR;
|
||||
}
|
||||
# endif // IOS
|
||||
#else
|
||||
procs->maxShaderCompilerThreadsKHR = glMaxShaderCompilerThreadsARB;
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLContext::initBugs(Bugs* bugs, Extensions const& exts,
|
||||
GLint major, GLint minor,
|
||||
char const* vendor,
|
||||
char const* renderer,
|
||||
char const* version,
|
||||
char const* shader) {
|
||||
|
||||
(void)major;
|
||||
(void)minor;
|
||||
(void)vendor;
|
||||
(void)renderer;
|
||||
(void)version;
|
||||
(void)shader;
|
||||
|
||||
const bool isAngle = strstr(renderer, "ANGLE");
|
||||
if (!isAngle) {
|
||||
if (strstr(renderer, "Adreno")) {
|
||||
// Qualcomm GPU
|
||||
bugs->invalidate_end_only_if_invalidate_start = true;
|
||||
|
||||
// On Adreno (As of 3/20) timer query seem to return the CPU time, not the GPU time.
|
||||
bugs->dont_use_timer_query = true;
|
||||
|
||||
// Blits to texture arrays are failing
|
||||
// This bug continues to reproduce, though at times we've seen it appear to "go away".
|
||||
// The standalone sample app that was written to show this problem still reproduces.
|
||||
// The working hypothesis is that some other state affects this behavior.
|
||||
bugs->disable_blit_into_texture_array = true;
|
||||
|
||||
// early exit condition is flattened in EASU code
|
||||
bugs->split_easu = true;
|
||||
|
||||
// initialize the non-used uniform array for Adreno drivers.
|
||||
bugs->enable_initialize_non_used_uniform_array = true;
|
||||
|
||||
int maj, min, driverMajor, driverMinor;
|
||||
int const c = sscanf(version, "OpenGL ES %d.%d V@%d.%d", // NOLINT(cert-err34-c)
|
||||
&maj, &min, &driverMajor, &driverMinor);
|
||||
if (c == 4) {
|
||||
// Workarounds based on version here.
|
||||
// Notes:
|
||||
// bugs.invalidate_end_only_if_invalidate_start
|
||||
// - appeared at least in
|
||||
// "OpenGL ES 3.2 V@0490.0 (GIT@85da404, I46ff5fc46f, 1606794520) (Date:11/30/20)"
|
||||
// - wasn't present in
|
||||
// "OpenGL ES 3.2 V@0490.0 (GIT@0905e9f, Ia11ce2d146, 1599072951) (Date:09/02/20)"
|
||||
// - has been confirmed fixed in V@570.1 by Qualcomm
|
||||
if (driverMajor < 490 || driverMajor > 570 ||
|
||||
(driverMajor == 570 && driverMinor >= 1)) {
|
||||
bugs->invalidate_end_only_if_invalidate_start = false;
|
||||
}
|
||||
}
|
||||
|
||||
// qualcomm seems to have no problem with this (which is good for us)
|
||||
bugs->allow_read_only_ancillary_feedback_loop = true;
|
||||
|
||||
// Older Adreno devices that support ES3.0 only tend to be extremely buggy, so we
|
||||
// fall back to ES2.0.
|
||||
if (major == 3 && minor == 0) {
|
||||
bugs->force_feature_level0 = true;
|
||||
}
|
||||
} else if (strstr(renderer, "Mali")) {
|
||||
// ARM GPU
|
||||
bugs->vao_doesnt_store_element_array_buffer_binding = true;
|
||||
if (strstr(renderer, "Mali-T")) {
|
||||
bugs->disable_glFlush = true;
|
||||
bugs->disable_shared_context_draws = true;
|
||||
bugs->texture_external_needs_rebind = true;
|
||||
// We have not verified that timer queries work on Mali-T, so we disable to be safe.
|
||||
bugs->dont_use_timer_query = true;
|
||||
}
|
||||
if (strstr(renderer, "Mali-G")) {
|
||||
// We have run into several problems with timer queries on Mali-Gxx:
|
||||
// - timer queries seem to cause memory corruptions in some cases on some devices
|
||||
// (see b/233754398)
|
||||
// - appeared at least in: "OpenGL ES 3.2 v1.r26p0-01eac0"
|
||||
// - wasn't present in: "OpenGL ES 3.2 v1.r32p1-00pxl1"
|
||||
// - timer queries sometime crash with an NPE (see b/273759031)
|
||||
bugs->dont_use_timer_query = true;
|
||||
}
|
||||
// Mali seems to have no problem with this (which is good for us)
|
||||
bugs->allow_read_only_ancillary_feedback_loop = true;
|
||||
} else if (strstr(renderer, "Intel")) {
|
||||
// Intel GPU
|
||||
bugs->vao_doesnt_store_element_array_buffer_binding = true;
|
||||
} else if (strstr(renderer, "PowerVR")) {
|
||||
// PowerVR GPU
|
||||
// On PowerVR (Rogue GE8320) glFlush doesn't seem to do anything, in particular,
|
||||
// it doesn't kick the GPU earlier, so don't issue these calls as they seem to slow
|
||||
// things down.
|
||||
bugs->disable_glFlush = true;
|
||||
// On PowerVR (Rogue GE8320) using gl_InstanceID too early in the shader doesn't work.
|
||||
bugs->powervr_shader_workarounds = true;
|
||||
// On PowerVR (Rogue GE8320) destroying a fbo after glBlitFramebuffer is effectively
|
||||
// equivalent to glFinish.
|
||||
bugs->delay_fbo_destruction = true;
|
||||
// PowerVR seems to have no problem with this (which is good for us)
|
||||
bugs->allow_read_only_ancillary_feedback_loop = true;
|
||||
// PowerVR has a shader compiler thread pinned on the last core
|
||||
bugs->disable_thread_affinity = true;
|
||||
} else if (strstr(renderer, "Apple")) {
|
||||
// Apple GPU
|
||||
} else if (strstr(renderer, "Tegra") ||
|
||||
strstr(renderer, "GeForce") ||
|
||||
strstr(renderer, "NV")) {
|
||||
// NVIDIA GPU
|
||||
} else if (strstr(renderer, "Vivante")) {
|
||||
// Vivante GPU
|
||||
} else if (strstr(renderer, "AMD") ||
|
||||
strstr(renderer, "ATI")) {
|
||||
// AMD/ATI GPU
|
||||
} else if (strstr(renderer, "Mozilla")) {
|
||||
bugs->disable_invalidate_framebuffer = true;
|
||||
}
|
||||
} else {
|
||||
// When running under ANGLE, it's a different set of workaround that we need.
|
||||
if (strstr(renderer, "Adreno")) {
|
||||
// Qualcomm GPU
|
||||
// early exit condition is flattened in EASU code
|
||||
// (that should be regardless of ANGLE, but we should double-check)
|
||||
bugs->split_easu = true;
|
||||
}
|
||||
// TODO: see if we could use `bugs.allow_read_only_ancillary_feedback_loop = true`
|
||||
}
|
||||
|
||||
#ifdef BACKEND_OPENGL_VERSION_GLES
|
||||
# ifndef IOS // IOS is guaranteed to have ES3.x
|
||||
if (UTILS_UNLIKELY(major == 2)) {
|
||||
if (UTILS_UNLIKELY(!exts.OES_vertex_array_object)) {
|
||||
// we activate this workaround path, which does the reset of array buffer
|
||||
bugs->vao_doesnt_store_element_array_buffer_binding = true;
|
||||
}
|
||||
}
|
||||
# endif // IOS
|
||||
#else
|
||||
// feedback loops are allowed on GL desktop as long as writes are disabled
|
||||
bugs->allow_read_only_ancillary_feedback_loop = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
FeatureLevel OpenGLContext::resolveFeatureLevel(GLint major, GLint minor,
|
||||
Extensions const& exts,
|
||||
Gets const& gets,
|
||||
Bugs const& bugs) noexcept {
|
||||
|
||||
constexpr auto const caps3 = FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_3];
|
||||
constexpr GLint MAX_VERTEX_SAMPLER_COUNT = caps3.MAX_VERTEX_SAMPLER_COUNT;
|
||||
constexpr GLint MAX_FRAGMENT_SAMPLER_COUNT = caps3.MAX_FRAGMENT_SAMPLER_COUNT;
|
||||
|
||||
(void)exts;
|
||||
(void)gets;
|
||||
(void)bugs;
|
||||
|
||||
FeatureLevel featureLevel = FeatureLevel::FEATURE_LEVEL_1;
|
||||
|
||||
#ifdef BACKEND_OPENGL_VERSION_GLES
|
||||
if (major == 3) {
|
||||
// Runtime OpenGL version is ES 3.x
|
||||
assert_invariant(gets.max_texture_image_units >= 16);
|
||||
assert_invariant(gets.max_combined_texture_image_units >= 32);
|
||||
if (minor >= 1) {
|
||||
// figure out our feature level
|
||||
if (exts.EXT_texture_cube_map_array) {
|
||||
featureLevel = FeatureLevel::FEATURE_LEVEL_2;
|
||||
if (gets.max_texture_image_units >= MAX_FRAGMENT_SAMPLER_COUNT &&
|
||||
gets.max_combined_texture_image_units >=
|
||||
(MAX_FRAGMENT_SAMPLER_COUNT + MAX_VERTEX_SAMPLER_COUNT)) {
|
||||
featureLevel = FeatureLevel::FEATURE_LEVEL_3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# ifndef IOS // IOS is guaranteed to have ES3.x
|
||||
else if (UTILS_UNLIKELY(major == 2)) {
|
||||
// Runtime OpenGL version is ES 2.x
|
||||
# if defined(BACKEND_OPENGL_LEVEL_GLES30)
|
||||
// mandatory extensions (all supported by Mali-400 and Adreno 304)
|
||||
assert_invariant(exts.OES_depth_texture);
|
||||
assert_invariant(exts.OES_depth24);
|
||||
assert_invariant(exts.OES_packed_depth_stencil);
|
||||
assert_invariant(exts.OES_rgb8_rgba8);
|
||||
assert_invariant(exts.OES_standard_derivatives);
|
||||
assert_invariant(exts.OES_texture_npot);
|
||||
# endif
|
||||
featureLevel = FeatureLevel::FEATURE_LEVEL_0;
|
||||
}
|
||||
# endif // IOS
|
||||
#else
|
||||
assert_invariant(gets.max_texture_image_units >= 16);
|
||||
assert_invariant(gets.max_combined_texture_image_units >= 32);
|
||||
if (major == 4) {
|
||||
assert_invariant(minor >= 1);
|
||||
if (minor >= 3) {
|
||||
// cubemap arrays are available as of OpenGL 4.0
|
||||
featureLevel = FeatureLevel::FEATURE_LEVEL_2;
|
||||
// figure out our feature level
|
||||
if (gets.max_texture_image_units >= MAX_FRAGMENT_SAMPLER_COUNT &&
|
||||
gets.max_combined_texture_image_units >=
|
||||
(MAX_FRAGMENT_SAMPLER_COUNT + MAX_VERTEX_SAMPLER_COUNT)) {
|
||||
featureLevel = FeatureLevel::FEATURE_LEVEL_3;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (bugs.force_feature_level0) {
|
||||
featureLevel = FeatureLevel::FEATURE_LEVEL_0;
|
||||
}
|
||||
|
||||
return featureLevel;
|
||||
}
|
||||
|
||||
#ifdef BACKEND_OPENGL_VERSION_GLES
|
||||
|
||||
void OpenGLContext::initExtensionsGLES() noexcept {
|
||||
void OpenGLContext::initExtensionsGLES(Extensions* ext, GLint major, GLint minor) noexcept {
|
||||
const char * const extensions = (const char*)glGetString(GL_EXTENSIONS);
|
||||
GLUtils::unordered_string_set const exts = GLUtils::split(extensions);
|
||||
if constexpr (DEBUG_PRINT_EXTENSIONS) {
|
||||
@@ -460,50 +560,50 @@ void OpenGLContext::initExtensionsGLES() noexcept {
|
||||
|
||||
// figure out and initialize the extensions we need
|
||||
using namespace std::literals;
|
||||
ext.APPLE_color_buffer_packed_float = exts.has("GL_APPLE_color_buffer_packed_float"sv);
|
||||
ext.EXT_clip_control = exts.has("GL_EXT_clip_control"sv);
|
||||
ext.EXT_color_buffer_float = exts.has("GL_EXT_color_buffer_float"sv);
|
||||
ext.EXT_color_buffer_half_float = exts.has("GL_EXT_color_buffer_half_float"sv);
|
||||
ext.EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
|
||||
ext.EXT_discard_framebuffer = exts.has("GL_EXT_discard_framebuffer"sv);
|
||||
ext.EXT_disjoint_timer_query = exts.has("GL_EXT_disjoint_timer_query"sv);
|
||||
ext.EXT_multisampled_render_to_texture = exts.has("GL_EXT_multisampled_render_to_texture"sv);
|
||||
ext.EXT_multisampled_render_to_texture2 = exts.has("GL_EXT_multisampled_render_to_texture2"sv);
|
||||
ext.EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv);
|
||||
ext->APPLE_color_buffer_packed_float = exts.has("GL_APPLE_color_buffer_packed_float"sv);
|
||||
ext->EXT_clip_control = exts.has("GL_EXT_clip_control"sv);
|
||||
ext->EXT_clip_cull_distance = exts.has("GL_EXT_clip_cull_distance"sv);
|
||||
ext->EXT_color_buffer_float = exts.has("GL_EXT_color_buffer_float"sv);
|
||||
ext->EXT_color_buffer_half_float = exts.has("GL_EXT_color_buffer_half_float"sv);
|
||||
ext->EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
|
||||
ext->EXT_discard_framebuffer = exts.has("GL_EXT_discard_framebuffer"sv);
|
||||
ext->EXT_disjoint_timer_query = exts.has("GL_EXT_disjoint_timer_query"sv);
|
||||
ext->EXT_multisampled_render_to_texture = exts.has("GL_EXT_multisampled_render_to_texture"sv);
|
||||
ext->EXT_multisampled_render_to_texture2 = exts.has("GL_EXT_multisampled_render_to_texture2"sv);
|
||||
ext->EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv);
|
||||
#if !defined(__EMSCRIPTEN__)
|
||||
ext.EXT_texture_compression_etc2 = true;
|
||||
ext->EXT_texture_compression_etc2 = true;
|
||||
#endif
|
||||
ext.EXT_texture_compression_s3tc = exts.has("GL_EXT_texture_compression_s3tc"sv);
|
||||
ext.EXT_texture_compression_s3tc_srgb = exts.has("GL_EXT_texture_compression_s3tc_srgb"sv);
|
||||
ext.EXT_texture_compression_rgtc = exts.has("GL_EXT_texture_compression_rgtc"sv);
|
||||
ext.EXT_texture_compression_bptc = exts.has("GL_EXT_texture_compression_bptc"sv);
|
||||
ext.EXT_texture_cube_map_array = exts.has("GL_EXT_texture_cube_map_array"sv) || exts.has("GL_OES_texture_cube_map_array"sv);
|
||||
ext.GOOGLE_cpp_style_line_directive = exts.has("GL_GOOGLE_cpp_style_line_directive"sv);
|
||||
ext.KHR_debug = exts.has("GL_KHR_debug"sv);
|
||||
ext.KHR_parallel_shader_compile = exts.has("GL_KHR_parallel_shader_compile"sv);
|
||||
ext.KHR_texture_compression_astc_hdr = exts.has("GL_KHR_texture_compression_astc_hdr"sv);
|
||||
ext.KHR_texture_compression_astc_ldr = exts.has("GL_KHR_texture_compression_astc_ldr"sv);
|
||||
ext.OES_depth_texture = exts.has("GL_OES_depth_texture"sv);
|
||||
ext.OES_depth24 = exts.has("GL_OES_depth24"sv);
|
||||
ext.OES_packed_depth_stencil = exts.has("GL_OES_packed_depth_stencil"sv);
|
||||
ext.OES_EGL_image_external_essl3 = exts.has("GL_OES_EGL_image_external_essl3"sv);
|
||||
ext.OES_rgb8_rgba8 = exts.has("GL_OES_rgb8_rgba8"sv);
|
||||
ext.OES_standard_derivatives = exts.has("GL_OES_standard_derivatives"sv);
|
||||
ext.OES_texture_npot = exts.has("GL_OES_texture_npot"sv);
|
||||
ext.OES_vertex_array_object = exts.has("GL_OES_vertex_array_object"sv);
|
||||
ext.WEBGL_compressed_texture_etc = exts.has("WEBGL_compressed_texture_etc"sv);
|
||||
ext.WEBGL_compressed_texture_s3tc = exts.has("WEBGL_compressed_texture_s3tc"sv);
|
||||
ext.WEBGL_compressed_texture_s3tc_srgb = exts.has("WEBGL_compressed_texture_s3tc_srgb"sv);
|
||||
ext->EXT_texture_compression_s3tc = exts.has("GL_EXT_texture_compression_s3tc"sv);
|
||||
ext->EXT_texture_compression_s3tc_srgb = exts.has("GL_EXT_texture_compression_s3tc_srgb"sv);
|
||||
ext->EXT_texture_compression_rgtc = exts.has("GL_EXT_texture_compression_rgtc"sv);
|
||||
ext->EXT_texture_compression_bptc = exts.has("GL_EXT_texture_compression_bptc"sv);
|
||||
ext->EXT_texture_cube_map_array = exts.has("GL_EXT_texture_cube_map_array"sv) || exts.has("GL_OES_texture_cube_map_array"sv);
|
||||
ext->GOOGLE_cpp_style_line_directive = exts.has("GL_GOOGLE_cpp_style_line_directive"sv);
|
||||
ext->KHR_debug = exts.has("GL_KHR_debug"sv);
|
||||
ext->KHR_parallel_shader_compile = exts.has("GL_KHR_parallel_shader_compile"sv);
|
||||
ext->KHR_texture_compression_astc_hdr = exts.has("GL_KHR_texture_compression_astc_hdr"sv);
|
||||
ext->KHR_texture_compression_astc_ldr = exts.has("GL_KHR_texture_compression_astc_ldr"sv);
|
||||
ext->OES_depth_texture = exts.has("GL_OES_depth_texture"sv);
|
||||
ext->OES_depth24 = exts.has("GL_OES_depth24"sv);
|
||||
ext->OES_packed_depth_stencil = exts.has("GL_OES_packed_depth_stencil"sv);
|
||||
ext->OES_EGL_image_external_essl3 = exts.has("GL_OES_EGL_image_external_essl3"sv);
|
||||
ext->OES_rgb8_rgba8 = exts.has("GL_OES_rgb8_rgba8"sv);
|
||||
ext->OES_standard_derivatives = exts.has("GL_OES_standard_derivatives"sv);
|
||||
ext->OES_texture_npot = exts.has("GL_OES_texture_npot"sv);
|
||||
ext->OES_vertex_array_object = exts.has("GL_OES_vertex_array_object"sv);
|
||||
ext->WEBGL_compressed_texture_etc = exts.has("WEBGL_compressed_texture_etc"sv);
|
||||
ext->WEBGL_compressed_texture_s3tc = exts.has("WEBGL_compressed_texture_s3tc"sv);
|
||||
ext->WEBGL_compressed_texture_s3tc_srgb = exts.has("WEBGL_compressed_texture_s3tc_srgb"sv);
|
||||
|
||||
// ES 3.2 implies EXT_color_buffer_float
|
||||
if (state.major > 3 || (state.major == 3 && state.minor >= 2)) {
|
||||
ext.EXT_color_buffer_float = true;
|
||||
if (major > 3 || (major == 3 && minor >= 2)) {
|
||||
ext->EXT_color_buffer_float = true;
|
||||
}
|
||||
|
||||
// ES 3.x implies EXT_discard_framebuffer and OES_vertex_array_object
|
||||
if (state.major >= 3) {
|
||||
ext.EXT_color_buffer_float = true;
|
||||
ext.OES_vertex_array_object = true;
|
||||
if (major >= 3) {
|
||||
ext->EXT_discard_framebuffer = true;
|
||||
ext->OES_vertex_array_object = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -511,7 +611,7 @@ void OpenGLContext::initExtensionsGLES() noexcept {
|
||||
|
||||
#ifdef BACKEND_OPENGL_VERSION_GL
|
||||
|
||||
void OpenGLContext::initExtensionsGL() noexcept {
|
||||
void OpenGLContext::initExtensionsGL(Extensions* ext, GLint major, GLint minor) noexcept {
|
||||
GLUtils::unordered_string_set exts;
|
||||
GLint n = 0;
|
||||
glGetIntegerv(GL_NUM_EXTENSIONS, &n);
|
||||
@@ -526,54 +626,52 @@ void OpenGLContext::initExtensionsGL() noexcept {
|
||||
}
|
||||
|
||||
using namespace std::literals;
|
||||
ext.APPLE_color_buffer_packed_float = true; // Assumes core profile.
|
||||
ext.ARB_shading_language_packing = exts.has("GL_ARB_shading_language_packing"sv);
|
||||
ext.EXT_color_buffer_float = true; // Assumes core profile.
|
||||
ext.EXT_color_buffer_half_float = true; // Assumes core profile.
|
||||
ext.EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
|
||||
ext.EXT_discard_framebuffer = false;
|
||||
ext.EXT_disjoint_timer_query = true;
|
||||
ext.EXT_multisampled_render_to_texture = false;
|
||||
ext.EXT_multisampled_render_to_texture2 = false;
|
||||
ext.EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv);
|
||||
ext.EXT_texture_compression_bptc = exts.has("GL_EXT_texture_compression_bptc"sv);
|
||||
ext.EXT_texture_compression_etc2 = exts.has("GL_ARB_ES3_compatibility"sv);
|
||||
ext.EXT_texture_compression_rgtc = exts.has("GL_EXT_texture_compression_rgtc"sv);
|
||||
ext.EXT_texture_compression_s3tc = exts.has("GL_EXT_texture_compression_s3tc"sv);
|
||||
ext.EXT_texture_compression_s3tc_srgb = exts.has("GL_EXT_texture_compression_s3tc_srgb"sv);
|
||||
ext.EXT_texture_cube_map_array = true;
|
||||
ext.EXT_texture_filter_anisotropic = exts.has("GL_EXT_texture_filter_anisotropic"sv);
|
||||
ext.EXT_texture_sRGB = exts.has("GL_EXT_texture_sRGB"sv);
|
||||
ext.GOOGLE_cpp_style_line_directive = exts.has("GL_GOOGLE_cpp_style_line_directive"sv);
|
||||
ext.KHR_parallel_shader_compile = exts.has("GL_KHR_parallel_shader_compile"sv);
|
||||
ext.KHR_texture_compression_astc_hdr = exts.has("GL_KHR_texture_compression_astc_hdr"sv);
|
||||
ext.KHR_texture_compression_astc_ldr = exts.has("GL_KHR_texture_compression_astc_ldr"sv);
|
||||
ext.OES_depth_texture = true;
|
||||
ext.OES_depth24 = true;
|
||||
ext.OES_EGL_image_external_essl3 = false;
|
||||
ext.OES_rgb8_rgba8 = true;
|
||||
ext.OES_standard_derivatives = true;
|
||||
ext.OES_texture_npot = true;
|
||||
ext.OES_vertex_array_object = true;
|
||||
ext.WEBGL_compressed_texture_etc = false;
|
||||
ext.WEBGL_compressed_texture_s3tc = false;
|
||||
ext.WEBGL_compressed_texture_s3tc_srgb = false;
|
||||
|
||||
auto const major = state.major;
|
||||
auto const minor = state.minor;
|
||||
ext->APPLE_color_buffer_packed_float = true; // Assumes core profile.
|
||||
ext->ARB_shading_language_packing = exts.has("GL_ARB_shading_language_packing"sv);
|
||||
ext->EXT_color_buffer_float = true; // Assumes core profile.
|
||||
ext->EXT_color_buffer_half_float = true; // Assumes core profile.
|
||||
ext->EXT_clip_cull_distance = true;
|
||||
ext->EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
|
||||
ext->EXT_discard_framebuffer = false;
|
||||
ext->EXT_disjoint_timer_query = true;
|
||||
ext->EXT_multisampled_render_to_texture = false;
|
||||
ext->EXT_multisampled_render_to_texture2 = false;
|
||||
ext->EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv);
|
||||
ext->EXT_texture_compression_bptc = exts.has("GL_EXT_texture_compression_bptc"sv);
|
||||
ext->EXT_texture_compression_etc2 = exts.has("GL_ARB_ES3_compatibility"sv);
|
||||
ext->EXT_texture_compression_rgtc = exts.has("GL_EXT_texture_compression_rgtc"sv);
|
||||
ext->EXT_texture_compression_s3tc = exts.has("GL_EXT_texture_compression_s3tc"sv);
|
||||
ext->EXT_texture_compression_s3tc_srgb = exts.has("GL_EXT_texture_compression_s3tc_srgb"sv);
|
||||
ext->EXT_texture_cube_map_array = true;
|
||||
ext->EXT_texture_filter_anisotropic = exts.has("GL_EXT_texture_filter_anisotropic"sv);
|
||||
ext->EXT_texture_sRGB = exts.has("GL_EXT_texture_sRGB"sv);
|
||||
ext->GOOGLE_cpp_style_line_directive = exts.has("GL_GOOGLE_cpp_style_line_directive"sv);
|
||||
ext->KHR_parallel_shader_compile = exts.has("GL_KHR_parallel_shader_compile"sv);
|
||||
ext->KHR_texture_compression_astc_hdr = exts.has("GL_KHR_texture_compression_astc_hdr"sv);
|
||||
ext->KHR_texture_compression_astc_ldr = exts.has("GL_KHR_texture_compression_astc_ldr"sv);
|
||||
ext->OES_depth_texture = true;
|
||||
ext->OES_depth24 = true;
|
||||
ext->OES_EGL_image_external_essl3 = false;
|
||||
ext->OES_rgb8_rgba8 = true;
|
||||
ext->OES_standard_derivatives = true;
|
||||
ext->OES_texture_npot = true;
|
||||
ext->OES_vertex_array_object = true;
|
||||
ext->WEBGL_compressed_texture_etc = false;
|
||||
ext->WEBGL_compressed_texture_s3tc = false;
|
||||
ext->WEBGL_compressed_texture_s3tc_srgb = false;
|
||||
|
||||
// OpenGL 4.2 implies ARB_shading_language_packing
|
||||
if (major > 4 || (major == 4 && minor >= 2)) {
|
||||
ext.ARB_shading_language_packing = true;
|
||||
ext->ARB_shading_language_packing = true;
|
||||
}
|
||||
// OpenGL 4.3 implies EXT_discard_framebuffer
|
||||
if (major > 4 || (major == 4 && minor >= 3)) {
|
||||
ext.EXT_discard_framebuffer = true;
|
||||
ext.KHR_debug = true;
|
||||
ext->EXT_discard_framebuffer = true;
|
||||
ext->KHR_debug = true;
|
||||
}
|
||||
// OpenGL 4.5 implies EXT_clip_control
|
||||
if (major > 4 || (major == 4 && minor >= 5)) {
|
||||
ext.EXT_clip_control = true;
|
||||
ext->EXT_clip_control = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -669,7 +767,7 @@ void OpenGLContext::deleteBuffers(GLsizei n, const GLuint* buffers, GLenum targe
|
||||
}
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
assert_invariant(state.major > 2 ||
|
||||
assert_invariant(mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1 ||
|
||||
(target != GL_UNIFORM_BUFFER && target != GL_TRANSFORM_FEEDBACK_BUFFER));
|
||||
|
||||
if (target == GL_UNIFORM_BUFFER || target == GL_TRANSFORM_FEEDBACK_BUFFER) {
|
||||
@@ -881,63 +979,4 @@ void OpenGLContext::resetState() noexcept {
|
||||
|
||||
}
|
||||
|
||||
OpenGLContext::FenceSync OpenGLContext::createFenceSync(
|
||||
OpenGLPlatform& platform) noexcept {
|
||||
|
||||
if (UTILS_UNLIKELY(isES2())) {
|
||||
assert_invariant(platform.canCreateFence());
|
||||
return { .fence = platform.createFence() };
|
||||
}
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
auto sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
return { .sync = sync };
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLContext::destroyFenceSync(
|
||||
OpenGLPlatform& platform, FenceSync sync) noexcept {
|
||||
|
||||
if (UTILS_UNLIKELY(isES2())) {
|
||||
platform.destroyFence(static_cast<Platform::Fence*>(sync.fence));
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
glDeleteSync(sync.sync);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
#endif
|
||||
}
|
||||
|
||||
OpenGLContext::FenceSync::Status OpenGLContext::clientWaitSync(
|
||||
OpenGLPlatform& platform, FenceSync sync) const noexcept {
|
||||
|
||||
if (UTILS_UNLIKELY(isES2())) {
|
||||
using Status = OpenGLContext::FenceSync::Status;
|
||||
auto const status = platform.waitFence(static_cast<Platform::Fence*>(sync.fence), 0u);
|
||||
switch (status) {
|
||||
case FenceStatus::ERROR: return Status::FAILURE;
|
||||
case FenceStatus::CONDITION_SATISFIED: return Status::CONDITION_SATISFIED;
|
||||
case FenceStatus::TIMEOUT_EXPIRED: return Status ::TIMEOUT_EXPIRED;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
GLenum const status = glClientWaitSync(sync.sync, 0, 0u);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
using Status = OpenGLContext::FenceSync::Status;
|
||||
switch (status) {
|
||||
case GL_ALREADY_SIGNALED: return Status::ALREADY_SIGNALED;
|
||||
case GL_TIMEOUT_EXPIRED: return Status::TIMEOUT_EXPIRED;
|
||||
case GL_CONDITION_SATISFIED: return Status::CONDITION_SATISFIED;
|
||||
default: return Status::FAILURE;
|
||||
}
|
||||
#else
|
||||
return FenceSync::Status::FAILURE;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namesapce filament
|
||||
|
||||
@@ -92,7 +92,7 @@ public:
|
||||
# ifndef BACKEND_OPENGL_LEVEL_GLES30
|
||||
return true;
|
||||
# else
|
||||
return state.major == 2;
|
||||
return mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0;
|
||||
# endif
|
||||
#else
|
||||
return false;
|
||||
@@ -150,28 +150,8 @@ public:
|
||||
void deleteBuffers(GLsizei n, const GLuint* buffers, GLenum target) noexcept;
|
||||
void deleteVertexArrays(GLsizei n, const GLuint* arrays) noexcept;
|
||||
|
||||
// we abstract GL's sync because it's not available in ES2, but we can use EGL's sync
|
||||
// instead, if available.
|
||||
struct FenceSync {
|
||||
enum class Status {
|
||||
ALREADY_SIGNALED,
|
||||
TIMEOUT_EXPIRED,
|
||||
CONDITION_SATISFIED,
|
||||
FAILURE
|
||||
};
|
||||
union {
|
||||
void* fence;
|
||||
GLsync sync;
|
||||
};
|
||||
};
|
||||
|
||||
FenceSync createFenceSync(OpenGLPlatform& platform) noexcept;
|
||||
void destroyFenceSync(OpenGLPlatform& platform, FenceSync sync) noexcept;
|
||||
FenceSync::Status clientWaitSync(OpenGLPlatform& platform, FenceSync sync) const noexcept;
|
||||
|
||||
|
||||
// glGet*() values
|
||||
struct {
|
||||
struct Gets {
|
||||
GLfloat max_anisotropy;
|
||||
GLint max_draw_buffers;
|
||||
GLint max_renderbuffer_size;
|
||||
@@ -190,10 +170,11 @@ public:
|
||||
} features = {};
|
||||
|
||||
// supported extensions detected at runtime
|
||||
struct {
|
||||
struct Extensions {
|
||||
bool APPLE_color_buffer_packed_float;
|
||||
bool ARB_shading_language_packing;
|
||||
bool EXT_clip_control;
|
||||
bool EXT_clip_cull_distance;
|
||||
bool EXT_color_buffer_float;
|
||||
bool EXT_color_buffer_half_float;
|
||||
bool EXT_debug_marker;
|
||||
@@ -228,7 +209,7 @@ public:
|
||||
bool WEBGL_compressed_texture_s3tc_srgb;
|
||||
} ext = {};
|
||||
|
||||
struct {
|
||||
struct Bugs {
|
||||
// Some drivers have issues with UBOs in the fragment shader when
|
||||
// glFlush() is called between draw calls.
|
||||
bool disable_glFlush;
|
||||
@@ -262,7 +243,7 @@ public:
|
||||
|
||||
// Some drivers can't blit from a sidecar renderbuffer into a layer of a texture array.
|
||||
// This technique is used for VSM with MSAA turned on.
|
||||
bool disable_sidecar_blit_into_texture_array;
|
||||
bool disable_blit_into_texture_array;
|
||||
|
||||
// Some drivers incorrectly flatten the early exit condition in the EASU code, in which
|
||||
// case we need an alternative algorithm
|
||||
@@ -280,6 +261,24 @@ public:
|
||||
// Some Adreno drivers crash in glDrawXXX() when there's an uninitialized uniform block,
|
||||
// even when the shader doesn't access it.
|
||||
bool enable_initialize_non_used_uniform_array;
|
||||
|
||||
// Workarounds specific to PowerVR GPUs affecting shaders (currently, we lump them all
|
||||
// under one specialization constant).
|
||||
// - gl_InstanceID is invalid when used first in the vertex shader
|
||||
bool powervr_shader_workarounds;
|
||||
|
||||
// On PowerVR destroying the destination of a glBlitFramebuffer operation is equivalent to
|
||||
// a glFinish. So we must delay the destruction until we know the GPU is finished.
|
||||
bool delay_fbo_destruction;
|
||||
|
||||
// The driver has some threads pinned, and we can't easily know on which core, it can hurt
|
||||
// performance more if we end-up pinned on the same one.
|
||||
bool disable_thread_affinity;
|
||||
|
||||
// Force feature level 0. Typically used for low end ES3 devices with significant driver
|
||||
// bugs or performance issues.
|
||||
bool force_feature_level0;
|
||||
|
||||
} bugs = {};
|
||||
|
||||
// state getters -- as needed.
|
||||
@@ -402,7 +401,7 @@ public:
|
||||
} window;
|
||||
} state;
|
||||
|
||||
struct {
|
||||
struct Procs {
|
||||
void (* bindVertexArray)(GLuint array);
|
||||
void (* deleteVertexArrays)(GLsizei n, const GLuint* arrays);
|
||||
void (* genVertexArrays)(GLsizei n, GLuint* arrays);
|
||||
@@ -415,6 +414,8 @@ public:
|
||||
void (* getQueryObjectui64v)(GLuint id, GLenum pname, GLuint64* params);
|
||||
|
||||
void (* invalidateFramebuffer)(GLenum target, GLsizei numAttachments, const GLenum *attachments);
|
||||
|
||||
void (* maxShaderCompilerThreadsKHR)(GLuint count);
|
||||
} procs{};
|
||||
|
||||
private:
|
||||
@@ -446,8 +447,8 @@ private:
|
||||
{ bugs.dont_use_timer_query,
|
||||
"dont_use_timer_query",
|
||||
""},
|
||||
{ bugs.disable_sidecar_blit_into_texture_array,
|
||||
"disable_sidecar_blit_into_texture_array",
|
||||
{ bugs.disable_blit_into_texture_array,
|
||||
"disable_blit_into_texture_array",
|
||||
""},
|
||||
{ bugs.split_easu,
|
||||
"split_easu",
|
||||
@@ -461,18 +462,55 @@ private:
|
||||
{ bugs.enable_initialize_non_used_uniform_array,
|
||||
"enable_initialize_non_used_uniform_array",
|
||||
""},
|
||||
{ bugs.powervr_shader_workarounds,
|
||||
"powervr_shader_workarounds",
|
||||
""},
|
||||
{ bugs.delay_fbo_destruction,
|
||||
"delay_fbo_destruction",
|
||||
""},
|
||||
{ bugs.disable_thread_affinity,
|
||||
"disable_thread_affinity",
|
||||
""},
|
||||
{ bugs.force_feature_level0,
|
||||
"force_feature_level0",
|
||||
""},
|
||||
}};
|
||||
|
||||
RenderPrimitive mDefaultVAO;
|
||||
|
||||
// this is chosen to minimize code size
|
||||
#if defined(BACKEND_OPENGL_VERSION_GLES)
|
||||
void initExtensionsGLES() noexcept;
|
||||
static void initExtensionsGLES(Extensions* ext, GLint major, GLint minor) noexcept;
|
||||
#endif
|
||||
#if defined(BACKEND_OPENGL_VERSION_GL)
|
||||
void initExtensionsGL() noexcept;
|
||||
static void initExtensionsGL(Extensions* ext, GLint major, GLint minor) noexcept;
|
||||
#endif
|
||||
|
||||
static void initExtensions(Extensions* ext, GLint major, GLint minor) noexcept {
|
||||
#if defined(BACKEND_OPENGL_VERSION_GLES)
|
||||
initExtensionsGLES(ext, major, minor);
|
||||
#endif
|
||||
#if defined(BACKEND_OPENGL_VERSION_GL)
|
||||
initExtensionsGL(ext, major, minor);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void initBugs(Bugs* bugs, Extensions const& exts,
|
||||
GLint major, GLint minor,
|
||||
char const* vendor,
|
||||
char const* renderer,
|
||||
char const* version,
|
||||
char const* shader
|
||||
);
|
||||
|
||||
static void initProcs(Procs* procs,
|
||||
Extensions const& exts, GLint major, GLint minor) noexcept;
|
||||
|
||||
static FeatureLevel resolveFeatureLevel(GLint major, GLint minor,
|
||||
Extensions const& exts,
|
||||
Gets const& gets,
|
||||
Bugs const& bugs) noexcept;
|
||||
|
||||
template <typename T, typename F>
|
||||
static inline void update_state(T& state, T const& expected, F functor, bool force = false) noexcept {
|
||||
if (UTILS_UNLIKELY(force || state != expected)) {
|
||||
@@ -565,7 +603,7 @@ void OpenGLContext::activeTexture(GLuint unit) noexcept {
|
||||
|
||||
void OpenGLContext::bindSampler(GLuint unit, GLuint sampler) noexcept {
|
||||
assert_invariant(unit < MAX_TEXTURE_UNIT_COUNT);
|
||||
assert_invariant(state.major > 2);
|
||||
assert_invariant(mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1);
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
update_state(state.textures.units[unit].sampler, sampler, [&]() {
|
||||
glBindSampler(unit, sampler);
|
||||
@@ -611,7 +649,7 @@ void OpenGLContext::bindVertexArray(RenderPrimitive const* p) noexcept {
|
||||
|
||||
void OpenGLContext::bindBufferRange(GLenum target, GLuint index, GLuint buffer,
|
||||
GLintptr offset, GLsizeiptr size) noexcept {
|
||||
assert_invariant(state.major > 2);
|
||||
assert_invariant(mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1);
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
# ifdef BACKEND_OPENGL_LEVEL_GLES31
|
||||
|
||||
@@ -50,13 +50,15 @@
|
||||
#define HAS_MAPBUFFERS 1
|
||||
#endif
|
||||
|
||||
#define DEBUG_MARKER_NONE 0
|
||||
#define DEBUG_MARKER_OPENGL 1
|
||||
#define DEBUG_MARKER_NONE 0x00 // no debug marker
|
||||
#define DEBUG_MARKER_OPENGL 0x01 // markers in the gl command queue (req. driver support)
|
||||
#define DEBUG_MARKER_BACKEND 0x02 // markers on the backend side (systrace)
|
||||
#define DEBUG_MARKER_ALL 0x03 // all markers
|
||||
|
||||
// set to the desired debug marker level
|
||||
#define DEBUG_MARKER_LEVEL DEBUG_MARKER_NONE
|
||||
|
||||
#if DEBUG_MARKER_LEVEL == DEBUG_MARKER_OPENGL
|
||||
#if DEBUG_MARKER_LEVEL > DEBUG_MARKER_NONE
|
||||
# define DEBUG_MARKER() \
|
||||
DebugMarker _debug_marker(*this, __func__);
|
||||
#else
|
||||
@@ -165,9 +167,11 @@ OpenGLDriver::DebugMarker::~DebugMarker() noexcept {
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept
|
||||
: mHandleAllocator("Handles", driverConfig.handleArenaSize),
|
||||
mSamplerMap(32),
|
||||
mPlatform(*platform) {
|
||||
: mPlatform(*platform),
|
||||
mContext(),
|
||||
mShaderCompilerService(*this),
|
||||
mHandleAllocator("Handles", driverConfig.handleArenaSize),
|
||||
mSamplerMap(32) {
|
||||
|
||||
std::fill(mSamplerBindings.begin(), mSamplerBindings.end(), nullptr);
|
||||
|
||||
@@ -187,27 +191,9 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi
|
||||
assert_invariant(mContext.ext.EXT_disjoint_timer_query);
|
||||
#endif
|
||||
|
||||
#if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_disjoint_timer_query)
|
||||
if (mContext.ext.EXT_disjoint_timer_query) {
|
||||
// timer queries are available
|
||||
if (mContext.bugs.dont_use_timer_query && mPlatform.canCreateFence()) {
|
||||
// however, they don't work well, revert to using fences if we can.
|
||||
mTimerQueryImpl = new OpenGLTimerQueryFence(mPlatform);
|
||||
} else {
|
||||
mTimerQueryImpl = new TimerQueryNative(mContext);
|
||||
}
|
||||
mFrameTimeSupported = true;
|
||||
} else
|
||||
#endif
|
||||
if (mPlatform.canCreateFence()) {
|
||||
// no timer queries, but we can use fences
|
||||
mTimerQueryImpl = new OpenGLTimerQueryFence(mPlatform);
|
||||
mFrameTimeSupported = true;
|
||||
} else {
|
||||
// no queries, no fences -- that's a problem
|
||||
mTimerQueryImpl = new TimerQueryFallback();
|
||||
mFrameTimeSupported = false;
|
||||
}
|
||||
mTimerQueryImpl = OpenGLTimerQueryFactory::init(mPlatform, *this);
|
||||
|
||||
mShaderCompilerService.init();
|
||||
}
|
||||
|
||||
OpenGLDriver::~OpenGLDriver() noexcept { // NOLINT(modernize-use-equals-default)
|
||||
@@ -225,13 +211,23 @@ void OpenGLDriver::terminate() {
|
||||
// wait for the GPU to finish executing all commands
|
||||
glFinish();
|
||||
|
||||
mShaderCompilerService.terminate();
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
// and make sure to execute all the GpuCommandCompleteOps callbacks
|
||||
executeGpuCommandsCompleteOps();
|
||||
|
||||
// as well as the FrameCompleteOps callbacks
|
||||
if (UTILS_UNLIKELY(!mFrameCompleteOps.empty())) {
|
||||
for (auto&& op: mFrameCompleteOps) {
|
||||
op();
|
||||
}
|
||||
mFrameCompleteOps.clear();
|
||||
}
|
||||
|
||||
// because we called glFinish(), all callbacks should have been executed
|
||||
assert_invariant(mGpuCommandCompleteOps.empty());
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
if (!getContext().isES2()) {
|
||||
for (auto& item: mSamplerMap) {
|
||||
mContext.unbindSampler(item.second);
|
||||
@@ -428,11 +424,7 @@ Handle<HwRenderTarget> OpenGLDriver::createRenderTargetS() noexcept {
|
||||
}
|
||||
|
||||
Handle<HwFence> OpenGLDriver::createFenceS() noexcept {
|
||||
return initHandle<HwFence>();
|
||||
}
|
||||
|
||||
Handle<HwSync> OpenGLDriver::createSyncS() noexcept {
|
||||
return initHandle<GLSync>();
|
||||
return initHandle<GLFence>();
|
||||
}
|
||||
|
||||
Handle<HwSwapChain> OpenGLDriver::createSwapChainS() noexcept {
|
||||
@@ -968,9 +960,14 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
|
||||
break;
|
||||
}
|
||||
|
||||
// depth/stencil attachment must match the rendertarget sample count
|
||||
// this is because EXT_multisampled_render_to_texture doesn't guarantee depth/stencil
|
||||
// is resolved.
|
||||
// depth/stencil attachments must match the rendertarget sample count
|
||||
// because EXT_multisampled_render_to_texture[2] doesn't resolve the depth/stencil
|
||||
// buffers:
|
||||
// for EXT_multisampled_render_to_texture
|
||||
// "the contents of the multisample buffer become undefined"
|
||||
// for EXT_multisampled_render_to_texture2
|
||||
// "the contents of the multisample buffer is discarded rather than resolved -
|
||||
// equivalent to the application calling InvalidateFramebuffer for this attachment"
|
||||
UTILS_UNUSED bool attachmentTypeNotSupportedByMSRTT = false;
|
||||
switch (attachment) {
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
@@ -1018,22 +1015,8 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
|
||||
attachmentTypeNotSupportedByMSRTT = true;
|
||||
}
|
||||
|
||||
// There's a bug with certain drivers preventing us from emulating
|
||||
// EXT_multisampled_render_to_texture when the texture is a TEXTURE_2D_ARRAY, so we'll simply
|
||||
// fall back to non-MSAA rendering for now. Also, MSRTT is never available for TEXTURE_2D_ARRAY,
|
||||
// and since we are a 2D array, we know we're sampleable and therefore that a resolve will
|
||||
// be needed.
|
||||
// Note that this affects VSM shadows in particular.
|
||||
// TODO: a better workaround would be to do the resolve by hand in that case
|
||||
const bool disableMultisampling =
|
||||
gl.bugs.disable_sidecar_blit_into_texture_array &&
|
||||
rt->gl.samples > 1 && t->samples <= 1 &&
|
||||
(target == GL_TEXTURE_2D_ARRAY ||
|
||||
target == GL_TEXTURE_CUBE_MAP_ARRAY); // implies MSRTT is not available
|
||||
|
||||
if (rt->gl.samples <= 1 ||
|
||||
(rt->gl.samples > 1 && t->samples > 1 && gl.features.multisample_texture) ||
|
||||
disableMultisampling) {
|
||||
(rt->gl.samples > 1 && t->samples > 1 && gl.features.multisample_texture)) {
|
||||
// on GL3.2 / GLES3.1 and above multisample is handled when creating the texture.
|
||||
// If multisampled textures are not supported and we end-up here, things should
|
||||
// still work, albeit without MSAA.
|
||||
@@ -1353,28 +1336,25 @@ void OpenGLDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
||||
void OpenGLDriver::createFenceR(Handle<HwFence> fh, int) {
|
||||
DEBUG_MARKER()
|
||||
|
||||
HwFence* f = handle_cast<HwFence*>(fh);
|
||||
f->fence = mPlatform.createFence();
|
||||
}
|
||||
GLFence* f = handle_cast<GLFence*>(fh);
|
||||
|
||||
void OpenGLDriver::createSyncR(Handle<HwSync> fh, int) {
|
||||
DEBUG_MARKER()
|
||||
|
||||
GLSync* f = handle_cast<GLSync *>(fh);
|
||||
f->handle = mContext.createFenceSync(mPlatform);
|
||||
|
||||
// check the status of the sync once a frame, since we must do this from our thread
|
||||
std::weak_ptr<GLSync::State> const weak = f->result;
|
||||
runEveryNowAndThen(
|
||||
[&platform = mPlatform, context = mContext, handle = f->handle, weak]() -> bool {
|
||||
auto result = weak.lock();
|
||||
if (result) {
|
||||
auto const status = context.clientWaitSync(platform, handle);
|
||||
result->status.store(status, std::memory_order_relaxed);
|
||||
return (status != OpenGLContext::FenceSync::Status::TIMEOUT_EXPIRED);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (mPlatform.canCreateFence() || mContext.isES2()) {
|
||||
assert_invariant(mPlatform.canCreateFence());
|
||||
f->fence = mPlatform.createFence();
|
||||
}
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
else {
|
||||
std::weak_ptr<GLFence::State> const weak = f->state;
|
||||
whenGpuCommandsComplete([weak](){
|
||||
auto state = weak.lock();
|
||||
if (state) {
|
||||
std::lock_guard const lock(state->lock);
|
||||
state->status = FenceStatus::CONDITION_SATISFIED;
|
||||
state->cond.notify_all();
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
|
||||
@@ -1406,10 +1386,8 @@ void OpenGLDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch,
|
||||
|
||||
void OpenGLDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
|
||||
DEBUG_MARKER()
|
||||
|
||||
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
|
||||
mContext.procs.genQueries(1u, &tq->gl.query);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
mTimerQueryImpl->createTimerQuery(tq);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
@@ -1464,7 +1442,6 @@ void OpenGLDriver::destroyProgram(Handle<HwProgram> ph) {
|
||||
DEBUG_MARKER()
|
||||
if (ph) {
|
||||
OpenGLProgram* p = handle_cast<OpenGLProgram*>(ph);
|
||||
cancelRunAtNextPassOp(p);
|
||||
destruct(ph, p);
|
||||
}
|
||||
}
|
||||
@@ -1515,12 +1492,33 @@ void OpenGLDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
|
||||
if (rt->gl.fbo) {
|
||||
// first unbind this framebuffer if needed
|
||||
gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glDeleteFramebuffers(1, &rt->gl.fbo);
|
||||
}
|
||||
if (rt->gl.fbo_read) {
|
||||
// first unbind this framebuffer if needed
|
||||
gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glDeleteFramebuffers(1, &rt->gl.fbo_read);
|
||||
}
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
if (UTILS_UNLIKELY(gl.bugs.delay_fbo_destruction)) {
|
||||
if (rt->gl.fbo) {
|
||||
whenFrameComplete([fbo = rt->gl.fbo]() {
|
||||
glDeleteFramebuffers(1, &fbo);
|
||||
});
|
||||
}
|
||||
if (rt->gl.fbo_read) {
|
||||
whenFrameComplete([fbo_read = rt->gl.fbo_read]() {
|
||||
glDeleteFramebuffers(1, &fbo_read);
|
||||
});
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
if (rt->gl.fbo) {
|
||||
glDeleteFramebuffers(1, &rt->gl.fbo);
|
||||
}
|
||||
if (rt->gl.fbo_read) {
|
||||
glDeleteFramebuffers(1, &rt->gl.fbo_read);
|
||||
}
|
||||
}
|
||||
destruct(rth, rt);
|
||||
}
|
||||
@@ -1569,20 +1567,11 @@ void OpenGLDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
|
||||
if (tqh) {
|
||||
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
|
||||
getContext().procs.deleteQueries(1u, &tq->gl.query);
|
||||
mTimerQueryImpl->destroyTimerQuery(tq);
|
||||
destruct(tqh, tq);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDriver::destroySync(Handle<HwSync> sh) {
|
||||
DEBUG_MARKER()
|
||||
if (sh) {
|
||||
GLSync* s = handle_cast<GLSync*>(sh);
|
||||
mContext.destroyFenceSync(mPlatform, s->handle);
|
||||
destruct(sh, s);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Synchronous APIs
|
||||
// These are called on the application's thread
|
||||
@@ -1685,24 +1674,39 @@ int64_t OpenGLDriver::getStreamTimestamp(Handle<HwStream> sh) {
|
||||
|
||||
void OpenGLDriver::destroyFence(Handle<HwFence> fh) {
|
||||
if (fh) {
|
||||
HwFence* f = handle_cast<HwFence*>(fh);
|
||||
mPlatform.destroyFence(f->fence);
|
||||
GLFence* f = handle_cast<GLFence*>(fh);
|
||||
if (mPlatform.canCreateFence() || mContext.isES2()) {
|
||||
mPlatform.destroyFence(f->fence);
|
||||
}
|
||||
destruct(fh, f);
|
||||
}
|
||||
}
|
||||
|
||||
FenceStatus OpenGLDriver::wait(Handle<HwFence> fh, uint64_t timeout) {
|
||||
FenceStatus OpenGLDriver::getFenceStatus(Handle<HwFence> fh) {
|
||||
if (fh) {
|
||||
HwFence* f = handle_cast<HwFence*>(fh);
|
||||
if (f->fence == nullptr) {
|
||||
// we can end-up here if:
|
||||
// - the platform doesn't support h/w fences
|
||||
// - wait() was called before the fence was asynchronously created.
|
||||
// This case is not handled in OpenGLDriver but is handled by FFence.
|
||||
// TODO: move FFence logic into the backend.
|
||||
return FenceStatus::ERROR;
|
||||
GLFence* f = handle_cast<GLFence*>(fh);
|
||||
if (mPlatform.canCreateFence() || mContext.isES2()) {
|
||||
if (f->fence == nullptr) {
|
||||
// we can end-up here if:
|
||||
// - the platform doesn't support h/w fences
|
||||
if (UTILS_UNLIKELY(!mPlatform.canCreateFence())) {
|
||||
return FenceStatus::ERROR;
|
||||
}
|
||||
// - wait() was called before the fence was asynchronously created.
|
||||
return FenceStatus::TIMEOUT_EXPIRED;
|
||||
}
|
||||
return mPlatform.waitFence(f->fence, 0);
|
||||
}
|
||||
return mPlatform.waitFence(f->fence, timeout);
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
else {
|
||||
assert_invariant(f->state);
|
||||
std::unique_lock lock(f->state->lock);
|
||||
f->state->cond.wait_for(lock, std::chrono::nanoseconds(0), [&state = f->state]() {
|
||||
return state->status != FenceStatus::TIMEOUT_EXPIRED;
|
||||
});
|
||||
return f->state->status;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return FenceStatus::ERROR;
|
||||
}
|
||||
@@ -1850,7 +1854,7 @@ bool OpenGLDriver::isFrameBufferFetchMultiSampleSupported() {
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isFrameTimeSupported() {
|
||||
return mFrameTimeSupported;
|
||||
return OpenGLTimerQueryFactory::isGpuTimeSupported();
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isAutoDepthResolveSupported() {
|
||||
@@ -1868,6 +1872,18 @@ bool OpenGLDriver::isSRGBSwapChainSupported() {
|
||||
return mPlatform.isSRGBSwapChainSupported();
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isStereoSupported() {
|
||||
// Stereo requires instancing and EXT_clip_cull_distance.
|
||||
if (UTILS_UNLIKELY(mContext.isES2())) {
|
||||
return false;
|
||||
}
|
||||
return mContext.ext.EXT_clip_cull_distance;
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isParallelShaderCompileSupported() {
|
||||
return mShaderCompilerService.isParallelShaderCompileSupported();
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
switch (workaround) {
|
||||
case Workaround::SPLIT_EASU:
|
||||
@@ -1876,6 +1892,12 @@ bool OpenGLDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
return mContext.bugs.allow_read_only_ancillary_feedback_loop;
|
||||
case Workaround::ADRENO_UNIFORM_ARRAY_CRASH:
|
||||
return mContext.bugs.enable_initialize_non_used_uniform_array;
|
||||
case Workaround::DISABLE_BLIT_INTO_TEXTURE_ARRAY:
|
||||
return mContext.bugs.disable_blit_into_texture_array;
|
||||
case Workaround::POWER_VR_SHADER_WORKAROUNDS:
|
||||
return mContext.bugs.powervr_shader_workarounds;
|
||||
case Workaround::DISABLE_THREAD_AFFINITY:
|
||||
return mContext.bugs.disable_thread_affinity;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -1912,6 +1934,16 @@ void OpenGLDriver::commit(Handle<HwSwapChain> sch) {
|
||||
|
||||
GLSwapChain* sc = handle_cast<GLSwapChain*>(sch);
|
||||
mPlatform.commit(sc->swapChain);
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
if (UTILS_UNLIKELY(!mFrameCompleteOps.empty())) {
|
||||
whenGpuCommandsComplete([ops = std::move(mFrameCompleteOps)]() {
|
||||
for (auto&& op: ops) {
|
||||
op();
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::makeCurrent(Handle<HwSwapChain> schDraw, Handle<HwSwapChain> schRead) {
|
||||
@@ -2556,8 +2588,6 @@ void OpenGLDriver::replaceStream(GLTexture* texture, GLStream* newStream) noexce
|
||||
void OpenGLDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
DEBUG_MARKER()
|
||||
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
|
||||
// reset the state of the result availability
|
||||
tq->elapsed.store(0, std::memory_order_relaxed);
|
||||
mTimerQueryImpl->beginTimeElapsedQuery(tq);
|
||||
}
|
||||
|
||||
@@ -2565,62 +2595,25 @@ void OpenGLDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
DEBUG_MARKER()
|
||||
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
|
||||
mTimerQueryImpl->endTimeElapsedQuery(tq);
|
||||
|
||||
runEveryNowAndThen([this, tq]() -> bool {
|
||||
if (!mTimerQueryImpl->queryResultAvailable(tq)) {
|
||||
// we need to try this one again later
|
||||
return false;
|
||||
}
|
||||
tq->elapsed.store(mTimerQueryImpl->queryResult(tq), std::memory_order_relaxed);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
bool OpenGLDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsedTime) {
|
||||
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
|
||||
uint64_t const d = tq->elapsed.load(std::memory_order_relaxed);
|
||||
if (!d) {
|
||||
return false;
|
||||
}
|
||||
if (elapsedTime) {
|
||||
*elapsedTime = d;
|
||||
}
|
||||
return true;
|
||||
return OpenGLTimerQueryInterface::getTimerQueryValue(tq, elapsedTime);
|
||||
}
|
||||
|
||||
SyncStatus OpenGLDriver::getSyncStatus(Handle<HwSync> sh) {
|
||||
GLSync* s = handle_cast<GLSync*>(sh);
|
||||
if (!s->result) {
|
||||
return SyncStatus::NOT_SIGNALED;
|
||||
void OpenGLDriver::compilePrograms(CompilerPriorityQueue priority,
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||
if (callback) {
|
||||
getShaderCompilerService().notifyWhenAllProgramsAreReady(handler, callback, user);
|
||||
}
|
||||
auto status = s->result->status.load(std::memory_order_relaxed);
|
||||
using Status = OpenGLContext::FenceSync::Status;
|
||||
switch (status) {
|
||||
case Status::CONDITION_SATISFIED:
|
||||
case Status::ALREADY_SIGNALED:
|
||||
return SyncStatus::SIGNALED;
|
||||
case Status::TIMEOUT_EXPIRED:
|
||||
return SyncStatus::NOT_SIGNALED;
|
||||
case Status::FAILURE:
|
||||
default:
|
||||
return SyncStatus::ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDriver::compilePrograms(CallbackHandler* handler,
|
||||
CallbackHandler::Callback callback, void* user) {
|
||||
// TODO: this works because currently `executeRenderPassOps` is only used for compiling
|
||||
// materials. If that changed, we'd have to only execute the callbacks related to
|
||||
// material compilation.
|
||||
executeRenderPassOps();
|
||||
scheduleCallback(handler, user, callback);
|
||||
}
|
||||
|
||||
void OpenGLDriver::beginRenderPass(Handle<HwRenderTarget> rth,
|
||||
const RenderPassParams& params) {
|
||||
DEBUG_MARKER()
|
||||
|
||||
executeRenderPassOps();
|
||||
getShaderCompilerService().tick();
|
||||
|
||||
auto& gl = mContext;
|
||||
|
||||
@@ -2924,7 +2917,7 @@ void OpenGLDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
GLuint OpenGLDriver::getSamplerSlow(SamplerParams params) const noexcept {
|
||||
assert_invariant(mSamplerMap.find(params.u) == mSamplerMap.end());
|
||||
assert_invariant(mSamplerMap.find(params) == mSamplerMap.end());
|
||||
|
||||
GLuint s;
|
||||
glGenSamplers(1, &s);
|
||||
@@ -2946,7 +2939,7 @@ GLuint OpenGLDriver::getSamplerSlow(SamplerParams params) const noexcept {
|
||||
}
|
||||
#endif
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
mSamplerMap[params.u] = s;
|
||||
mSamplerMap[params] = s;
|
||||
return s;
|
||||
}
|
||||
#endif
|
||||
@@ -2961,29 +2954,34 @@ void OpenGLDriver::insertEventMarker(char const* string, uint32_t len) {
|
||||
}
|
||||
|
||||
void OpenGLDriver::pushGroupMarker(char const* string, uint32_t len) {
|
||||
|
||||
#ifdef GL_EXT_debug_marker
|
||||
auto& gl = mContext;
|
||||
if (UTILS_LIKELY(gl.ext.EXT_debug_marker)) {
|
||||
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_OPENGL
|
||||
if (UTILS_LIKELY(mContext.ext.EXT_debug_marker)) {
|
||||
glPushGroupMarkerEXT(GLsizei(len ? len : strlen(string)), string);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_NAME_BEGIN(string);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_BACKEND
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_NAME_BEGIN(string);
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::popGroupMarker(int) {
|
||||
#ifdef GL_EXT_debug_marker
|
||||
auto& gl = mContext;
|
||||
if (UTILS_LIKELY(gl.ext.EXT_debug_marker)) {
|
||||
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_OPENGL
|
||||
if (UTILS_LIKELY(mContext.ext.EXT_debug_marker)) {
|
||||
glPopGroupMarkerEXT();
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_NAME_END();
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_BACKEND
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_NAME_END();
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::startCapture(int) {
|
||||
@@ -3177,37 +3175,11 @@ void OpenGLDriver::readBufferSubData(backend::BufferObjectHandle boh,
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::whenGpuCommandsComplete(std::function<void()> fn) noexcept {
|
||||
OpenGLContext::FenceSync sync = mContext.createFenceSync(mPlatform);
|
||||
mGpuCommandCompleteOps.emplace_back(sync, std::move(fn));
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
|
||||
void OpenGLDriver::runEveryNowAndThen(std::function<bool()> fn) noexcept {
|
||||
mEveryNowAndThenOps.push_back(std::move(fn));
|
||||
}
|
||||
|
||||
void OpenGLDriver::executeGpuCommandsCompleteOps() noexcept {
|
||||
auto& v = mGpuCommandCompleteOps;
|
||||
auto it = v.begin();
|
||||
while (it != v.end()) {
|
||||
using Status = OpenGLContext::FenceSync::Status;
|
||||
auto const status = mContext.clientWaitSync(mPlatform, it->first);
|
||||
if (status == Status::ALREADY_SIGNALED || status == Status::CONDITION_SATISFIED) {
|
||||
it->second();
|
||||
mContext.destroyFenceSync(mPlatform, it->first);
|
||||
it = v.erase(it);
|
||||
} else if (UTILS_UNLIKELY(status == Status::FAILURE)) {
|
||||
// This should never happen, but is very problematic if it does, as we might leak
|
||||
// some data depending on what the callback does. However, we clean up our own state.
|
||||
mContext.destroyFenceSync(mPlatform, it->first);
|
||||
it = v.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDriver::executeEveryNowAndThenOps() noexcept {
|
||||
auto& v = mEveryNowAndThenOps;
|
||||
auto it = v.begin();
|
||||
@@ -3220,24 +3192,45 @@ void OpenGLDriver::executeEveryNowAndThenOps() noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDriver::runAtNextRenderPass(void* token, std::function<void()> fn) noexcept {
|
||||
assert_invariant(mRunAtNextRenderPassOps.find(token) == mRunAtNextRenderPassOps.end());
|
||||
mRunAtNextRenderPassOps[token] = std::move(fn);
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
void OpenGLDriver::whenFrameComplete(const std::function<void()>& fn) noexcept {
|
||||
mFrameCompleteOps.push_back(fn);
|
||||
}
|
||||
|
||||
void OpenGLDriver::cancelRunAtNextPassOp(void* token) noexcept {
|
||||
mRunAtNextRenderPassOps.erase(token);
|
||||
void OpenGLDriver::whenGpuCommandsComplete(const std::function<void()>& fn) noexcept {
|
||||
GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
mGpuCommandCompleteOps.emplace_back(sync, fn);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
|
||||
void OpenGLDriver::executeRenderPassOps() noexcept {
|
||||
auto& ops = mRunAtNextRenderPassOps;
|
||||
if (!ops.empty()) {
|
||||
for (auto& item: ops) {
|
||||
item.second();
|
||||
void OpenGLDriver::executeGpuCommandsCompleteOps() noexcept {
|
||||
auto& v = mGpuCommandCompleteOps;
|
||||
auto it = v.begin();
|
||||
while (it != v.end()) {
|
||||
auto const& [sync, fn] = *it;
|
||||
GLenum const syncStatus = glClientWaitSync(sync, 0, 0u);
|
||||
switch (syncStatus) {
|
||||
case GL_TIMEOUT_EXPIRED:
|
||||
// not ready
|
||||
++it;
|
||||
break;
|
||||
case GL_ALREADY_SIGNALED:
|
||||
case GL_CONDITION_SATISFIED:
|
||||
// ready
|
||||
it->second();
|
||||
glDeleteSync(sync);
|
||||
it = v.erase(it);
|
||||
break;
|
||||
default:
|
||||
// This should never happen, but is very problematic if it does, as we might leak
|
||||
// some data depending on what the callback does. However, we clean up our own state.
|
||||
glDeleteSync(sync);
|
||||
it = v.erase(it);
|
||||
break;
|
||||
}
|
||||
ops.clear();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Rendering ops
|
||||
@@ -3247,6 +3240,7 @@ void OpenGLDriver::tick(int) {
|
||||
DEBUG_MARKER()
|
||||
executeGpuCommandsCompleteOps();
|
||||
executeEveryNowAndThenOps();
|
||||
getShaderCompilerService().tick();
|
||||
}
|
||||
|
||||
void OpenGLDriver::beginFrame(
|
||||
@@ -3276,7 +3270,7 @@ void OpenGLDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
|
||||
}
|
||||
|
||||
void OpenGLDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
|
||||
FrameCompletedCallback callback, void* user) {
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||
DEBUG_MARKER()
|
||||
}
|
||||
|
||||
@@ -3311,19 +3305,19 @@ void OpenGLDriver::flush(int) {
|
||||
if (!gl.bugs.disable_glFlush) {
|
||||
glFlush();
|
||||
}
|
||||
mTimerQueryImpl->flush();
|
||||
}
|
||||
|
||||
void OpenGLDriver::finish(int) {
|
||||
DEBUG_MARKER()
|
||||
glFinish();
|
||||
mTimerQueryImpl->flush();
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
executeGpuCommandsCompleteOps();
|
||||
assert_invariant(mGpuCommandCompleteOps.empty());
|
||||
#endif
|
||||
executeEveryNowAndThenOps();
|
||||
// Note: since we executed a glFinish(), all pending tasks should be done
|
||||
assert_invariant(mGpuCommandCompleteOps.empty());
|
||||
|
||||
// however, some tasks rely on a separated thread to publish their result (e.g.
|
||||
// However, some tasks rely on a separated thread to publish their result (e.g.
|
||||
// endTimerQuery), so the result could very well not be ready, and the task will
|
||||
// linger a bit longer, this is only true for mEveryNowAndThenOps tasks.
|
||||
// The fallout of this is that we can't assert that mEveryNowAndThenOps is empty.
|
||||
@@ -3556,7 +3550,7 @@ void OpenGLDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph, uint
|
||||
}
|
||||
|
||||
void OpenGLDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
|
||||
executeRenderPassOps();
|
||||
getShaderCompilerService().tick();
|
||||
|
||||
OpenGLProgram* p = handle_cast<OpenGLProgram*>(program);
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "DriverBase.h"
|
||||
#include "GLUtils.h"
|
||||
#include "OpenGLContext.h"
|
||||
#include "ShaderCompilerService.h"
|
||||
|
||||
#include "private/backend/Driver.h"
|
||||
#include "private/backend/HandleAllocator.h"
|
||||
@@ -150,15 +151,12 @@ public:
|
||||
|
||||
struct GLTimerQuery : public HwTimerQuery {
|
||||
struct State {
|
||||
std::atomic<uint64_t> elapsed{};
|
||||
std::atomic_bool available{};
|
||||
struct {
|
||||
GLuint query;
|
||||
} gl;
|
||||
std::atomic<int64_t> elapsed{};
|
||||
};
|
||||
struct {
|
||||
GLuint query = 0;
|
||||
std::shared_ptr<State> emulation;
|
||||
} gl;
|
||||
// 0 means not available, otherwise query result in ns.
|
||||
std::atomic<uint64_t> elapsed{};
|
||||
std::shared_ptr<State> state;
|
||||
};
|
||||
|
||||
struct GLStream : public HwStream {
|
||||
@@ -195,24 +193,32 @@ public:
|
||||
TargetBufferFlags targets = {};
|
||||
};
|
||||
|
||||
struct GLSync : public HwSync {
|
||||
using HwSync::HwSync;
|
||||
struct GLFence : public HwFence {
|
||||
using HwFence::HwFence;
|
||||
struct State {
|
||||
std::atomic<OpenGLContext::FenceSync::Status> status{
|
||||
OpenGLContext::FenceSync::Status::TIMEOUT_EXPIRED };
|
||||
std::mutex lock;
|
||||
std::condition_variable cond;
|
||||
FenceStatus status{ FenceStatus::TIMEOUT_EXPIRED };
|
||||
};
|
||||
OpenGLContext::FenceSync handle{};
|
||||
std::shared_ptr<State> result{ std::make_shared<GLSync::State>() };
|
||||
std::shared_ptr<State> state{ std::make_shared<GLFence::State>() };
|
||||
};
|
||||
|
||||
OpenGLDriver(OpenGLDriver const&) = delete;
|
||||
OpenGLDriver& operator=(OpenGLDriver const&) = delete;
|
||||
|
||||
private:
|
||||
OpenGLPlatform& mPlatform;
|
||||
OpenGLContext mContext;
|
||||
ShaderCompilerService mShaderCompilerService;
|
||||
|
||||
friend class OpenGLTimerQueryFactory;
|
||||
friend class TimerQueryNative;
|
||||
OpenGLContext& getContext() noexcept { return mContext; }
|
||||
|
||||
ShaderCompilerService& getShaderCompilerService() noexcept {
|
||||
return mShaderCompilerService;
|
||||
}
|
||||
|
||||
ShaderModel getShaderModel() const noexcept final;
|
||||
|
||||
/*
|
||||
@@ -273,6 +279,7 @@ private:
|
||||
}
|
||||
|
||||
friend class OpenGLProgram;
|
||||
friend class ShaderCompilerService;
|
||||
|
||||
/* Extension management... */
|
||||
|
||||
@@ -327,7 +334,7 @@ private:
|
||||
assert_invariant(!sp.padding1);
|
||||
assert_invariant(!sp.padding2);
|
||||
auto& samplerMap = mSamplerMap;
|
||||
auto pos = samplerMap.find(sp.u);
|
||||
auto pos = samplerMap.find(sp);
|
||||
if (UTILS_UNLIKELY(pos == samplerMap.end())) {
|
||||
return getSamplerSlow(sp);
|
||||
}
|
||||
@@ -361,7 +368,8 @@ private:
|
||||
// sampler buffer binding points (nullptr if not used)
|
||||
std::array<GLSamplerGroup*, Program::SAMPLER_BINDING_COUNT> mSamplerBindings = {}; // 4 pointers
|
||||
|
||||
mutable tsl::robin_map<uint32_t, GLuint> mSamplerMap;
|
||||
mutable tsl::robin_map<SamplerParams, GLuint,
|
||||
SamplerParams::Hasher, SamplerParams::EqualTo> mSamplerMap;
|
||||
|
||||
// this must be accessed from the driver thread only
|
||||
std::vector<GLTexture*> mTexturesWithStreamsAttached;
|
||||
@@ -373,28 +381,25 @@ private:
|
||||
void detachStream(GLTexture* t) noexcept;
|
||||
void replaceStream(GLTexture* t, GLStream* stream) noexcept;
|
||||
|
||||
OpenGLPlatform& mPlatform;
|
||||
|
||||
void updateTextureLodRange(GLTexture* texture, int8_t targetLevel) noexcept;
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
// tasks executed on the main thread after the fence signaled
|
||||
void whenGpuCommandsComplete(std::function<void()> fn) noexcept;
|
||||
void whenGpuCommandsComplete(const std::function<void()>& fn) noexcept;
|
||||
void executeGpuCommandsCompleteOps() noexcept;
|
||||
std::vector<std::pair<OpenGLContext::FenceSync, std::function<void()>>> mGpuCommandCompleteOps;
|
||||
std::vector<std::pair<GLsync, std::function<void()>>> mGpuCommandCompleteOps;
|
||||
|
||||
void whenFrameComplete(const std::function<void()>& fn) noexcept;
|
||||
std::vector<std::function<void()>> mFrameCompleteOps;
|
||||
#endif
|
||||
|
||||
// tasks regularly executed on the main thread at until they return true
|
||||
void runEveryNowAndThen(std::function<bool()> fn) noexcept;
|
||||
void executeEveryNowAndThenOps() noexcept;
|
||||
std::vector<std::function<bool()>> mEveryNowAndThenOps;
|
||||
|
||||
void runAtNextRenderPass(void* token, std::function<void()> fn) noexcept;
|
||||
void executeRenderPassOps() noexcept;
|
||||
void cancelRunAtNextPassOp(void* token) noexcept;
|
||||
tsl::robin_map<void*, std::function<void()>> mRunAtNextRenderPassOps;
|
||||
|
||||
// timer query implementation
|
||||
OpenGLTimerQueryInterface* mTimerQueryImpl = nullptr;
|
||||
bool mFrameTimeSupported = false;
|
||||
|
||||
// for ES2 sRGB support
|
||||
GLSwapChain* mCurrentDrawSwapChain = nullptr;
|
||||
|
||||
@@ -109,4 +109,14 @@ TargetBufferFlags OpenGLPlatform::getPreservedFlags(UTILS_UNUSED SwapChain* swap
|
||||
return TargetBufferFlags::NONE;
|
||||
}
|
||||
|
||||
bool OpenGLPlatform::isExtraContextSupported() const noexcept {
|
||||
return false;
|
||||
}
|
||||
|
||||
void OpenGLPlatform::createContext(bool) {
|
||||
}
|
||||
|
||||
void OpenGLPlatform::releaseContext() noexcept {
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -16,377 +16,88 @@
|
||||
|
||||
#include "OpenGLProgram.h"
|
||||
|
||||
#include "OpenGLBlobCache.h"
|
||||
#include "OpenGLDriver.h"
|
||||
|
||||
#include "BlobCacheKey.h"
|
||||
#include "OpenGLDriver.h"
|
||||
#include "ShaderCompilerService.h"
|
||||
|
||||
#include <utils/debug.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/Systrace.h>
|
||||
|
||||
#include <private/backend/BackendUtils.h>
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
using namespace filament::math;
|
||||
using namespace utils;
|
||||
using namespace backend;
|
||||
|
||||
static void logCompilationError(utils::io::ostream& out,
|
||||
ShaderStage shaderType, const char* name,
|
||||
GLuint shaderId, CString const& sourceCode) noexcept;
|
||||
struct OpenGLProgram::LazyInitializationData {
|
||||
Program::UniformBlockInfo uniformBlockInfo;
|
||||
Program::SamplerGroupInfo samplerGroupInfo;
|
||||
std::array<Program::UniformInfo, Program::UNIFORM_BINDING_COUNT> bindingUniformInfo;
|
||||
};
|
||||
|
||||
static void logProgramLinkError(utils::io::ostream& out,
|
||||
const char* name, GLuint program) noexcept;
|
||||
|
||||
static inline std::string to_string(bool b) noexcept {
|
||||
return b ? "true" : "false";
|
||||
}
|
||||
|
||||
static inline std::string to_string(int i) noexcept {
|
||||
return std::to_string(i);
|
||||
}
|
||||
|
||||
static inline std::string to_string(float f) noexcept {
|
||||
return "float(" + std::to_string(f) + ")";
|
||||
}
|
||||
|
||||
OpenGLProgram::OpenGLProgram() noexcept
|
||||
: mInitialized(false), mValid(true), mLazyInitializationData(nullptr) {
|
||||
}
|
||||
OpenGLProgram::OpenGLProgram() noexcept = default;
|
||||
|
||||
OpenGLProgram::OpenGLProgram(OpenGLDriver& gld, Program&& program) noexcept
|
||||
: HwProgram(std::move(program.getName())),
|
||||
mInitialized(false), mValid(true),
|
||||
mLazyInitializationData{ new(LazyInitializationData) } {
|
||||
: HwProgram(std::move(program.getName())) {
|
||||
|
||||
OpenGLContext& context = gld.getContext();
|
||||
|
||||
mLazyInitializationData->samplerGroupInfo = std::move(program.getSamplerGroupInfo());
|
||||
auto* const lazyInitializationData = new(std::nothrow) LazyInitializationData();
|
||||
lazyInitializationData->samplerGroupInfo = std::move(program.getSamplerGroupInfo());
|
||||
if (UTILS_UNLIKELY(gld.getContext().isES2())) {
|
||||
mLazyInitializationData->bindingUniformInfo = std::move(program.getBindingUniformInfo());
|
||||
mLazyInitializationData->attributes = std::move(program.getAttributes());
|
||||
lazyInitializationData->bindingUniformInfo = std::move(program.getBindingUniformInfo());
|
||||
} else {
|
||||
mLazyInitializationData->uniformBlockInfo = std::move(program.getUniformBlockBindings());
|
||||
lazyInitializationData->uniformBlockInfo = std::move(program.getUniformBlockBindings());
|
||||
}
|
||||
|
||||
BlobCacheKey key;
|
||||
gl.program = OpenGLBlobCache::retrieve(&key, gld.mPlatform, program);
|
||||
if (!gl.program) {
|
||||
// this cannot fail because we check compilation status after linking the program
|
||||
// shaders[] is filled with id of shader stages present.
|
||||
OpenGLProgram::compileShaders(context,
|
||||
std::move(program.getShadersSource()),
|
||||
program.getSpecializationConstants(),
|
||||
gl.shaders,
|
||||
mLazyInitializationData->shaderSourceCode);
|
||||
ShaderCompilerService& compiler = gld.getShaderCompilerService();
|
||||
mToken = compiler.createProgram(name, std::move(program));
|
||||
|
||||
gld.runAtNextRenderPass(this, [this, &gld, &context, key = std::move(key)]() {
|
||||
// by this point we must not have a GL program
|
||||
assert_invariant(!gl.program);
|
||||
// we also can't be in the initialized state
|
||||
assert_invariant(!mInitialized);
|
||||
// we must have our lazy initialization data
|
||||
assert_invariant(mLazyInitializationData);
|
||||
// link the program, this also cannot fail because status is checked later.
|
||||
gl.program = OpenGLProgram::linkProgram(context,
|
||||
mLazyInitializationData, gl.shaders);
|
||||
|
||||
if (key) {
|
||||
// attempt to cache
|
||||
OpenGLBlobCache::insert(gld.mPlatform, key, gl.program);
|
||||
}
|
||||
});
|
||||
}
|
||||
ShaderCompilerService::setUserData(mToken, lazyInitializationData);
|
||||
}
|
||||
|
||||
OpenGLProgram::~OpenGLProgram() noexcept {
|
||||
if (!mInitialized) {
|
||||
// mLazyInitializationData is aliased with mIndicesRuns
|
||||
delete mLazyInitializationData;
|
||||
if (mToken) {
|
||||
// if the token is non-nullptr it means the program has not been used, and
|
||||
// we need to clean-up.
|
||||
assert_invariant(gl.program == 0);
|
||||
|
||||
LazyInitializationData* const lazyInitializationData =
|
||||
(LazyInitializationData *)ShaderCompilerService::getUserData(mToken);
|
||||
delete lazyInitializationData;
|
||||
|
||||
ShaderCompilerService::terminate(mToken);
|
||||
}
|
||||
|
||||
delete [] mUniformsRecords;
|
||||
const GLuint program = gl.program;
|
||||
UTILS_NOUNROLL
|
||||
for (GLuint const shader: gl.shaders) {
|
||||
if (shader) {
|
||||
if (program) {
|
||||
glDetachShader(program, shader);
|
||||
}
|
||||
glDeleteShader(shader);
|
||||
}
|
||||
}
|
||||
if (program) {
|
||||
glDeleteProgram(program);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Compile shaders in the ShaderSource. This cannot fail because compilation failures are not
|
||||
* checked until after the program is linked.
|
||||
* This always returns the GL shader IDs or zero a shader stage is not present.
|
||||
*/
|
||||
void OpenGLProgram::compileShaders(OpenGLContext& context,
|
||||
Program::ShaderSource shadersSource,
|
||||
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
|
||||
GLuint shaderIds[Program::SHADER_TYPE_COUNT],
|
||||
UTILS_UNUSED_IN_RELEASE std::array<CString, Program::SHADER_TYPE_COUNT>& outShaderSourceCode) noexcept {
|
||||
void OpenGLProgram::initialize(OpenGLDriver& gld) {
|
||||
|
||||
SYSTRACE_CALL();
|
||||
|
||||
auto appendSpecConstantString = +[](std::string& s, Program::SpecializationConstant const& sc) {
|
||||
s += "#define SPIRV_CROSS_CONSTANT_ID_" + std::to_string(sc.id) + ' ';
|
||||
s += std::visit([](auto&& arg) { return to_string(arg); }, sc.value);
|
||||
s += '\n';
|
||||
return s;
|
||||
};
|
||||
assert_invariant(gl.program == 0);
|
||||
assert_invariant(mToken);
|
||||
|
||||
std::string specializationConstantString;
|
||||
for (auto const& sc : specializationConstants) {
|
||||
appendSpecConstantString(specializationConstantString, sc);
|
||||
LazyInitializationData* const lazyInitializationData =
|
||||
(LazyInitializationData *)ShaderCompilerService::getUserData(mToken);
|
||||
|
||||
ShaderCompilerService& compiler = gld.getShaderCompilerService();
|
||||
gl.program = compiler.getProgram(mToken);
|
||||
|
||||
assert_invariant(mToken == nullptr);
|
||||
if (gl.program) {
|
||||
assert_invariant(lazyInitializationData);
|
||||
initializeProgramState(gld.getContext(), gl.program, *lazyInitializationData);
|
||||
delete lazyInitializationData;
|
||||
}
|
||||
if (!specializationConstantString.empty()) {
|
||||
specializationConstantString += '\n';
|
||||
}
|
||||
|
||||
// build all shaders
|
||||
UTILS_NOUNROLL
|
||||
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
|
||||
const ShaderStage stage = static_cast<ShaderStage>(i);
|
||||
GLenum glShaderType{};
|
||||
switch (stage) {
|
||||
case ShaderStage::VERTEX:
|
||||
glShaderType = GL_VERTEX_SHADER;
|
||||
break;
|
||||
case ShaderStage::FRAGMENT:
|
||||
glShaderType = GL_FRAGMENT_SHADER;
|
||||
break;
|
||||
case ShaderStage::COMPUTE:
|
||||
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
|
||||
glShaderType = GL_COMPUTE_SHADER;
|
||||
#else
|
||||
continue;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
if (UTILS_LIKELY(!shadersSource[i].empty())) {
|
||||
Program::ShaderBlob& shader = shadersSource[i];
|
||||
|
||||
// remove GOOGLE_cpp_style_line_directive
|
||||
std::string_view const source = process_GOOGLE_cpp_style_line_directive(context,
|
||||
reinterpret_cast<char*>(shader.data()), shader.size());
|
||||
|
||||
// add support for ARB_shading_language_packing if needed
|
||||
auto const packingFunctions = process_ARB_shading_language_packing(context);
|
||||
|
||||
// split shader source, so we can insert the specification constants and the packing functions
|
||||
auto const [prolog, body] = splitShaderSource(source);
|
||||
|
||||
const std::array<const char*, 4> sources = {
|
||||
prolog.data(),
|
||||
specializationConstantString.c_str(),
|
||||
packingFunctions.data(),
|
||||
body.data()
|
||||
};
|
||||
|
||||
const std::array<GLint, 4> lengths = {
|
||||
(GLint)prolog.length(),
|
||||
(GLint)specializationConstantString.length(),
|
||||
(GLint)packingFunctions.length(),
|
||||
(GLint)body.length() - 1 // null terminated
|
||||
};
|
||||
|
||||
GLuint const shaderId = glCreateShader(glShaderType);
|
||||
glShaderSource(shaderId, sources.size(), sources.data(), lengths.data());
|
||||
glCompileShader(shaderId);
|
||||
|
||||
#ifndef NDEBUG
|
||||
// for debugging we return the original shader source (without the modifications we
|
||||
// made here), otherwise the line numbers wouldn't match.
|
||||
outShaderSourceCode[i] = { source.data(), source.length() };
|
||||
#endif
|
||||
|
||||
shaderIds[i] = shaderId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If usages of the Google-style line directive are present, remove them, as some
|
||||
// drivers don't allow the quotation marks. This happens in-place.
|
||||
std::string_view OpenGLProgram::process_GOOGLE_cpp_style_line_directive(OpenGLContext& context,
|
||||
char* source, size_t len) noexcept {
|
||||
if (!context.ext.GOOGLE_cpp_style_line_directive) {
|
||||
if (UTILS_UNLIKELY(requestsGoogleLineDirectivesExtension({ source, len }))) {
|
||||
removeGoogleLineDirectives(source, len); // length is unaffected
|
||||
}
|
||||
}
|
||||
return { source, len };
|
||||
}
|
||||
|
||||
// Tragically, OpenGL 4.1 doesn't support unpackHalf2x16 (appeared in 4.2) and
|
||||
// macOS doesn't support GL_ARB_shading_language_packing
|
||||
std::string_view OpenGLProgram::process_ARB_shading_language_packing(OpenGLContext& context) noexcept {
|
||||
using namespace std::literals;
|
||||
#ifdef BACKEND_OPENGL_VERSION_GL
|
||||
if (!context.isAtLeastGL<4, 2>() && !context.ext.ARB_shading_language_packing) {
|
||||
return R"(
|
||||
|
||||
// these don't handle denormals, NaNs or inf
|
||||
float u16tofp32(highp uint v) {
|
||||
v <<= 16u;
|
||||
highp uint s = v & 0x80000000u;
|
||||
highp uint n = v & 0x7FFFFFFFu;
|
||||
highp uint nz = n == 0u ? 0u : 0xFFFFFFFF;
|
||||
return uintBitsToFloat(s | ((((n >> 3u) + (0x70u << 23))) & nz));
|
||||
}
|
||||
vec2 unpackHalf2x16(highp uint v) {
|
||||
return vec2(u16tofp32(v&0xFFFFu), u16tofp32(v>>16u));
|
||||
}
|
||||
uint fp32tou16(float val) {
|
||||
uint f32 = floatBitsToUint(val);
|
||||
uint f16 = 0u;
|
||||
uint sign = (f32 >> 16) & 0x8000u;
|
||||
int exponent = int((f32 >> 23) & 0xFFu) - 127;
|
||||
uint mantissa = f32 & 0x007FFFFFu;
|
||||
if (exponent > 15) {
|
||||
f16 = sign | (0x1Fu << 10);
|
||||
} else if (exponent > -15) {
|
||||
exponent += 15;
|
||||
mantissa >>= 13;
|
||||
f16 = sign | uint(exponent << 10) | mantissa;
|
||||
} else {
|
||||
f16 = sign;
|
||||
}
|
||||
return f16;
|
||||
}
|
||||
highp uint packHalf2x16(vec2 v) {
|
||||
highp uint x = fp32tou16(v.x);
|
||||
highp uint y = fp32tou16(v.y);
|
||||
return (y << 16) | x;
|
||||
}
|
||||
)"sv;
|
||||
}
|
||||
#endif // BACKEND_OPENGL_VERSION_GL
|
||||
return ""sv;
|
||||
}
|
||||
|
||||
// split shader source code in two, the first section goes from the start to the line after the
|
||||
// last #extension, and the 2nd part goes from there to the end.
|
||||
std::array<std::string_view, 2> OpenGLProgram::splitShaderSource(std::string_view source) noexcept {
|
||||
auto start = source.find("#version");
|
||||
assert_invariant(start != std::string_view::npos);
|
||||
|
||||
auto pos = source.rfind("\n#extension");
|
||||
if (pos == std::string_view::npos) {
|
||||
pos = start;
|
||||
} else {
|
||||
++pos;
|
||||
}
|
||||
|
||||
auto eol = source.find('\n', pos) + 1;
|
||||
assert_invariant(eol != std::string_view::npos);
|
||||
|
||||
std::string_view const version = source.substr(start, eol - start);
|
||||
std::string_view const body = source.substr(version.length(), source.length() - version.length());
|
||||
return { version, body };
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a program from the given shader IDs and links it. This cannot fail because errors
|
||||
* are checked later. This always returns a valid GL program ID (which doesn't mean the
|
||||
* program itself is valid).
|
||||
*/
|
||||
GLuint OpenGLProgram::linkProgram(OpenGLContext& context,
|
||||
LazyInitializationData* const lazyInitializationData,
|
||||
const GLuint shaderIds[Program::SHADER_TYPE_COUNT]) noexcept {
|
||||
|
||||
SYSTRACE_CALL();
|
||||
|
||||
GLuint const program = glCreateProgram();
|
||||
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
|
||||
if (shaderIds[i]) {
|
||||
glAttachShader(program, shaderIds[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(context.isES2())) {
|
||||
for (auto const& [ name, loc ] : lazyInitializationData->attributes) {
|
||||
glBindAttribLocation(program, loc, name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
glLinkProgram(program);
|
||||
return program;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks a program link status and logs errors and frees resources on failure.
|
||||
* Returns true on success.
|
||||
*/
|
||||
bool OpenGLProgram::checkProgramStatus(const char* name,
|
||||
GLuint& program, GLuint shaderIds[Program::SHADER_TYPE_COUNT],
|
||||
std::array<CString, Program::SHADER_TYPE_COUNT> const& shaderSourceCode) noexcept {
|
||||
|
||||
SYSTRACE_CALL();
|
||||
|
||||
GLint status;
|
||||
glGetProgramiv(program, GL_LINK_STATUS, &status);
|
||||
if (UTILS_LIKELY(status == GL_TRUE)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// only if the link fails, we check the compilation status
|
||||
UTILS_NOUNROLL
|
||||
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
|
||||
const ShaderStage type = static_cast<ShaderStage>(i);
|
||||
const GLuint shader = shaderIds[i];
|
||||
if (shader) {
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
|
||||
if (status != GL_TRUE) {
|
||||
logCompilationError(slog.e, type, name, shader, shaderSourceCode[i]);
|
||||
}
|
||||
glDetachShader(program, shader);
|
||||
glDeleteShader(shader);
|
||||
shaderIds[i] = 0;
|
||||
}
|
||||
}
|
||||
// log the link error as well
|
||||
logProgramLinkError(slog.e, name, program);
|
||||
glDeleteProgram(program);
|
||||
program = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
void OpenGLProgram::initialize(OpenGLContext& context) {
|
||||
// by this point we must have a GL program
|
||||
assert_invariant(gl.program);
|
||||
// we also can't be in the initialized state
|
||||
assert_invariant(!mInitialized);
|
||||
// we must have our lazy initialization data
|
||||
assert_invariant(mLazyInitializationData);
|
||||
|
||||
// we must copy mLazyInitializationData locally because it is aliased with mIndicesRuns
|
||||
auto* const pInitializationData = mLazyInitializationData;
|
||||
|
||||
// check status of program linking and shader compilation, logs error and free all resources
|
||||
// in case of error.
|
||||
mValid = OpenGLProgram::checkProgramStatus(name.c_str_safe(),
|
||||
gl.program, gl.shaders, pInitializationData->shaderSourceCode);
|
||||
|
||||
if (UTILS_LIKELY(mValid)) {
|
||||
initializeProgramState(context, gl.program, *pInitializationData);
|
||||
}
|
||||
|
||||
// and destroy all temporary init data
|
||||
delete pInitializationData;
|
||||
// mInitialized means mLazyInitializationData is no more valid
|
||||
mInitialized = true;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -592,64 +303,5 @@ void OpenGLProgram::setRec709ColorSpace(bool rec709) const noexcept {
|
||||
glUniform1i(mRec709Location, rec709);
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
void logCompilationError(io::ostream& out, ShaderStage shaderType,
|
||||
const char* name, GLuint shaderId,
|
||||
UTILS_UNUSED_IN_RELEASE CString const& sourceCode) noexcept {
|
||||
|
||||
auto to_string = [](ShaderStage type) -> const char* {
|
||||
switch (type) {
|
||||
case ShaderStage::VERTEX: return "vertex";
|
||||
case ShaderStage::FRAGMENT: return "fragment";
|
||||
case ShaderStage::COMPUTE: return "compute";
|
||||
}
|
||||
};
|
||||
|
||||
{ // scope for the temporary string storage
|
||||
GLint length = 0;
|
||||
glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &length);
|
||||
|
||||
CString infoLog(length);
|
||||
glGetShaderInfoLog(shaderId, length, nullptr, infoLog.data());
|
||||
|
||||
out << "Compilation error in " << to_string(shaderType) << " shader \"" << name << "\":\n"
|
||||
<< "\"" << infoLog.c_str() << "\""
|
||||
<< io::endl;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
std::string_view const shader{ sourceCode.data(), sourceCode.size() };
|
||||
size_t lc = 1;
|
||||
size_t start = 0;
|
||||
std::string line;
|
||||
while (true) {
|
||||
size_t const end = shader.find('\n', start);
|
||||
if (end == std::string::npos) {
|
||||
line = shader.substr(start);
|
||||
} else {
|
||||
line = shader.substr(start, end - start);
|
||||
}
|
||||
out << lc++ << ": " << line.c_str() << '\n';
|
||||
if (end == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
start = end + 1;
|
||||
}
|
||||
out << io::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
void logProgramLinkError(io::ostream& out, char const* name, GLuint program) noexcept {
|
||||
GLint length = 0;
|
||||
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);
|
||||
|
||||
CString infoLog(length);
|
||||
glGetProgramInfoLog(program, length, nullptr, infoLog.data());
|
||||
|
||||
out << "Link error in \"" << name << "\":\n"
|
||||
<< "\"" << infoLog.c_str() << "\""
|
||||
<< io::endl;
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -18,22 +18,23 @@
|
||||
#define TNT_FILAMENT_BACKEND_OPENGL_OPENGLPROGRAM_H
|
||||
|
||||
#include "DriverBase.h"
|
||||
#include "OpenGLDriver.h"
|
||||
|
||||
#include "private/backend/Driver.h"
|
||||
#include "backend/Program.h"
|
||||
#include "OpenGLContext.h"
|
||||
#include "ShaderCompilerService.h"
|
||||
|
||||
#include <private/backend/Driver.h>
|
||||
#include <backend/Program.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include <vector>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class OpenGLDriver;
|
||||
|
||||
class OpenGLProgram : public HwProgram {
|
||||
public:
|
||||
|
||||
@@ -41,11 +42,11 @@ public:
|
||||
OpenGLProgram(OpenGLDriver& gld, Program&& program) noexcept;
|
||||
~OpenGLProgram() noexcept;
|
||||
|
||||
bool isValid() const noexcept { return mValid; }
|
||||
bool isValid() const noexcept { return mToken || gl.program != 0; }
|
||||
|
||||
void use(OpenGLDriver* const gld, OpenGLContext& context) noexcept {
|
||||
if (UTILS_UNLIKELY(!mInitialized)) {
|
||||
initialize(context);
|
||||
if (UTILS_UNLIKELY(!gl.program)) {
|
||||
initialize(*gld);
|
||||
}
|
||||
|
||||
context.useProgram(gl.program);
|
||||
@@ -67,7 +68,6 @@ public:
|
||||
}
|
||||
|
||||
struct {
|
||||
GLuint shaders[Program::SHADER_TYPE_COUNT] = {};
|
||||
GLuint program = 0;
|
||||
} gl; // 12 bytes
|
||||
|
||||
@@ -77,69 +77,29 @@ public:
|
||||
|
||||
private:
|
||||
// keep these away from of other class attributes
|
||||
struct LazyInitializationData {
|
||||
Program::UniformBlockInfo uniformBlockInfo;
|
||||
Program::SamplerGroupInfo samplerGroupInfo;
|
||||
std::array<Program::UniformInfo, Program::UNIFORM_BINDING_COUNT> bindingUniformInfo;
|
||||
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> attributes;
|
||||
std::array<utils::CString, Program::SHADER_TYPE_COUNT> shaderSourceCode;
|
||||
};
|
||||
struct LazyInitializationData;
|
||||
|
||||
static void compileShaders(OpenGLContext& context,
|
||||
Program::ShaderSource shadersSource,
|
||||
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
|
||||
GLuint shaderIds[Program::SHADER_TYPE_COUNT],
|
||||
std::array<utils::CString, Program::SHADER_TYPE_COUNT>& outShaderSourceCode) noexcept;
|
||||
|
||||
static std::string_view process_GOOGLE_cpp_style_line_directive(OpenGLContext& context,
|
||||
char* source, size_t len) noexcept;
|
||||
|
||||
static std::string_view process_ARB_shading_language_packing(OpenGLContext& context) noexcept;
|
||||
|
||||
static std::array<std::string_view, 2> splitShaderSource(std::string_view source) noexcept;
|
||||
|
||||
static GLuint linkProgram(OpenGLContext& context,
|
||||
LazyInitializationData* lazyInitializationData,
|
||||
const GLuint shaderIds[Program::SHADER_TYPE_COUNT]) noexcept;
|
||||
|
||||
static bool checkProgramStatus(const char* name,
|
||||
GLuint& program, GLuint shaderIds[Program::SHADER_TYPE_COUNT],
|
||||
std::array<utils::CString, Program::SHADER_TYPE_COUNT> const& shaderSourceCode) noexcept;
|
||||
|
||||
void initialize(OpenGLContext& context);
|
||||
void initialize(OpenGLDriver& gld);
|
||||
|
||||
void initializeProgramState(OpenGLContext& context, GLuint program,
|
||||
LazyInitializationData& lazyInitializationData) noexcept;
|
||||
|
||||
void updateSamplers(OpenGLDriver* gld) const noexcept;
|
||||
|
||||
ShaderCompilerService::program_token_t mToken{};
|
||||
|
||||
// number of bindings actually used by this program
|
||||
uint8_t mUsedBindingsCount = 0u;
|
||||
// whether lazy initialization has been performed
|
||||
bool mInitialized : 1;
|
||||
// whether lazy initialization was successful
|
||||
bool mValid : 1;
|
||||
UTILS_UNUSED uint8_t padding[2] = {};
|
||||
UTILS_UNUSED uint8_t padding[3] = {};
|
||||
std::array<uint8_t, Program::SAMPLER_BINDING_COUNT> mUsedSamplerBindingPoints; // 4 bytes
|
||||
|
||||
// only for ES2
|
||||
// only needed for ES2
|
||||
using LocationInfo = utils::FixedCapacityVector<GLint>;
|
||||
struct UniformsRecord {
|
||||
Program::UniformInfo uniforms;
|
||||
LocationInfo locations;
|
||||
mutable uint16_t age = std::numeric_limits<uint16_t>::max();
|
||||
};
|
||||
|
||||
union {
|
||||
// when mInitialized == true:
|
||||
// information about each USED sampler buffer per binding (no gaps)
|
||||
std::array<uint8_t, Program::SAMPLER_BINDING_COUNT> mUsedSamplerBindingPoints; // 4 bytes
|
||||
// when mInitialized == false:
|
||||
// lazy initialization data pointer
|
||||
LazyInitializationData* mLazyInitializationData;
|
||||
};
|
||||
|
||||
// only needed for ES2
|
||||
UniformsRecord const* mUniformsRecords = nullptr;
|
||||
GLint mRec709Location = -1;
|
||||
};
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <backend/platforms/OpenGLPlatform.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/JobSystem.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Systrace.h>
|
||||
#include <utils/debug.h>
|
||||
@@ -30,43 +31,111 @@ using namespace GLUtils;
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
bool OpenGLTimerQueryFactory::mGpuTimeSupported = false;
|
||||
|
||||
OpenGLTimerQueryInterface* OpenGLTimerQueryFactory::init(
|
||||
OpenGLPlatform& platform, OpenGLDriver& driver) noexcept {
|
||||
(void)driver;
|
||||
|
||||
OpenGLTimerQueryInterface* impl;
|
||||
|
||||
#if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_disjoint_timer_query)
|
||||
auto& context = driver.getContext();
|
||||
if (context.ext.EXT_disjoint_timer_query) {
|
||||
// timer queries are available
|
||||
if (context.bugs.dont_use_timer_query && platform.canCreateFence()) {
|
||||
// however, they don't work well, revert to using fences if we can.
|
||||
impl = new(std::nothrow) OpenGLTimerQueryFence(platform);
|
||||
} else {
|
||||
impl = new(std::nothrow) TimerQueryNative(driver);
|
||||
}
|
||||
mGpuTimeSupported = true;
|
||||
} else
|
||||
#endif
|
||||
if (platform.canCreateFence()) {
|
||||
// no timer queries, but we can use fences
|
||||
impl = new(std::nothrow) OpenGLTimerQueryFence(platform);
|
||||
mGpuTimeSupported = true;
|
||||
} else {
|
||||
// no queries, no fences -- that's a problem
|
||||
impl = new(std::nothrow) TimerQueryFallback();
|
||||
mGpuTimeSupported = false;
|
||||
}
|
||||
return impl;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
OpenGLTimerQueryInterface::~OpenGLTimerQueryInterface() = default;
|
||||
|
||||
// This is a backend synchronous call
|
||||
bool OpenGLTimerQueryInterface::getTimerQueryValue(GLTimerQuery* tq, uint64_t* elapsedTime) noexcept {
|
||||
if (UTILS_LIKELY(tq->state)) {
|
||||
int64_t const elapsed = tq->state->elapsed.load(std::memory_order_relaxed);
|
||||
bool const available = elapsed > 0;
|
||||
if (available) {
|
||||
*elapsedTime = elapsed;
|
||||
}
|
||||
return available;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
#if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_disjoint_timer_query)
|
||||
|
||||
TimerQueryNative::TimerQueryNative(OpenGLContext& context) : mContext(context) {
|
||||
TimerQueryNative::TimerQueryNative(OpenGLDriver& driver)
|
||||
: mDriver(driver) {
|
||||
}
|
||||
|
||||
TimerQueryNative::~TimerQueryNative() = default;
|
||||
|
||||
void TimerQueryNative::flush() {
|
||||
}
|
||||
|
||||
void TimerQueryNative::beginTimeElapsedQuery(GLTimerQuery* query) {
|
||||
mContext.procs.beginQuery(GL_TIME_ELAPSED, query->gl.query);
|
||||
void TimerQueryNative::createTimerQuery(GLTimerQuery* tq) {
|
||||
if (UTILS_UNLIKELY(!tq->state)) {
|
||||
tq->state = std::make_shared<GLTimerQuery::State>();
|
||||
}
|
||||
mDriver.getContext().procs.genQueries(1u, &tq->state->gl.query);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
|
||||
void TimerQueryNative::endTimeElapsedQuery(GLTimerQuery*) {
|
||||
mContext.procs.endQuery(GL_TIME_ELAPSED);
|
||||
void TimerQueryNative::destroyTimerQuery(GLTimerQuery* tq) {
|
||||
assert_invariant(tq->state);
|
||||
mDriver.getContext().procs.deleteQueries(1u, &tq->state->gl.query);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
}
|
||||
|
||||
bool TimerQueryNative::queryResultAvailable(GLTimerQuery* query) {
|
||||
GLuint available = 0;
|
||||
mContext.procs.getQueryObjectuiv(query->gl.query, GL_QUERY_RESULT_AVAILABLE, &available);
|
||||
void TimerQueryNative::beginTimeElapsedQuery(GLTimerQuery* tq) {
|
||||
assert_invariant(tq->state);
|
||||
tq->state->elapsed.store(0);
|
||||
mDriver.getContext().procs.beginQuery(GL_TIME_ELAPSED, tq->state->gl.query);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
return available != 0;
|
||||
}
|
||||
|
||||
uint64_t TimerQueryNative::queryResult(GLTimerQuery* query) {
|
||||
GLuint64 elapsedTime = 0;
|
||||
// we won't end-up here if we're on ES and don't have GL_EXT_disjoint_timer_query
|
||||
mContext.procs.getQueryObjectui64v(query->gl.query, GL_QUERY_RESULT, &elapsedTime);
|
||||
void TimerQueryNative::endTimeElapsedQuery(GLTimerQuery* tq) {
|
||||
assert_invariant(tq->state);
|
||||
mDriver.getContext().procs.endQuery(GL_TIME_ELAPSED);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
return elapsedTime;
|
||||
|
||||
std::weak_ptr<GLTimerQuery::State> const weak = tq->state;
|
||||
|
||||
mDriver.runEveryNowAndThen([context = mDriver.getContext(), weak]() -> bool {
|
||||
auto state = weak.lock();
|
||||
if (state) {
|
||||
GLuint available = 0;
|
||||
context.procs.getQueryObjectuiv(state->gl.query, GL_QUERY_RESULT_AVAILABLE, &available);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
if (!available) {
|
||||
// we need to try this one again later
|
||||
return false;
|
||||
}
|
||||
GLuint64 elapsedTime = 0;
|
||||
// we won't end-up here if we're on ES and don't have GL_EXT_disjoint_timer_query
|
||||
context.procs.getQueryObjectui64v(state->gl.query, GL_QUERY_RESULT, &elapsedTime);
|
||||
state->elapsed.store((int64_t)elapsedTime, std::memory_order_relaxed);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -77,6 +146,8 @@ OpenGLTimerQueryFence::OpenGLTimerQueryFence(OpenGLPlatform& platform)
|
||||
: mPlatform(platform) {
|
||||
mQueue.reserve(2);
|
||||
mThread = std::thread([this]() {
|
||||
utils::JobSystem::setThreadName("OpenGLTimerQueryFence");
|
||||
utils::JobSystem::setThreadPriority(utils::JobSystem::Priority::URGENT_DISPLAY);
|
||||
auto& queue = mQueue;
|
||||
bool exitRequested;
|
||||
do {
|
||||
@@ -101,7 +172,9 @@ OpenGLTimerQueryFence::~OpenGLTimerQueryFence() {
|
||||
mExitRequested = true;
|
||||
mCondition.notify_one();
|
||||
lock.unlock();
|
||||
mThread.join();
|
||||
if (mThread.joinable()) {
|
||||
mThread.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,59 +184,60 @@ void OpenGLTimerQueryFence::enqueue(OpenGLTimerQueryFence::Job&& job) {
|
||||
mCondition.notify_one();
|
||||
}
|
||||
|
||||
void OpenGLTimerQueryFence::flush() {
|
||||
// Use calls to flush() as a proxy for when the GPU work started.
|
||||
GLTimerQuery* query = mActiveQuery;
|
||||
if (query) {
|
||||
uint64_t const elapsed = query->gl.emulation->elapsed.load(std::memory_order_relaxed);
|
||||
if (!elapsed) {
|
||||
uint64_t const now = clock::now().time_since_epoch().count();
|
||||
query->gl.emulation->elapsed.store(now, std::memory_order_relaxed);
|
||||
//SYSTRACE_CONTEXT();
|
||||
//SYSTRACE_ASYNC_BEGIN("gpu", query->gl.query);
|
||||
}
|
||||
void OpenGLTimerQueryFence::createTimerQuery(GLTimerQuery* tq) {
|
||||
if (UTILS_UNLIKELY(!tq->state)) {
|
||||
tq->state = std::make_shared<GLTimerQuery::State>();
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLTimerQueryFence::beginTimeElapsedQuery(GLTimerQuery* query) {
|
||||
assert_invariant(!mActiveQuery);
|
||||
// We can't use a fence to figure out when a GPU operation starts (only when it finishes)
|
||||
// so instead, we use when glFlush() was issued as a proxy.
|
||||
if (UTILS_UNLIKELY(!query->gl.emulation)) {
|
||||
query->gl.emulation = std::make_shared<GLTimerQuery::State>();
|
||||
}
|
||||
query->gl.emulation->elapsed.store(0, std::memory_order_relaxed);
|
||||
query->gl.emulation->available.store(false);
|
||||
mActiveQuery = query;
|
||||
void OpenGLTimerQueryFence::destroyTimerQuery(GLTimerQuery* tq) {
|
||||
assert_invariant(tq->state);
|
||||
}
|
||||
|
||||
void OpenGLTimerQueryFence::endTimeElapsedQuery(GLTimerQuery* query) {
|
||||
assert_invariant(mActiveQuery);
|
||||
void OpenGLTimerQueryFence::beginTimeElapsedQuery(GLTimerQuery* tq) {
|
||||
assert_invariant(tq->state);
|
||||
tq->state->elapsed.store(0);
|
||||
|
||||
Platform::Fence* fence = mPlatform.createFence();
|
||||
std::weak_ptr<GLTimerQuery::State> const weak = query->gl.emulation;
|
||||
mActiveQuery = nullptr;
|
||||
//uint32_t cookie = cookie = query->gl.query;
|
||||
std::weak_ptr<GLTimerQuery::State> const weak = tq->state;
|
||||
|
||||
// FIXME: this implementation of beginTimeElapsedQuery is usually wrong; it ends up
|
||||
// measuring the current CPU time because the fence signals immediately (usually there is
|
||||
// no work on the GPU at this point). We could workaround this by sending a small glClear
|
||||
// on a dummy target for instance, or somehow latch the begin time at the next renderpass
|
||||
// start.
|
||||
|
||||
push([&platform = mPlatform, fence, weak]() {
|
||||
auto emulation = weak.lock();
|
||||
if (emulation) {
|
||||
auto state = weak.lock();
|
||||
if (state) {
|
||||
platform.waitFence(fence, FENCE_WAIT_FOR_EVER);
|
||||
auto now = clock::now().time_since_epoch().count();
|
||||
auto then = emulation->elapsed.load(std::memory_order_relaxed);
|
||||
emulation->elapsed.store(now - then, std::memory_order_relaxed);
|
||||
emulation->available.store(true);
|
||||
//SYSTRACE_CONTEXT();
|
||||
//SYSTRACE_ASYNC_END("gpu", cookie);
|
||||
int64_t const then = clock::now().time_since_epoch().count();
|
||||
state->elapsed.store(-then, std::memory_order_relaxed);
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_ASYNC_BEGIN("OpenGLTimerQueryFence", intptr_t(state.get()));
|
||||
}
|
||||
platform.destroyFence(fence);
|
||||
});
|
||||
}
|
||||
|
||||
bool OpenGLTimerQueryFence::queryResultAvailable(GLTimerQuery* query) {
|
||||
return query->gl.emulation->available.load();
|
||||
}
|
||||
void OpenGLTimerQueryFence::endTimeElapsedQuery(GLTimerQuery* tq) {
|
||||
assert_invariant(tq->state);
|
||||
Platform::Fence* fence = mPlatform.createFence();
|
||||
std::weak_ptr<GLTimerQuery::State> const weak = tq->state;
|
||||
|
||||
uint64_t OpenGLTimerQueryFence::queryResult(GLTimerQuery* query) {
|
||||
return query->gl.emulation->elapsed;
|
||||
push([&platform = mPlatform, fence, weak]() {
|
||||
auto state = weak.lock();
|
||||
if (state) {
|
||||
platform.waitFence(fence, FENCE_WAIT_FOR_EVER);
|
||||
int64_t const now = clock::now().time_since_epoch().count();
|
||||
int64_t const then = state->elapsed.load(std::memory_order_relaxed);
|
||||
assert_invariant(then < 0);
|
||||
state->elapsed.store(now + then, std::memory_order_relaxed);
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_ASYNC_END("OpenGLTimerQueryFence", intptr_t(state.get()));
|
||||
}
|
||||
platform.destroyFence(fence);
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
@@ -172,30 +246,30 @@ TimerQueryFallback::TimerQueryFallback() = default;
|
||||
|
||||
TimerQueryFallback::~TimerQueryFallback() = default;
|
||||
|
||||
void TimerQueryFallback::flush() {
|
||||
}
|
||||
|
||||
void TimerQueryFallback::beginTimeElapsedQuery(OpenGLTimerQueryInterface::GLTimerQuery* query) {
|
||||
if (!query->gl.emulation) {
|
||||
query->gl.emulation = std::make_shared<GLTimerQuery::State>();
|
||||
void TimerQueryFallback::createTimerQuery(GLTimerQuery* tq) {
|
||||
if (UTILS_UNLIKELY(!tq->state)) {
|
||||
tq->state = std::make_shared<GLTimerQuery::State>();
|
||||
}
|
||||
// this implementation clearly doesn't work at all, but we have no h/w support
|
||||
query->gl.emulation->available.store(false, std::memory_order_relaxed);
|
||||
query->gl.emulation->elapsed = clock::now().time_since_epoch().count();
|
||||
}
|
||||
|
||||
void TimerQueryFallback::endTimeElapsedQuery(OpenGLTimerQueryInterface::GLTimerQuery* query) {
|
||||
// this implementation clearly doesn't work at all, but we have no h/w support
|
||||
query->gl.emulation->elapsed = clock::now().time_since_epoch().count() - query->gl.emulation->elapsed;
|
||||
query->gl.emulation->available.store(true, std::memory_order_relaxed);
|
||||
void TimerQueryFallback::destroyTimerQuery(GLTimerQuery* tq) {
|
||||
assert_invariant(tq->state);
|
||||
}
|
||||
|
||||
bool TimerQueryFallback::queryResultAvailable(OpenGLTimerQueryInterface::GLTimerQuery* query) {
|
||||
return query->gl.emulation->available.load(std::memory_order_relaxed);
|
||||
void TimerQueryFallback::beginTimeElapsedQuery(OpenGLTimerQueryInterface::GLTimerQuery* tq) {
|
||||
assert_invariant(tq->state);
|
||||
// this implementation measures the CPU time, but we have no h/w support
|
||||
int64_t const then = clock::now().time_since_epoch().count();
|
||||
tq->state->elapsed.store(-then, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
uint64_t TimerQueryFallback::queryResult(OpenGLTimerQueryInterface::GLTimerQuery* query) {
|
||||
return query->gl.emulation->elapsed;
|
||||
void TimerQueryFallback::endTimeElapsedQuery(OpenGLTimerQueryInterface::GLTimerQuery* tq) {
|
||||
assert_invariant(tq->state);
|
||||
// this implementation measures the CPU time, but we have no h/w support
|
||||
int64_t const now = clock::now().time_since_epoch().count();
|
||||
int64_t const then = tq->state->elapsed.load(std::memory_order_relaxed);
|
||||
assert_invariant(then < 0);
|
||||
tq->state->elapsed.store(now + then, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -20,20 +20,36 @@
|
||||
#include "OpenGLDriver.h"
|
||||
|
||||
#include <utils/Condition.h>
|
||||
#include <utils/Mutex.h>
|
||||
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class OpenGLPlatform;
|
||||
class OpenGLTimerQueryInterface;
|
||||
|
||||
/*
|
||||
* we need two implementation of timer queries (only elapsed time), because
|
||||
* We need two implementation of timer queries (only elapsed time), because
|
||||
* on some gpu disjoint_timer_query/arb_timer_query is much less accurate than
|
||||
* using fences.
|
||||
*
|
||||
* These classes implement the various strategies...
|
||||
*/
|
||||
|
||||
|
||||
class OpenGLTimerQueryFactory {
|
||||
static bool mGpuTimeSupported;
|
||||
public:
|
||||
static OpenGLTimerQueryInterface* init(
|
||||
OpenGLPlatform& platform, OpenGLDriver& driver) noexcept;
|
||||
|
||||
static bool isGpuTimeSupported() noexcept {
|
||||
return mGpuTimeSupported;
|
||||
}
|
||||
};
|
||||
|
||||
class OpenGLTimerQueryInterface {
|
||||
protected:
|
||||
using GLTimerQuery = OpenGLDriver::GLTimerQuery;
|
||||
@@ -41,26 +57,26 @@ protected:
|
||||
|
||||
public:
|
||||
virtual ~OpenGLTimerQueryInterface();
|
||||
virtual void flush() = 0;
|
||||
virtual void createTimerQuery(GLTimerQuery* query) = 0;
|
||||
virtual void destroyTimerQuery(GLTimerQuery* query) = 0;
|
||||
virtual void beginTimeElapsedQuery(GLTimerQuery* query) = 0;
|
||||
virtual void endTimeElapsedQuery(GLTimerQuery* query) = 0;
|
||||
virtual bool queryResultAvailable(GLTimerQuery* query) = 0;
|
||||
virtual uint64_t queryResult(GLTimerQuery* query) = 0;
|
||||
|
||||
static bool getTimerQueryValue(GLTimerQuery* tq, uint64_t* elapsedTime) noexcept;
|
||||
};
|
||||
|
||||
#if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_disjoint_timer_query)
|
||||
|
||||
class TimerQueryNative : public OpenGLTimerQueryInterface {
|
||||
public:
|
||||
explicit TimerQueryNative(OpenGLContext& context);
|
||||
explicit TimerQueryNative(OpenGLDriver& driver);
|
||||
~TimerQueryNative() override;
|
||||
private:
|
||||
void flush() override;
|
||||
void createTimerQuery(GLTimerQuery* query) override;
|
||||
void destroyTimerQuery(GLTimerQuery* query) override;
|
||||
void beginTimeElapsedQuery(GLTimerQuery* query) override;
|
||||
void endTimeElapsedQuery(GLTimerQuery* query) override;
|
||||
bool queryResultAvailable(GLTimerQuery* query) override;
|
||||
uint64_t queryResult(GLTimerQuery* query) override;
|
||||
OpenGLContext& mContext;
|
||||
OpenGLDriver& mDriver;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -71,13 +87,12 @@ public:
|
||||
~OpenGLTimerQueryFence() override;
|
||||
private:
|
||||
using Job = std::function<void()>;
|
||||
void flush() override;
|
||||
void beginTimeElapsedQuery(GLTimerQuery* query) override;
|
||||
void endTimeElapsedQuery(GLTimerQuery* query) override;
|
||||
bool queryResultAvailable(GLTimerQuery* query) override;
|
||||
uint64_t queryResult(GLTimerQuery* query) override;
|
||||
void enqueue(Job&& job);
|
||||
void createTimerQuery(GLTimerQuery* query) override;
|
||||
void destroyTimerQuery(GLTimerQuery* query) override;
|
||||
void beginTimeElapsedQuery(GLTimerQuery* tq) override;
|
||||
void endTimeElapsedQuery(GLTimerQuery* tq) override;
|
||||
|
||||
void enqueue(Job&& job);
|
||||
template<typename CALLABLE, typename ... ARGS>
|
||||
void push(CALLABLE&& func, ARGS&& ... args) {
|
||||
enqueue(Job(std::bind(std::forward<CALLABLE>(func), std::forward<ARGS>(args)...)));
|
||||
@@ -89,7 +104,6 @@ private:
|
||||
mutable utils::Condition mCondition;
|
||||
std::vector<Job> mQueue;
|
||||
bool mExitRequested = false;
|
||||
GLTimerQuery* mActiveQuery = nullptr;
|
||||
};
|
||||
|
||||
class TimerQueryFallback : public OpenGLTimerQueryInterface {
|
||||
@@ -97,11 +111,10 @@ public:
|
||||
explicit TimerQueryFallback();
|
||||
~TimerQueryFallback() override;
|
||||
private:
|
||||
void flush() override;
|
||||
void createTimerQuery(GLTimerQuery* query) override;
|
||||
void destroyTimerQuery(GLTimerQuery* query) override;
|
||||
void beginTimeElapsedQuery(GLTimerQuery* query) override;
|
||||
void endTimeElapsedQuery(GLTimerQuery* query) override;
|
||||
bool queryResultAvailable(GLTimerQuery* query) override;
|
||||
uint64_t queryResult(GLTimerQuery* query) override;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -0,0 +1,796 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "ShaderCompilerService.h"
|
||||
|
||||
#include "BlobCacheKey.h"
|
||||
#include "OpenGLBlobCache.h"
|
||||
#include "OpenGLDriver.h"
|
||||
|
||||
#include <private/backend/BackendUtils.h>
|
||||
|
||||
#include <backend/platforms/OpenGLPlatform.h>
|
||||
#include <backend/Program.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/CString.h>
|
||||
#include <utils/JobSystem.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Systrace.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
using namespace utils;
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
static void logCompilationError(utils::io::ostream& out,
|
||||
ShaderStage shaderType, const char* name,
|
||||
GLuint shaderId, CString const& sourceCode) noexcept;
|
||||
|
||||
static void logProgramLinkError(utils::io::ostream& out,
|
||||
const char* name, GLuint program) noexcept;
|
||||
|
||||
static inline std::string to_string(bool b) noexcept {
|
||||
return b ? "true" : "false";
|
||||
}
|
||||
|
||||
static inline std::string to_string(int i) noexcept {
|
||||
return std::to_string(i);
|
||||
}
|
||||
|
||||
static inline std::string to_string(float f) noexcept {
|
||||
return "float(" + std::to_string(f) + ")";
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
|
||||
struct ProgramData {
|
||||
GLuint program{};
|
||||
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders{};
|
||||
};
|
||||
|
||||
~OpenGLProgramToken() override;
|
||||
|
||||
OpenGLProgramToken(ShaderCompilerService& compiler, utils::CString const& name) noexcept
|
||||
: compiler(compiler), name(name) {
|
||||
}
|
||||
|
||||
ShaderCompilerService& compiler;
|
||||
utils::CString const& name;
|
||||
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> attributes;
|
||||
std::array<utils::CString, Program::SHADER_TYPE_COUNT> shaderSourceCode;
|
||||
void* user = nullptr;
|
||||
struct {
|
||||
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders{};
|
||||
GLuint program = 0;
|
||||
} gl; // 12 bytes
|
||||
|
||||
|
||||
// Sets the programData, typically from the compiler thread, and signal the main thread.
|
||||
// This is similar to std::promise::set_value.
|
||||
void set(ProgramData const& data) noexcept {
|
||||
std::unique_lock const l(lock);
|
||||
programData = data;
|
||||
signaled = true;
|
||||
cond.notify_one();
|
||||
}
|
||||
|
||||
// Get the programBinary, wait if necessary.
|
||||
// This is similar to std::future::get
|
||||
ProgramData const& get() const noexcept {
|
||||
std::unique_lock l(lock);
|
||||
cond.wait(l, [this](){ return signaled; });
|
||||
return programData;
|
||||
}
|
||||
|
||||
// Checks if the programBinary is ready.
|
||||
// This is similar to std::future::wait_for(0s)
|
||||
bool isReady() const noexcept {
|
||||
std::unique_lock l(lock);
|
||||
using namespace std::chrono_literals;
|
||||
return cond.wait_for(l, 0s, [this](){ return signaled; });
|
||||
}
|
||||
|
||||
CallbackManager::Handle handle{};
|
||||
BlobCacheKey key;
|
||||
mutable utils::Mutex lock;
|
||||
mutable utils::Condition cond;
|
||||
ProgramData programData;
|
||||
bool signaled = false;
|
||||
|
||||
bool canceled = false; // not part of the signaling
|
||||
};
|
||||
|
||||
ShaderCompilerService::OpenGLProgramToken::~OpenGLProgramToken() = default;
|
||||
|
||||
void ShaderCompilerService::setUserData(const program_token_t& token, void* user) noexcept {
|
||||
token->user = user;
|
||||
}
|
||||
|
||||
void* ShaderCompilerService::getUserData(const program_token_t& token) noexcept {
|
||||
return token->user;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
ShaderCompilerService::ShaderCompilerService(OpenGLDriver& driver)
|
||||
: mDriver(driver),
|
||||
mCallbackManager(driver),
|
||||
KHR_parallel_shader_compile(driver.getContext().ext.KHR_parallel_shader_compile) {
|
||||
}
|
||||
|
||||
ShaderCompilerService::~ShaderCompilerService() noexcept = default;
|
||||
|
||||
bool ShaderCompilerService::isParallelShaderCompileSupported() const noexcept {
|
||||
return KHR_parallel_shader_compile || mShaderCompilerThreadCount;
|
||||
}
|
||||
|
||||
void ShaderCompilerService::init() noexcept {
|
||||
// If we have KHR_parallel_shader_compile, we always use it, it should be more resource
|
||||
// friendly.
|
||||
if (!KHR_parallel_shader_compile) {
|
||||
// - on Adreno there is a single compiler object. We can't use a pool > 1
|
||||
// also glProgramBinary blocks if other threads are compiling.
|
||||
// - on Mali shader compilation can be multi-threaded, but program linking happens on
|
||||
// a single service thread, so we don't bother using more than one thread either.
|
||||
// - on PowerVR shader compilation and linking can be multi-threaded.
|
||||
// How many threads should we use?
|
||||
// - on macOS (M1 MacBook Pro/Ventura) there is global lock around all GL APIs when using
|
||||
// a shared context, so parallel shader compilation yields no benefit.
|
||||
// - on windows/linux we could use more threads, tbd.
|
||||
if (mDriver.mPlatform.isExtraContextSupported()) {
|
||||
// By default, we use one thread at the same priority as the gl thread. This is the
|
||||
// safest choice that avoids priority inversions.
|
||||
uint32_t poolSize = 1;
|
||||
JobSystem::Priority priority = JobSystem::Priority::DISPLAY;
|
||||
|
||||
auto const& renderer = mDriver.getContext().state.renderer;
|
||||
if (UTILS_UNLIKELY(strstr(renderer, "PowerVR"))) {
|
||||
// The PowerVR driver support parallel shader compilation well, so we use 2
|
||||
// threads, we can use lower priority threads here because urgent compilations
|
||||
// will most likely happen on the main gl thread. Using too many thread can
|
||||
// increase memory pressure significantly.
|
||||
poolSize = 2;
|
||||
priority = JobSystem::Priority::BACKGROUND;
|
||||
}
|
||||
|
||||
mShaderCompilerThreadCount = poolSize;
|
||||
mCompilerThreadPool.init(mShaderCompilerThreadCount,
|
||||
[&platform = mDriver.mPlatform, priority]() {
|
||||
// give the thread a name
|
||||
JobSystem::setThreadName("CompilerThreadPool");
|
||||
// run at a slightly lower priority than other filament threads
|
||||
JobSystem::setThreadPriority(priority);
|
||||
// create a gl context current to this thread
|
||||
platform.createContext(true);
|
||||
},
|
||||
[&platform = mDriver.mPlatform]() {
|
||||
// release context and thread state
|
||||
platform.releaseContext();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderCompilerService::terminate() noexcept {
|
||||
// Finally stop the thread pool immediately. Pending jobs will be discarded. We guarantee by
|
||||
// construction that nobody is waiting on a token (because waiting is only done on the main
|
||||
// backend thread, and if we're here, we're on the backend main thread).
|
||||
mCompilerThreadPool.terminate();
|
||||
|
||||
mRunAtNextTickOps.clear();
|
||||
|
||||
// We could have some pending callbacks here, we need to execute them.
|
||||
// This is equivalent to calling cancelTickOp() on all active tokens.
|
||||
mCallbackManager.terminate();
|
||||
}
|
||||
|
||||
ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||
utils::CString const& name, Program&& program) {
|
||||
auto& gl = mDriver.getContext();
|
||||
|
||||
auto token = std::make_shared<OpenGLProgramToken>(*this, name);
|
||||
if (UTILS_UNLIKELY(gl.isES2())) {
|
||||
token->attributes = std::move(program.getAttributes());
|
||||
}
|
||||
|
||||
token->gl.program = OpenGLBlobCache::retrieve(&token->key, mDriver.mPlatform, program);
|
||||
if (token->gl.program) {
|
||||
return token;
|
||||
}
|
||||
|
||||
token->handle = mCallbackManager.get();
|
||||
|
||||
CompilerPriorityQueue const priorityQueue = program.getPriorityQueue();
|
||||
if (mShaderCompilerThreadCount) {
|
||||
// queue a compile job
|
||||
mCompilerThreadPool.queue(priorityQueue, token,
|
||||
[this, &gl, program = std::move(program), token]() mutable {
|
||||
// compile the shaders
|
||||
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders{};
|
||||
std::array<utils::CString, Program::SHADER_TYPE_COUNT> shaderSourceCode;
|
||||
compileShaders(gl,
|
||||
std::move(program.getShadersSource()),
|
||||
program.getSpecializationConstants(),
|
||||
shaders,
|
||||
shaderSourceCode);
|
||||
|
||||
// link the program
|
||||
GLuint const glProgram = linkProgram(gl, shaders, token->attributes);
|
||||
|
||||
OpenGLProgramToken::ProgramData programData;
|
||||
programData.shaders = shaders;
|
||||
|
||||
// We need to query the link status here to guarantee that the
|
||||
// program is compiled and linked now (we don't want this to be
|
||||
// deferred to later). We don't care about the result at this point.
|
||||
GLint status;
|
||||
glGetProgramiv(glProgram, GL_LINK_STATUS, &status);
|
||||
programData.program = glProgram;
|
||||
|
||||
token->gl.program = programData.program;
|
||||
|
||||
// we don't need to check for success here, it'll be done on the
|
||||
// main thread side.
|
||||
token->set(programData);
|
||||
|
||||
mCallbackManager.put(token->handle);
|
||||
|
||||
// caching must be the last thing we do
|
||||
if (token->key) {
|
||||
// Attempt to cache. This calls glGetProgramBinary.
|
||||
OpenGLBlobCache::insert(mDriver.mPlatform, token->key, glProgram);
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
// this cannot fail because we check compilation status after linking the program
|
||||
// shaders[] is filled with id of shader stages present.
|
||||
compileShaders(gl,
|
||||
std::move(program.getShadersSource()),
|
||||
program.getSpecializationConstants(),
|
||||
token->gl.shaders,
|
||||
token->shaderSourceCode);
|
||||
|
||||
runAtNextTick(priorityQueue, token, [this, token](Job const&) {
|
||||
if (KHR_parallel_shader_compile) {
|
||||
// don't attempt to link this program if all shaders are not done compiling
|
||||
GLint status;
|
||||
if (token->gl.program) {
|
||||
glGetProgramiv(token->gl.program, GL_COMPLETION_STATUS, &status);
|
||||
if (status == GL_FALSE) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
for (auto shader: token->gl.shaders) {
|
||||
if (shader) {
|
||||
glGetShaderiv(shader, GL_COMPLETION_STATUS, &status);
|
||||
if (status == GL_FALSE) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!token->gl.program) {
|
||||
// link the program, this also cannot fail because status is checked later.
|
||||
token->gl.program = linkProgram(mDriver.getContext(),
|
||||
token->gl.shaders, token->attributes);
|
||||
if (KHR_parallel_shader_compile) {
|
||||
// wait until the link finishes...
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
assert_invariant(token->gl.program);
|
||||
|
||||
mCallbackManager.put(token->handle);
|
||||
|
||||
if (token->key) {
|
||||
// TODO: technically we don't have to cache right now. Is it advantageous to
|
||||
// do this later, maybe depending on CPU usage?
|
||||
// attempt to cache if we don't have a thread pool (otherwise it's done
|
||||
// by the pool).
|
||||
OpenGLBlobCache::insert(mDriver.mPlatform, token->key, token->gl.program);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t& token) {
|
||||
GLuint const program = initialize(token);
|
||||
assert_invariant(token == nullptr);
|
||||
#ifndef FILAMENT_ENABLE_MATDBG
|
||||
assert_invariant(program);
|
||||
#endif
|
||||
return program;
|
||||
}
|
||||
|
||||
/* static*/ void ShaderCompilerService::terminate(program_token_t& token) {
|
||||
assert_invariant(token);
|
||||
|
||||
token->canceled = true;
|
||||
|
||||
token->compiler.cancelTickOp(token);
|
||||
|
||||
for (GLuint& shader: token->gl.shaders) {
|
||||
if (shader) {
|
||||
if (token->gl.program) {
|
||||
glDetachShader(token->gl.program, shader);
|
||||
}
|
||||
glDeleteShader(shader);
|
||||
shader = 0;
|
||||
}
|
||||
}
|
||||
if (token->gl.program) {
|
||||
glDeleteProgram(token->gl.program);
|
||||
}
|
||||
|
||||
token.reset();
|
||||
}
|
||||
|
||||
void ShaderCompilerService::tick() {
|
||||
// we don't need to run executeTickOps() if we're using the thread-pool
|
||||
if (UTILS_UNLIKELY(!mShaderCompilerThreadCount)) {
|
||||
executeTickOps();
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderCompilerService::notifyWhenAllProgramsAreReady(
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||
if (callback) {
|
||||
mCallbackManager.setCallback(handler, callback, user);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
void ShaderCompilerService::getProgramFromCompilerPool(program_token_t& token) noexcept {
|
||||
OpenGLProgramToken::ProgramData const& programData{ token->get() };
|
||||
if (!token->canceled) {
|
||||
token->gl.shaders = programData.shaders;
|
||||
token->gl.program = programData.program;
|
||||
}
|
||||
}
|
||||
|
||||
GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
|
||||
SYSTRACE_CALL();
|
||||
if (!token->gl.program) {
|
||||
if (mShaderCompilerThreadCount) {
|
||||
// we need this program right now, remove it from the queue
|
||||
auto job = mCompilerThreadPool.dequeue(token);
|
||||
if (job) {
|
||||
// if we were able to remove it, we execute the job now, otherwise it means
|
||||
// it's being executed right now.
|
||||
job();
|
||||
}
|
||||
|
||||
if (!token->canceled) {
|
||||
token->compiler.cancelTickOp(token);
|
||||
}
|
||||
|
||||
// Block until we get the program from the pool. Generally this wouldn't block
|
||||
// because we just compiled the program above, when executing job.
|
||||
ShaderCompilerService::getProgramFromCompilerPool(token);
|
||||
} else if (KHR_parallel_shader_compile) {
|
||||
// we force the program link -- which might stall, either here or below in
|
||||
// checkProgramStatus(), but we don't have a choice, we need to use the program now.
|
||||
token->compiler.cancelTickOp(token);
|
||||
|
||||
token->gl.program = linkProgram(mDriver.getContext(),
|
||||
token->gl.shaders, token->attributes);
|
||||
|
||||
assert_invariant(token->gl.program);
|
||||
|
||||
mCallbackManager.put(token->handle);
|
||||
|
||||
if (token->key) {
|
||||
OpenGLBlobCache::insert(mDriver.mPlatform, token->key, token->gl.program);
|
||||
}
|
||||
} else {
|
||||
// if we don't have a program yet, block until we get it.
|
||||
tick();
|
||||
}
|
||||
}
|
||||
|
||||
// by this point we must have a GL program
|
||||
assert_invariant(token->gl.program);
|
||||
|
||||
GLuint program = 0;
|
||||
|
||||
// check status of program linking and shader compilation, logs error and free all resources
|
||||
// in case of error.
|
||||
bool const success = checkProgramStatus(token);
|
||||
if (UTILS_LIKELY(success)) {
|
||||
program = token->gl.program;
|
||||
// no need to keep the shaders around
|
||||
UTILS_NOUNROLL
|
||||
for (GLuint& shader: token->gl.shaders) {
|
||||
if (shader) {
|
||||
glDetachShader(program, shader);
|
||||
glDeleteShader(shader);
|
||||
shader = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// and destroy all temporary init data
|
||||
token = nullptr;
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Compile shaders in the ShaderSource. This cannot fail because compilation failures are not
|
||||
* checked until after the program is linked.
|
||||
* This always returns the GL shader IDs or zero a shader stage is not present.
|
||||
*/
|
||||
void ShaderCompilerService::compileShaders(OpenGLContext& context,
|
||||
Program::ShaderSource shadersSource,
|
||||
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
|
||||
std::array<GLuint, Program::SHADER_TYPE_COUNT>& outShaders,
|
||||
UTILS_UNUSED_IN_RELEASE std::array<CString, Program::SHADER_TYPE_COUNT>& outShaderSourceCode) noexcept {
|
||||
|
||||
SYSTRACE_CALL();
|
||||
|
||||
auto appendSpecConstantString = +[](std::string& s, Program::SpecializationConstant const& sc) {
|
||||
s += "#define SPIRV_CROSS_CONSTANT_ID_" + std::to_string(sc.id) + ' ';
|
||||
s += std::visit([](auto&& arg) { return to_string(arg); }, sc.value);
|
||||
s += '\n';
|
||||
return s;
|
||||
};
|
||||
|
||||
std::string specializationConstantString;
|
||||
for (auto const& sc : specializationConstants) {
|
||||
appendSpecConstantString(specializationConstantString, sc);
|
||||
}
|
||||
if (!specializationConstantString.empty()) {
|
||||
specializationConstantString += '\n';
|
||||
}
|
||||
|
||||
// build all shaders
|
||||
UTILS_NOUNROLL
|
||||
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
|
||||
const ShaderStage stage = static_cast<ShaderStage>(i);
|
||||
GLenum glShaderType{};
|
||||
switch (stage) {
|
||||
case ShaderStage::VERTEX:
|
||||
glShaderType = GL_VERTEX_SHADER;
|
||||
break;
|
||||
case ShaderStage::FRAGMENT:
|
||||
glShaderType = GL_FRAGMENT_SHADER;
|
||||
break;
|
||||
case ShaderStage::COMPUTE:
|
||||
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
|
||||
glShaderType = GL_COMPUTE_SHADER;
|
||||
#else
|
||||
continue;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
if (UTILS_LIKELY(!shadersSource[i].empty())) {
|
||||
Program::ShaderBlob& shader = shadersSource[i];
|
||||
|
||||
// remove GOOGLE_cpp_style_line_directive
|
||||
std::string_view const source = process_GOOGLE_cpp_style_line_directive(context,
|
||||
reinterpret_cast<char*>(shader.data()), shader.size());
|
||||
|
||||
// add support for ARB_shading_language_packing if needed
|
||||
auto const packingFunctions = process_ARB_shading_language_packing(context);
|
||||
|
||||
// split shader source, so we can insert the specification constants and the packing functions
|
||||
auto const [prolog, body] = splitShaderSource(source);
|
||||
|
||||
const std::array<const char*, 4> sources = {
|
||||
prolog.data(),
|
||||
specializationConstantString.c_str(),
|
||||
packingFunctions.data(),
|
||||
body.data()
|
||||
};
|
||||
|
||||
const std::array<GLint, 4> lengths = {
|
||||
(GLint)prolog.length(),
|
||||
(GLint)specializationConstantString.length(),
|
||||
(GLint)packingFunctions.length(),
|
||||
(GLint)body.length() - 1 // null terminated
|
||||
};
|
||||
|
||||
GLuint const shaderId = glCreateShader(glShaderType);
|
||||
glShaderSource(shaderId, sources.size(), sources.data(), lengths.data());
|
||||
glCompileShader(shaderId);
|
||||
|
||||
#ifndef NDEBUG
|
||||
// for debugging we return the original shader source (without the modifications we
|
||||
// made here), otherwise the line numbers wouldn't match.
|
||||
outShaderSourceCode[i] = { source.data(), source.length() };
|
||||
#endif
|
||||
|
||||
outShaders[i] = shaderId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If usages of the Google-style line directive are present, remove them, as some
|
||||
// drivers don't allow the quotation marks. This happens in-place.
|
||||
std::string_view ShaderCompilerService::process_GOOGLE_cpp_style_line_directive(OpenGLContext& context,
|
||||
char* source, size_t len) noexcept {
|
||||
if (!context.ext.GOOGLE_cpp_style_line_directive) {
|
||||
if (UTILS_UNLIKELY(requestsGoogleLineDirectivesExtension({ source, len }))) {
|
||||
removeGoogleLineDirectives(source, len); // length is unaffected
|
||||
}
|
||||
}
|
||||
return { source, len };
|
||||
}
|
||||
|
||||
// Tragically, OpenGL 4.1 doesn't support unpackHalf2x16 (appeared in 4.2) and
|
||||
// macOS doesn't support GL_ARB_shading_language_packing
|
||||
std::string_view ShaderCompilerService::process_ARB_shading_language_packing(OpenGLContext& context) noexcept {
|
||||
using namespace std::literals;
|
||||
#ifdef BACKEND_OPENGL_VERSION_GL
|
||||
if (!context.isAtLeastGL<4, 2>() && !context.ext.ARB_shading_language_packing) {
|
||||
return R"(
|
||||
|
||||
// these don't handle denormals, NaNs or inf
|
||||
float u16tofp32(highp uint v) {
|
||||
v <<= 16u;
|
||||
highp uint s = v & 0x80000000u;
|
||||
highp uint n = v & 0x7FFFFFFFu;
|
||||
highp uint nz = (n == 0u) ? 0u : 0xFFFFFFFFu;
|
||||
return uintBitsToFloat(s | ((((n >> 3u) + (0x70u << 23u))) & nz));
|
||||
}
|
||||
vec2 unpackHalf2x16(highp uint v) {
|
||||
return vec2(u16tofp32(v&0xFFFFu), u16tofp32(v>>16u));
|
||||
}
|
||||
uint fp32tou16(float val) {
|
||||
uint f32 = floatBitsToUint(val);
|
||||
uint f16 = 0u;
|
||||
uint sign = (f32 >> 16u) & 0x8000u;
|
||||
int exponent = int((f32 >> 23u) & 0xFFu) - 127;
|
||||
uint mantissa = f32 & 0x007FFFFFu;
|
||||
if (exponent > 15) {
|
||||
f16 = sign | (0x1Fu << 10u);
|
||||
} else if (exponent > -15) {
|
||||
exponent += 15;
|
||||
mantissa >>= 13;
|
||||
f16 = sign | uint(exponent << 10) | mantissa;
|
||||
} else {
|
||||
f16 = sign;
|
||||
}
|
||||
return f16;
|
||||
}
|
||||
highp uint packHalf2x16(vec2 v) {
|
||||
highp uint x = fp32tou16(v.x);
|
||||
highp uint y = fp32tou16(v.y);
|
||||
return (y << 16u) | x;
|
||||
}
|
||||
)"sv;
|
||||
}
|
||||
#endif // BACKEND_OPENGL_VERSION_GL
|
||||
return ""sv;
|
||||
}
|
||||
|
||||
// split shader source code in two, the first section goes from the start to the line after the
|
||||
// last #extension, and the 2nd part goes from there to the end.
|
||||
std::array<std::string_view, 2> ShaderCompilerService::splitShaderSource(std::string_view source) noexcept {
|
||||
auto start = source.find("#version");
|
||||
assert_invariant(start != std::string_view::npos);
|
||||
|
||||
auto pos = source.rfind("\n#extension");
|
||||
if (pos == std::string_view::npos) {
|
||||
pos = start;
|
||||
} else {
|
||||
++pos;
|
||||
}
|
||||
|
||||
auto eol = source.find('\n', pos) + 1;
|
||||
assert_invariant(eol != std::string_view::npos);
|
||||
|
||||
std::string_view const version = source.substr(start, eol - start);
|
||||
std::string_view const body = source.substr(version.length(), source.length() - version.length());
|
||||
return { version, body };
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a program from the given shader IDs and links it. This cannot fail because errors
|
||||
* are checked later. This always returns a valid GL program ID (which doesn't mean the
|
||||
* program itself is valid).
|
||||
*/
|
||||
GLuint ShaderCompilerService::linkProgram(OpenGLContext& context,
|
||||
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders,
|
||||
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> const& attributes) noexcept {
|
||||
|
||||
SYSTRACE_CALL();
|
||||
|
||||
GLuint const program = glCreateProgram();
|
||||
for (auto shader : shaders) {
|
||||
if (shader) {
|
||||
glAttachShader(program, shader);
|
||||
}
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(context.isES2())) {
|
||||
for (auto const& [ name, loc ] : attributes) {
|
||||
glBindAttribLocation(program, loc, name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
glLinkProgram(program);
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
void ShaderCompilerService::runAtNextTick(CompilerPriorityQueue priority,
|
||||
const program_token_t& token, Job job) noexcept {
|
||||
// insert items in order of priority and at the end of the range
|
||||
auto& ops = mRunAtNextTickOps;
|
||||
auto const pos = std::lower_bound(ops.begin(), ops.end(), priority,
|
||||
[](ContainerType const& lhs, CompilerPriorityQueue priorityQueue) {
|
||||
return std::get<0>(lhs) < priorityQueue;
|
||||
});
|
||||
ops.emplace(pos, priority, token, std::move(job));
|
||||
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_VALUE32("ShaderCompilerService Jobs", mRunAtNextTickOps.size());
|
||||
}
|
||||
|
||||
void ShaderCompilerService::cancelTickOp(program_token_t token) noexcept {
|
||||
// We do a linear search here, but this is rare, and we know the list is pretty small.
|
||||
auto& ops = mRunAtNextTickOps;
|
||||
auto pos = std::find_if(ops.begin(), ops.end(), [&](const auto& item) {
|
||||
return std::get<1>(item) == token;
|
||||
});
|
||||
if (pos != ops.end()) {
|
||||
ops.erase(pos);
|
||||
}
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_VALUE32("ShaderCompilerService Jobs", ops.size());
|
||||
}
|
||||
|
||||
void ShaderCompilerService::executeTickOps() noexcept {
|
||||
auto& ops = mRunAtNextTickOps;
|
||||
auto it = ops.begin();
|
||||
while (it != ops.end()) {
|
||||
Job const& job = std::get<2>(*it);
|
||||
bool const remove = job.fn(job);
|
||||
if (remove) {
|
||||
it = ops.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_VALUE32("ShaderCompilerService Jobs", ops.size());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* Checks a program link status and logs errors and frees resources on failure.
|
||||
* Returns true on success.
|
||||
*/
|
||||
bool ShaderCompilerService::checkProgramStatus(program_token_t const& token) noexcept {
|
||||
|
||||
SYSTRACE_CALL();
|
||||
|
||||
assert_invariant(token->gl.program);
|
||||
|
||||
GLint status;
|
||||
glGetProgramiv(token->gl.program, GL_LINK_STATUS, &status);
|
||||
if (UTILS_LIKELY(status == GL_TRUE)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// only if the link fails, we check the compilation status
|
||||
UTILS_NOUNROLL
|
||||
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
|
||||
const ShaderStage type = static_cast<ShaderStage>(i);
|
||||
const GLuint shader = token->gl.shaders[i];
|
||||
if (shader) {
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
|
||||
if (status != GL_TRUE) {
|
||||
logCompilationError(slog.e, type,
|
||||
token->name.c_str_safe(), shader, token->shaderSourceCode[i]);
|
||||
}
|
||||
glDetachShader(token->gl.program, shader);
|
||||
glDeleteShader(shader);
|
||||
token->gl.shaders[i] = 0;
|
||||
}
|
||||
}
|
||||
// log the link error as well
|
||||
logProgramLinkError(slog.e, token->name.c_str_safe(), token->gl.program);
|
||||
glDeleteProgram(token->gl.program);
|
||||
token->gl.program = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
void logCompilationError(io::ostream& out, ShaderStage shaderType,
|
||||
const char* name, GLuint shaderId,
|
||||
UTILS_UNUSED_IN_RELEASE CString const& sourceCode) noexcept {
|
||||
|
||||
auto to_string = [](ShaderStage type) -> const char* {
|
||||
switch (type) {
|
||||
case ShaderStage::VERTEX: return "vertex";
|
||||
case ShaderStage::FRAGMENT: return "fragment";
|
||||
case ShaderStage::COMPUTE: return "compute";
|
||||
}
|
||||
};
|
||||
|
||||
{ // scope for the temporary string storage
|
||||
GLint length = 0;
|
||||
glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &length);
|
||||
|
||||
CString infoLog(length);
|
||||
glGetShaderInfoLog(shaderId, length, nullptr, infoLog.data());
|
||||
|
||||
out << "Compilation error in " << to_string(shaderType) << " shader \"" << name << "\":\n"
|
||||
<< "\"" << infoLog.c_str() << "\""
|
||||
<< io::endl;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
std::string_view const shader{ sourceCode.data(), sourceCode.size() };
|
||||
size_t lc = 1;
|
||||
size_t start = 0;
|
||||
std::string line;
|
||||
while (true) {
|
||||
size_t const end = shader.find('\n', start);
|
||||
if (end == std::string::npos) {
|
||||
line = shader.substr(start);
|
||||
} else {
|
||||
line = shader.substr(start, end - start);
|
||||
}
|
||||
out << lc++ << ": " << line.c_str() << '\n';
|
||||
if (end == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
start = end + 1;
|
||||
}
|
||||
out << io::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
void logProgramLinkError(io::ostream& out, char const* name, GLuint program) noexcept {
|
||||
GLint length = 0;
|
||||
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);
|
||||
|
||||
CString infoLog(length);
|
||||
glGetProgramInfoLog(program, length, nullptr, infoLog.data());
|
||||
|
||||
out << "Link error in \"" << name << "\":\n"
|
||||
<< "\"" << infoLog.c_str() << "\""
|
||||
<< io::endl;
|
||||
}
|
||||
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* 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_OPENGL_SHADERCOMPILERSERVICE_H
|
||||
#define TNT_FILAMENT_BACKEND_OPENGL_SHADERCOMPILERSERVICE_H
|
||||
|
||||
#include "gl_headers.h"
|
||||
|
||||
#include "CallbackManager.h"
|
||||
#include "CompilerThreadPool.h"
|
||||
|
||||
#include <backend/CallbackHandler.h>
|
||||
#include <backend/Program.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Invocable.h>
|
||||
#include <utils/JobSystem.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class OpenGLDriver;
|
||||
class OpenGLContext;
|
||||
class OpenGLPlatform;
|
||||
class Program;
|
||||
class CallbackHandler;
|
||||
|
||||
/*
|
||||
* A class handling shader compilation that supports asynchronous compilation.
|
||||
*/
|
||||
class ShaderCompilerService {
|
||||
struct OpenGLProgramToken;
|
||||
|
||||
public:
|
||||
using program_token_t = std::shared_ptr<OpenGLProgramToken>;
|
||||
|
||||
explicit ShaderCompilerService(OpenGLDriver& driver);
|
||||
|
||||
ShaderCompilerService(ShaderCompilerService const& rhs) = delete;
|
||||
ShaderCompilerService(ShaderCompilerService&& rhs) = delete;
|
||||
ShaderCompilerService& operator=(ShaderCompilerService const& rhs) = delete;
|
||||
ShaderCompilerService& operator=(ShaderCompilerService&& rhs) = delete;
|
||||
|
||||
~ShaderCompilerService() noexcept;
|
||||
|
||||
bool isParallelShaderCompileSupported() const noexcept;
|
||||
|
||||
void init() noexcept;
|
||||
void terminate() noexcept;
|
||||
|
||||
// creates a program (compile + link) asynchronously if supported
|
||||
program_token_t createProgram(utils::CString const& name, Program&& program);
|
||||
|
||||
// Return the GL program, blocks if necessary. The Token is destroyed and becomes invalid.
|
||||
GLuint getProgram(program_token_t& token);
|
||||
|
||||
// Must be called at least once per frame.
|
||||
void tick();
|
||||
|
||||
// Destroys a valid token and all associated resources. Used to "cancel" a program compilation.
|
||||
static void terminate(program_token_t& token);
|
||||
|
||||
// stores a user data pointer in the token
|
||||
static void setUserData(const program_token_t& token, void* user) noexcept;
|
||||
|
||||
// retrieves the user data pointer stored in the token
|
||||
static void* getUserData(const program_token_t& token) noexcept;
|
||||
|
||||
// call the callback when all active programs are ready
|
||||
void notifyWhenAllProgramsAreReady(
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user);
|
||||
|
||||
private:
|
||||
OpenGLDriver& mDriver;
|
||||
CallbackManager mCallbackManager;
|
||||
CompilerThreadPool mCompilerThreadPool;
|
||||
|
||||
const bool KHR_parallel_shader_compile;
|
||||
uint32_t mShaderCompilerThreadCount = 0u;
|
||||
|
||||
GLuint initialize(ShaderCompilerService::program_token_t& token) noexcept;
|
||||
|
||||
static void getProgramFromCompilerPool(program_token_t& token) noexcept;
|
||||
|
||||
static void compileShaders(
|
||||
OpenGLContext& context,
|
||||
Program::ShaderSource shadersSource,
|
||||
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
|
||||
std::array<GLuint, Program::SHADER_TYPE_COUNT>& outShaders,
|
||||
std::array<utils::CString, Program::SHADER_TYPE_COUNT>& outShaderSourceCode) noexcept;
|
||||
|
||||
static std::string_view process_GOOGLE_cpp_style_line_directive(OpenGLContext& context,
|
||||
char* source, size_t len) noexcept;
|
||||
|
||||
static std::string_view process_ARB_shading_language_packing(OpenGLContext& context) noexcept;
|
||||
|
||||
static std::array<std::string_view, 2> splitShaderSource(std::string_view source) noexcept;
|
||||
|
||||
static GLuint linkProgram(OpenGLContext& context,
|
||||
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders,
|
||||
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> const& attributes) noexcept;
|
||||
|
||||
static bool checkProgramStatus(program_token_t const& token) noexcept;
|
||||
|
||||
struct Job {
|
||||
template<typename FUNC>
|
||||
Job(FUNC&& fn) : fn(std::forward<FUNC>(fn)) {}
|
||||
Job(std::function<bool(Job const& job)> fn,
|
||||
CallbackHandler* handler, void* user, CallbackHandler::Callback callback)
|
||||
: fn(std::move(fn)), handler(handler), user(user), callback(callback) {
|
||||
}
|
||||
std::function<bool(Job const& job)> fn;
|
||||
CallbackHandler* handler = nullptr;
|
||||
void* user = nullptr;
|
||||
CallbackHandler::Callback callback{};
|
||||
};
|
||||
|
||||
void runAtNextTick(CompilerPriorityQueue priority,
|
||||
const program_token_t& token, Job job) noexcept;
|
||||
void executeTickOps() noexcept;
|
||||
void cancelTickOp(program_token_t token) noexcept;
|
||||
// order of insertion is important
|
||||
|
||||
using ContainerType = std::tuple<CompilerPriorityQueue, program_token_t, Job>;
|
||||
std::vector<ContainerType> mRunAtNextTickOps;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_OPENGL_SHADERCOMPILERSERVICE_H
|
||||
@@ -174,6 +174,12 @@ using namespace glext;
|
||||
# define GL_ZERO_TO_ONE GL_ZERO_TO_ONE_EXT
|
||||
#endif
|
||||
|
||||
#ifdef GL_KHR_parallel_shader_compile
|
||||
# define GL_COMPLETION_STATUS GL_COMPLETION_STATUS_KHR
|
||||
#else
|
||||
# define GL_COMPLETION_STATUS 0x91B1
|
||||
#endif
|
||||
|
||||
// we need GL_TEXTURE_CUBE_MAP_ARRAY defined, but we won't use it if the extension/feature
|
||||
// is not available.
|
||||
#if defined(GL_EXT_texture_cube_map_array)
|
||||
@@ -182,6 +188,12 @@ using namespace glext;
|
||||
# define GL_TEXTURE_CUBE_MAP_ARRAY 0x9009
|
||||
#endif
|
||||
|
||||
#if defined(GL_EXT_clip_cull_distance)
|
||||
# define GL_CLIP_DISTANCE0 GL_CLIP_DISTANCE0_EXT
|
||||
#else
|
||||
# define GL_CLIP_DISTANCE0 0x3000
|
||||
#endif
|
||||
|
||||
#if defined(GL_KHR_debug)
|
||||
# define GL_DEBUG_OUTPUT GL_DEBUG_OUTPUT_KHR
|
||||
# define GL_DEBUG_OUTPUT_SYNCHRONOUS GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR
|
||||
@@ -223,6 +235,14 @@ using namespace glext;
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#else // All version OpenGL below
|
||||
|
||||
#ifdef GL_ARB_parallel_shader_compile
|
||||
# define GL_COMPLETION_STATUS GL_COMPLETION_STATUS_ARB
|
||||
#else
|
||||
# define GL_COMPLETION_STATUS 0x91B1
|
||||
#endif
|
||||
|
||||
#endif // GL_ES_VERSION_2_0
|
||||
|
||||
// This is just to simplify the implementation (i.e. so we don't have to have #ifdefs everywhere)
|
||||
|
||||
@@ -50,6 +50,7 @@ struct PlatformCocoaGLImpl {
|
||||
NSOpenGLContext* mGLContext = nullptr;
|
||||
CocoaGLSwapChain* mCurrentSwapChain = nullptr;
|
||||
std::vector<NSView*> mHeadlessSwapChains;
|
||||
std::vector<NSOpenGLContext*> mAdditionalContexts;
|
||||
CVOpenGLTextureCacheRef mTextureCache = nullptr;
|
||||
std::unique_ptr<CocoaExternalImage::SharedGl> mExternalImageSharedGl;
|
||||
void updateOpenGLContext(NSView *nsView, bool resetView, bool clearView);
|
||||
@@ -171,6 +172,26 @@ Driver* PlatformCocoaGL::createDriver(void* sharedContext, const Platform::Drive
|
||||
return OpenGLPlatform::createDefaultDriver(this, sharedContext, driverConfig);
|
||||
}
|
||||
|
||||
bool PlatformCocoaGL::isExtraContextSupported() const noexcept {
|
||||
// macOS supports shared contexts however, it looks like the implementation uses a global
|
||||
// lock around all GL APIs. It's a problem for API calls that take a long time to execute,
|
||||
// one such call is e.g.: glCompileProgram.
|
||||
return true;
|
||||
}
|
||||
|
||||
void PlatformCocoaGL::createContext(bool shared) {
|
||||
NSOpenGLPixelFormatAttribute pixelFormatAttributes[] = {
|
||||
NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
|
||||
NSOpenGLPFAAccelerated, (NSOpenGLPixelFormatAttribute) true,
|
||||
0, 0,
|
||||
};
|
||||
NSOpenGLContext* const sharedContext = shared ? pImpl->mGLContext : nil;
|
||||
NSOpenGLPixelFormat* const pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:pixelFormatAttributes];
|
||||
NSOpenGLContext* const nsOpenGLContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:sharedContext];
|
||||
[nsOpenGLContext makeCurrentContext];
|
||||
pImpl->mAdditionalContexts.push_back(nsOpenGLContext);
|
||||
}
|
||||
|
||||
int PlatformCocoaGL::getOSVersion() const noexcept {
|
||||
return 0;
|
||||
}
|
||||
@@ -179,6 +200,9 @@ void PlatformCocoaGL::terminate() noexcept {
|
||||
CFRelease(pImpl->mTextureCache);
|
||||
pImpl->mExternalImageSharedGl.reset();
|
||||
pImpl->mGLContext = nil;
|
||||
for (auto& context : pImpl->mAdditionalContexts) {
|
||||
context = nil;
|
||||
}
|
||||
bluegl::unbind();
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ struct PlatformCocoaTouchGLImpl {
|
||||
EAGLContext* mGLContext = nullptr;
|
||||
CAEAGLLayer* mCurrentGlLayer = nullptr;
|
||||
std::vector<CAEAGLLayer*> mHeadlessGlLayers;
|
||||
std::vector<EAGLContext*> mAdditionalContexts;
|
||||
CGRect mCurrentGlLayerRect;
|
||||
GLuint mDefaultFramebuffer = 0;
|
||||
GLuint mDefaultColorbuffer = 0;
|
||||
@@ -93,6 +94,20 @@ Driver* PlatformCocoaTouchGL::createDriver(void* const sharedGLContext, const Pl
|
||||
return OpenGLPlatform::createDefaultDriver(this, sharedGLContext, driverConfig);
|
||||
}
|
||||
|
||||
bool PlatformCocoaTouchGL::isExtraContextSupported() const noexcept {
|
||||
return true;
|
||||
}
|
||||
|
||||
void PlatformCocoaTouchGL::createContext(bool shared) {
|
||||
EAGLSharegroup* const sharegroup = shared ? pImpl->mGLContext.sharegroup : nil;
|
||||
EAGLContext* const context = [[EAGLContext alloc]
|
||||
initWithAPI:kEAGLRenderingAPIOpenGLES3
|
||||
sharegroup:sharegroup];
|
||||
ASSERT_POSTCONDITION(context, "Unable to create extra OpenGL ES context.");
|
||||
[EAGLContext setCurrentContext:context];
|
||||
pImpl->mAdditionalContexts.push_back(context);
|
||||
}
|
||||
|
||||
void PlatformCocoaTouchGL::terminate() noexcept {
|
||||
CFRelease(pImpl->mTextureCache);
|
||||
pImpl->mGLContext = nil;
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#ifndef EGL_CONTEXT_OPENGL_BACKWARDS_COMPATIBLE_ANGLE
|
||||
# define EGL_CONTEXT_OPENGL_BACKWARDS_COMPATIBLE_ANGLE 0x3483
|
||||
@@ -114,9 +115,14 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon
|
||||
|
||||
auto extensions = GLUtils::split(eglQueryString(mEGLDisplay, EGL_EXTENSIONS));
|
||||
ext.egl.ANDROID_recordable = extensions.has("EGL_ANDROID_recordable");
|
||||
ext.egl.KHR_create_context = extensions.has("EGL_KHR_create_context");
|
||||
ext.egl.KHR_gl_colorspace = extensions.has("EGL_KHR_gl_colorspace");
|
||||
ext.egl.KHR_create_context = extensions.has("EGL_KHR_create_context");
|
||||
ext.egl.KHR_no_config_context = extensions.has("EGL_KHR_no_config_context");
|
||||
ext.egl.KHR_surfaceless_context = extensions.has("KHR_surfaceless_context");
|
||||
if (ext.egl.KHR_create_context) {
|
||||
// KHR_create_context implies KHR_surfaceless_context for ES3.x contexts
|
||||
ext.egl.KHR_surfaceless_context = true;
|
||||
}
|
||||
|
||||
eglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC) eglGetProcAddress("eglCreateSyncKHR");
|
||||
eglDestroySyncKHR = (PFNEGLDESTROYSYNCKHRPROC) eglGetProcAddress("eglDestroySyncKHR");
|
||||
@@ -180,13 +186,6 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon
|
||||
eglConfig = mEGLConfig;
|
||||
}
|
||||
|
||||
// create the dummy surface, just for being able to make the context current.
|
||||
mEGLDummySurface = eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, pbufferAttribs);
|
||||
if (UTILS_UNLIKELY(mEGLDummySurface == EGL_NO_SURFACE)) {
|
||||
logEglError("eglCreatePbufferSurface");
|
||||
goto error;
|
||||
}
|
||||
|
||||
for (size_t tries = 0; tries < 3; tries++) {
|
||||
mEGLContext = eglCreateContext(mEGLDisplay, eglConfig,
|
||||
(EGLContext)sharedContext, contextAttribs.data());
|
||||
@@ -219,12 +218,34 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (ext.egl.KHR_surfaceless_context) {
|
||||
// Adreno 306 driver advertises KHR_create_context but doesn't support passing
|
||||
// EGL_NO_SURFACE to eglMakeCurrent with a 3.0 context.
|
||||
if (UTILS_UNLIKELY(!eglMakeCurrent(mEGLDisplay,
|
||||
EGL_NO_SURFACE, EGL_NO_SURFACE, mEGLContext))) {
|
||||
if (eglGetError() == EGL_BAD_MATCH) {
|
||||
ext.egl.KHR_surfaceless_context = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(!ext.egl.KHR_surfaceless_context)) {
|
||||
// create the dummy surface, just for being able to make the context current.
|
||||
mEGLDummySurface = eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, pbufferAttribs);
|
||||
if (UTILS_UNLIKELY(mEGLDummySurface == EGL_NO_SURFACE)) {
|
||||
logEglError("eglCreatePbufferSurface");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(!makeCurrent(mEGLDummySurface, mEGLDummySurface))) {
|
||||
// eglMakeCurrent failed
|
||||
logEglError("eglMakeCurrent");
|
||||
goto error;
|
||||
}
|
||||
|
||||
mContextAttribs = std::move(contextAttribs);
|
||||
|
||||
initializeGlExtensions();
|
||||
|
||||
// this is needed with older emulators/API levels on Android
|
||||
@@ -251,6 +272,44 @@ error:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool PlatformEGL::isExtraContextSupported() const noexcept {
|
||||
return ext.egl.KHR_surfaceless_context;
|
||||
}
|
||||
|
||||
void PlatformEGL::createContext(bool shared) {
|
||||
EGLConfig config = ext.egl.KHR_no_config_context ? EGL_NO_CONFIG_KHR : mEGLConfig;
|
||||
|
||||
EGLContext context = eglCreateContext(mEGLDisplay, config,
|
||||
shared ? mEGLContext : EGL_NO_CONTEXT, mContextAttribs.data());
|
||||
|
||||
if (UTILS_UNLIKELY(context == EGL_NO_CONTEXT)) {
|
||||
// eglCreateContext failed
|
||||
logEglError("eglCreateContext");
|
||||
}
|
||||
|
||||
assert_invariant(context != EGL_NO_CONTEXT);
|
||||
|
||||
eglMakeCurrent(mEGLDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, context);
|
||||
|
||||
mAdditionalContexts.push_back(context);
|
||||
}
|
||||
|
||||
void PlatformEGL::releaseContext() noexcept {
|
||||
EGLContext context = eglGetCurrentContext();
|
||||
eglMakeCurrent(mEGLDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
if (context != EGL_NO_CONTEXT) {
|
||||
eglDestroyContext(mEGLDisplay, context);
|
||||
}
|
||||
|
||||
mAdditionalContexts.erase(
|
||||
std::remove_if(mAdditionalContexts.begin(), mAdditionalContexts.end(),
|
||||
[context](EGLContext c) {
|
||||
return c == context;
|
||||
}), mAdditionalContexts.end());
|
||||
|
||||
eglReleaseThread();
|
||||
}
|
||||
|
||||
EGLBoolean PlatformEGL::makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) noexcept {
|
||||
if (UTILS_UNLIKELY((drawSurface != mCurrentDrawSurface || readSurface != mCurrentReadSurface))) {
|
||||
mCurrentDrawSurface = drawSurface;
|
||||
@@ -261,9 +320,15 @@ EGLBoolean PlatformEGL::makeCurrent(EGLSurface drawSurface, EGLSurface readSurfa
|
||||
}
|
||||
|
||||
void PlatformEGL::terminate() noexcept {
|
||||
// it's always allowed to use EGL_NO_SURFACE, EGL_NO_CONTEXT
|
||||
eglMakeCurrent(mEGLDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
eglDestroySurface(mEGLDisplay, mEGLDummySurface);
|
||||
if (mEGLDummySurface) {
|
||||
eglDestroySurface(mEGLDisplay, mEGLDummySurface);
|
||||
}
|
||||
eglDestroyContext(mEGLDisplay, mEGLContext);
|
||||
for (auto context : mAdditionalContexts) {
|
||||
eglDestroyContext(mEGLDisplay, context);
|
||||
}
|
||||
eglTerminate(mEGLDisplay);
|
||||
eglReleaseThread();
|
||||
}
|
||||
@@ -441,7 +506,7 @@ FenceStatus PlatformEGL::waitFence(
|
||||
#ifdef EGL_KHR_reusable_sync
|
||||
EGLSyncKHR sync = (EGLSyncKHR) fence;
|
||||
if (sync != EGL_NO_SYNC_KHR) {
|
||||
EGLint status = eglClientWaitSyncKHR(mEGLDisplay, sync, 0, (EGLTimeKHR)timeout);
|
||||
EGLint const status = eglClientWaitSyncKHR(mEGLDisplay, sync, 0, (EGLTimeKHR)timeout);
|
||||
if (status == EGL_CONDITION_SATISFIED_KHR) {
|
||||
return FenceStatus::CONDITION_SATISFIED;
|
||||
}
|
||||
|
||||
@@ -74,10 +74,11 @@ struct WGLSwapChain {
|
||||
bool isHeadless = false;
|
||||
};
|
||||
|
||||
static PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribs = nullptr;
|
||||
|
||||
Driver* PlatformWGL::createDriver(void* const sharedGLContext,
|
||||
const Platform::DriverConfig& driverConfig) noexcept {
|
||||
int result = 0;
|
||||
PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribs = nullptr;
|
||||
int pixelFormat = 0;
|
||||
|
||||
mPfd = {
|
||||
@@ -123,13 +124,15 @@ Driver* PlatformWGL::createDriver(void* const sharedGLContext,
|
||||
(PFNWGLCREATECONTEXTATTRIBSARBPROC) wglGetProcAddress("wglCreateContextAttribsARB");
|
||||
|
||||
// try all versions down, from GL 4.5 to 4.1
|
||||
|
||||
|
||||
for (int minor = 5; minor >= 1; minor--) {
|
||||
int const attribs[] = {
|
||||
mAttribs = {
|
||||
WGL_CONTEXT_MAJOR_VERSION_ARB, 4,
|
||||
WGL_CONTEXT_MINOR_VERSION_ARB, minor,
|
||||
0
|
||||
};
|
||||
mContext = wglCreateContextAttribs(whdc, (HGLRC)sharedGLContext, attribs);
|
||||
mContext = wglCreateContextAttribs(whdc, (HGLRC)sharedGLContext, mAttribs.data());
|
||||
if (mContext) {
|
||||
break;
|
||||
}
|
||||
@@ -164,12 +167,25 @@ error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool PlatformWGL::isExtraContextSupported() const noexcept {
|
||||
return true;
|
||||
}
|
||||
|
||||
void PlatformWGL::createContext(bool shared) {
|
||||
HGLRC context = wglCreateContextAttribs(mWhdc, shared ? mContext : nullptr, mAttribs.data());
|
||||
wglMakeCurrent(mWhdc, context);
|
||||
mAdditionalContexts.push_back(context);
|
||||
}
|
||||
|
||||
void PlatformWGL::terminate() noexcept {
|
||||
wglMakeCurrent(NULL, NULL);
|
||||
if (mContext) {
|
||||
wglDeleteContext(mContext);
|
||||
mContext = NULL;
|
||||
}
|
||||
for (auto& context : mAdditionalContexts) {
|
||||
wglDeleteContext(mContext);
|
||||
}
|
||||
if (mHWnd && mWhdc) {
|
||||
ReleaseDC(mHWnd, mWhdc);
|
||||
DestroyWindow(mHWnd);
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
|
||||
#include "VulkanBlitter.h"
|
||||
#include "VulkanContext.h"
|
||||
#include "VulkanFboCache.h"
|
||||
#include "VulkanHandles.h"
|
||||
#include "VulkanSamplerCache.h"
|
||||
#include "VulkanTexture.h"
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Panic.h>
|
||||
@@ -116,8 +119,7 @@ VulkanBlitter::VulkanBlitter(VulkanStagePool& stagePool, VulkanPipelineCache& pi
|
||||
mSamplerCache(samplerCache) {}
|
||||
|
||||
void VulkanBlitter::initialize(VkPhysicalDevice physicalDevice, VkDevice device,
|
||||
VmaAllocator allocator, std::shared_ptr<VulkanCommands> commands,
|
||||
std::shared_ptr<VulkanTexture> emptyTexture) noexcept {
|
||||
VmaAllocator allocator, VulkanCommands* commands, VulkanTexture* emptyTexture) noexcept {
|
||||
mPhysicalDevice = physicalDevice;
|
||||
mDevice = device;
|
||||
mAllocator = allocator;
|
||||
@@ -135,16 +137,20 @@ void VulkanBlitter::blitColor(BlitArgs args) {
|
||||
VkFormatProperties info;
|
||||
vkGetPhysicalDeviceFormatProperties(gpu, src.getFormat(), &info);
|
||||
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT,
|
||||
"Source format is not blittable")) {
|
||||
"Source format is not blittable %d", src.getFormat())) {
|
||||
return;
|
||||
}
|
||||
vkGetPhysicalDeviceFormatProperties(gpu, dst.getFormat(), &info);
|
||||
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT,
|
||||
"Destination format is not blittable")) {
|
||||
"Destination format is not blittable %d", dst.getFormat())) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
VkCommandBuffer const cmdbuffer = mCommands->get().cmdbuffer;
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer const cmdbuffer = commands.cmdbuffer;
|
||||
commands.acquire(src.texture);
|
||||
commands.acquire(dst.texture);
|
||||
|
||||
blitFast(cmdbuffer, aspect, args.filter, args.srcTarget->getExtent(), src, dst,
|
||||
args.srcRectPair, args.dstRectPair);
|
||||
}
|
||||
@@ -159,12 +165,12 @@ void VulkanBlitter::blitDepth(BlitArgs args) {
|
||||
VkFormatProperties info;
|
||||
vkGetPhysicalDeviceFormatProperties(gpu, src.getFormat(), &info);
|
||||
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT,
|
||||
"Depth format is not blittable")) {
|
||||
"Depth src format is not blittable %d", src.getFormat())) {
|
||||
return;
|
||||
}
|
||||
vkGetPhysicalDeviceFormatProperties(gpu, dst.getFormat(), &info);
|
||||
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT,
|
||||
"Depth format is not blittable")) {
|
||||
"Depth dst format is not blittable %d", dst.getFormat())) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
@@ -172,16 +178,20 @@ void VulkanBlitter::blitDepth(BlitArgs args) {
|
||||
assert_invariant(src.texture && dst.texture);
|
||||
|
||||
if (src.texture->samples > 1 && dst.texture->samples == 1) {
|
||||
blitSlowDepth(aspect, args.filter, args.srcTarget->getExtent(), src, dst, args.srcRectPair,
|
||||
blitSlowDepth(args.filter, args.srcTarget->getExtent(), src, dst, args.srcRectPair,
|
||||
args.dstRectPair);
|
||||
return;
|
||||
}
|
||||
VkCommandBuffer const cmdbuffer = mCommands->get().cmdbuffer;
|
||||
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer const cmdbuffer = commands.cmdbuffer;
|
||||
commands.acquire(src.texture);
|
||||
commands.acquire(dst.texture);
|
||||
blitFast(cmdbuffer, aspect, args.filter, args.srcTarget->getExtent(), src, dst, args.srcRectPair,
|
||||
args.dstRectPair);
|
||||
}
|
||||
|
||||
void VulkanBlitter::shutdown() noexcept {
|
||||
void VulkanBlitter::terminate() noexcept {
|
||||
if (mDevice) {
|
||||
delete mDepthResolveProgram;
|
||||
mDepthResolveProgram = nullptr;
|
||||
@@ -198,9 +208,6 @@ void VulkanBlitter::shutdown() noexcept {
|
||||
mParamsBuffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
mCommands.reset();
|
||||
mEmptyTexture.reset();
|
||||
}
|
||||
|
||||
// If we created these shader modules in the constructor, the device might not be ready yet.
|
||||
@@ -249,13 +256,16 @@ void VulkanBlitter::lazyInit() noexcept {
|
||||
+1.0f, +1.0f,
|
||||
};
|
||||
|
||||
mTriangleBuffer = new VulkanBuffer(mAllocator, mCommands, mStagePool,
|
||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, sizeof(kTriangleVertices));
|
||||
VulkanCommandBuffer& commands = mCommands->get();
|
||||
VkCommandBuffer const cmdbuffer = commands.cmdbuffer;
|
||||
|
||||
mTriangleBuffer->loadFromCpu(kTriangleVertices, 0, sizeof(kTriangleVertices));
|
||||
mTriangleBuffer = new VulkanBuffer(mAllocator, mStagePool, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
|
||||
sizeof(kTriangleVertices));
|
||||
|
||||
mParamsBuffer = new VulkanBuffer(mAllocator, mCommands, mStagePool,
|
||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, sizeof(BlitterUniforms));
|
||||
mTriangleBuffer->loadFromCpu(cmdbuffer, kTriangleVertices, 0, sizeof(kTriangleVertices));
|
||||
|
||||
mParamsBuffer = new VulkanBuffer(mAllocator, mStagePool, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
|
||||
sizeof(BlitterUniforms));
|
||||
}
|
||||
|
||||
// At a high level, the procedure for resolving depth looks like this:
|
||||
@@ -263,27 +273,35 @@ void VulkanBlitter::lazyInit() noexcept {
|
||||
// 2. Begin render pass
|
||||
// 3. Draw a big triangle
|
||||
// 4. End render pass.
|
||||
void VulkanBlitter::blitSlowDepth(VkImageAspectFlags aspect, VkFilter filter,
|
||||
const VkExtent2D srcExtent, VulkanAttachment src, VulkanAttachment dst,
|
||||
const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]) {
|
||||
void VulkanBlitter::blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, VulkanAttachment src,
|
||||
VulkanAttachment dst, const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]) {
|
||||
lazyInit();
|
||||
|
||||
const BlitterUniforms uniforms = {
|
||||
.sampleCount = src.texture->samples,
|
||||
.inverseSampleCount = 1.0f / float(src.texture->samples),
|
||||
VulkanCommandBuffer* commands = &mCommands->get();
|
||||
VkCommandBuffer const cmdbuffer = commands->cmdbuffer;
|
||||
commands->acquire(src.texture);
|
||||
commands->acquire(dst.texture);
|
||||
|
||||
BlitterUniforms const uniforms = {
|
||||
.sampleCount = src.texture->samples,
|
||||
.inverseSampleCount = 1.0f / float(src.texture->samples),
|
||||
};
|
||||
mParamsBuffer->loadFromCpu(&uniforms, 0, sizeof(uniforms));
|
||||
mParamsBuffer->loadFromCpu(cmdbuffer, &uniforms, 0, sizeof(uniforms));
|
||||
|
||||
VkImageAspectFlags const aspect = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
|
||||
// We will transition the src into sampler layout and also keep it in sampler layout for
|
||||
// consistency.
|
||||
VulkanLayout const samplerLayout = VulkanLayout::DEPTH_SAMPLER;
|
||||
|
||||
// BEGIN RENDER PASS
|
||||
// -----------------
|
||||
|
||||
VulkanLayout const layout = VulkanLayout::DEPTH_ATTACHMENT;
|
||||
|
||||
const VulkanFboCache::RenderPassKey rpkey = {
|
||||
.initialColorLayoutMask = 0,
|
||||
.initialDepthLayout = VulkanLayout::UNDEFINED,
|
||||
.renderPassDepthLayout = layout,
|
||||
.finalDepthLayout = layout,
|
||||
.renderPassDepthLayout = samplerLayout,
|
||||
.finalDepthLayout = samplerLayout,
|
||||
.depthFormat = dst.getFormat(),
|
||||
.clear = {},
|
||||
.discardStart = TargetBufferFlags::DEPTH,
|
||||
@@ -301,7 +319,7 @@ void VulkanBlitter::blitSlowDepth(VkImageAspectFlags aspect, VkFilter filter,
|
||||
.samples = rpkey.samples,
|
||||
.color = {},
|
||||
.resolve = {},
|
||||
.depth = dst.getImageView(VK_IMAGE_ASPECT_DEPTH_BIT),
|
||||
.depth = dst.getImageView(aspect),
|
||||
};
|
||||
const VkFramebuffer vkfb = mFramebufferCache.getFramebuffer(fbkey);
|
||||
|
||||
@@ -315,12 +333,16 @@ void VulkanBlitter::blitSlowDepth(VkImageAspectFlags aspect, VkFilter filter,
|
||||
renderPassInfo.renderArea.extent.width = dstRect[1].x - dstRect[0].x;
|
||||
renderPassInfo.renderArea.extent.height = dstRect[1].y - dstRect[0].y;
|
||||
|
||||
// Even though we don't clear anything, we have to provide a clear value.
|
||||
VkClearValue clearValues[1] = {};
|
||||
renderPassInfo.clearValueCount = 1;
|
||||
renderPassInfo.pClearValues = clearValues;
|
||||
// We need to transition the source into a sampler since it'll be sampled in the shader.
|
||||
const VkImageSubresourceRange srcRange = {
|
||||
.aspectMask = aspect,
|
||||
.baseMipLevel = src.level,
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = src.layer,
|
||||
.layerCount = 1,
|
||||
};
|
||||
src.texture->transitionLayout(cmdbuffer, srcRange, samplerLayout);
|
||||
|
||||
const VkCommandBuffer cmdbuffer = mCommands->get().cmdbuffer;
|
||||
vkCmdBeginRenderPass(cmdbuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
VkViewport viewport = {
|
||||
@@ -371,6 +393,7 @@ void VulkanBlitter::blitSlowDepth(VkImageAspectFlags aspect, VkFilter filter,
|
||||
VkSampler vksampler = mSamplerCache.getSampler({});
|
||||
|
||||
VkDescriptorImageInfo samplers[VulkanPipelineCache::SAMPLER_BINDING_COUNT];
|
||||
VulkanTexture* textures[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {nullptr};
|
||||
for (auto& sampler : samplers) {
|
||||
sampler = {
|
||||
.sampler = vksampler,
|
||||
@@ -382,10 +405,11 @@ void VulkanBlitter::blitSlowDepth(VkImageAspectFlags aspect, VkFilter filter,
|
||||
samplers[0] = {
|
||||
.sampler = vksampler,
|
||||
.imageView = src.getImageView(VK_IMAGE_ASPECT_DEPTH_BIT),
|
||||
.imageLayout = ImgUtil::getVkLayout(VulkanLayout::DEPTH_ATTACHMENT),
|
||||
.imageLayout = ImgUtil::getVkLayout(samplerLayout),
|
||||
};
|
||||
textures[0] = src.texture;
|
||||
|
||||
mPipelineCache.bindSamplers(samplers,
|
||||
mPipelineCache.bindSamplers(samplers, textures,
|
||||
VulkanPipelineCache::getUsageFlags(0, ShaderStageFlags::FRAGMENT));
|
||||
|
||||
auto previousUbo = mPipelineCache.getUniformBufferBinding(0);
|
||||
@@ -402,7 +426,7 @@ void VulkanBlitter::blitSlowDepth(VkImageAspectFlags aspect, VkFilter filter,
|
||||
|
||||
mPipelineCache.bindScissor(cmdbuffer, scissor);
|
||||
|
||||
if (!mPipelineCache.bindPipeline(cmdbuffer)) {
|
||||
if (!mPipelineCache.bindPipeline(commands)) {
|
||||
assert_invariant(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,8 +36,7 @@ public:
|
||||
VulkanFboCache& fboCache, VulkanSamplerCache& samplerCache) noexcept;
|
||||
|
||||
void initialize(VkPhysicalDevice physicalDevice, VkDevice device, VmaAllocator allocator,
|
||||
std::shared_ptr<VulkanCommands> commands,
|
||||
std::shared_ptr<VulkanTexture> emptyTexture) noexcept;
|
||||
VulkanCommands* commands, VulkanTexture* emptyTexture) noexcept;
|
||||
|
||||
struct BlitArgs {
|
||||
const VulkanRenderTarget* dstTarget;
|
||||
@@ -51,14 +50,13 @@ public:
|
||||
void blitColor(BlitArgs args);
|
||||
void blitDepth(BlitArgs args);
|
||||
|
||||
void shutdown() noexcept;
|
||||
void terminate() noexcept;
|
||||
|
||||
private:
|
||||
void lazyInit() noexcept;
|
||||
|
||||
void blitSlowDepth(VkImageAspectFlags aspect, VkFilter filter, const VkExtent2D srcExtent,
|
||||
VulkanAttachment src, VulkanAttachment dst, const VkOffset3D srcRect[2],
|
||||
const VkOffset3D dstRect[2]);
|
||||
void blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, VulkanAttachment src,
|
||||
VulkanAttachment dst, const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]);
|
||||
|
||||
VulkanBuffer* mTriangleBuffer = nullptr;
|
||||
VulkanBuffer* mParamsBuffer = nullptr;
|
||||
@@ -67,8 +65,8 @@ private:
|
||||
UTILS_UNUSED VkPhysicalDevice mPhysicalDevice;
|
||||
VkDevice mDevice;
|
||||
VmaAllocator mAllocator;
|
||||
std::shared_ptr<VulkanCommands> mCommands;
|
||||
std::shared_ptr<VulkanTexture> mEmptyTexture;
|
||||
VulkanCommands* mCommands;
|
||||
VulkanTexture* mEmptyTexture;
|
||||
|
||||
VulkanStagePool& mStagePool;
|
||||
VulkanPipelineCache& mPipelineCache;
|
||||
|
||||
@@ -23,9 +23,11 @@ using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
VulkanBuffer::VulkanBuffer(VmaAllocator allocator, std::shared_ptr<VulkanCommands> commands,
|
||||
VulkanStagePool& stagePool, VkBufferUsageFlags usage, uint32_t numBytes)
|
||||
: mAllocator(allocator), mCommands(commands), mStagePool(stagePool), mUsage(usage) {
|
||||
VulkanBuffer::VulkanBuffer(VmaAllocator allocator, VulkanStagePool& stagePool,
|
||||
VkBufferUsageFlags usage, uint32_t numBytes)
|
||||
: mAllocator(allocator),
|
||||
mStagePool(stagePool),
|
||||
mUsage(usage) {
|
||||
|
||||
// for now make sure that only 1 bit is set in usage
|
||||
// (because loadFromCpu() assumes that somewhat)
|
||||
@@ -51,10 +53,10 @@ void VulkanBuffer::terminate() {
|
||||
vmaDestroyBuffer(mAllocator, mGpuBuffer, mGpuMemory);
|
||||
mGpuMemory = VK_NULL_HANDLE;
|
||||
mGpuBuffer = VK_NULL_HANDLE;
|
||||
mCommands.reset();
|
||||
}
|
||||
|
||||
void VulkanBuffer::loadFromCpu(const void* cpuData, uint32_t byteOffset, uint32_t numBytes) const {
|
||||
void VulkanBuffer::loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint32_t byteOffset,
|
||||
uint32_t numBytes) const {
|
||||
assert_invariant(byteOffset == 0);
|
||||
VulkanStage const* stage = mStagePool.acquireStage(numBytes);
|
||||
void* mapped;
|
||||
@@ -63,10 +65,8 @@ void VulkanBuffer::loadFromCpu(const void* cpuData, uint32_t byteOffset, uint32_
|
||||
vmaUnmapMemory(mAllocator, stage->memory);
|
||||
vmaFlushAllocation(mAllocator, stage->memory, byteOffset, numBytes);
|
||||
|
||||
const VkCommandBuffer cmdbuffer = mCommands->get().cmdbuffer;
|
||||
|
||||
VkBufferCopy region{ .size = numBytes };
|
||||
vkCmdCopyBuffer(cmdbuffer, stage->buffer, mGpuBuffer, 1, ®ion);
|
||||
vkCmdCopyBuffer(cmdbuf, stage->buffer, mGpuBuffer, 1, ®ion);
|
||||
|
||||
// Firstly, ensure that the copy finishes before the next draw call.
|
||||
// Secondly, in case the user decides to upload another chunk (without ever using the first one)
|
||||
@@ -100,7 +100,7 @@ void VulkanBuffer::loadFromCpu(const void* cpuData, uint32_t byteOffset, uint32_
|
||||
.size = VK_WHOLE_SIZE,
|
||||
};
|
||||
|
||||
vkCmdPipelineBarrier(cmdbuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, dstStageMask, 0, 0, nullptr, 1,
|
||||
vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_TRANSFER_BIT, dstStageMask, 0, 0, nullptr, 1,
|
||||
&barrier, 0, nullptr);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,15 +25,18 @@ namespace filament::backend {
|
||||
// Encapsulates a Vulkan buffer, its attached DeviceMemory and a staging area.
|
||||
class VulkanBuffer {
|
||||
public:
|
||||
VulkanBuffer(VmaAllocator allocator, std::shared_ptr<VulkanCommands> commands,
|
||||
VulkanStagePool& stagePool, VkBufferUsageFlags usage, uint32_t numBytes);
|
||||
VulkanBuffer(VmaAllocator allocator, VulkanStagePool& stagePool, VkBufferUsageFlags usage,
|
||||
uint32_t numBytes);
|
||||
~VulkanBuffer();
|
||||
void terminate();
|
||||
void loadFromCpu(const void* cpuData, uint32_t byteOffset, uint32_t numBytes) const;
|
||||
VkBuffer getGpuBuffer() const { return mGpuBuffer; }
|
||||
void loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint32_t byteOffset,
|
||||
uint32_t numBytes) const;
|
||||
VkBuffer getGpuBuffer() const {
|
||||
return mGpuBuffer;
|
||||
}
|
||||
|
||||
private:
|
||||
VmaAllocator mAllocator;
|
||||
std::shared_ptr<VulkanCommands> mCommands;
|
||||
VulkanStagePool& mStagePool;
|
||||
|
||||
VmaAllocation mGpuMemory = VK_NULL_HANDLE;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "VulkanCommands.h"
|
||||
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanContext.h"
|
||||
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Panic.h>
|
||||
@@ -32,6 +33,8 @@ using namespace utils;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
using Timestamp = VulkanGroupMarkers::Timestamp;
|
||||
|
||||
VulkanCmdFence::VulkanCmdFence(VkDevice device, bool signaled) : device(device) {
|
||||
VkFenceCreateInfo fenceCreateInfo { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
|
||||
if (signaled) {
|
||||
@@ -64,12 +67,71 @@ static VkCommandPool createPool(VkDevice device, uint32_t queueFamilyIndex) {
|
||||
|
||||
}
|
||||
|
||||
VulkanCommands::VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex) : mDevice(device),
|
||||
mQueue(queue), mPool(createPool(mDevice, queueFamilyIndex)) {
|
||||
VkSemaphoreCreateInfo sci { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
|
||||
for (auto& semaphore : mSubmissionSignals) {
|
||||
void VulkanGroupMarkers::push(std::string const& marker, Timestamp start) noexcept {
|
||||
mMarkers.push_back(marker);
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
mTimestamps.push_back(start.time_since_epoch().count() > 0.0
|
||||
? start
|
||||
: std::chrono::high_resolution_clock::now());
|
||||
#endif
|
||||
}
|
||||
|
||||
std::pair<std::string, Timestamp> VulkanGroupMarkers::pop() noexcept {
|
||||
auto const marker = mMarkers.back();
|
||||
mMarkers.pop_back();
|
||||
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
auto const timestamp = mTimestamps.back();
|
||||
mTimestamps.pop_back();
|
||||
return std::make_pair(marker, timestamp);
|
||||
#else
|
||||
return std::make_pair(marker, Timestamp{});
|
||||
#endif
|
||||
}
|
||||
|
||||
std::pair<std::string, Timestamp> VulkanGroupMarkers::pop_bottom() noexcept {
|
||||
auto const marker = mMarkers.front();
|
||||
mMarkers.pop_front();
|
||||
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
auto const timestamp = mTimestamps.front();
|
||||
mTimestamps.pop_front();
|
||||
return std::make_pair(marker, timestamp);
|
||||
#else
|
||||
return std::make_pair(marker, Timestamp{});
|
||||
#endif
|
||||
}
|
||||
|
||||
std::pair<std::string, Timestamp> VulkanGroupMarkers::top() const {
|
||||
assert_invariant(!empty());
|
||||
auto const marker = mMarkers.back();
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
auto const topTimestamp = mTimestamps.top();
|
||||
return std::make_pair(marker, topTimestamp);
|
||||
#else
|
||||
return std::make_pair(marker, Timestamp{});
|
||||
#endif
|
||||
}
|
||||
|
||||
bool VulkanGroupMarkers::empty() const noexcept {
|
||||
return mMarkers.empty();
|
||||
}
|
||||
|
||||
VulkanCommands::VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex,
|
||||
VulkanContext* context, VulkanResourceAllocator* allocator)
|
||||
: mDevice(device),
|
||||
mQueue(queue),
|
||||
mPool(createPool(mDevice, queueFamilyIndex)),
|
||||
mContext(context),
|
||||
mStorage(CAPACITY) {
|
||||
VkSemaphoreCreateInfo sci{.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
|
||||
for (auto& semaphore: mSubmissionSignals) {
|
||||
vkCreateSemaphore(mDevice, &sci, nullptr, &semaphore);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < CAPACITY; ++i) {
|
||||
mStorage[i] = std::make_unique<VulkanCommandBuffer>(allocator);
|
||||
}
|
||||
}
|
||||
|
||||
VulkanCommands::~VulkanCommands() {
|
||||
@@ -81,9 +143,9 @@ VulkanCommands::~VulkanCommands() {
|
||||
}
|
||||
}
|
||||
|
||||
VulkanCommandBuffer const& VulkanCommands::get() {
|
||||
if (mCurrent) {
|
||||
return *mCurrent;
|
||||
VulkanCommandBuffer& VulkanCommands::get() {
|
||||
if (mCurrentCommandBufferIndex >= 0) {
|
||||
return *mStorage[mCurrentCommandBufferIndex].get();
|
||||
}
|
||||
|
||||
// If we ran out of available command buffers, stall until one finishes. This is very rare.
|
||||
@@ -99,15 +161,18 @@ VulkanCommandBuffer const& VulkanCommands::get() {
|
||||
gc();
|
||||
}
|
||||
|
||||
VulkanCommandBuffer* currentbuf = nullptr;
|
||||
// Find an available slot.
|
||||
for (VulkanCommandBuffer& wrapper : mStorage) {
|
||||
if (wrapper.cmdbuffer == VK_NULL_HANDLE) {
|
||||
mCurrent = &wrapper;
|
||||
for (size_t i = 0; i < CAPACITY; ++i) {
|
||||
auto wrapper = mStorage[i].get();
|
||||
if (wrapper->cmdbuffer == VK_NULL_HANDLE) {
|
||||
mCurrentCommandBufferIndex = static_cast<int8_t>(i);
|
||||
currentbuf = wrapper;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert_invariant(mCurrent);
|
||||
assert_invariant(currentbuf);
|
||||
--mAvailableCount;
|
||||
|
||||
// Create the low-level command buffer.
|
||||
@@ -117,38 +182,46 @@ VulkanCommandBuffer const& VulkanCommands::get() {
|
||||
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
.commandBufferCount = 1
|
||||
};
|
||||
vkAllocateCommandBuffers(mDevice, &allocateInfo, &mCurrent->cmdbuffer);
|
||||
vkAllocateCommandBuffers(mDevice, &allocateInfo, ¤tbuf->cmdbuffer);
|
||||
|
||||
// Note that the fence wrapper uses shared_ptr because a DriverAPI fence can also have ownership
|
||||
// over it. The destruction of the low-level fence occurs either in VulkanCommands::gc(), or in
|
||||
// VulkanDriver::destroyFence(), both of which are safe spots.
|
||||
mCurrent->fence = std::make_shared<VulkanCmdFence>(mDevice);
|
||||
currentbuf->fence = std::make_shared<VulkanCmdFence>(mDevice);
|
||||
|
||||
// Begin writing into the command buffer.
|
||||
const VkCommandBufferBeginInfo binfo {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
||||
};
|
||||
vkBeginCommandBuffer(mCurrent->cmdbuffer, &binfo);
|
||||
vkBeginCommandBuffer(currentbuf->cmdbuffer, &binfo);
|
||||
|
||||
// Notify the observer that a new command buffer has been activated.
|
||||
if (mObserver) {
|
||||
mObserver->onCommandBuffer(*mCurrent);
|
||||
mObserver->onCommandBuffer(*currentbuf);
|
||||
}
|
||||
|
||||
return *mCurrent;
|
||||
// We push the current markers onto a temporary stack. This must be placed after currentbuf is
|
||||
// set to the new command buffer since pushGroupMarker also calls get().
|
||||
while (mCarriedOverMarkers && !mCarriedOverMarkers->empty()) {
|
||||
auto [marker, time] = mCarriedOverMarkers->pop();
|
||||
pushGroupMarker(marker.c_str(), time);
|
||||
}
|
||||
|
||||
return *currentbuf;
|
||||
}
|
||||
|
||||
bool VulkanCommands::flush() {
|
||||
// It's perfectly fine to call flush when no commands have been written.
|
||||
if (mCurrent == nullptr) {
|
||||
if (mCurrentCommandBufferIndex < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int64_t index = mCurrent - &mStorage[0];
|
||||
VkSemaphore renderingFinished = mSubmissionSignals[index];
|
||||
int8_t const index = mCurrentCommandBufferIndex;
|
||||
VulkanCommandBuffer const* currentbuf = mStorage[index].get();
|
||||
VkSemaphore const renderingFinished = mSubmissionSignals[index];
|
||||
|
||||
vkEndCommandBuffer(mCurrent->cmdbuffer);
|
||||
vkEndCommandBuffer(currentbuf->cmdbuffer);
|
||||
|
||||
// If the injected semaphore is an "image available" semaphore that has not yet been signaled,
|
||||
// it is sometimes fine to start executing commands anyway, as along as we stall the GPU at the
|
||||
@@ -172,7 +245,7 @@ bool VulkanCommands::flush() {
|
||||
.pWaitSemaphores = signals,
|
||||
.pWaitDstStageMask = waitDestStageMasks,
|
||||
.commandBufferCount = 1,
|
||||
.pCommandBuffers = &mCurrent->cmdbuffer,
|
||||
.pCommandBuffers = ¤tbuf->cmdbuffer,
|
||||
.signalSemaphoreCount = 1u,
|
||||
.pSignalSemaphores = &renderingFinished,
|
||||
};
|
||||
@@ -185,51 +258,74 @@ bool VulkanCommands::flush() {
|
||||
signals[submitInfo.waitSemaphoreCount++] = mInjectedSignal;
|
||||
}
|
||||
|
||||
if (FILAMENT_VULKAN_VERBOSE) {
|
||||
slog.i << "Submitting cmdbuffer=" << mCurrent->cmdbuffer
|
||||
<< " wait=(" << signals[0] << ", " << signals[1] << ") "
|
||||
<< " signal=" << renderingFinished
|
||||
<< io::endl;
|
||||
// To address a validation warning.
|
||||
if (submitInfo.waitSemaphoreCount == 0) {
|
||||
submitInfo.pWaitSemaphores = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
auto& cmdfence = mCurrent->fence;
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
slog.i << "Submitting cmdbuffer=" << currentbuf->cmdbuffer
|
||||
<< " wait=(" << signals[0] << ", " << signals[1] << ") "
|
||||
<< " signal=" << renderingFinished
|
||||
<< io::endl;
|
||||
#endif
|
||||
|
||||
// Before actually submitting, we need to pop any leftover group markers.
|
||||
while (mGroupMarkers && !mGroupMarkers->empty()) {
|
||||
if (!mCarriedOverMarkers) {
|
||||
mCarriedOverMarkers = std::make_unique<VulkanGroupMarkers>();
|
||||
}
|
||||
auto const [marker, time] = mGroupMarkers->top();
|
||||
mCarriedOverMarkers->push(marker, time);
|
||||
// We still need to call through to vkCmdEndDebugUtilsLabelEXT.
|
||||
popGroupMarker();
|
||||
}
|
||||
|
||||
auto& cmdfence = currentbuf->fence;
|
||||
std::unique_lock<utils::Mutex> lock(cmdfence->mutex);
|
||||
cmdfence->status.store(VK_NOT_READY);
|
||||
UTILS_UNUSED_IN_RELEASE VkResult result = vkQueueSubmit(mQueue, 1, &submitInfo, cmdfence->fence);
|
||||
cmdfence->condition.notify_all();
|
||||
lock.unlock();
|
||||
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
if (result != VK_SUCCESS) {
|
||||
utils::slog.d <<"Failed command buffer submission result: " << result << utils::io::endl;
|
||||
}
|
||||
#endif
|
||||
assert_invariant(result == VK_SUCCESS);
|
||||
|
||||
mSubmissionSignal = renderingFinished;
|
||||
mInjectedSignal = VK_NULL_HANDLE;
|
||||
mCurrent = nullptr;
|
||||
mCurrentCommandBufferIndex = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
VkSemaphore VulkanCommands::acquireFinishedSignal() {
|
||||
VkSemaphore semaphore = mSubmissionSignal;
|
||||
mSubmissionSignal = VK_NULL_HANDLE;
|
||||
if (FILAMENT_VULKAN_VERBOSE) {
|
||||
slog.i << "Acquiring " << semaphore << " (e.g. for vkQueuePresentKHR)" << io::endl;
|
||||
}
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
slog.i << "Acquiring " << semaphore << " (e.g. for vkQueuePresentKHR)" << io::endl;
|
||||
#endif
|
||||
return semaphore;
|
||||
}
|
||||
|
||||
void VulkanCommands::injectDependency(VkSemaphore next) {
|
||||
assert_invariant(mInjectedSignal == VK_NULL_HANDLE);
|
||||
mInjectedSignal = next;
|
||||
if (FILAMENT_VULKAN_VERBOSE) {
|
||||
slog.i << "Injecting " << next << " (e.g. due to vkAcquireNextImageKHR)" << io::endl;
|
||||
}
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
slog.i << "Injecting " << next << " (e.g. due to vkAcquireNextImageKHR)" << io::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
void VulkanCommands::wait() {
|
||||
VkFence fences[CAPACITY];
|
||||
uint32_t count = 0;
|
||||
for (auto& wrapper : mStorage) {
|
||||
if (wrapper.cmdbuffer != VK_NULL_HANDLE && mCurrent != &wrapper) {
|
||||
fences[count++] = wrapper.fence->fence;
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; i < CAPACITY; i++) {
|
||||
auto wrapper = mStorage[i].get();
|
||||
if (wrapper->cmdbuffer != VK_NULL_HANDLE
|
||||
&& mCurrentCommandBufferIndex != static_cast<int8_t>(i)) {
|
||||
fences[count++] = wrapper->fence->fence;
|
||||
}
|
||||
}
|
||||
if (count > 0) {
|
||||
@@ -238,24 +334,34 @@ void VulkanCommands::wait() {
|
||||
}
|
||||
|
||||
void VulkanCommands::gc() {
|
||||
for (auto& wrapper : mStorage) {
|
||||
if (wrapper.cmdbuffer != VK_NULL_HANDLE) {
|
||||
VkResult result = vkWaitForFences(mDevice, 1, &wrapper.fence->fence, VK_TRUE, 0);
|
||||
if (result == VK_SUCCESS) {
|
||||
vkFreeCommandBuffers(mDevice, mPool, 1, &wrapper.cmdbuffer);
|
||||
wrapper.cmdbuffer = VK_NULL_HANDLE;
|
||||
wrapper.fence->status.store(VK_SUCCESS);
|
||||
wrapper.fence.reset();
|
||||
++mAvailableCount;
|
||||
}
|
||||
VkCommandBuffer buffers[CAPACITY];
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; i < CAPACITY; i++) {
|
||||
auto wrapper = mStorage[i].get();
|
||||
if (wrapper->cmdbuffer == VK_NULL_HANDLE) {
|
||||
continue;
|
||||
}
|
||||
VkResult const result = vkWaitForFences(mDevice, 1, &wrapper->fence->fence, VK_TRUE, 0);
|
||||
if (result != VK_SUCCESS) {
|
||||
continue;
|
||||
}
|
||||
buffers[count++] = wrapper->cmdbuffer;
|
||||
wrapper->cmdbuffer = VK_NULL_HANDLE;
|
||||
wrapper->fence->status.store(VK_SUCCESS);
|
||||
wrapper->fence.reset();
|
||||
wrapper->clearResources();
|
||||
++mAvailableCount;
|
||||
}
|
||||
if (count > 0) {
|
||||
vkFreeCommandBuffers(mDevice, mPool, count, buffers);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanCommands::updateFences() {
|
||||
for (auto& wrapper : mStorage) {
|
||||
if (wrapper.cmdbuffer != VK_NULL_HANDLE) {
|
||||
VulkanCmdFence* fence = wrapper.fence.get();
|
||||
for (size_t i = 0; i < CAPACITY; i++) {
|
||||
auto wrapper = mStorage[i].get();
|
||||
if (wrapper->cmdbuffer != VK_NULL_HANDLE) {
|
||||
VulkanCmdFence* fence = wrapper->fence.get();
|
||||
if (fence) {
|
||||
VkResult status = vkGetFenceStatus(mDevice, fence->fence);
|
||||
// This is either VK_SUCCESS, VK_NOT_READY, or VK_ERROR_DEVICE_LOST.
|
||||
@@ -265,7 +371,95 @@ void VulkanCommands::updateFences() {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
void VulkanCommands::pushGroupMarker(char const* str, VulkanGroupMarkers::Timestamp timestamp) {
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
// If the timestamp is not 0, then we are carrying over a marker across buffer submits.
|
||||
// If it is 0, then this is a normal marker push and we should just print debug line as usual.
|
||||
if (timestamp.time_since_epoch().count() == 0.0) {
|
||||
utils::slog.d << "----> " << str << utils::io::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
// TODO: Add group marker color to the Driver API
|
||||
const VkCommandBuffer cmdbuffer = get().cmdbuffer;
|
||||
|
||||
if (!mGroupMarkers) {
|
||||
mGroupMarkers = std::make_unique<VulkanGroupMarkers>();
|
||||
}
|
||||
mGroupMarkers->push(str, timestamp);
|
||||
|
||||
if (mContext->isDebugUtilsSupported()) {
|
||||
VkDebugUtilsLabelEXT labelInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
|
||||
.pLabelName = str,
|
||||
.color = {0, 1, 0, 1},
|
||||
};
|
||||
vkCmdBeginDebugUtilsLabelEXT(cmdbuffer, &labelInfo);
|
||||
} else if (mContext->isDebugMarkersSupported()) {
|
||||
VkDebugMarkerMarkerInfoEXT markerInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT,
|
||||
.pMarkerName = str,
|
||||
.color = {0.0f, 1.0f, 0.0f, 1.0f},
|
||||
};
|
||||
vkCmdDebugMarkerBeginEXT(cmdbuffer, &markerInfo);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanCommands::popGroupMarker() {
|
||||
assert_invariant(mGroupMarkers);
|
||||
|
||||
if (!mGroupMarkers->empty()) {
|
||||
const VkCommandBuffer cmdbuffer = get().cmdbuffer;
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
auto const [marker, startTime] = mGroupMarkers->pop();
|
||||
auto const endTime = std::chrono::high_resolution_clock::now();
|
||||
std::chrono::duration<double> diff = endTime - startTime;
|
||||
utils::slog.d << "<---- " << marker << " elapsed: " << (diff.count() * 1000) << " ms\n"
|
||||
<< utils::io::flush;
|
||||
#else
|
||||
mGroupMarkers->pop();
|
||||
#endif
|
||||
|
||||
if (mContext->isDebugUtilsSupported()) {
|
||||
vkCmdEndDebugUtilsLabelEXT(cmdbuffer);
|
||||
} else if (mContext->isDebugMarkersSupported()) {
|
||||
vkCmdDebugMarkerEndEXT(cmdbuffer);
|
||||
}
|
||||
} else if (mCarriedOverMarkers && !mCarriedOverMarkers->empty()) {
|
||||
// It could be that pop is called between flush() and get() (new command buffer), in which
|
||||
// case the marker is in "carried over" state, we'd just remove that. Since the
|
||||
// mCarriedOverMarkers is in the opposite order, we pop the bottom instead of the top.
|
||||
mCarriedOverMarkers->pop_bottom();
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanCommands::insertEventMarker(char const* string, uint32_t len) {
|
||||
VkCommandBuffer const cmdbuffer = get().cmdbuffer;
|
||||
if (mContext->isDebugUtilsSupported()) {
|
||||
VkDebugUtilsLabelEXT labelInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
|
||||
.pLabelName = string,
|
||||
.color = {1, 1, 0, 1},
|
||||
};
|
||||
vkCmdInsertDebugUtilsLabelEXT(cmdbuffer, &labelInfo);
|
||||
} else if (mContext->isDebugMarkersSupported()) {
|
||||
VkDebugMarkerMarkerInfoEXT markerInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT,
|
||||
.pMarkerName = string,
|
||||
.color = {0.0f, 1.0f, 0.0f, 1.0f},
|
||||
};
|
||||
vkCmdDebugMarkerInsertEXT(cmdbuffer, &markerInfo);
|
||||
}
|
||||
}
|
||||
|
||||
std::string VulkanCommands::getTopGroupMarker() const {
|
||||
if (!mGroupMarkers || mGroupMarkers->empty()) {
|
||||
return "";
|
||||
}
|
||||
return std::get<0>(mGroupMarkers->top());
|
||||
}
|
||||
|
||||
}// namespace filament::backend
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning( pop )
|
||||
|
||||
@@ -19,15 +19,43 @@
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
|
||||
#include "DriverBase.h"
|
||||
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanResources.h"
|
||||
|
||||
#include <utils/Condition.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Mutex.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <chrono>
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
struct VulkanContext;
|
||||
|
||||
class VulkanGroupMarkers {
|
||||
public:
|
||||
using Timestamp = std::chrono::time_point<std::chrono::high_resolution_clock>;
|
||||
|
||||
void push(std::string const& marker, Timestamp start = {}) noexcept;
|
||||
std::pair<std::string, Timestamp> pop() noexcept;
|
||||
std::pair<std::string, Timestamp> pop_bottom() noexcept;
|
||||
std::pair<std::string, Timestamp> top() const;
|
||||
bool empty() const noexcept;
|
||||
|
||||
private:
|
||||
std::list<std::string> mMarkers;
|
||||
#if FILAMENT_VULKAN_VERBOSE
|
||||
std::list<Timestamp> mTimestamps;
|
||||
#endif
|
||||
};
|
||||
|
||||
// Wrapper to enable use of shared_ptr for implementing shared ownership of low-level Vulkan fences.
|
||||
struct VulkanCmdFence {
|
||||
VulkanCmdFence(VkDevice device, bool signaled = false);
|
||||
@@ -43,11 +71,28 @@ struct VulkanCmdFence {
|
||||
// DriverApi fence object and should not be destroyed until both the DriverApi object is freed and
|
||||
// we're done waiting on the most recent submission of the given command buffer.
|
||||
struct VulkanCommandBuffer {
|
||||
VulkanCommandBuffer() {}
|
||||
VulkanCommandBuffer(VulkanResourceAllocator* allocator)
|
||||
: mResourceManager(allocator) {}
|
||||
|
||||
VulkanCommandBuffer(VulkanCommandBuffer const&) = delete;
|
||||
VulkanCommandBuffer& operator=(VulkanCommandBuffer const&) = delete;
|
||||
VkCommandBuffer cmdbuffer = VK_NULL_HANDLE;
|
||||
std::shared_ptr<VulkanCmdFence> fence;
|
||||
|
||||
inline void acquire(VulkanResource* resource) {
|
||||
mResourceManager.acquire(resource);
|
||||
}
|
||||
|
||||
inline void acquire(VulkanAcquireOnlyResourceManager* srcResources) {
|
||||
mResourceManager.acquire(srcResources);
|
||||
}
|
||||
|
||||
inline void clearResources() {
|
||||
mResourceManager.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
VulkanAcquireOnlyResourceManager mResourceManager;
|
||||
};
|
||||
|
||||
// Allows classes to be notified after a new command buffer has been activated.
|
||||
@@ -85,11 +130,12 @@ public:
|
||||
//
|
||||
class VulkanCommands {
|
||||
public:
|
||||
VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex);
|
||||
VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex,
|
||||
VulkanContext* context, VulkanResourceAllocator* allocator);
|
||||
~VulkanCommands();
|
||||
|
||||
// Creates a "current" command buffer if none exists, otherwise returns the current one.
|
||||
VulkanCommandBuffer const& get();
|
||||
VulkanCommandBuffer& get();
|
||||
|
||||
// Submits the current command buffer if it exists, then sets "current" to null.
|
||||
// If there are no outstanding commands then nothing happens and this returns false.
|
||||
@@ -117,18 +163,33 @@ class VulkanCommands {
|
||||
// The observer's event handler can only be called during get().
|
||||
void setObserver(CommandBufferObserver* observer) { mObserver = observer; }
|
||||
|
||||
void pushGroupMarker(char const* str, VulkanGroupMarkers::Timestamp timestamp = {});
|
||||
|
||||
void popGroupMarker();
|
||||
|
||||
void insertEventMarker(char const* string, uint32_t len);
|
||||
|
||||
std::string getTopGroupMarker() const;
|
||||
|
||||
private:
|
||||
static constexpr int CAPACITY = VK_MAX_COMMAND_BUFFERS;
|
||||
const VkDevice mDevice;
|
||||
const VkQueue mQueue;
|
||||
const VkCommandPool mPool;
|
||||
VulkanCommandBuffer* mCurrent = nullptr;
|
||||
VkDevice const mDevice;
|
||||
VkQueue const mQueue;
|
||||
VkCommandPool const mPool;
|
||||
VulkanContext const* mContext;
|
||||
|
||||
// int8 only goes up to 127, therefore capacity must be less than that.
|
||||
static_assert(CAPACITY < 128);
|
||||
int8_t mCurrentCommandBufferIndex = -1;
|
||||
VkSemaphore mSubmissionSignal = {};
|
||||
VkSemaphore mInjectedSignal = {};
|
||||
VulkanCommandBuffer mStorage[CAPACITY] = {};
|
||||
utils::FixedCapacityVector<std::unique_ptr<VulkanCommandBuffer>> mStorage;
|
||||
VkSemaphore mSubmissionSignals[CAPACITY] = {};
|
||||
size_t mAvailableCount = CAPACITY;
|
||||
CommandBufferObserver* mObserver = nullptr;
|
||||
|
||||
std::unique_ptr<VulkanGroupMarkers> mGroupMarkers;
|
||||
std::unique_ptr<VulkanGroupMarkers> mCarriedOverMarkers;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
|
||||
#include "VulkanContext.h"
|
||||
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanHandles.h"
|
||||
#include "VulkanMemory.h"
|
||||
#include "VulkanTexture.h"
|
||||
@@ -83,10 +85,10 @@ VkImageSubresourceRange VulkanAttachment::getSubresourceRange(VkImageAspectFlags
|
||||
VulkanTimestamps::VulkanTimestamps(VkDevice device) : mDevice(device) {
|
||||
// Create a timestamp pool large enough to hold a pair of queries for each timer.
|
||||
VkQueryPoolCreateInfo tqpCreateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
|
||||
.queryType = VK_QUERY_TYPE_TIMESTAMP,
|
||||
.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
|
||||
.queryType = VK_QUERY_TYPE_TIMESTAMP,
|
||||
};
|
||||
std::unique_lock<utils::Mutex> timestamps_lock(mMutex);
|
||||
std::unique_lock<utils::Mutex> lock(mMutex);
|
||||
tqpCreateInfo.queryCount = mUsed.size() * 2;
|
||||
VkResult result = vkCreateQueryPool(mDevice, &tqpCreateInfo, VKALLOC, &mPool);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "vkCreateQueryPool error.");
|
||||
@@ -113,23 +115,23 @@ void VulkanTimestamps::clearQuery(uint32_t queryIndex) {
|
||||
|
||||
void VulkanTimestamps::beginQuery(VulkanCommandBuffer const* commands,
|
||||
VulkanTimerQuery* query) {
|
||||
uint32_t const index = query->startingQueryIndex;
|
||||
uint32_t const index = query->getStartingQueryIndex();
|
||||
|
||||
vkCmdResetQueryPool(commands->cmdbuffer, mPool, index, 2);
|
||||
vkCmdWriteTimestamp(commands->cmdbuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, mPool, index);
|
||||
|
||||
// We stash this because getResult might come before beginquery.
|
||||
query->cmdbuffer.store(commands);
|
||||
// We stash this because getResult might come before the query is actually processed.
|
||||
query->setFence(commands->fence);
|
||||
}
|
||||
|
||||
void VulkanTimestamps::endQuery(VulkanCommandBuffer const* commands,
|
||||
VulkanTimerQuery const* query) {
|
||||
uint32_t const index = query->stoppingQueryIndex;
|
||||
uint32_t const index = query->getStoppingQueryIndex();
|
||||
vkCmdWriteTimestamp(commands->cmdbuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, mPool, index);
|
||||
}
|
||||
|
||||
VulkanTimestamps::QueryResult VulkanTimestamps::getResult(VulkanTimerQuery const* query) {
|
||||
uint32_t const index = query->startingQueryIndex;
|
||||
uint32_t const index = query->getStartingQueryIndex();
|
||||
QueryResult result;
|
||||
size_t const dataSize = result.size() * sizeof(uint64_t);
|
||||
VkDeviceSize const stride = sizeof(uint64_t) * 2;
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKANCONTEXT_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANCONTEXT_H
|
||||
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanImageUtility.h"
|
||||
#include "VulkanPipelineCache.h"
|
||||
@@ -38,74 +37,10 @@ struct VulkanSwapChain;
|
||||
struct VulkanTexture;
|
||||
class VulkanStagePool;
|
||||
struct VulkanTimerQuery;
|
||||
|
||||
// TODO: We used std::shared_ptr in various places across the vulkan backend, but VulkanTexture*
|
||||
// also maps to HwTexture so it's not possible to switch in VulkanAttachment. Hence we introduce
|
||||
// this temporary class. We need to revisit the ownership pattern and use of smart pointer in the
|
||||
// vulkan backend, in particular std::shared_ptr uses atomic, which is overhead we do not need.
|
||||
struct TexturePointer {
|
||||
TexturePointer() = default;
|
||||
explicit TexturePointer(VulkanTexture* tex) :mTexture(tex) {}
|
||||
explicit TexturePointer(std::shared_ptr<VulkanTexture> tex) :mTexture(tex) {}
|
||||
|
||||
inline TexturePointer& operator=(VulkanTexture* tex) {
|
||||
mTexture = tex;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline TexturePointer& operator=(std::shared_ptr<VulkanTexture> tex) {
|
||||
mTexture = tex;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Be careful not to leak here. Should only be used in local scope.
|
||||
explicit operator VulkanTexture*() const {
|
||||
if (mTexture.index() == 0) {
|
||||
return std::get<0>(mTexture);
|
||||
}
|
||||
return std::get<1>(mTexture).get();
|
||||
}
|
||||
|
||||
// Be careful not to leak here. Should only be used in local scope.
|
||||
explicit operator VulkanTexture const*() const {
|
||||
if (mTexture.index() == 0) {
|
||||
return std::get<0>(mTexture);
|
||||
}
|
||||
return std::get<1>(mTexture).get();
|
||||
}
|
||||
|
||||
explicit operator std::shared_ptr<VulkanTexture>() const {
|
||||
assert_invariant(mTexture.index() == 1);
|
||||
return std::get<1>(mTexture);
|
||||
}
|
||||
|
||||
inline operator bool() const {
|
||||
if (mTexture.index() == 0) {
|
||||
return std::get<0>(mTexture) != nullptr;
|
||||
}
|
||||
return (bool) std::get<1>(mTexture);
|
||||
}
|
||||
|
||||
inline VulkanTexture* operator->() {
|
||||
if (mTexture.index() == 0) {
|
||||
return std::get<0>(mTexture);
|
||||
}
|
||||
return std::get<1>(mTexture).get();
|
||||
}
|
||||
|
||||
inline VulkanTexture const* operator->() const {
|
||||
if (mTexture.index() == 0) {
|
||||
return std::get<0>(mTexture);
|
||||
}
|
||||
return std::get<1>(mTexture).get();
|
||||
}
|
||||
|
||||
private:
|
||||
std::variant<VulkanTexture*, std::shared_ptr<VulkanTexture>> mTexture;
|
||||
};
|
||||
struct VulkanCommandBuffer;
|
||||
|
||||
struct VulkanAttachment {
|
||||
TexturePointer texture;
|
||||
VulkanTexture* texture;
|
||||
uint8_t level = 0;
|
||||
uint16_t layer = 0;
|
||||
VkImage getImage() const;
|
||||
@@ -162,8 +97,7 @@ public:
|
||||
}
|
||||
flags >>= 1;
|
||||
}
|
||||
ASSERT_POSTCONDITION(false, "Unable to find a memory type that meets requirements.");
|
||||
return (uint32_t) ~0ul;
|
||||
return (uint32_t) VK_MAX_MEMORY_TYPES;
|
||||
}
|
||||
|
||||
inline VkFormat getDepthFormat() const {
|
||||
@@ -188,22 +122,6 @@ public:
|
||||
inline bool isDebugUtilsSupported() const noexcept {
|
||||
return mDebugUtilsSupported;
|
||||
}
|
||||
inline bool isPortabilitySubsetSupported() const noexcept {
|
||||
return mPortabilitySubsetSupported;
|
||||
}
|
||||
inline bool isPortabilityEnumerationSupported() const noexcept {
|
||||
return mPortabilityEnumerationSupported;
|
||||
}
|
||||
|
||||
inline bool isMaintenance1Supported() const noexcept {
|
||||
return mMaintenanceSupported[0];
|
||||
}
|
||||
inline bool isMaintenance2Supported() const noexcept {
|
||||
return mMaintenanceSupported[1];
|
||||
}
|
||||
inline bool isMaintenance3Supported() const noexcept {
|
||||
return mMaintenanceSupported[2];
|
||||
}
|
||||
|
||||
private:
|
||||
VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
|
||||
@@ -211,9 +129,6 @@ private:
|
||||
VkPhysicalDeviceFeatures mPhysicalDeviceFeatures = {};
|
||||
bool mDebugMarkersSupported = false;
|
||||
bool mDebugUtilsSupported = false;
|
||||
bool mPortabilitySubsetSupported = false;
|
||||
bool mPortabilityEnumerationSupported = false;
|
||||
bool mMaintenanceSupported[3] = {};
|
||||
|
||||
VkFormat mDepthFormat;
|
||||
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 "VulkanDisposer.h"
|
||||
#include "VulkanConstants.h"
|
||||
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
// Always wait at least 3 frames after a DriverAPI-level resource has been destroyed for safe
|
||||
// destruction, due to potential usage by outstanding command buffers and triple buffering.
|
||||
static constexpr uint32_t FRAMES_BEFORE_EVICTION = VK_MAX_COMMAND_BUFFERS;
|
||||
|
||||
void VulkanDisposer::createDisposable(Key resource, std::function<void()> destructor) noexcept {
|
||||
mDisposables[resource].destructor = destructor;
|
||||
}
|
||||
|
||||
void VulkanDisposer::removeReference(Key resource) noexcept {
|
||||
// Null can be passed in as a no-op, this is not an error.
|
||||
if (resource == nullptr) {
|
||||
return;
|
||||
}
|
||||
assert_invariant(mDisposables[resource].refcount > 0);
|
||||
--mDisposables[resource].refcount;
|
||||
}
|
||||
|
||||
void VulkanDisposer::acquire(Key resource) noexcept {
|
||||
// It's fine to "acquire" a non-managed resource, it's just a no-op.
|
||||
if (resource == nullptr) {
|
||||
return;
|
||||
}
|
||||
auto iter = mDisposables.find(resource);
|
||||
if (iter == mDisposables.end()) {
|
||||
return;
|
||||
}
|
||||
Disposable& disposable = iter.value();
|
||||
assert_invariant(disposable.refcount > 0 && disposable.refcount < 65535);
|
||||
|
||||
// If an auto-decrement is already in place, do not increase the ref count.
|
||||
if (disposable.remainingFrames == 0) {
|
||||
++disposable.refcount;
|
||||
}
|
||||
|
||||
disposable.remainingFrames = FRAMES_BEFORE_EVICTION;
|
||||
}
|
||||
|
||||
void VulkanDisposer::gc() noexcept {
|
||||
// First decrement the frame count of all resources that were held by a command buffer.
|
||||
// If any of these reaches zero, decrement its reference count.
|
||||
for (auto iter = mDisposables.begin(); iter != mDisposables.end(); ++iter) {
|
||||
Disposable& disposable = iter.value();
|
||||
if (disposable.refcount > 0 && disposable.remainingFrames > 0) {
|
||||
if (--disposable.remainingFrames == 0) {
|
||||
removeReference(iter.key());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Next, destroy all resources with a zero refcount.
|
||||
decltype(mDisposables) disposables;
|
||||
for (auto iter : mDisposables) {
|
||||
Disposable& disposable = iter.second;
|
||||
if (disposable.refcount == 0) {
|
||||
disposable.destructor();
|
||||
} else {
|
||||
disposables.insert({iter.first, disposable});
|
||||
}
|
||||
}
|
||||
disposables.swap(mDisposables);
|
||||
}
|
||||
|
||||
void VulkanDisposer::reset() noexcept {
|
||||
#ifndef NDEBUG
|
||||
utils::slog.i << mDisposables.size() << " disposables are outstanding." << utils::io::endl;
|
||||
#endif
|
||||
for (auto iter : mDisposables) {
|
||||
iter.second.destructor();
|
||||
}
|
||||
mDisposables.clear();
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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_VULKANDISPOSER_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANDISPOSER_H
|
||||
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
// VulkanDisposer tracks resources (such as textures or vertex buffers) that need deferred
|
||||
// destruction due to potential use by a Vulkan command buffer. Resources are represented with void*
|
||||
// to allow callers to use any type of handle.
|
||||
class VulkanDisposer {
|
||||
public:
|
||||
using Key = const void*;
|
||||
|
||||
// Adds the given resource to the disposer and sets its reference count to 1.
|
||||
void createDisposable(Key resource, std::function<void()> destructor) noexcept;
|
||||
|
||||
// Decrements the reference count.
|
||||
void removeReference(Key resource) noexcept;
|
||||
|
||||
// Increments the reference count and auto-decrements it after FRAMES_BEFORE_EVICTION frames.
|
||||
// This is used to indicate that the current command buffer has a reference to the resource.
|
||||
void acquire(Key resource) noexcept;
|
||||
|
||||
// Invokes the destructor function for each disposable with a 0 refcount.
|
||||
void gc() noexcept;
|
||||
|
||||
// Invokes the destructor function for all disposables, regardless of reference count.
|
||||
void reset() noexcept;
|
||||
|
||||
private:
|
||||
struct Disposable {
|
||||
uint16_t refcount = 1;
|
||||
uint16_t remainingFrames = 0;
|
||||
std::function<void()> destructor = []() {};
|
||||
};
|
||||
tsl::robin_map<Key, Disposable> mDisposables;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_VULKANDISPOSER_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,22 +17,23 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKANDRIVER_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANDRIVER_H
|
||||
|
||||
#include "VulkanPipelineCache.h"
|
||||
#include "VulkanBlitter.h"
|
||||
#include "VulkanDisposer.h"
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanContext.h"
|
||||
#include "VulkanFboCache.h"
|
||||
#include "VulkanHandles.h"
|
||||
#include "VulkanPipelineCache.h"
|
||||
#include "VulkanReadPixels.h"
|
||||
#include "VulkanResourceAllocator.h"
|
||||
#include "VulkanSamplerCache.h"
|
||||
#include "VulkanStagePool.h"
|
||||
#include "VulkanUtility.h"
|
||||
|
||||
#include "private/backend/Driver.h"
|
||||
#include "private/backend/HandleAllocator.h"
|
||||
#include "DriverBase.h"
|
||||
#include "private/backend/Driver.h"
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Allocator.h>
|
||||
#include <utils/compiler.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -45,8 +46,8 @@ public:
|
||||
Platform::DriverConfig const& driverConfig) noexcept;
|
||||
|
||||
private:
|
||||
|
||||
void debugCommandBegin(CommandStream* cmds, bool synchronous, const char* methodName) noexcept override;
|
||||
void debugCommandBegin(CommandStream* cmds, bool synchronous,
|
||||
const char* methodName) noexcept override;
|
||||
|
||||
inline VulkanDriver(VulkanPlatform* platform, VulkanContext const& context,
|
||||
Platform::DriverConfig const& driverConfig) noexcept;
|
||||
@@ -60,77 +61,24 @@ private:
|
||||
template<typename T>
|
||||
friend class ConcreteDispatcher;
|
||||
|
||||
#define DECL_DRIVER_API(methodName, paramsDecl, params) \
|
||||
#define DECL_DRIVER_API(methodName, paramsDecl, params) \
|
||||
UTILS_ALWAYS_INLINE inline void methodName(paramsDecl);
|
||||
|
||||
#define DECL_DRIVER_API_SYNCHRONOUS(RetType, methodName, paramsDecl, params) \
|
||||
#define DECL_DRIVER_API_SYNCHRONOUS(RetType, methodName, paramsDecl, params) \
|
||||
RetType methodName(paramsDecl) override;
|
||||
|
||||
#define DECL_DRIVER_API_RETURN(RetType, methodName, paramsDecl, params) \
|
||||
RetType methodName##S() noexcept override; \
|
||||
#define DECL_DRIVER_API_RETURN(RetType, methodName, paramsDecl, params) \
|
||||
RetType methodName##S() noexcept override; \
|
||||
UTILS_ALWAYS_INLINE inline void methodName##R(RetType, paramsDecl);
|
||||
|
||||
#include "private/backend/DriverAPI.inc"
|
||||
|
||||
VulkanDriver(VulkanDriver const&) = delete;
|
||||
VulkanDriver& operator = (VulkanDriver const&) = delete;
|
||||
VulkanDriver& operator=(VulkanDriver const&) = delete;
|
||||
|
||||
private:
|
||||
|
||||
template<typename D, typename ... ARGS>
|
||||
Handle<D> initHandle(ARGS&& ... args) noexcept {
|
||||
return mHandleAllocator.allocateAndConstruct<D>(std::forward<ARGS>(args) ...);
|
||||
}
|
||||
|
||||
template<typename D>
|
||||
Handle<D> allocHandle() noexcept {
|
||||
return mHandleAllocator.allocate<D>();
|
||||
}
|
||||
|
||||
template<typename D, typename B, typename ... ARGS>
|
||||
typename std::enable_if<std::is_base_of<B, D>::value, D>::type*
|
||||
construct(Handle<B> const& handle, ARGS&& ... args) noexcept {
|
||||
return mHandleAllocator.construct<D, B>(handle, std::forward<ARGS>(args) ...);
|
||||
}
|
||||
|
||||
template<typename B, typename D,
|
||||
typename = typename std::enable_if<std::is_base_of<B, D>::value, D>::type>
|
||||
void destruct(Handle<B> handle, D const* p) noexcept {
|
||||
return mHandleAllocator.deallocate(handle, p);
|
||||
}
|
||||
|
||||
template<typename Dp, typename B>
|
||||
typename std::enable_if_t<
|
||||
std::is_pointer_v<Dp> &&
|
||||
std::is_base_of_v<B, typename std::remove_pointer_t<Dp>>, Dp>
|
||||
handle_cast(Handle<B>& handle) noexcept {
|
||||
return mHandleAllocator.handle_cast<Dp, B>(handle);
|
||||
}
|
||||
|
||||
template<typename Dp, typename B>
|
||||
inline typename std::enable_if_t<
|
||||
std::is_pointer_v<Dp> &&
|
||||
std::is_base_of_v<B, typename std::remove_pointer_t<Dp>>, Dp>
|
||||
handle_cast(Handle<B> const& handle) noexcept {
|
||||
return mHandleAllocator.handle_cast<Dp, B>(handle);
|
||||
}
|
||||
|
||||
template<typename D, typename B>
|
||||
void destruct(Handle<B> handle) noexcept {
|
||||
destruct(handle, handle_cast<D const*>(handle));
|
||||
}
|
||||
|
||||
// This version of destruct takes a VulkanContext and calls a terminate(VulkanContext&)
|
||||
// on the handle before calling the dtor
|
||||
template<typename Dp, typename B>
|
||||
void destructBuffer(Handle<B> handle) noexcept {
|
||||
auto ptr = handle_cast<Dp*>(handle);
|
||||
ptr->terminate();
|
||||
mHandleAllocator.deallocate(handle, ptr);
|
||||
}
|
||||
|
||||
inline void setRenderPrimitiveBuffer(Handle<HwRenderPrimitive> rph,
|
||||
Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh);
|
||||
inline void setRenderPrimitiveBuffer(Handle<HwRenderPrimitive> rph, Handle<HwVertexBuffer> vbh,
|
||||
Handle<HwIndexBuffer> ibh);
|
||||
|
||||
inline void setRenderPrimitiveRange(Handle<HwRenderPrimitive> rph, PrimitiveType pt,
|
||||
uint32_t offset, uint32_t minIndex, uint32_t maxIndex, uint32_t count);
|
||||
@@ -138,9 +86,9 @@ private:
|
||||
void collectGarbage();
|
||||
|
||||
VulkanPlatform* mPlatform = nullptr;
|
||||
std::shared_ptr<VulkanCommands> mCommands;
|
||||
std::unique_ptr<VulkanCommands> mCommands;
|
||||
std::unique_ptr<VulkanTimestamps> mTimestamps;
|
||||
std::shared_ptr<VulkanTexture> mEmptyTexture;
|
||||
std::unique_ptr<VulkanTexture> mEmptyTexture;
|
||||
|
||||
VulkanSwapChain* mCurrentSwapChain = nullptr;
|
||||
VulkanRenderTarget* mDefaultRenderTarget = nullptr;
|
||||
@@ -148,17 +96,22 @@ private:
|
||||
VmaAllocator mAllocator = VK_NULL_HANDLE;
|
||||
VkDebugReportCallbackEXT mDebugCallback = VK_NULL_HANDLE;
|
||||
VkDebugUtilsMessengerEXT mDebugMessenger = VK_NULL_HANDLE;
|
||||
std::string mCurrentDebugMarker;
|
||||
|
||||
VulkanContext mContext = {};
|
||||
HandleAllocatorVK mHandleAllocator;
|
||||
VulkanResourceAllocator mResourceAllocator;
|
||||
VulkanResourceManager mResourceManager;
|
||||
|
||||
// Used for resources that are created synchronously and used and destroyed on the backend
|
||||
// thread.
|
||||
VulkanThreadSafeResourceManager mThreadSafeResourceManager;
|
||||
|
||||
VulkanPipelineCache mPipelineCache;
|
||||
VulkanDisposer mDisposer;
|
||||
VulkanStagePool mStagePool;
|
||||
VulkanFboCache mFramebufferCache;
|
||||
VulkanSamplerCache mSamplerCache;
|
||||
VulkanBlitter mBlitter;
|
||||
VulkanSamplerGroup* mSamplerBindings[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {};
|
||||
VulkanReadPixels mReadPixels;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -68,7 +68,7 @@ void VulkanFboCache::initialize(VkDevice device) noexcept { mDevice = device; }
|
||||
|
||||
VulkanFboCache::~VulkanFboCache() {
|
||||
ASSERT_POSTCONDITION(mFramebufferCache.empty() && mRenderPassCache.empty(),
|
||||
"Please explicitly call reset() while the VkDevice is still alive.");
|
||||
"Please explicitly call terminate() while the VkDevice is still alive.");
|
||||
}
|
||||
|
||||
VkFramebuffer VulkanFboCache::getFramebuffer(FboKey config) noexcept {
|
||||
|
||||
@@ -46,10 +46,12 @@ static void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeig
|
||||
rect->extent.height = std::max(top - y, 0);
|
||||
}
|
||||
|
||||
VulkanProgram::VulkanProgram(VkDevice device, const Program& builder) noexcept :
|
||||
HwProgram(builder.getName()), mDevice(device) {
|
||||
VulkanProgram::VulkanProgram(VkDevice device, const Program& builder) noexcept
|
||||
: HwProgram(builder.getName()),
|
||||
VulkanResource(VulkanResourceType::PROGRAM),
|
||||
mDevice(device) {
|
||||
auto const& blobs = builder.getShadersSource();
|
||||
VkShaderModule* modules[2] = { &bundle.vertex, &bundle.fragment };
|
||||
VkShaderModule* modules[2] = {&bundle.vertex, &bundle.fragment};
|
||||
// TODO: handle compute shaders.
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
const auto& blob = blobs[i];
|
||||
@@ -108,14 +110,14 @@ VulkanProgram::VulkanProgram(VkDevice device, const Program& builder) noexcept :
|
||||
// Make a copy of the binding map
|
||||
samplerGroupInfo = builder.getSamplerGroupInfo();
|
||||
if constexpr (FILAMENT_VULKAN_VERBOSE) {
|
||||
utils::slog.d << "Created VulkanProgram " << builder
|
||||
<< ", shaders = (" << bundle.vertex << ", " << bundle.fragment << ")"
|
||||
<< utils::io::endl;
|
||||
utils::slog.d << "Created VulkanProgram " << builder << ", shaders = (" << bundle.vertex
|
||||
<< ", " << bundle.fragment << ")" << utils::io::endl;
|
||||
}
|
||||
}
|
||||
|
||||
VulkanProgram::VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs) noexcept :
|
||||
mDevice(device) {
|
||||
VulkanProgram::VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs) noexcept
|
||||
: VulkanResource(VulkanResourceType::PROGRAM),
|
||||
mDevice(device) {
|
||||
bundle.vertex = vs;
|
||||
bundle.fragment = fs;
|
||||
}
|
||||
@@ -127,23 +129,29 @@ VulkanProgram::~VulkanProgram() {
|
||||
}
|
||||
|
||||
// Creates a special "default" render target (i.e. associated with the swap chain)
|
||||
VulkanRenderTarget::VulkanRenderTarget() : HwRenderTarget(0, 0), mOffscreen(false), mSamples(1) {}
|
||||
VulkanRenderTarget::VulkanRenderTarget() :
|
||||
HwRenderTarget(0, 0),
|
||||
VulkanResource(VulkanResourceType::RENDER_TARGET),
|
||||
mOffscreen(false), mSamples(1) {}
|
||||
|
||||
void VulkanRenderTarget::bindToSwapChain(VulkanSwapChain& swapChain) {
|
||||
assert_invariant(!mOffscreen);
|
||||
VkExtent2D const extent = swapChain.getExtent();
|
||||
mColor[0] = { .texture = TexturePointer(swapChain.getCurrentColor()) };
|
||||
mDepth = { .texture = TexturePointer(swapChain.getDepth()) };
|
||||
mColor[0] = { .texture = swapChain.getCurrentColor() };
|
||||
mDepth = { .texture = swapChain.getDepth() };
|
||||
width = extent.width;
|
||||
height = extent.height;
|
||||
}
|
||||
|
||||
VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
VulkanContext const& context, VmaAllocator allocator,
|
||||
std::shared_ptr<VulkanCommands> commands, uint32_t width, uint32_t height, uint8_t samples,
|
||||
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
|
||||
uint32_t width, uint32_t height, uint8_t samples,
|
||||
VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
|
||||
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool)
|
||||
: HwRenderTarget(width, height), mOffscreen(true), mSamples(samples) {
|
||||
: HwRenderTarget(width, height),
|
||||
VulkanResource(VulkanResourceType::RENDER_TARGET),
|
||||
mOffscreen(true),
|
||||
mSamples(samples) {
|
||||
for (int index = 0; index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; index++) {
|
||||
mColor[index] = color[index];
|
||||
}
|
||||
@@ -167,13 +175,14 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica
|
||||
if (texture && texture->samples == 1) {
|
||||
auto msTexture = texture->getSidecar();
|
||||
if (UTILS_UNLIKELY(!msTexture)) {
|
||||
msTexture = std::make_shared<VulkanTexture>(device, physicalDevice, context,
|
||||
allocator, commands, texture->target,
|
||||
((VulkanTexture const*) texture)->levels, texture->format, samples,
|
||||
texture->width, texture->height, texture->depth, texture->usage, stagePool);
|
||||
// TODO: This should be allocated with the ResourceAllocator.
|
||||
msTexture = new VulkanTexture(device, physicalDevice, context, allocator, commands,
|
||||
texture->target, ((VulkanTexture const*) texture)->levels, texture->format,
|
||||
samples, texture->width, texture->height, texture->depth, texture->usage,
|
||||
stagePool, true /* heap allocated */);
|
||||
texture->setSidecar(msTexture);
|
||||
}
|
||||
mMsaaAttachments[index] = {.texture = TexturePointer(msTexture)};
|
||||
mMsaaAttachments[index] = {.texture = msTexture};
|
||||
}
|
||||
if (texture && texture->samples > 1) {
|
||||
mMsaaAttachments[index] = mColor[index];
|
||||
@@ -194,17 +203,17 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica
|
||||
uint8_t const msLevel = 1;
|
||||
|
||||
// Create sidecar MSAA texture for the depth attachment if it does not already exist.
|
||||
std::shared_ptr<VulkanTexture> msTexture = depthTexture->getSidecar();
|
||||
VulkanTexture* msTexture = depthTexture->getSidecar();
|
||||
if (UTILS_UNLIKELY(!msTexture)) {
|
||||
msTexture = std::make_shared<VulkanTexture>(device, physicalDevice, context, allocator,
|
||||
msTexture = new VulkanTexture(device, physicalDevice, context, allocator,
|
||||
commands, depthTexture->target, msLevel, depthTexture->format, samples,
|
||||
depthTexture->width, depthTexture->height, depthTexture->depth, depthTexture->usage,
|
||||
stagePool);
|
||||
stagePool, true /* heap allocated */);
|
||||
depthTexture->setSidecar(msTexture);
|
||||
}
|
||||
|
||||
mMsaaDepthAttachment = {
|
||||
.texture = TexturePointer(msTexture),
|
||||
.texture = msTexture,
|
||||
.level = msLevel,
|
||||
.layer = mDepth.layer,
|
||||
};
|
||||
@@ -258,16 +267,23 @@ uint8_t VulkanRenderTarget::getColorTargetCount(const VulkanRenderPass& pass) co
|
||||
}
|
||||
|
||||
VulkanVertexBuffer::VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool,
|
||||
uint8_t bufferCount, uint8_t attributeCount,
|
||||
uint32_t elementCount, AttributeArray const& attribs) :
|
||||
HwVertexBuffer(bufferCount, attributeCount, elementCount, attribs),
|
||||
buffers(bufferCount, nullptr) {}
|
||||
VulkanResourceAllocator* allocator, uint8_t bufferCount, uint8_t attributeCount,
|
||||
uint32_t elementCount, AttributeArray const& attribs)
|
||||
: HwVertexBuffer(bufferCount, attributeCount, elementCount, attribs),
|
||||
VulkanResource(VulkanResourceType::VERTEX_BUFFER),
|
||||
buffers(bufferCount, nullptr),
|
||||
mResources(allocator) {}
|
||||
|
||||
VulkanBufferObject::VulkanBufferObject(VmaAllocator allocator,
|
||||
std::shared_ptr<VulkanCommands> commands, VulkanStagePool& stagePool, uint32_t byteCount,
|
||||
BufferObjectBinding bindingType, BufferUsage usage)
|
||||
void VulkanVertexBuffer::setBuffer(VulkanBufferObject* bufferObject, uint32_t index) {
|
||||
buffers[index] = &bufferObject->buffer;
|
||||
mResources.acquire(bufferObject);
|
||||
}
|
||||
|
||||
VulkanBufferObject::VulkanBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool,
|
||||
uint32_t byteCount, BufferObjectBinding bindingType, BufferUsage usage)
|
||||
: HwBufferObject(byteCount),
|
||||
buffer(allocator, commands, stagePool, getBufferObjectUsage(bindingType), byteCount),
|
||||
VulkanResource(VulkanResourceType::BUFFER_OBJECT),
|
||||
buffer(allocator, stagePool, getBufferObjectUsage(bindingType), byteCount),
|
||||
bindingType(bindingType) {}
|
||||
|
||||
void VulkanRenderPrimitive::setPrimitiveType(PrimitiveType pt) {
|
||||
@@ -295,21 +311,35 @@ void VulkanRenderPrimitive::setBuffers(VulkanVertexBuffer* vertexBuffer,
|
||||
VulkanIndexBuffer* indexBuffer) {
|
||||
this->vertexBuffer = vertexBuffer;
|
||||
this->indexBuffer = indexBuffer;
|
||||
mResources.acquire(vertexBuffer);
|
||||
mResources.acquire(indexBuffer);
|
||||
}
|
||||
|
||||
VulkanTimerQuery::VulkanTimerQuery(std::tuple<uint32_t, uint32_t> indices)
|
||||
: startingQueryIndex(std::get<0>(indices)), stoppingQueryIndex(std::get<1>(indices)) {}
|
||||
: VulkanThreadSafeResource(VulkanResourceType::TIMER_QUERY),
|
||||
mStartingQueryIndex(std::get<0>(indices)),
|
||||
mStoppingQueryIndex(std::get<1>(indices)) {}
|
||||
|
||||
bool VulkanTimerQuery::isCompleted() const noexcept {
|
||||
void VulkanTimerQuery::setFence(std::shared_ptr<VulkanCmdFence> fence) noexcept {
|
||||
std::unique_lock<utils::Mutex> lock(mFenceMutex);
|
||||
mFence = fence;
|
||||
}
|
||||
|
||||
bool VulkanTimerQuery::isCompleted() noexcept {
|
||||
std::unique_lock<utils::Mutex> 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 require that the
|
||||
// timestamp has at least been written into a processed command buffer.
|
||||
VulkanCommandBuffer const* cmdbuf = cmdbuffer.load();
|
||||
if (!cmdbuf || !cmdbuf->fence) { return false; }
|
||||
|
||||
VkResult status = cmdbuf->fence->status.load(std::memory_order_relaxed);
|
||||
if (status != VK_SUCCESS) { return false; }
|
||||
// This fence indicates that the corresponding buffer has been completed.
|
||||
if (!mFence) {
|
||||
return false;
|
||||
}
|
||||
VkResult status = mFence->status.load(std::memory_order_relaxed);
|
||||
if (status != VK_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -14,23 +14,28 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKANHANDLES_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANHANDLES_H
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKANHANDLES_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANHANDLES_H
|
||||
|
||||
// This needs to be at the top
|
||||
#include "DriverBase.h"
|
||||
|
||||
#include "VulkanDriver.h"
|
||||
#include "VulkanPipelineCache.h"
|
||||
#include "VulkanBuffer.h"
|
||||
#include "VulkanPipelineCache.h"
|
||||
#include "VulkanResources.h"
|
||||
#include "VulkanSwapChain.h"
|
||||
#include "VulkanTexture.h"
|
||||
#include "VulkanUtility.h"
|
||||
|
||||
#include "private/backend/SamplerGroup.h"
|
||||
|
||||
#include <utils/Mutex.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanTimestamps;
|
||||
|
||||
struct VulkanProgram : public HwProgram {
|
||||
struct VulkanProgram : public HwProgram, VulkanResource {
|
||||
VulkanProgram(VkDevice device, const Program& builder) noexcept;
|
||||
VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs) noexcept;
|
||||
~VulkanProgram();
|
||||
@@ -49,11 +54,11 @@ private:
|
||||
//
|
||||
// We use private inheritance to shield clients from the width / height fields in HwRenderTarget,
|
||||
// which are not representative when this is the default render target.
|
||||
struct VulkanRenderTarget : private HwRenderTarget {
|
||||
struct VulkanRenderTarget : private HwRenderTarget, VulkanResource {
|
||||
// Creates an offscreen render target.
|
||||
VulkanRenderTarget(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
VulkanContext const& context, VmaAllocator allocator,
|
||||
std::shared_ptr<VulkanCommands> commands, uint32_t width, uint32_t height,
|
||||
VulkanCommands* commands, uint32_t width, uint32_t height,
|
||||
uint8_t samples, VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
|
||||
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool);
|
||||
|
||||
@@ -82,75 +87,110 @@ private:
|
||||
uint8_t mSamples : 7;
|
||||
};
|
||||
|
||||
struct VulkanVertexBuffer : public HwVertexBuffer {
|
||||
struct VulkanBufferObject;
|
||||
|
||||
struct VulkanVertexBuffer : public HwVertexBuffer, VulkanResource {
|
||||
VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool,
|
||||
uint8_t bufferCount, uint8_t attributeCount, uint32_t elementCount,
|
||||
AttributeArray const& attributes);
|
||||
VulkanResourceAllocator* allocator, uint8_t bufferCount, uint8_t attributeCount,
|
||||
uint32_t elementCount, AttributeArray const& attributes);
|
||||
|
||||
void setBuffer(VulkanBufferObject* bufferObject, uint32_t index);
|
||||
|
||||
inline void terminate() {
|
||||
mResources.clear();
|
||||
}
|
||||
|
||||
utils::FixedCapacityVector<VulkanBuffer const*> buffers;
|
||||
|
||||
private:
|
||||
FixedSizeVulkanResourceManager mResources;
|
||||
};
|
||||
|
||||
struct VulkanIndexBuffer : public HwIndexBuffer {
|
||||
VulkanIndexBuffer(VmaAllocator allocator, std::shared_ptr<VulkanCommands> commands,
|
||||
VulkanStagePool& stagePool, uint8_t elementSize, uint32_t indexCount)
|
||||
struct VulkanIndexBuffer : public HwIndexBuffer, VulkanResource {
|
||||
VulkanIndexBuffer(VmaAllocator allocator, VulkanStagePool& stagePool, uint8_t elementSize,
|
||||
uint32_t indexCount)
|
||||
: HwIndexBuffer(elementSize, indexCount),
|
||||
buffer(allocator, commands, stagePool, VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
|
||||
elementSize * indexCount),
|
||||
VulkanResource(VulkanResourceType::INDEX_BUFFER),
|
||||
buffer(allocator, stagePool, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, elementSize * indexCount),
|
||||
indexType(elementSize == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32) {}
|
||||
void terminate() { buffer.terminate(); }
|
||||
|
||||
void terminate() {
|
||||
buffer.terminate();
|
||||
}
|
||||
VulkanBuffer buffer;
|
||||
const VkIndexType indexType;
|
||||
};
|
||||
|
||||
struct VulkanBufferObject : public HwBufferObject {
|
||||
VulkanBufferObject(VmaAllocator allocator, std::shared_ptr<VulkanCommands> commands,
|
||||
VulkanStagePool& stagePool, uint32_t byteCount, BufferObjectBinding bindingType,
|
||||
BufferUsage usage);
|
||||
struct VulkanBufferObject : public HwBufferObject, VulkanResource {
|
||||
VulkanBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool, uint32_t byteCount,
|
||||
BufferObjectBinding bindingType, BufferUsage usage);
|
||||
void terminate() {
|
||||
buffer.terminate();
|
||||
buffer.terminate();
|
||||
}
|
||||
VulkanBuffer buffer;
|
||||
const BufferObjectBinding bindingType;
|
||||
};
|
||||
|
||||
struct VulkanSamplerGroup : public HwSamplerGroup {
|
||||
struct VulkanSamplerGroup : public HwSamplerGroup, VulkanResource {
|
||||
// NOTE: we have to use out-of-line allocation here because the size of a Handle<> is limited
|
||||
std::unique_ptr<SamplerGroup> sb; // FIXME: this shouldn't depend on filament::SamplerGroup
|
||||
explicit VulkanSamplerGroup(size_t size) noexcept : sb(new SamplerGroup(size)) { }
|
||||
std::unique_ptr<SamplerGroup> sb;// FIXME: this shouldn't depend on filament::SamplerGroup
|
||||
explicit VulkanSamplerGroup(size_t size) noexcept
|
||||
: VulkanResource(VulkanResourceType::SAMPLER_GROUP),
|
||||
sb(new SamplerGroup(size)) {}
|
||||
};
|
||||
|
||||
struct VulkanRenderPrimitive : public HwRenderPrimitive {
|
||||
struct VulkanRenderPrimitive : public HwRenderPrimitive, VulkanResource {
|
||||
VulkanRenderPrimitive(VulkanResourceAllocator* allocator)
|
||||
: VulkanResource(VulkanResourceType::RENDER_PRIMITIVE),
|
||||
mResources(allocator) {}
|
||||
|
||||
~VulkanRenderPrimitive() {
|
||||
mResources.clear();
|
||||
}
|
||||
|
||||
void setPrimitiveType(PrimitiveType pt);
|
||||
void setBuffers(VulkanVertexBuffer* vertexBuffer, VulkanIndexBuffer* indexBuffer);
|
||||
VulkanVertexBuffer* vertexBuffer = nullptr;
|
||||
VulkanIndexBuffer* indexBuffer = nullptr;
|
||||
VkPrimitiveTopology primitiveTopology;
|
||||
|
||||
private:
|
||||
FixedSizeVulkanResourceManager mResources;
|
||||
};
|
||||
|
||||
struct VulkanFence : public HwFence {
|
||||
explicit VulkanFence(const VulkanCommandBuffer& commands) : fence(commands.fence) {}
|
||||
struct VulkanFence : public HwFence, VulkanResource {
|
||||
VulkanFence()
|
||||
: VulkanResource(VulkanResourceType::FENCE) {}
|
||||
|
||||
explicit VulkanFence(std::shared_ptr<VulkanCmdFence> fence)
|
||||
: VulkanResource(VulkanResourceType::FENCE),
|
||||
fence(fence) {}
|
||||
|
||||
std::shared_ptr<VulkanCmdFence> fence;
|
||||
};
|
||||
|
||||
struct VulkanSync : public HwSync {
|
||||
VulkanSync() = default;
|
||||
explicit VulkanSync(const VulkanCommandBuffer& commands) : fence(commands.fence) {}
|
||||
std::shared_ptr<VulkanCmdFence> fence;
|
||||
};
|
||||
|
||||
struct VulkanTimerQuery : public HwTimerQuery {
|
||||
struct VulkanTimerQuery : public HwTimerQuery, VulkanThreadSafeResource {
|
||||
explicit VulkanTimerQuery(std::tuple<uint32_t, uint32_t> indices);
|
||||
~VulkanTimerQuery();
|
||||
|
||||
bool isCompleted() const noexcept;
|
||||
void setFence(std::shared_ptr<VulkanCmdFence> fence) noexcept;
|
||||
|
||||
bool isCompleted() noexcept;
|
||||
|
||||
uint32_t getStartingQueryIndex() const {
|
||||
return mStartingQueryIndex;
|
||||
}
|
||||
|
||||
uint32_t getStoppingQueryIndex() const {
|
||||
return mStoppingQueryIndex;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t startingQueryIndex;
|
||||
uint32_t stoppingQueryIndex;
|
||||
uint32_t mStartingQueryIndex;
|
||||
uint32_t mStoppingQueryIndex;
|
||||
|
||||
// TODO: should be safe to hold this reference (since VulkanCommanBuffers is a ring).
|
||||
// But explore better options.
|
||||
std::atomic<VulkanCommandBuffer const*> cmdbuffer = nullptr;
|
||||
friend class VulkanTimestamps;
|
||||
std::shared_ptr<VulkanCmdFence> mFence;
|
||||
utils::Mutex mFenceMutex;
|
||||
};
|
||||
|
||||
inline constexpr VkBufferUsageFlagBits getBufferObjectUsage(
|
||||
|
||||
@@ -101,11 +101,14 @@ getVkTransition(const VulkanLayoutTransition& transition) {
|
||||
srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
||||
srcStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||
break;
|
||||
case VulkanLayout::PRESENT:
|
||||
case VulkanLayout::COLOR_ATTACHMENT_RESOLVE:
|
||||
srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||||
break;
|
||||
case VulkanLayout::PRESENT:
|
||||
srcAccessMask = VK_ACCESS_NONE;
|
||||
srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (transition.newLayout) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user