Compare commits
65
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 |
+7
-7
@@ -5,7 +5,7 @@
|
|||||||
To build Filament, you must first install the following tools:
|
To build Filament, you must first install the following tools:
|
||||||
|
|
||||||
- CMake 3.19 (or more recent)
|
- 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)
|
- [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
|
Additional dependencies may be required for your operating system. Please refer to the appropriate
|
||||||
@@ -87,10 +87,10 @@ Options can also be set with the CMake GUI.
|
|||||||
|
|
||||||
Make sure you've installed the following dependencies:
|
Make sure you've installed the following dependencies:
|
||||||
|
|
||||||
- `clang-7` or higher
|
- `clang-14` or higher
|
||||||
- `libglu1-mesa-dev`
|
- `libglu1-mesa-dev`
|
||||||
- `libc++-7-dev` (`libcxx-devel` and `libcxx-static` on Fedora) or higher
|
- `libc++-14-dev` (`libcxx-devel` and `libcxx-static` on Fedora) or higher
|
||||||
- `libc++abi-7-dev` (`libcxxabi-static` on Fedora) or higher
|
- `libc++abi-14-dev` (`libcxxabi-static` on Fedora) or higher
|
||||||
- `ninja-build`
|
- `ninja-build`
|
||||||
- `libxi-dev`
|
- `libxi-dev`
|
||||||
- `libxcomposite-dev` (`libXcomposite-devel` on Fedora)
|
- `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
|
$ mkdir out/cmake-release
|
||||||
$ cd 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++ \
|
$ CC=/usr/bin/clang CXX=/usr/bin/clang++ CXXFLAGS=-stdlib=libc++ \
|
||||||
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../release/filament ../..
|
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:
|
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-14 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/cc cc /usr/bin/clang 100
|
$ update-alternatives --install /usr/bin/cc cc /usr/bin/clang 100
|
||||||
$ update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 100
|
$ update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 100
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -7,3 +7,9 @@ for next branch cut* header.
|
|||||||
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||||
|
|
||||||
## Release notes for next branch cut
|
## Release notes for next branch cut
|
||||||
|
|
||||||
|
- 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 {
|
dependencies {
|
||||||
implementation 'com.google.android.filament:filament-android:1.40.5'
|
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 |
|
| 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) | 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/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/filament-utils-android) | KTX loading, Kotlin math, and camera utilities, depends on `gltfio-android`. |
|
||||||
| [](https://maven-badges.herokuapp.com/maven-central/com.google.android.filament/filamat-android) | A runtime material builder/compiler. This library is large but contains a full shader compiler/validator/optimizer and supports both OpenGL and Vulkan. |
|
| [](https://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:
|
iOS projects can use CocoaPods to install the latest release:
|
||||||
|
|
||||||
```
|
```
|
||||||
pod 'Filament', '~> 1.40.5'
|
pod 'Filament', '~> 1.42.0'
|
||||||
```
|
```
|
||||||
|
|
||||||
### Snapshots
|
### Snapshots
|
||||||
|
|||||||
@@ -7,6 +7,17 @@ A new header is inserted each time a *tag* is created.
|
|||||||
Instead, if you are authoring a PR for the main branch, add your release note to
|
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||||
|
|
||||||
|
## v1.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
|
## v1.40.5
|
||||||
|
|
||||||
- backend: Disable timer queries on all Mali GPUs (fixes b/233754398)
|
- backend: Disable timer queries on all Mali GPUs (fixes b/233754398)
|
||||||
|
|||||||
@@ -246,17 +246,21 @@ Java_com_google_android_filament_MaterialInstance_nSetFloatParameterArray(JNIEnv
|
|||||||
env->ReleaseStringUTFChars(name_, name);
|
env->ReleaseStringUTFChars(name_, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// defined in TextureSampler.cpp
|
||||||
|
namespace filament::JniUtils {
|
||||||
|
TextureSampler from_long(jlong params) noexcept;
|
||||||
|
} // TextureSamplerJniUtils
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_com_google_android_filament_MaterialInstance_nSetParameterTexture(
|
Java_com_google_android_filament_MaterialInstance_nSetParameterTexture(
|
||||||
JNIEnv *env, jclass, jlong nativeMaterialInstance, jstring name_,
|
JNIEnv *env, jclass, jlong nativeMaterialInstance, jstring name_,
|
||||||
jlong nativeTexture, jint sampler_) {
|
jlong nativeTexture, jlong sampler_) {
|
||||||
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
|
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
|
||||||
Texture* texture = (Texture*) nativeTexture;
|
Texture* texture = (Texture*) nativeTexture;
|
||||||
TextureSampler& sampler = reinterpret_cast<TextureSampler&>(sampler_);
|
|
||||||
|
|
||||||
const char *name = env->GetStringUTFChars(name_, 0);
|
const char *name = env->GetStringUTFChars(name_, 0);
|
||||||
instance->setParameter(name, texture, sampler);
|
instance->setParameter(name, texture, JniUtils::from_long(sampler_));
|
||||||
env->ReleaseStringUTFChars(name_, name);
|
env->ReleaseStringUTFChars(name_, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,11 +27,10 @@ extern "C" JNIEXPORT void JNICALL
|
|||||||
Java_com_google_android_filament_SwapChain_nSetFrameCompletedCallback(JNIEnv* env, jclass,
|
Java_com_google_android_filament_SwapChain_nSetFrameCompletedCallback(JNIEnv* env, jclass,
|
||||||
jlong nativeSwapChain, jobject handler, jobject runnable) {
|
jlong nativeSwapChain, jobject handler, jobject runnable) {
|
||||||
SwapChain* swapChain = (SwapChain*) nativeSwapChain;
|
SwapChain* swapChain = (SwapChain*) nativeSwapChain;
|
||||||
auto *callback = JniCallback::make(env, handler, runnable);
|
auto* callback = JniCallback::make(env, handler, runnable);
|
||||||
swapChain->setFrameCompletedCallback([](void* user) {
|
swapChain->setFrameCompletedCallback(nullptr, [callback](SwapChain* swapChain) {
|
||||||
JniCallback* callback = (JniCallback*)user;
|
|
||||||
JniCallback::postToJavaAndDestroy(callback);
|
JniCallback::postToJavaAndDestroy(callback);
|
||||||
}, callback);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jboolean JNICALL
|
extern "C" JNIEXPORT jboolean JNICALL
|
||||||
|
|||||||
@@ -18,142 +18,139 @@
|
|||||||
|
|
||||||
#include <filament/TextureSampler.h>
|
#include <filament/TextureSampler.h>
|
||||||
|
|
||||||
|
#include <utils/algorithm.h>
|
||||||
|
|
||||||
using namespace filament;
|
using namespace filament;
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL
|
namespace filament::JniUtils {
|
||||||
Java_com_google_android_filament_TextureSampler_nCreateSampler(JNIEnv *env, jclass type, jint min,
|
|
||||||
|
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) {
|
jint max, jint s, jint t, jint r) {
|
||||||
return TextureSampler(static_cast<TextureSampler::MinFilter>(min),
|
TextureSampler sampler(static_cast<TextureSampler::MinFilter>(min),
|
||||||
static_cast<TextureSampler::MagFilter>(max), static_cast<TextureSampler::WrapMode>(s),
|
static_cast<TextureSampler::MagFilter>(max),
|
||||||
static_cast<TextureSampler::WrapMode>(t),
|
static_cast<TextureSampler::WrapMode>(s),
|
||||||
static_cast<TextureSampler::WrapMode>(r)).getSamplerParams().u;
|
static_cast<TextureSampler::WrapMode>(t),
|
||||||
|
static_cast<TextureSampler::WrapMode>(r));
|
||||||
|
return to_long(sampler);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL
|
extern "C" JNIEXPORT jlong JNICALL
|
||||||
Java_com_google_android_filament_TextureSampler_nCreateCompareSampler(JNIEnv *env, jclass type,
|
Java_com_google_android_filament_TextureSampler_nCreateCompareSampler(JNIEnv *, jclass,
|
||||||
jint mode, jint function) {
|
jint mode, jint function) {
|
||||||
return TextureSampler(static_cast<TextureSampler::CompareMode>(mode),
|
TextureSampler sampler(static_cast<TextureSampler::CompareMode>(mode),
|
||||||
static_cast<TextureSampler::CompareFunc>(function)).getSamplerParams().u;
|
static_cast<TextureSampler::CompareFunc>(function));
|
||||||
|
return to_long(sampler);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL
|
extern "C" JNIEXPORT jint JNICALL
|
||||||
Java_com_google_android_filament_TextureSampler_nGetMinFilter(JNIEnv *env, jclass type,
|
Java_com_google_android_filament_TextureSampler_nGetMinFilter(JNIEnv *, jclass, jlong sampler) {
|
||||||
jint sampler_) {
|
return static_cast<jint>(from_long(sampler).getMinFilter());
|
||||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
|
||||||
return static_cast<jint>(sampler.getMinFilter());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL
|
extern "C" JNIEXPORT jlong JNICALL
|
||||||
Java_com_google_android_filament_TextureSampler_nSetMinFilter(JNIEnv *env, jclass type,
|
Java_com_google_android_filament_TextureSampler_nSetMinFilter(JNIEnv *, jclass, jlong sampler_, jint filter) {
|
||||||
jint sampler_, jint filter) {
|
TextureSampler sampler{from_long(sampler_)};
|
||||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
|
||||||
sampler.setMinFilter(static_cast<TextureSampler::MinFilter>(filter));
|
sampler.setMinFilter(static_cast<TextureSampler::MinFilter>(filter));
|
||||||
return sampler.getSamplerParams().u;
|
return to_long(sampler);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL
|
extern "C" JNIEXPORT jint JNICALL
|
||||||
Java_com_google_android_filament_TextureSampler_nGetMagFilter(JNIEnv *env, jclass type,
|
Java_com_google_android_filament_TextureSampler_nGetMagFilter(JNIEnv *, jclass, jlong sampler) {
|
||||||
jint sampler_) {
|
return static_cast<jint>(from_long(sampler).getMagFilter());
|
||||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
|
||||||
return static_cast<jint>(sampler.getMagFilter());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL
|
extern "C" JNIEXPORT jlong JNICALL
|
||||||
Java_com_google_android_filament_TextureSampler_nSetMagFilter(JNIEnv *env, jclass type,
|
Java_com_google_android_filament_TextureSampler_nSetMagFilter(JNIEnv *, jclass, jlong sampler_, jint filter) {
|
||||||
jint sampler_, jint filter) {
|
TextureSampler sampler{from_long(sampler_)};
|
||||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
|
||||||
sampler.setMagFilter(static_cast<TextureSampler::MagFilter>(filter));
|
sampler.setMagFilter(static_cast<TextureSampler::MagFilter>(filter));
|
||||||
return sampler.getSamplerParams().u;
|
return to_long(sampler);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL
|
extern "C" JNIEXPORT jint JNICALL
|
||||||
Java_com_google_android_filament_TextureSampler_nGetWrapModeS(JNIEnv *env, jclass type,
|
Java_com_google_android_filament_TextureSampler_nGetWrapModeS(JNIEnv *, jclass, jlong sampler) {
|
||||||
jint sampler_) {
|
return static_cast<jint>(from_long(sampler).getWrapModeS());
|
||||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
|
||||||
return static_cast<jint>(sampler.getWrapModeS());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL
|
extern "C" JNIEXPORT jlong JNICALL
|
||||||
Java_com_google_android_filament_TextureSampler_nSetWrapModeS(JNIEnv *env, jclass type,
|
Java_com_google_android_filament_TextureSampler_nSetWrapModeS(JNIEnv *, jclass, jlong sampler_, jint mode) {
|
||||||
jint sampler_, jint mode) {
|
TextureSampler sampler{from_long(sampler_)};
|
||||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
|
||||||
sampler.setWrapModeS(static_cast<TextureSampler::WrapMode>(mode));
|
sampler.setWrapModeS(static_cast<TextureSampler::WrapMode>(mode));
|
||||||
return sampler.getSamplerParams().u;
|
return to_long(sampler);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL
|
extern "C" JNIEXPORT jint JNICALL
|
||||||
Java_com_google_android_filament_TextureSampler_nGetWrapModeT(JNIEnv *env, jclass type,
|
Java_com_google_android_filament_TextureSampler_nGetWrapModeT(JNIEnv *, jclass, jlong sampler) {
|
||||||
jint sampler_) {
|
return static_cast<jint>(from_long(sampler).getWrapModeT());
|
||||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
|
||||||
return static_cast<jint>(sampler.getWrapModeT());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL
|
extern "C" JNIEXPORT jlong JNICALL
|
||||||
Java_com_google_android_filament_TextureSampler_nSetWrapModeT(JNIEnv *env, jclass type,
|
Java_com_google_android_filament_TextureSampler_nSetWrapModeT(JNIEnv *, jclass, jlong sampler_, jint mode) {
|
||||||
jint sampler_, jint mode) {
|
TextureSampler sampler{from_long(sampler_)};
|
||||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
|
||||||
sampler.setWrapModeT(static_cast<TextureSampler::WrapMode>(mode));
|
sampler.setWrapModeT(static_cast<TextureSampler::WrapMode>(mode));
|
||||||
return sampler.getSamplerParams().u;
|
return to_long(sampler);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL
|
extern "C" JNIEXPORT jint JNICALL
|
||||||
Java_com_google_android_filament_TextureSampler_nGetWrapModeR(JNIEnv *env, jclass type,
|
Java_com_google_android_filament_TextureSampler_nGetWrapModeR(JNIEnv *, jclass, jlong sampler) {
|
||||||
jint sampler_) {
|
return static_cast<jint>(from_long(sampler).getWrapModeR());
|
||||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
|
||||||
return static_cast<jint>(sampler.getWrapModeR());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL
|
extern "C" JNIEXPORT jlong JNICALL
|
||||||
Java_com_google_android_filament_TextureSampler_nSetWrapModeR(JNIEnv *env, jclass type,
|
Java_com_google_android_filament_TextureSampler_nSetWrapModeR(JNIEnv *, jclass, jlong sampler_, jint mode) {
|
||||||
jint sampler_, jint mode) {
|
TextureSampler sampler{from_long(sampler_)};
|
||||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
|
||||||
sampler.setWrapModeR(static_cast<TextureSampler::WrapMode>(mode));
|
sampler.setWrapModeR(static_cast<TextureSampler::WrapMode>(mode));
|
||||||
return sampler.getSamplerParams().u;
|
return to_long(sampler);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL
|
extern "C" JNIEXPORT jint JNICALL
|
||||||
Java_com_google_android_filament_TextureSampler_nGetCompareMode(JNIEnv *env, jclass type,
|
Java_com_google_android_filament_TextureSampler_nGetCompareMode(JNIEnv *, jclass, jlong sampler) {
|
||||||
jint sampler_) {
|
return static_cast<jint>(from_long(sampler).getCompareMode());
|
||||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
|
||||||
return static_cast<jint>(sampler.getCompareMode());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL
|
extern "C" JNIEXPORT jlong JNICALL
|
||||||
Java_com_google_android_filament_TextureSampler_nSetCompareMode(JNIEnv *env, jclass type,
|
Java_com_google_android_filament_TextureSampler_nSetCompareMode(JNIEnv *, jclass, jlong sampler_, jint mode) {
|
||||||
jint sampler_, jint mode) {
|
TextureSampler sampler{from_long(sampler_)};
|
||||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
|
||||||
sampler.setCompareMode(static_cast<TextureSampler::CompareMode>(mode),
|
sampler.setCompareMode(static_cast<TextureSampler::CompareMode>(mode),
|
||||||
sampler.getCompareFunc());
|
sampler.getCompareFunc());
|
||||||
return sampler.getSamplerParams().u;
|
return to_long(sampler);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL
|
extern "C" JNIEXPORT jint JNICALL
|
||||||
Java_com_google_android_filament_TextureSampler_nGetCompareFunction(JNIEnv *env, jclass type,
|
Java_com_google_android_filament_TextureSampler_nGetCompareFunction(JNIEnv *, jclass, jlong sampler) {
|
||||||
jint sampler_) {
|
return static_cast<jint>(from_long(sampler).getCompareFunc());
|
||||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
|
||||||
return static_cast<jint>(sampler.getCompareFunc());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL
|
extern "C" JNIEXPORT jlong JNICALL
|
||||||
Java_com_google_android_filament_TextureSampler_nSetCompareFunction(JNIEnv *env, jclass type,
|
Java_com_google_android_filament_TextureSampler_nSetCompareFunction(JNIEnv *, jclass, jlong sampler_, jint function) {
|
||||||
jint sampler_, jint function) {
|
TextureSampler sampler{from_long(sampler_)};
|
||||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
|
||||||
sampler.setCompareMode(sampler.getCompareMode(),
|
sampler.setCompareMode(sampler.getCompareMode(),
|
||||||
static_cast<TextureSampler::CompareFunc>(function));
|
static_cast<TextureSampler::CompareFunc>(function));
|
||||||
return sampler.getSamplerParams().u;
|
return to_long(sampler);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jfloat JNICALL
|
extern "C" JNIEXPORT jfloat JNICALL
|
||||||
Java_com_google_android_filament_TextureSampler_nGetAnisotropy(JNIEnv *env, jclass type,
|
Java_com_google_android_filament_TextureSampler_nGetAnisotropy(JNIEnv *, jclass, jlong sampler) {
|
||||||
jint sampler_) {
|
return from_long(sampler).getAnisotropy();
|
||||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
|
||||||
return sampler.getAnisotropy();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL
|
extern "C" JNIEXPORT jlong JNICALL
|
||||||
Java_com_google_android_filament_TextureSampler_nSetAnisotropy(JNIEnv *env, jclass type,
|
Java_com_google_android_filament_TextureSampler_nSetAnisotropy(JNIEnv *, jclass, jlong sampler_, jfloat anisotropy) {
|
||||||
jint sampler_, jfloat anisotropy) {
|
TextureSampler sampler{from_long(sampler_)};
|
||||||
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
|
|
||||||
sampler.setAnisotropy(anisotropy);
|
sampler.setAnisotropy(anisotropy);
|
||||||
return sampler.getSamplerParams().u;
|
return to_long(sampler);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -900,7 +900,7 @@ public class MaterialInstance {
|
|||||||
@IntRange(from = 0) int offset, @IntRange(from = 1) int count);
|
@IntRange(from = 0) int offset, @IntRange(from = 1) int count);
|
||||||
|
|
||||||
private static native void nSetParameterTexture(long nativeMaterialInstance,
|
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,
|
private static native void nSetScissor(long nativeMaterialInstance,
|
||||||
@IntRange(from = 0) int left, @IntRange(from = 0) int bottom,
|
@IntRange(from = 0) int left, @IntRange(from = 0) int bottom,
|
||||||
|
|||||||
@@ -81,8 +81,6 @@ public class RenderTarget {
|
|||||||
/**
|
/**
|
||||||
* Sets a texture to a given attachment point.
|
* 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 attachment The attachment point of the texture.
|
||||||
* @param texture The associated texture object.
|
* @param texture The associated texture object.
|
||||||
* @return A reference to this Builder for chaining calls.
|
* @return A reference to this Builder for chaining calls.
|
||||||
|
|||||||
@@ -137,10 +137,6 @@ public class SwapChain {
|
|||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <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
|
* Warning: Only Filament's Metal backend supports frame callbacks. Other backends ignore the
|
||||||
* callback (which will never be called) and proceed normally.
|
* callback (which will never be called) and proceed normally.
|
||||||
* </p>
|
* </p>
|
||||||
|
|||||||
+19
-19
@@ -126,7 +126,7 @@ public class TextureSampler {
|
|||||||
NEVER
|
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.
|
* 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 long nCreateSampler(int min, int max, int s, int t, int r);
|
||||||
private static native int nCreateCompareSampler(int mode, int function);
|
private static native long nCreateCompareSampler(int mode, int function);
|
||||||
|
|
||||||
private static native int nGetMinFilter(int sampler);
|
private static native int nGetMinFilter(long sampler);
|
||||||
private static native int nSetMinFilter(int sampler, int filter);
|
private static native long nSetMinFilter(long sampler, int filter);
|
||||||
private static native int nGetMagFilter(int sampler);
|
private static native int nGetMagFilter(long sampler);
|
||||||
private static native int nSetMagFilter(int sampler, int filter);
|
private static native long nSetMagFilter(long sampler, int filter);
|
||||||
|
|
||||||
private static native int nGetWrapModeS(int sampler);
|
private static native int nGetWrapModeS(long sampler);
|
||||||
private static native int nSetWrapModeS(int sampler, int mode);
|
private static native long nSetWrapModeS(long sampler, int mode);
|
||||||
private static native int nGetWrapModeT(int sampler);
|
private static native int nGetWrapModeT(long sampler);
|
||||||
private static native int nSetWrapModeT(int sampler, int mode);
|
private static native long nSetWrapModeT(long sampler, int mode);
|
||||||
private static native int nGetWrapModeR(int sampler);
|
private static native int nGetWrapModeR(long sampler);
|
||||||
private static native int nSetWrapModeR(int sampler, int mode);
|
private static native long nSetWrapModeR(long sampler, int mode);
|
||||||
|
|
||||||
private static native int nGetCompareMode(int sampler);
|
private static native int nGetCompareMode(long sampler);
|
||||||
private static native int nSetCompareMode(int sampler, int mode);
|
private static native long nSetCompareMode(long sampler, int mode);
|
||||||
private static native int nGetCompareFunction(int sampler);
|
private static native int nGetCompareFunction(long sampler);
|
||||||
private static native int nSetCompareFunction(int sampler, int function);
|
private static native long nSetCompareFunction(long sampler, int function);
|
||||||
|
|
||||||
private static native float nGetAnisotropy(int sampler);
|
private static native float nGetAnisotropy(long sampler);
|
||||||
private static native int nSetAnisotropy(int sampler, float anisotropy);
|
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.Asserts.assertFloat4In;
|
||||||
import static com.google.android.filament.Colors.LinearColor;
|
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}.
|
* Encompasses all the state needed for rendering a {@link Scene}.
|
||||||
*
|
*
|
||||||
@@ -965,7 +967,8 @@ public class View {
|
|||||||
options.heightFalloff, options.cutOffDistance,
|
options.heightFalloff, options.cutOffDistance,
|
||||||
options.color[0], options.color[1], options.color[2],
|
options.color[0], options.color[1], options.color[2],
|
||||||
options.density, options.inScatteringStart, options.inScatteringSize,
|
options.density, options.inScatteringStart, options.inScatteringSize,
|
||||||
options.fogColorFromIbl, options.skyColor.getNativeObject(),
|
options.fogColorFromIbl,
|
||||||
|
options.skyColor == null ? 0 : options.skyColor.getNativeObject(),
|
||||||
options.enabled);
|
options.enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1095,10 +1098,29 @@ public class View {
|
|||||||
nPick(getNativeObject(), x, y, handler, internalCallback);
|
nPick(getNativeObject(), x, y, handler, internalCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UsedByNative("View.cpp")
|
||||||
private static class InternalOnPickCallback implements Runnable {
|
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) {
|
public InternalOnPickCallback(OnPickCallback mUserCallback) {
|
||||||
this.mUserCallback = mUserCallback;
|
this.mUserCallback = mUserCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
mPickingQueryResult.renderable = mRenderable;
|
mPickingQueryResult.renderable = mRenderable;
|
||||||
@@ -1108,13 +1130,6 @@ public class View {
|
|||||||
mPickingQueryResult.fragCoords[2] = mFragCoordsZ;
|
mPickingQueryResult.fragCoords[2] = mFragCoordsZ;
|
||||||
mUserCallback.onPick(mPickingQueryResult);
|
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)
|
* 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)
|
* bloom x/y aspect-ratio (1/32 to 32)
|
||||||
*/
|
*/
|
||||||
public float anamorphism = 1.0f;
|
public float anamorphism = 1.0f;
|
||||||
/**
|
/**
|
||||||
* number of blur levels (3 to 11)
|
* number of blur levels (1 to 11)
|
||||||
*/
|
*/
|
||||||
public int levels = 6;
|
public int levels = 6;
|
||||||
/**
|
/**
|
||||||
@@ -1971,4 +1986,11 @@ public class View {
|
|||||||
*/
|
*/
|
||||||
public float penumbraRatioScale = 1.0f;
|
public float penumbraRatioScale = 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for stereoscopic (multi-eye) rendering.
|
||||||
|
*/
|
||||||
|
public static class StereoscopicOptions {
|
||||||
|
public boolean enabled = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
GROUP=com.google.android.filament
|
GROUP=com.google.android.filament
|
||||||
VERSION_NAME=1.40.5
|
VERSION_NAME=1.42.0
|
||||||
|
|
||||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||||
|
|
||||||
|
|||||||
+21
@@ -28,6 +28,7 @@ import com.google.android.filament.Fence
|
|||||||
import com.google.android.filament.IndirectLight
|
import com.google.android.filament.IndirectLight
|
||||||
import com.google.android.filament.Skybox
|
import com.google.android.filament.Skybox
|
||||||
import com.google.android.filament.View
|
import com.google.android.filament.View
|
||||||
|
import com.google.android.filament.View.OnPickCallback
|
||||||
import com.google.android.filament.utils.*
|
import com.google.android.filament.utils.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -56,7 +57,9 @@ class MainActivity : Activity() {
|
|||||||
private lateinit var modelViewer: ModelViewer
|
private lateinit var modelViewer: ModelViewer
|
||||||
private lateinit var titlebarHint: TextView
|
private lateinit var titlebarHint: TextView
|
||||||
private val doubleTapListener = DoubleTapListener()
|
private val doubleTapListener = DoubleTapListener()
|
||||||
|
private val singleTapListener = SingleTapListener()
|
||||||
private lateinit var doubleTapDetector: GestureDetector
|
private lateinit var doubleTapDetector: GestureDetector
|
||||||
|
private lateinit var singleTapDetector: GestureDetector
|
||||||
private var remoteServer: RemoteServer? = null
|
private var remoteServer: RemoteServer? = null
|
||||||
private var statusToast: Toast? = null
|
private var statusToast: Toast? = null
|
||||||
private var statusText: String? = null
|
private var statusText: String? = null
|
||||||
@@ -77,6 +80,7 @@ class MainActivity : Activity() {
|
|||||||
choreographer = Choreographer.getInstance()
|
choreographer = Choreographer.getInstance()
|
||||||
|
|
||||||
doubleTapDetector = GestureDetector(applicationContext, doubleTapListener)
|
doubleTapDetector = GestureDetector(applicationContext, doubleTapListener)
|
||||||
|
singleTapDetector = GestureDetector(applicationContext, singleTapListener)
|
||||||
|
|
||||||
modelViewer = ModelViewer(surfaceView)
|
modelViewer = ModelViewer(surfaceView)
|
||||||
viewerContent.view = modelViewer.view
|
viewerContent.view = modelViewer.view
|
||||||
@@ -88,6 +92,7 @@ class MainActivity : Activity() {
|
|||||||
surfaceView.setOnTouchListener { _, event ->
|
surfaceView.setOnTouchListener { _, event ->
|
||||||
modelViewer.onTouchEvent(event)
|
modelViewer.onTouchEvent(event)
|
||||||
doubleTapDetector.onTouchEvent(event)
|
doubleTapDetector.onTouchEvent(event)
|
||||||
|
singleTapDetector.onTouchEvent(event)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,6 +234,7 @@ class MainActivity : Activity() {
|
|||||||
modelViewer.scene.skybox = sky
|
modelViewer.scene.skybox = sky
|
||||||
modelViewer.scene.indirectLight = ibl
|
modelViewer.scene.indirectLight = ibl
|
||||||
viewerContent.indirectLight = ibl
|
viewerContent.indirectLight = ibl
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -430,4 +436,19 @@ class MainActivity : Activity() {
|
|||||||
return super.onDoubleTap(e)
|
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() {
|
private fun setupView() {
|
||||||
val ssaoOptions = view.ambientOcclusionOptions
|
// ambient occlusion is the cheapest effect that adds a lot of quality
|
||||||
ssaoOptions.enabled = true
|
view.ambientOcclusionOptions = view.ambientOcclusionOptions.apply {
|
||||||
view.ambientOcclusionOptions = ssaoOptions
|
enabled = true
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: Try to disable post-processing (tone-mapping, etc.) to see the difference
|
// NOTE: Try to disable post-processing (tone-mapping, etc.) to see the difference
|
||||||
// view.isPostProcessingEnabled = false
|
// view.isPostProcessingEnabled = false
|
||||||
|
|||||||
@@ -1139,7 +1139,8 @@ Type
|
|||||||
: array of `string`
|
: array of `string`
|
||||||
|
|
||||||
Value
|
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
|
Description
|
||||||
: Used to specify a list of shader variants that the application guarantees will never be
|
: 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
|
- `fog`, used when global fog is applied to the scene
|
||||||
- `vsm`, used when VSM shadows are enabled and the object is a shadow receiver
|
- `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
|
- `ssr`, used when screen-space reflections are enabled in the View
|
||||||
|
- `stereo`, used when stereoscopic rendering is enabled in the View
|
||||||
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
|
||||||
material {
|
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/BackendUtils.cpp
|
||||||
src/BlobCacheKey.cpp
|
src/BlobCacheKey.cpp
|
||||||
src/Callable.cpp
|
src/Callable.cpp
|
||||||
src/CallbackHandler.cpp
|
|
||||||
src/CircularBuffer.cpp
|
src/CircularBuffer.cpp
|
||||||
src/CommandBufferQueue.cpp
|
src/CommandBufferQueue.cpp
|
||||||
src/CommandStream.cpp
|
src/CommandStream.cpp
|
||||||
|
src/CompilerThreadPool.cpp
|
||||||
src/Driver.cpp
|
src/Driver.cpp
|
||||||
src/Handle.cpp
|
src/Handle.cpp
|
||||||
src/HandleAllocator.cpp
|
src/HandleAllocator.cpp
|
||||||
@@ -55,6 +55,7 @@ set(PRIVATE_HDRS
|
|||||||
include/private/backend/PlatformFactory.h
|
include/private/backend/PlatformFactory.h
|
||||||
include/private/backend/SamplerGroup.h
|
include/private/backend/SamplerGroup.h
|
||||||
src/CommandStreamDispatcher.h
|
src/CommandStreamDispatcher.h
|
||||||
|
src/CompilerThreadPool.h
|
||||||
src/DataReshaper.h
|
src/DataReshaper.h
|
||||||
src/DriverBase.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)
|
if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3 AND NOT FILAMENT_USE_SWIFTSHADER)
|
||||||
list(APPEND SRCS
|
list(APPEND SRCS
|
||||||
include/backend/platforms/OpenGLPlatform.h
|
include/backend/platforms/OpenGLPlatform.h
|
||||||
|
src/opengl/CallbackManager.h
|
||||||
|
src/opengl/CallbackManager.cpp
|
||||||
src/opengl/gl_headers.cpp
|
src/opengl/gl_headers.cpp
|
||||||
src/opengl/gl_headers.h
|
src/opengl/gl_headers.h
|
||||||
src/opengl/GLUtils.cpp
|
src/opengl/GLUtils.cpp
|
||||||
@@ -174,8 +177,6 @@ if (FILAMENT_SUPPORTS_VULKAN)
|
|||||||
src/vulkan/VulkanConstants.h
|
src/vulkan/VulkanConstants.h
|
||||||
src/vulkan/VulkanContext.cpp
|
src/vulkan/VulkanContext.cpp
|
||||||
src/vulkan/VulkanContext.h
|
src/vulkan/VulkanContext.h
|
||||||
src/vulkan/VulkanDisposer.cpp
|
|
||||||
src/vulkan/VulkanDisposer.h
|
|
||||||
src/vulkan/VulkanDriver.cpp
|
src/vulkan/VulkanDriver.cpp
|
||||||
src/vulkan/VulkanDriver.h
|
src/vulkan/VulkanDriver.h
|
||||||
src/vulkan/VulkanDriverFactory.h
|
src/vulkan/VulkanDriverFactory.h
|
||||||
@@ -197,6 +198,9 @@ if (FILAMENT_SUPPORTS_VULKAN)
|
|||||||
src/vulkan/VulkanSwapChain.h
|
src/vulkan/VulkanSwapChain.h
|
||||||
src/vulkan/VulkanReadPixels.cpp
|
src/vulkan/VulkanReadPixels.cpp
|
||||||
src/vulkan/VulkanReadPixels.h
|
src/vulkan/VulkanReadPixels.h
|
||||||
|
src/vulkan/VulkanResourceAllocator.h
|
||||||
|
src/vulkan/VulkanResources.cpp
|
||||||
|
src/vulkan/VulkanResources.h
|
||||||
src/vulkan/VulkanTexture.cpp
|
src/vulkan/VulkanTexture.cpp
|
||||||
src/vulkan/VulkanTexture.h
|
src/vulkan/VulkanTexture.h
|
||||||
src/vulkan/VulkanUtility.cpp
|
src/vulkan/VulkanUtility.cpp
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public:
|
|||||||
virtual void post(void* user, Callback callback) = 0;
|
virtual void post(void* user, Callback callback) = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual ~CallbackHandler();
|
virtual ~CallbackHandler() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace filament::backend
|
} // namespace filament::backend
|
||||||
|
|||||||
@@ -796,32 +796,53 @@ enum class SamplerCompareFunc : uint8_t {
|
|||||||
|
|
||||||
//! Sampler parameters
|
//! Sampler parameters
|
||||||
struct SamplerParams { // NOLINT
|
struct SamplerParams { // NOLINT
|
||||||
union {
|
SamplerMagFilter filterMag : 1; //!< magnification filter (NEAREST)
|
||||||
struct {
|
SamplerMinFilter filterMin : 3; //!< minification filter (NEAREST)
|
||||||
SamplerMagFilter filterMag : 1; //!< magnification filter (NEAREST)
|
SamplerWrapMode wrapS : 2; //!< s-coordinate wrap mode (CLAMP_TO_EDGE)
|
||||||
SamplerMinFilter filterMin : 3; //!< minification filter (NEAREST)
|
SamplerWrapMode wrapT : 2; //!< t-coordinate wrap mode (CLAMP_TO_EDGE)
|
||||||
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)
|
SamplerWrapMode wrapR : 2; //!< r-coordinate wrap mode (CLAMP_TO_EDGE)
|
||||||
uint8_t anisotropyLog2 : 3; //!< anisotropy level (0)
|
uint8_t anisotropyLog2 : 3; //!< anisotropy level (0)
|
||||||
SamplerCompareMode compareMode : 1; //!< sampler compare mode (NONE)
|
SamplerCompareMode compareMode : 1; //!< sampler compare mode (NONE)
|
||||||
uint8_t padding0 : 2; //!< reserved. must be 0.
|
uint8_t padding0 : 2; //!< reserved. must be 0.
|
||||||
|
|
||||||
SamplerCompareFunc compareFunc : 3; //!< sampler comparison function (LE)
|
SamplerCompareFunc compareFunc : 3; //!< sampler comparison function (LE)
|
||||||
uint8_t padding1 : 5; //!< reserved. must be 0.
|
uint8_t padding1 : 5; //!< reserved. must be 0.
|
||||||
|
uint8_t padding2 : 8; //!< reserved. must be 0.
|
||||||
|
|
||||||
uint8_t padding2 : 8; //!< reserved. must be 0.
|
struct Hasher {
|
||||||
};
|
size_t operator()(SamplerParams p) const noexcept {
|
||||||
uint32_t u;
|
// 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:
|
private:
|
||||||
friend inline bool operator < (SamplerParams lhs, SamplerParams rhs) {
|
friend inline bool operator < (SamplerParams lhs, SamplerParams rhs) noexcept {
|
||||||
return lhs.u < rhs.u;
|
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
|
//! blending equation function
|
||||||
enum class BlendEquation : uint8_t {
|
enum class BlendEquation : uint8_t {
|
||||||
@@ -1126,8 +1147,6 @@ static_assert(sizeof(StencilState) == 12u,
|
|||||||
|
|
||||||
using FrameScheduledCallback = void(*)(PresentCallable callable, void* user);
|
using FrameScheduledCallback = void(*)(PresentCallable callable, void* user);
|
||||||
|
|
||||||
using FrameCompletedCallback = void(*)(void* user);
|
|
||||||
|
|
||||||
enum class Workaround : uint16_t {
|
enum class Workaround : uint16_t {
|
||||||
// The EASU pass must split because shader compiler flattens early-exit branch
|
// The EASU pass must split because shader compiler flattens early-exit branch
|
||||||
SPLIT_EASU,
|
SPLIT_EASU,
|
||||||
@@ -1141,6 +1160,11 @@ enum class Workaround : uint16_t {
|
|||||||
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.
|
// Adreno drivers sometimes aren't able to blit into a layer of a texture array.
|
||||||
DISABLE_BLIT_INTO_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
|
} // namespace filament::backend
|
||||||
|
|||||||
@@ -288,6 +288,12 @@ public:
|
|||||||
* @see terminate()
|
* @see terminate()
|
||||||
*/
|
*/
|
||||||
virtual void createContext(bool shared);
|
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
|
} // namespace filament
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ public:
|
|||||||
PlatformEGL() noexcept;
|
PlatformEGL() noexcept;
|
||||||
bool isExtraContextSupported() const noexcept override;
|
bool isExtraContextSupported() const noexcept override;
|
||||||
void createContext(bool shared) override;
|
void createContext(bool shared) override;
|
||||||
|
void releaseContext() noexcept override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
@@ -139,6 +140,7 @@ protected:
|
|||||||
bool KHR_create_context = false;
|
bool KHR_create_context = false;
|
||||||
bool KHR_gl_colorspace = false;
|
bool KHR_gl_colorspace = false;
|
||||||
bool KHR_no_config_context = false;
|
bool KHR_no_config_context = false;
|
||||||
|
bool KHR_surfaceless_context = false;
|
||||||
} egl;
|
} egl;
|
||||||
} ext;
|
} ext;
|
||||||
|
|
||||||
|
|||||||
@@ -73,14 +73,32 @@ public:
|
|||||||
// a cost here (writing and reading the stack at each iteration), in the end it's
|
// 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.
|
// probably better to pay the cost at just one location.
|
||||||
intptr_t next;
|
intptr_t next;
|
||||||
|
driver.mCurrentExecutingCommand = this;
|
||||||
mExecute(driver, this, &next);
|
mExecute(driver, this, &next);
|
||||||
return reinterpret_cast<CommandBase*>(reinterpret_cast<intptr_t>(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;
|
inline ~CommandBase() noexcept = default;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Execute mExecute;
|
Execute mExecute;
|
||||||
|
std::array<intptr_t, 16> mCallstack = {0};
|
||||||
};
|
};
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
@@ -218,6 +236,7 @@ public:
|
|||||||
using Cmd = COMMAND_TYPE(methodName); \
|
using Cmd = COMMAND_TYPE(methodName); \
|
||||||
void* const p = allocateCommand(CommandBase::align(sizeof(Cmd))); \
|
void* const p = allocateCommand(CommandBase::align(sizeof(Cmd))); \
|
||||||
new(p) Cmd(mDispatcher.methodName##_, APPLY(std::move, params)); \
|
new(p) Cmd(mDispatcher.methodName##_, APPLY(std::move, params)); \
|
||||||
|
((Cmd*)p)->captureCallstack(); \
|
||||||
DEBUG_COMMAND_END(methodName, false); \
|
DEBUG_COMMAND_END(methodName, false); \
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,6 +256,7 @@ public:
|
|||||||
using Cmd = COMMAND_TYPE(methodName##R); \
|
using Cmd = COMMAND_TYPE(methodName##R); \
|
||||||
void* const p = allocateCommand(CommandBase::align(sizeof(Cmd))); \
|
void* const p = allocateCommand(CommandBase::align(sizeof(Cmd))); \
|
||||||
new(p) Cmd(mDispatcher.methodName##_, RetType(result), APPLY(std::move, params)); \
|
new(p) Cmd(mDispatcher.methodName##_, RetType(result), APPLY(std::move, params)); \
|
||||||
|
((Cmd*)p)->captureCallstack(); \
|
||||||
DEBUG_COMMAND_END(methodName, false); \
|
DEBUG_COMMAND_END(methodName, false); \
|
||||||
return result; \
|
return result; \
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ template<typename T>
|
|||||||
class ConcreteDispatcher;
|
class ConcreteDispatcher;
|
||||||
class Dispatcher;
|
class Dispatcher;
|
||||||
class CommandStream;
|
class CommandStream;
|
||||||
|
class CommandBase;
|
||||||
|
|
||||||
class Driver {
|
class Driver {
|
||||||
public:
|
public:
|
||||||
@@ -83,6 +84,8 @@ public:
|
|||||||
virtual void debugCommandEnd(CommandStream* cmds,
|
virtual void debugCommandEnd(CommandStream* cmds,
|
||||||
bool synchronous, const char* methodName) noexcept = 0;
|
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
|
* 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.
|
* 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,
|
DECL_DRIVER_API_N(setFrameCompletedCallback,
|
||||||
backend::SwapChainHandle, sch,
|
backend::SwapChainHandle, sch,
|
||||||
backend::FrameCompletedCallback, callback,
|
backend::CallbackHandler*, handler,
|
||||||
|
backend::CallbackHandler::Callback, callback,
|
||||||
void*, user)
|
void*, user)
|
||||||
|
|
||||||
DECL_DRIVER_API_N(setPresentationTime,
|
DECL_DRIVER_API_N(setPresentationTime,
|
||||||
@@ -273,6 +274,7 @@ DECL_DRIVER_API_N(destroyRenderTarget, backend::RenderTargetHandle, rth)
|
|||||||
DECL_DRIVER_API_N(destroySwapChain, backend::SwapChainHandle, sch)
|
DECL_DRIVER_API_N(destroySwapChain, backend::SwapChainHandle, sch)
|
||||||
DECL_DRIVER_API_N(destroyStream, backend::StreamHandle, sh)
|
DECL_DRIVER_API_N(destroyStream, backend::StreamHandle, sh)
|
||||||
DECL_DRIVER_API_N(destroyTimerQuery, backend::TimerQueryHandle, sh)
|
DECL_DRIVER_API_N(destroyTimerQuery, backend::TimerQueryHandle, sh)
|
||||||
|
DECL_DRIVER_API_N(destroyFence, backend::FenceHandle, fh)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Synchronous APIs
|
* Synchronous APIs
|
||||||
@@ -286,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(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(int64_t, getStreamTimestamp, backend::StreamHandle, stream)
|
||||||
DECL_DRIVER_API_SYNCHRONOUS_N(void, updateStreams, backend::DriverApi*, driver)
|
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, getFenceStatus, backend::FenceHandle, fh)
|
||||||
DECL_DRIVER_API_SYNCHRONOUS_N(backend::FenceStatus, wait, backend::FenceHandle, fh, uint64_t, timeout)
|
|
||||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isTextureFormatSupported, backend::TextureFormat, format)
|
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isTextureFormatSupported, backend::TextureFormat, format)
|
||||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isTextureSwizzleSupported)
|
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isTextureSwizzleSupported)
|
||||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isTextureFormatMipmappable, backend::TextureFormat, format)
|
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isTextureFormatMipmappable, backend::TextureFormat, format)
|
||||||
@@ -297,6 +298,8 @@ DECL_DRIVER_API_SYNCHRONOUS_0(bool, isFrameBufferFetchMultiSampleSupported)
|
|||||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isFrameTimeSupported)
|
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isFrameTimeSupported)
|
||||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isAutoDepthResolveSupported)
|
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isAutoDepthResolveSupported)
|
||||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isSRGBSwapChainSupported)
|
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(uint8_t, getMaxDrawBuffers)
|
||||||
DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getMaxUniformBufferSize)
|
DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getMaxUniformBufferSize)
|
||||||
DECL_DRIVER_API_SYNCHRONOUS_0(math::float2, getClipSpaceParams)
|
DECL_DRIVER_API_SYNCHRONOUS_0(math::float2, getClipSpaceParams)
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2021 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <backend/CallbackHandler.h>
|
|
||||||
|
|
||||||
namespace filament::backend {
|
|
||||||
|
|
||||||
CallbackHandler::~CallbackHandler() = default;
|
|
||||||
|
|
||||||
} // namespace filament::backend
|
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -165,13 +165,6 @@ public:
|
|||||||
|
|
||||||
void purge() noexcept final;
|
void purge() noexcept final;
|
||||||
|
|
||||||
// --------------------------------------------------------------------------------------------
|
|
||||||
// Privates
|
|
||||||
// --------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
protected:
|
|
||||||
class CallbackDataDetails;
|
|
||||||
|
|
||||||
// Helpers...
|
// Helpers...
|
||||||
struct CallbackData {
|
struct CallbackData {
|
||||||
CallbackData(CallbackData const &) = delete;
|
CallbackData(CallbackData const &) = delete;
|
||||||
@@ -202,6 +195,13 @@ protected:
|
|||||||
|
|
||||||
void scheduleCallback(CallbackHandler* handler, void* user, CallbackHandler::Callback callback);
|
void scheduleCallback(CallbackHandler* handler, void* user, CallbackHandler::Callback callback);
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
|
// Privates
|
||||||
|
// --------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
protected:
|
||||||
|
class CallbackDataDetails;
|
||||||
|
|
||||||
inline void scheduleDestroy(BufferDescriptor&& buffer) noexcept {
|
inline void scheduleDestroy(BufferDescriptor&& buffer) noexcept {
|
||||||
if (buffer.hasCallback()) {
|
if (buffer.hasCallback()) {
|
||||||
scheduleDestroySlow(std::move(buffer));
|
scheduleDestroySlow(std::move(buffer));
|
||||||
|
|||||||
@@ -176,9 +176,9 @@ void MetalDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MetalDriver::setFrameCompletedCallback(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);
|
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 {
|
void MetalDriver::execute(std::function<void(void)> const& fn) noexcept {
|
||||||
@@ -607,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);
|
auto* fence = handle_cast<MetalFence>(fh);
|
||||||
if (!fence) {
|
if (!fence) {
|
||||||
return FenceStatus::ERROR;
|
return FenceStatus::ERROR;
|
||||||
}
|
}
|
||||||
return fence->wait(timeout);
|
return fence->wait(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MetalDriver::isTextureFormatSupported(TextureFormat format) {
|
bool MetalDriver::isTextureFormatSupported(TextureFormat format) {
|
||||||
@@ -696,6 +696,14 @@ bool MetalDriver::isSRGBSwapChainSupported() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MetalDriver::isStereoSupported() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MetalDriver::isParallelShaderCompileSupported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool MetalDriver::isWorkaroundNeeded(Workaround workaround) {
|
bool MetalDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||||
switch (workaround) {
|
switch (workaround) {
|
||||||
case Workaround::SPLIT_EASU:
|
case Workaround::SPLIT_EASU:
|
||||||
@@ -708,6 +716,8 @@ bool MetalDriver::isWorkaroundNeeded(Workaround workaround) {
|
|||||||
return mContext->bugs.a8xStaticTextureTargetError;
|
return mContext->bugs.a8xStaticTextureTargetError;
|
||||||
case Workaround::DISABLE_BLIT_INTO_TEXTURE_ARRAY:
|
case Workaround::DISABLE_BLIT_INTO_TEXTURE_ARRAY:
|
||||||
return false;
|
return false;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,8 @@ public:
|
|||||||
void releaseDrawable();
|
void releaseDrawable();
|
||||||
|
|
||||||
void setFrameScheduledCallback(FrameScheduledCallback callback, void* user);
|
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
|
// For CAMetalLayer-backed SwapChains, presents the drawable or schedules a
|
||||||
// FrameScheduledCallback.
|
// FrameScheduledCallback.
|
||||||
@@ -112,8 +113,11 @@ private:
|
|||||||
FrameScheduledCallback frameScheduledCallback = nullptr;
|
FrameScheduledCallback frameScheduledCallback = nullptr;
|
||||||
void* frameScheduledUserData = nullptr;
|
void* frameScheduledUserData = nullptr;
|
||||||
|
|
||||||
FrameCompletedCallback frameCompletedCallback = nullptr;
|
struct {
|
||||||
void* frameCompletedUserData = nullptr;
|
CallbackHandler* handler = nullptr;
|
||||||
|
CallbackHandler::Callback callback = {};
|
||||||
|
void* user = nullptr;
|
||||||
|
} frameCompleted;
|
||||||
};
|
};
|
||||||
|
|
||||||
class MetalBufferObject : public HwBufferObject {
|
class MetalBufferObject : public HwBufferObject {
|
||||||
|
|||||||
@@ -194,13 +194,15 @@ void MetalSwapChain::setFrameScheduledCallback(FrameScheduledCallback callback,
|
|||||||
frameScheduledUserData = user;
|
frameScheduledUserData = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MetalSwapChain::setFrameCompletedCallback(FrameCompletedCallback callback, void* user) {
|
void MetalSwapChain::setFrameCompletedCallback(CallbackHandler* handler,
|
||||||
frameCompletedCallback = callback;
|
CallbackHandler::Callback callback, void* user) {
|
||||||
frameCompletedUserData = user;
|
frameCompleted.handler = handler;
|
||||||
|
frameCompleted.callback = callback;
|
||||||
|
frameCompleted.user = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MetalSwapChain::present() {
|
void MetalSwapChain::present() {
|
||||||
if (frameCompletedCallback) {
|
if (frameCompleted.callback) {
|
||||||
scheduleFrameCompletedCallback();
|
scheduleFrameCompletedCallback();
|
||||||
}
|
}
|
||||||
if (drawable) {
|
if (drawable) {
|
||||||
@@ -244,30 +246,17 @@ void MetalSwapChain::scheduleFrameScheduledCallback() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MetalSwapChain::scheduleFrameCompletedCallback() {
|
void MetalSwapChain::scheduleFrameCompletedCallback() {
|
||||||
if (!frameCompletedCallback) {
|
if (!frameCompleted.callback) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FrameCompletedCallback callback = frameCompletedCallback;
|
CallbackHandler* handler = frameCompleted.handler;
|
||||||
void* userData = frameCompletedUserData;
|
void* user = frameCompleted.user;
|
||||||
[getPendingCommandBuffer(&context) addCompletedHandler:^(id<MTLCommandBuffer> cb) {
|
CallbackHandler::Callback callback = frameCompleted.callback;
|
||||||
struct CallbackData {
|
|
||||||
void* userData;
|
|
||||||
FrameCompletedCallback callback;
|
|
||||||
};
|
|
||||||
CallbackData* data = new CallbackData();
|
|
||||||
data->userData = userData;
|
|
||||||
data->callback = callback;
|
|
||||||
|
|
||||||
// Instantiate a BufferDescriptor with a callback for the sole purpose of passing it to
|
MetalDriver* driver = context.driver;
|
||||||
// scheduleDestroy. This forces the BufferDescriptor callback (and thus the
|
[getPendingCommandBuffer(&context) addCompletedHandler:^(id<MTLCommandBuffer> cb) {
|
||||||
// FrameCompletedCallback) to be called on the user thread.
|
driver->scheduleCallback(handler, user, callback);
|
||||||
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));
|
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ namespace filament {
|
|||||||
namespace backend {
|
namespace backend {
|
||||||
|
|
||||||
inline bool operator==(const SamplerParams& lhs, const SamplerParams& rhs) {
|
inline bool operator==(const SamplerParams& lhs, const SamplerParams& rhs) {
|
||||||
return lhs.u == rhs.u;
|
return SamplerParams::EqualTo{}(lhs, rhs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rasterization Bindings
|
// Rasterization Bindings
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ void NoopDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void NoopDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
|
void NoopDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
|
||||||
FrameCompletedCallback callback, void* user) {
|
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ void NoopDriver::updateStreams(CommandStream* driver) {
|
|||||||
void NoopDriver::destroyFence(Handle<HwFence> fh) {
|
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;
|
return FenceStatus::CONDITION_SATISFIED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,6 +174,14 @@ bool NoopDriver::isSRGBSwapChainSupported() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool NoopDriver::isStereoSupported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NoopDriver::isParallelShaderCompileSupported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool NoopDriver::isWorkaroundNeeded(Workaround) {
|
bool NoopDriver::isWorkaroundNeeded(Workaround) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -49,6 +49,7 @@ bool OpenGLContext::queryOpenGLVersion(GLint* major, GLint* minor) noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
OpenGLContext::OpenGLContext() noexcept {
|
OpenGLContext::OpenGLContext() noexcept {
|
||||||
|
|
||||||
state.vao.p = &mDefaultVAO;
|
state.vao.p = &mDefaultVAO;
|
||||||
|
|
||||||
// These queries work with all GL/GLES versions!
|
// These queries work with all GL/GLES versions!
|
||||||
@@ -61,252 +62,74 @@ OpenGLContext::OpenGLContext() noexcept {
|
|||||||
"[" << state.version << "], [" << state.shader << "]" << io::endl;
|
"[" << 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);
|
queryOpenGLVersion(&state.major, &state.minor);
|
||||||
|
|
||||||
glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &gets.max_renderbuffer_size);
|
OpenGLContext::initExtensions(&ext, state.major, state.minor);
|
||||||
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &gets.max_texture_image_units);
|
|
||||||
glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &gets.max_combined_texture_image_units);
|
|
||||||
|
|
||||||
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
|
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||||
glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &gets.max_uniform_block_size);
|
glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE,
|
||||||
glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &gets.max_uniform_buffer_bindings);
|
&gets.max_uniform_block_size);
|
||||||
glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &gets.uniform_buffer_offset_alignment);
|
glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS,
|
||||||
glGetIntegerv(GL_MAX_SAMPLES, &gets.max_samples);
|
&gets.max_uniform_buffer_bindings);
|
||||||
glGetIntegerv(GL_MAX_DRAW_BUFFERS, &gets.max_draw_buffers);
|
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,
|
glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,
|
||||||
&gets.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
|
#endif
|
||||||
} else {
|
#endif
|
||||||
|
}
|
||||||
|
#ifdef BACKEND_OPENGL_VERSION_GLES
|
||||||
|
else {
|
||||||
gets.max_uniform_block_size = 0;
|
gets.max_uniform_block_size = 0;
|
||||||
gets.max_uniform_buffer_bindings = 0;
|
gets.max_uniform_buffer_bindings = 0;
|
||||||
gets.uniform_buffer_offset_alignment = 0;
|
gets.uniform_buffer_offset_alignment = 0;
|
||||||
gets.max_samples = 1;
|
gets.max_samples = 1;
|
||||||
gets.max_draw_buffers = 1;
|
gets.max_draw_buffers = 1;
|
||||||
gets.max_transform_feedback_separate_attribs = 0;
|
gets.max_transform_feedback_separate_attribs = 0;
|
||||||
}
|
gets.max_anisotropy = 1;
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
// no-op if not supported
|
|
||||||
procs.maxShaderCompilerThreadsKHR = +[](GLuint) {};
|
|
||||||
|
|
||||||
#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;
|
|
||||||
|
|
||||||
procs.maxShaderCompilerThreadsKHR = glMaxShaderCompilerThreadsKHR;
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
procs.maxShaderCompilerThreadsKHR = glMaxShaderCompilerThreadsARB;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef GL_EXT_texture_filter_anisotropic
|
|
||||||
if (ext.EXT_texture_filter_anisotropic) {
|
|
||||||
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gets.max_anisotropy);
|
|
||||||
}
|
}
|
||||||
#endif
|
#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 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(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")) {
|
|
||||||
// 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(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 << "Feature level: " << +mFeatureLevel << '\n';
|
||||||
slog.v << "Active workarounds: " << '\n';
|
slog.v << "Active workarounds: " << '\n';
|
||||||
@@ -332,14 +155,14 @@ OpenGLContext::OpenGLContext() noexcept {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
#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
|
#endif
|
||||||
|
|
||||||
setDefaultState();
|
setDefaultState();
|
||||||
|
|
||||||
#ifdef GL_EXT_texture_filter_anisotropic
|
#ifdef GL_EXT_texture_filter_anisotropic
|
||||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
#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
|
// make sure we don't have any error flag
|
||||||
while (glGetError() != GL_NO_ERROR) { }
|
while (glGetError() != GL_NO_ERROR) { }
|
||||||
|
|
||||||
@@ -445,9 +268,287 @@ void OpenGLContext::setDefaultState() noexcept {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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
|
#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);
|
const char * const extensions = (const char*)glGetString(GL_EXTENSIONS);
|
||||||
GLUtils::unordered_string_set const exts = GLUtils::split(extensions);
|
GLUtils::unordered_string_set const exts = GLUtils::split(extensions);
|
||||||
if constexpr (DEBUG_PRINT_EXTENSIONS) {
|
if constexpr (DEBUG_PRINT_EXTENSIONS) {
|
||||||
@@ -459,51 +560,50 @@ void OpenGLContext::initExtensionsGLES() noexcept {
|
|||||||
|
|
||||||
// figure out and initialize the extensions we need
|
// figure out and initialize the extensions we need
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
ext.APPLE_color_buffer_packed_float = exts.has("GL_APPLE_color_buffer_packed_float"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_control = exts.has("GL_EXT_clip_control"sv);
|
||||||
ext.EXT_clip_cull_distance = exts.has("GL_EXT_clip_cull_distance"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_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_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_debug_marker = exts.has("GL_EXT_debug_marker"sv);
|
||||||
ext.EXT_discard_framebuffer = exts.has("GL_EXT_discard_framebuffer"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_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_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_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->EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv);
|
||||||
#if !defined(__EMSCRIPTEN__)
|
#if !defined(__EMSCRIPTEN__)
|
||||||
ext.EXT_texture_compression_etc2 = true;
|
ext->EXT_texture_compression_etc2 = true;
|
||||||
#endif
|
#endif
|
||||||
ext.EXT_texture_compression_s3tc = exts.has("GL_EXT_texture_compression_s3tc"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_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_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_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->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->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_debug = exts.has("GL_KHR_debug"sv);
|
||||||
ext.KHR_parallel_shader_compile = exts.has("GL_KHR_parallel_shader_compile"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_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->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_depth_texture = exts.has("GL_OES_depth_texture"sv);
|
||||||
ext.OES_depth24 = exts.has("GL_OES_depth24"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_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_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_rgb8_rgba8 = exts.has("GL_OES_rgb8_rgba8"sv);
|
||||||
ext.OES_standard_derivatives = exts.has("GL_OES_standard_derivatives"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_texture_npot = exts.has("GL_OES_texture_npot"sv);
|
||||||
ext.OES_vertex_array_object = exts.has("GL_OES_vertex_array_object"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_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 = exts.has("WEBGL_compressed_texture_s3tc"sv);
|
||||||
ext.WEBGL_compressed_texture_s3tc_srgb = exts.has("WEBGL_compressed_texture_s3tc_srgb"sv);
|
ext->WEBGL_compressed_texture_s3tc_srgb = exts.has("WEBGL_compressed_texture_s3tc_srgb"sv);
|
||||||
|
|
||||||
// ES 3.2 implies EXT_color_buffer_float
|
// ES 3.2 implies EXT_color_buffer_float
|
||||||
if (state.major > 3 || (state.major == 3 && state.minor >= 2)) {
|
if (major > 3 || (major == 3 && minor >= 2)) {
|
||||||
ext.EXT_color_buffer_float = true;
|
ext->EXT_color_buffer_float = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ES 3.x implies EXT_discard_framebuffer and OES_vertex_array_object
|
// ES 3.x implies EXT_discard_framebuffer and OES_vertex_array_object
|
||||||
if (state.major >= 3) {
|
if (major >= 3) {
|
||||||
ext.EXT_color_buffer_float = true;
|
ext->EXT_discard_framebuffer = true;
|
||||||
ext.OES_vertex_array_object = true;
|
ext->OES_vertex_array_object = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -511,7 +611,7 @@ void OpenGLContext::initExtensionsGLES() noexcept {
|
|||||||
|
|
||||||
#ifdef BACKEND_OPENGL_VERSION_GL
|
#ifdef BACKEND_OPENGL_VERSION_GL
|
||||||
|
|
||||||
void OpenGLContext::initExtensionsGL() noexcept {
|
void OpenGLContext::initExtensionsGL(Extensions* ext, GLint major, GLint minor) noexcept {
|
||||||
GLUtils::unordered_string_set exts;
|
GLUtils::unordered_string_set exts;
|
||||||
GLint n = 0;
|
GLint n = 0;
|
||||||
glGetIntegerv(GL_NUM_EXTENSIONS, &n);
|
glGetIntegerv(GL_NUM_EXTENSIONS, &n);
|
||||||
@@ -526,55 +626,52 @@ void OpenGLContext::initExtensionsGL() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
ext.APPLE_color_buffer_packed_float = true; // Assumes core profile.
|
ext->APPLE_color_buffer_packed_float = true; // Assumes core profile.
|
||||||
ext.ARB_shading_language_packing = exts.has("GL_ARB_shading_language_packing"sv);
|
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_float = true; // Assumes core profile.
|
||||||
ext.EXT_color_buffer_half_float = true; // Assumes core profile.
|
ext->EXT_color_buffer_half_float = true; // Assumes core profile.
|
||||||
ext.EXT_clip_cull_distance = true;
|
ext->EXT_clip_cull_distance = true;
|
||||||
ext.EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
|
ext->EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
|
||||||
ext.EXT_discard_framebuffer = false;
|
ext->EXT_discard_framebuffer = false;
|
||||||
ext.EXT_disjoint_timer_query = true;
|
ext->EXT_disjoint_timer_query = true;
|
||||||
ext.EXT_multisampled_render_to_texture = false;
|
ext->EXT_multisampled_render_to_texture = false;
|
||||||
ext.EXT_multisampled_render_to_texture2 = false;
|
ext->EXT_multisampled_render_to_texture2 = false;
|
||||||
ext.EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv);
|
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_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_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_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 = 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_s3tc_srgb = exts.has("GL_EXT_texture_compression_s3tc_srgb"sv);
|
||||||
ext.EXT_texture_cube_map_array = true;
|
ext->EXT_texture_cube_map_array = true;
|
||||||
ext.EXT_texture_filter_anisotropic = exts.has("GL_EXT_texture_filter_anisotropic"sv);
|
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->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->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_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_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->KHR_texture_compression_astc_ldr = exts.has("GL_KHR_texture_compression_astc_ldr"sv);
|
||||||
ext.OES_depth_texture = true;
|
ext->OES_depth_texture = true;
|
||||||
ext.OES_depth24 = true;
|
ext->OES_depth24 = true;
|
||||||
ext.OES_EGL_image_external_essl3 = false;
|
ext->OES_EGL_image_external_essl3 = false;
|
||||||
ext.OES_rgb8_rgba8 = true;
|
ext->OES_rgb8_rgba8 = true;
|
||||||
ext.OES_standard_derivatives = true;
|
ext->OES_standard_derivatives = true;
|
||||||
ext.OES_texture_npot = true;
|
ext->OES_texture_npot = true;
|
||||||
ext.OES_vertex_array_object = true;
|
ext->OES_vertex_array_object = true;
|
||||||
ext.WEBGL_compressed_texture_etc = false;
|
ext->WEBGL_compressed_texture_etc = false;
|
||||||
ext.WEBGL_compressed_texture_s3tc = false;
|
ext->WEBGL_compressed_texture_s3tc = false;
|
||||||
ext.WEBGL_compressed_texture_s3tc_srgb = false;
|
ext->WEBGL_compressed_texture_s3tc_srgb = false;
|
||||||
|
|
||||||
auto const major = state.major;
|
|
||||||
auto const minor = state.minor;
|
|
||||||
|
|
||||||
// OpenGL 4.2 implies ARB_shading_language_packing
|
// OpenGL 4.2 implies ARB_shading_language_packing
|
||||||
if (major > 4 || (major == 4 && minor >= 2)) {
|
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
|
// OpenGL 4.3 implies EXT_discard_framebuffer
|
||||||
if (major > 4 || (major == 4 && minor >= 3)) {
|
if (major > 4 || (major == 4 && minor >= 3)) {
|
||||||
ext.EXT_discard_framebuffer = true;
|
ext->EXT_discard_framebuffer = true;
|
||||||
ext.KHR_debug = true;
|
ext->KHR_debug = true;
|
||||||
}
|
}
|
||||||
// OpenGL 4.5 implies EXT_clip_control
|
// OpenGL 4.5 implies EXT_clip_control
|
||||||
if (major > 4 || (major == 4 && minor >= 5)) {
|
if (major > 4 || (major == 4 && minor >= 5)) {
|
||||||
ext.EXT_clip_control = true;
|
ext->EXT_clip_control = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -670,7 +767,7 @@ void OpenGLContext::deleteBuffers(GLsizei n, const GLuint* buffers, GLenum targe
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
#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));
|
(target != GL_UNIFORM_BUFFER && target != GL_TRANSFORM_FEEDBACK_BUFFER));
|
||||||
|
|
||||||
if (target == GL_UNIFORM_BUFFER || target == GL_TRANSFORM_FEEDBACK_BUFFER) {
|
if (target == GL_UNIFORM_BUFFER || target == GL_TRANSFORM_FEEDBACK_BUFFER) {
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ public:
|
|||||||
# ifndef BACKEND_OPENGL_LEVEL_GLES30
|
# ifndef BACKEND_OPENGL_LEVEL_GLES30
|
||||||
return true;
|
return true;
|
||||||
# else
|
# else
|
||||||
return state.major == 2;
|
return mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0;
|
||||||
# endif
|
# endif
|
||||||
#else
|
#else
|
||||||
return false;
|
return false;
|
||||||
@@ -151,7 +151,7 @@ public:
|
|||||||
void deleteVertexArrays(GLsizei n, const GLuint* arrays) noexcept;
|
void deleteVertexArrays(GLsizei n, const GLuint* arrays) noexcept;
|
||||||
|
|
||||||
// glGet*() values
|
// glGet*() values
|
||||||
struct {
|
struct Gets {
|
||||||
GLfloat max_anisotropy;
|
GLfloat max_anisotropy;
|
||||||
GLint max_draw_buffers;
|
GLint max_draw_buffers;
|
||||||
GLint max_renderbuffer_size;
|
GLint max_renderbuffer_size;
|
||||||
@@ -170,7 +170,7 @@ public:
|
|||||||
} features = {};
|
} features = {};
|
||||||
|
|
||||||
// supported extensions detected at runtime
|
// supported extensions detected at runtime
|
||||||
struct {
|
struct Extensions {
|
||||||
bool APPLE_color_buffer_packed_float;
|
bool APPLE_color_buffer_packed_float;
|
||||||
bool ARB_shading_language_packing;
|
bool ARB_shading_language_packing;
|
||||||
bool EXT_clip_control;
|
bool EXT_clip_control;
|
||||||
@@ -209,7 +209,7 @@ public:
|
|||||||
bool WEBGL_compressed_texture_s3tc_srgb;
|
bool WEBGL_compressed_texture_s3tc_srgb;
|
||||||
} ext = {};
|
} ext = {};
|
||||||
|
|
||||||
struct {
|
struct Bugs {
|
||||||
// Some drivers have issues with UBOs in the fragment shader when
|
// Some drivers have issues with UBOs in the fragment shader when
|
||||||
// glFlush() is called between draw calls.
|
// glFlush() is called between draw calls.
|
||||||
bool disable_glFlush;
|
bool disable_glFlush;
|
||||||
@@ -261,6 +261,24 @@ public:
|
|||||||
// Some Adreno drivers crash in glDrawXXX() when there's an uninitialized uniform block,
|
// Some Adreno drivers crash in glDrawXXX() when there's an uninitialized uniform block,
|
||||||
// even when the shader doesn't access it.
|
// even when the shader doesn't access it.
|
||||||
bool enable_initialize_non_used_uniform_array;
|
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 = {};
|
} bugs = {};
|
||||||
|
|
||||||
// state getters -- as needed.
|
// state getters -- as needed.
|
||||||
@@ -383,7 +401,7 @@ public:
|
|||||||
} window;
|
} window;
|
||||||
} state;
|
} state;
|
||||||
|
|
||||||
struct {
|
struct Procs {
|
||||||
void (* bindVertexArray)(GLuint array);
|
void (* bindVertexArray)(GLuint array);
|
||||||
void (* deleteVertexArrays)(GLsizei n, const GLuint* arrays);
|
void (* deleteVertexArrays)(GLsizei n, const GLuint* arrays);
|
||||||
void (* genVertexArrays)(GLsizei n, GLuint* arrays);
|
void (* genVertexArrays)(GLsizei n, GLuint* arrays);
|
||||||
@@ -444,18 +462,55 @@ private:
|
|||||||
{ bugs.enable_initialize_non_used_uniform_array,
|
{ bugs.enable_initialize_non_used_uniform_array,
|
||||||
"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;
|
RenderPrimitive mDefaultVAO;
|
||||||
|
|
||||||
// this is chosen to minimize code size
|
// this is chosen to minimize code size
|
||||||
#if defined(BACKEND_OPENGL_VERSION_GLES)
|
#if defined(BACKEND_OPENGL_VERSION_GLES)
|
||||||
void initExtensionsGLES() noexcept;
|
static void initExtensionsGLES(Extensions* ext, GLint major, GLint minor) noexcept;
|
||||||
#endif
|
#endif
|
||||||
#if defined(BACKEND_OPENGL_VERSION_GL)
|
#if defined(BACKEND_OPENGL_VERSION_GL)
|
||||||
void initExtensionsGL() noexcept;
|
static void initExtensionsGL(Extensions* ext, GLint major, GLint minor) noexcept;
|
||||||
#endif
|
#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>
|
template <typename T, typename F>
|
||||||
static inline void update_state(T& state, T const& expected, F functor, bool force = false) noexcept {
|
static inline void update_state(T& state, T const& expected, F functor, bool force = false) noexcept {
|
||||||
if (UTILS_UNLIKELY(force || state != expected)) {
|
if (UTILS_UNLIKELY(force || state != expected)) {
|
||||||
@@ -548,7 +603,7 @@ void OpenGLContext::activeTexture(GLuint unit) noexcept {
|
|||||||
|
|
||||||
void OpenGLContext::bindSampler(GLuint unit, GLuint sampler) noexcept {
|
void OpenGLContext::bindSampler(GLuint unit, GLuint sampler) noexcept {
|
||||||
assert_invariant(unit < MAX_TEXTURE_UNIT_COUNT);
|
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
|
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||||
update_state(state.textures.units[unit].sampler, sampler, [&]() {
|
update_state(state.textures.units[unit].sampler, sampler, [&]() {
|
||||||
glBindSampler(unit, sampler);
|
glBindSampler(unit, sampler);
|
||||||
@@ -594,7 +649,7 @@ void OpenGLContext::bindVertexArray(RenderPrimitive const* p) noexcept {
|
|||||||
|
|
||||||
void OpenGLContext::bindBufferRange(GLenum target, GLuint index, GLuint buffer,
|
void OpenGLContext::bindBufferRange(GLenum target, GLuint index, GLuint buffer,
|
||||||
GLintptr offset, GLsizeiptr size) noexcept {
|
GLintptr offset, GLsizeiptr size) noexcept {
|
||||||
assert_invariant(state.major > 2);
|
assert_invariant(mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1);
|
||||||
|
|
||||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||||
# ifdef BACKEND_OPENGL_LEVEL_GLES31
|
# ifdef BACKEND_OPENGL_LEVEL_GLES31
|
||||||
|
|||||||
@@ -191,27 +191,7 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi
|
|||||||
assert_invariant(mContext.ext.EXT_disjoint_timer_query);
|
assert_invariant(mContext.ext.EXT_disjoint_timer_query);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_disjoint_timer_query)
|
mTimerQueryImpl = OpenGLTimerQueryFactory::init(mPlatform, *this);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
mShaderCompilerService.init();
|
mShaderCompilerService.init();
|
||||||
}
|
}
|
||||||
@@ -237,6 +217,14 @@ void OpenGLDriver::terminate() {
|
|||||||
// and make sure to execute all the GpuCommandCompleteOps callbacks
|
// and make sure to execute all the GpuCommandCompleteOps callbacks
|
||||||
executeGpuCommandsCompleteOps();
|
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
|
// because we called glFinish(), all callbacks should have been executed
|
||||||
assert_invariant(mGpuCommandCompleteOps.empty());
|
assert_invariant(mGpuCommandCompleteOps.empty());
|
||||||
|
|
||||||
@@ -1356,35 +1344,15 @@ void OpenGLDriver::createFenceR(Handle<HwFence> fh, int) {
|
|||||||
}
|
}
|
||||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||||
else {
|
else {
|
||||||
f->sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
|
||||||
std::weak_ptr<GLFence::State> const weak = f->state;
|
std::weak_ptr<GLFence::State> const weak = f->state;
|
||||||
runEveryNowAndThen(
|
whenGpuCommandsComplete([weak](){
|
||||||
[sync = f->sync, weak]() -> bool {
|
auto state = weak.lock();
|
||||||
auto state = weak.lock();
|
if (state) {
|
||||||
if (state) {
|
std::lock_guard const lock(state->lock);
|
||||||
FenceStatus fenceStatus;
|
state->status = FenceStatus::CONDITION_SATISFIED;
|
||||||
GLenum const syncStatus = glClientWaitSync(sync, 0, 0u);
|
state->cond.notify_all();
|
||||||
switch (syncStatus) {
|
}
|
||||||
case GL_TIMEOUT_EXPIRED:
|
});
|
||||||
fenceStatus = FenceStatus::TIMEOUT_EXPIRED;
|
|
||||||
break;
|
|
||||||
case GL_ALREADY_SIGNALED:
|
|
||||||
case GL_CONDITION_SATISFIED:
|
|
||||||
fenceStatus = FenceStatus::CONDITION_SATISFIED;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
fenceStatus = FenceStatus::ERROR;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (fenceStatus != FenceStatus::TIMEOUT_EXPIRED) {
|
|
||||||
std::lock_guard const lock(state->lock);
|
|
||||||
state->status = fenceStatus;
|
|
||||||
state->cond.notify_all();
|
|
||||||
}
|
|
||||||
return (fenceStatus != FenceStatus::TIMEOUT_EXPIRED);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -1418,10 +1386,8 @@ void OpenGLDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch,
|
|||||||
|
|
||||||
void OpenGLDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
|
void OpenGLDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
|
||||||
DEBUG_MARKER()
|
DEBUG_MARKER()
|
||||||
|
|
||||||
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
|
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
|
||||||
mContext.procs.genQueries(1u, &tq->gl.query);
|
mTimerQueryImpl->createTimerQuery(tq);
|
||||||
CHECK_GL_ERROR(utils::slog.e)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
@@ -1526,12 +1492,33 @@ void OpenGLDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
|
|||||||
if (rt->gl.fbo) {
|
if (rt->gl.fbo) {
|
||||||
// first unbind this framebuffer if needed
|
// first unbind this framebuffer if needed
|
||||||
gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
|
gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
glDeleteFramebuffers(1, &rt->gl.fbo);
|
|
||||||
}
|
}
|
||||||
if (rt->gl.fbo_read) {
|
if (rt->gl.fbo_read) {
|
||||||
// first unbind this framebuffer if needed
|
// first unbind this framebuffer if needed
|
||||||
gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
|
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);
|
destruct(rth, rt);
|
||||||
}
|
}
|
||||||
@@ -1580,7 +1567,7 @@ void OpenGLDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
|
|||||||
|
|
||||||
if (tqh) {
|
if (tqh) {
|
||||||
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
|
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
|
||||||
getContext().procs.deleteQueries(1u, &tq->gl.query);
|
mTimerQueryImpl->destroyTimerQuery(tq);
|
||||||
destruct(tqh, tq);
|
destruct(tqh, tq);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1691,17 +1678,11 @@ void OpenGLDriver::destroyFence(Handle<HwFence> fh) {
|
|||||||
if (mPlatform.canCreateFence() || mContext.isES2()) {
|
if (mPlatform.canCreateFence() || mContext.isES2()) {
|
||||||
mPlatform.destroyFence(f->fence);
|
mPlatform.destroyFence(f->fence);
|
||||||
}
|
}
|
||||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
|
||||||
else {
|
|
||||||
glDeleteSync(f->sync);
|
|
||||||
CHECK_GL_ERROR(utils::slog.e)
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
destruct(fh, f);
|
destruct(fh, f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FenceStatus OpenGLDriver::wait(Handle<HwFence> fh, uint64_t timeout) {
|
FenceStatus OpenGLDriver::getFenceStatus(Handle<HwFence> fh) {
|
||||||
if (fh) {
|
if (fh) {
|
||||||
GLFence* f = handle_cast<GLFence*>(fh);
|
GLFence* f = handle_cast<GLFence*>(fh);
|
||||||
if (mPlatform.canCreateFence() || mContext.isES2()) {
|
if (mPlatform.canCreateFence() || mContext.isES2()) {
|
||||||
@@ -1714,14 +1695,13 @@ FenceStatus OpenGLDriver::wait(Handle<HwFence> fh, uint64_t timeout) {
|
|||||||
// - wait() was called before the fence was asynchronously created.
|
// - wait() was called before the fence was asynchronously created.
|
||||||
return FenceStatus::TIMEOUT_EXPIRED;
|
return FenceStatus::TIMEOUT_EXPIRED;
|
||||||
}
|
}
|
||||||
return mPlatform.waitFence(f->fence, timeout);
|
return mPlatform.waitFence(f->fence, 0);
|
||||||
}
|
}
|
||||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||||
else {
|
else {
|
||||||
assert_invariant(f->state);
|
assert_invariant(f->state);
|
||||||
std::unique_lock lock(f->state->lock);
|
std::unique_lock lock(f->state->lock);
|
||||||
f->state->cond.wait_for(lock,
|
f->state->cond.wait_for(lock, std::chrono::nanoseconds(0), [&state = f->state]() {
|
||||||
std::chrono::nanoseconds(timeout), [&state = f->state]() {
|
|
||||||
return state->status != FenceStatus::TIMEOUT_EXPIRED;
|
return state->status != FenceStatus::TIMEOUT_EXPIRED;
|
||||||
});
|
});
|
||||||
return f->state->status;
|
return f->state->status;
|
||||||
@@ -1874,7 +1854,7 @@ bool OpenGLDriver::isFrameBufferFetchMultiSampleSupported() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLDriver::isFrameTimeSupported() {
|
bool OpenGLDriver::isFrameTimeSupported() {
|
||||||
return mFrameTimeSupported;
|
return OpenGLTimerQueryFactory::isGpuTimeSupported();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLDriver::isAutoDepthResolveSupported() {
|
bool OpenGLDriver::isAutoDepthResolveSupported() {
|
||||||
@@ -1892,6 +1872,18 @@ bool OpenGLDriver::isSRGBSwapChainSupported() {
|
|||||||
return mPlatform.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) {
|
bool OpenGLDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||||
switch (workaround) {
|
switch (workaround) {
|
||||||
case Workaround::SPLIT_EASU:
|
case Workaround::SPLIT_EASU:
|
||||||
@@ -1902,6 +1894,10 @@ bool OpenGLDriver::isWorkaroundNeeded(Workaround workaround) {
|
|||||||
return mContext.bugs.enable_initialize_non_used_uniform_array;
|
return mContext.bugs.enable_initialize_non_used_uniform_array;
|
||||||
case Workaround::DISABLE_BLIT_INTO_TEXTURE_ARRAY:
|
case Workaround::DISABLE_BLIT_INTO_TEXTURE_ARRAY:
|
||||||
return mContext.bugs.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:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1938,6 +1934,16 @@ void OpenGLDriver::commit(Handle<HwSwapChain> sch) {
|
|||||||
|
|
||||||
GLSwapChain* sc = handle_cast<GLSwapChain*>(sch);
|
GLSwapChain* sc = handle_cast<GLSwapChain*>(sch);
|
||||||
mPlatform.commit(sc->swapChain);
|
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) {
|
void OpenGLDriver::makeCurrent(Handle<HwSwapChain> schDraw, Handle<HwSwapChain> schRead) {
|
||||||
@@ -2582,8 +2588,6 @@ void OpenGLDriver::replaceStream(GLTexture* texture, GLStream* newStream) noexce
|
|||||||
void OpenGLDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
|
void OpenGLDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||||
DEBUG_MARKER()
|
DEBUG_MARKER()
|
||||||
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
|
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
|
||||||
// reset the state of the result availability
|
|
||||||
tq->elapsed.store(0, std::memory_order_relaxed);
|
|
||||||
mTimerQueryImpl->beginTimeElapsedQuery(tq);
|
mTimerQueryImpl->beginTimeElapsedQuery(tq);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2591,33 +2595,17 @@ void OpenGLDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
|
|||||||
DEBUG_MARKER()
|
DEBUG_MARKER()
|
||||||
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
|
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
|
||||||
mTimerQueryImpl->endTimeElapsedQuery(tq);
|
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) {
|
bool OpenGLDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsedTime) {
|
||||||
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
|
GLTimerQuery* tq = handle_cast<GLTimerQuery*>(tqh);
|
||||||
uint64_t const d = tq->elapsed.load(std::memory_order_relaxed);
|
return OpenGLTimerQueryInterface::getTimerQueryValue(tq, elapsedTime);
|
||||||
if (!d) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (elapsedTime) {
|
|
||||||
*elapsedTime = d;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLDriver::compilePrograms(CompilerPriorityQueue priority,
|
void OpenGLDriver::compilePrograms(CompilerPriorityQueue priority,
|
||||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||||
if (callback) {
|
if (callback) {
|
||||||
getShaderCompilerService().notifyWhenAllProgramsAreReady(priority, handler, callback, user);
|
getShaderCompilerService().notifyWhenAllProgramsAreReady(handler, callback, user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2929,7 +2917,7 @@ void OpenGLDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
|
|||||||
|
|
||||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||||
GLuint OpenGLDriver::getSamplerSlow(SamplerParams params) const noexcept {
|
GLuint OpenGLDriver::getSamplerSlow(SamplerParams params) const noexcept {
|
||||||
assert_invariant(mSamplerMap.find(params.u) == mSamplerMap.end());
|
assert_invariant(mSamplerMap.find(params) == mSamplerMap.end());
|
||||||
|
|
||||||
GLuint s;
|
GLuint s;
|
||||||
glGenSamplers(1, &s);
|
glGenSamplers(1, &s);
|
||||||
@@ -2951,7 +2939,7 @@ GLuint OpenGLDriver::getSamplerSlow(SamplerParams params) const noexcept {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
CHECK_GL_ERROR(utils::slog.e)
|
CHECK_GL_ERROR(utils::slog.e)
|
||||||
mSamplerMap[params.u] = s;
|
mSamplerMap[params] = s;
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -3205,6 +3193,10 @@ void OpenGLDriver::executeEveryNowAndThenOps() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||||
|
void OpenGLDriver::whenFrameComplete(const std::function<void()>& fn) noexcept {
|
||||||
|
mFrameCompleteOps.push_back(fn);
|
||||||
|
}
|
||||||
|
|
||||||
void OpenGLDriver::whenGpuCommandsComplete(const std::function<void()>& fn) noexcept {
|
void OpenGLDriver::whenGpuCommandsComplete(const std::function<void()>& fn) noexcept {
|
||||||
GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||||
mGpuCommandCompleteOps.emplace_back(sync, fn);
|
mGpuCommandCompleteOps.emplace_back(sync, fn);
|
||||||
@@ -3278,7 +3270,7 @@ void OpenGLDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
|
void OpenGLDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
|
||||||
FrameCompletedCallback callback, void* user) {
|
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||||
DEBUG_MARKER()
|
DEBUG_MARKER()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -151,14 +151,12 @@ public:
|
|||||||
|
|
||||||
struct GLTimerQuery : public HwTimerQuery {
|
struct GLTimerQuery : public HwTimerQuery {
|
||||||
struct State {
|
struct State {
|
||||||
|
struct {
|
||||||
|
GLuint query;
|
||||||
|
} gl;
|
||||||
std::atomic<int64_t> elapsed{};
|
std::atomic<int64_t> elapsed{};
|
||||||
};
|
};
|
||||||
struct {
|
std::shared_ptr<State> state;
|
||||||
GLuint query = 0;
|
|
||||||
std::shared_ptr<State> emulation;
|
|
||||||
} gl;
|
|
||||||
// 0 means not available, otherwise query result in ns.
|
|
||||||
std::atomic<uint64_t> elapsed{};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GLStream : public HwStream {
|
struct GLStream : public HwStream {
|
||||||
@@ -202,7 +200,6 @@ public:
|
|||||||
std::condition_variable cond;
|
std::condition_variable cond;
|
||||||
FenceStatus status{ FenceStatus::TIMEOUT_EXPIRED };
|
FenceStatus status{ FenceStatus::TIMEOUT_EXPIRED };
|
||||||
};
|
};
|
||||||
GLsync sync;
|
|
||||||
std::shared_ptr<State> state{ std::make_shared<GLFence::State>() };
|
std::shared_ptr<State> state{ std::make_shared<GLFence::State>() };
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -214,6 +211,8 @@ private:
|
|||||||
OpenGLContext mContext;
|
OpenGLContext mContext;
|
||||||
ShaderCompilerService mShaderCompilerService;
|
ShaderCompilerService mShaderCompilerService;
|
||||||
|
|
||||||
|
friend class OpenGLTimerQueryFactory;
|
||||||
|
friend class TimerQueryNative;
|
||||||
OpenGLContext& getContext() noexcept { return mContext; }
|
OpenGLContext& getContext() noexcept { return mContext; }
|
||||||
|
|
||||||
ShaderCompilerService& getShaderCompilerService() noexcept {
|
ShaderCompilerService& getShaderCompilerService() noexcept {
|
||||||
@@ -335,7 +334,7 @@ private:
|
|||||||
assert_invariant(!sp.padding1);
|
assert_invariant(!sp.padding1);
|
||||||
assert_invariant(!sp.padding2);
|
assert_invariant(!sp.padding2);
|
||||||
auto& samplerMap = mSamplerMap;
|
auto& samplerMap = mSamplerMap;
|
||||||
auto pos = samplerMap.find(sp.u);
|
auto pos = samplerMap.find(sp);
|
||||||
if (UTILS_UNLIKELY(pos == samplerMap.end())) {
|
if (UTILS_UNLIKELY(pos == samplerMap.end())) {
|
||||||
return getSamplerSlow(sp);
|
return getSamplerSlow(sp);
|
||||||
}
|
}
|
||||||
@@ -369,7 +368,8 @@ private:
|
|||||||
// sampler buffer binding points (nullptr if not used)
|
// sampler buffer binding points (nullptr if not used)
|
||||||
std::array<GLSamplerGroup*, Program::SAMPLER_BINDING_COUNT> mSamplerBindings = {}; // 4 pointers
|
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
|
// this must be accessed from the driver thread only
|
||||||
std::vector<GLTexture*> mTexturesWithStreamsAttached;
|
std::vector<GLTexture*> mTexturesWithStreamsAttached;
|
||||||
@@ -388,6 +388,9 @@ private:
|
|||||||
void whenGpuCommandsComplete(const std::function<void()>& fn) noexcept;
|
void whenGpuCommandsComplete(const std::function<void()>& fn) noexcept;
|
||||||
void executeGpuCommandsCompleteOps() noexcept;
|
void executeGpuCommandsCompleteOps() noexcept;
|
||||||
std::vector<std::pair<GLsync, 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
|
#endif
|
||||||
|
|
||||||
// tasks regularly executed on the main thread at until they return true
|
// tasks regularly executed on the main thread at until they return true
|
||||||
@@ -397,7 +400,6 @@ private:
|
|||||||
|
|
||||||
// timer query implementation
|
// timer query implementation
|
||||||
OpenGLTimerQueryInterface* mTimerQueryImpl = nullptr;
|
OpenGLTimerQueryInterface* mTimerQueryImpl = nullptr;
|
||||||
bool mFrameTimeSupported = false;
|
|
||||||
|
|
||||||
// for ES2 sRGB support
|
// for ES2 sRGB support
|
||||||
GLSwapChain* mCurrentDrawSwapChain = nullptr;
|
GLSwapChain* mCurrentDrawSwapChain = nullptr;
|
||||||
|
|||||||
@@ -116,4 +116,7 @@ bool OpenGLPlatform::isExtraContextSupported() const noexcept {
|
|||||||
void OpenGLPlatform::createContext(bool) {
|
void OpenGLPlatform::createContext(bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OpenGLPlatform::releaseContext() noexcept {
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace filament::backend
|
} // namespace filament::backend
|
||||||
|
|||||||
@@ -31,40 +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;
|
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)
|
#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;
|
TimerQueryNative::~TimerQueryNative() = default;
|
||||||
|
|
||||||
void TimerQueryNative::beginTimeElapsedQuery(GLTimerQuery* query) {
|
void TimerQueryNative::createTimerQuery(GLTimerQuery* tq) {
|
||||||
mContext.procs.beginQuery(GL_TIME_ELAPSED, query->gl.query);
|
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)
|
CHECK_GL_ERROR(utils::slog.e)
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimerQueryNative::endTimeElapsedQuery(GLTimerQuery*) {
|
void TimerQueryNative::destroyTimerQuery(GLTimerQuery* tq) {
|
||||||
mContext.procs.endQuery(GL_TIME_ELAPSED);
|
assert_invariant(tq->state);
|
||||||
|
mDriver.getContext().procs.deleteQueries(1u, &tq->state->gl.query);
|
||||||
CHECK_GL_ERROR(utils::slog.e)
|
CHECK_GL_ERROR(utils::slog.e)
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TimerQueryNative::queryResultAvailable(GLTimerQuery* query) {
|
void TimerQueryNative::beginTimeElapsedQuery(GLTimerQuery* tq) {
|
||||||
GLuint available = 0;
|
assert_invariant(tq->state);
|
||||||
mContext.procs.getQueryObjectuiv(query->gl.query, GL_QUERY_RESULT_AVAILABLE, &available);
|
tq->state->elapsed.store(0);
|
||||||
|
mDriver.getContext().procs.beginQuery(GL_TIME_ELAPSED, tq->state->gl.query);
|
||||||
CHECK_GL_ERROR(utils::slog.e)
|
CHECK_GL_ERROR(utils::slog.e)
|
||||||
return available != 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t TimerQueryNative::queryResult(GLTimerQuery* query) {
|
void TimerQueryNative::endTimeElapsedQuery(GLTimerQuery* tq) {
|
||||||
GLuint64 elapsedTime = 0;
|
assert_invariant(tq->state);
|
||||||
// we won't end-up here if we're on ES and don't have GL_EXT_disjoint_timer_query
|
mDriver.getContext().procs.endQuery(GL_TIME_ELAPSED);
|
||||||
mContext.procs.getQueryObjectui64v(query->gl.query, GL_QUERY_RESULT, &elapsedTime);
|
|
||||||
CHECK_GL_ERROR(utils::slog.e)
|
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
|
#endif
|
||||||
@@ -101,7 +172,9 @@ OpenGLTimerQueryFence::~OpenGLTimerQueryFence() {
|
|||||||
mExitRequested = true;
|
mExitRequested = true;
|
||||||
mCondition.notify_one();
|
mCondition.notify_one();
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
mThread.join();
|
if (mThread.joinable()) {
|
||||||
|
mThread.join();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,87 +184,92 @@ void OpenGLTimerQueryFence::enqueue(OpenGLTimerQueryFence::Job&& job) {
|
|||||||
mCondition.notify_one();
|
mCondition.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLTimerQueryFence::beginTimeElapsedQuery(GLTimerQuery* query) {
|
void OpenGLTimerQueryFence::createTimerQuery(GLTimerQuery* tq) {
|
||||||
if (UTILS_UNLIKELY(!query->gl.emulation)) {
|
if (UTILS_UNLIKELY(!tq->state)) {
|
||||||
query->gl.emulation = std::make_shared<GLTimerQuery::State>();
|
tq->state = std::make_shared<GLTimerQuery::State>();
|
||||||
}
|
}
|
||||||
query->gl.emulation->elapsed.store(0);
|
}
|
||||||
|
|
||||||
|
void OpenGLTimerQueryFence::destroyTimerQuery(GLTimerQuery* tq) {
|
||||||
|
assert_invariant(tq->state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenGLTimerQueryFence::beginTimeElapsedQuery(GLTimerQuery* tq) {
|
||||||
|
assert_invariant(tq->state);
|
||||||
|
tq->state->elapsed.store(0);
|
||||||
|
|
||||||
Platform::Fence* fence = mPlatform.createFence();
|
Platform::Fence* fence = mPlatform.createFence();
|
||||||
std::weak_ptr<GLTimerQuery::State> const weak = query->gl.emulation;
|
std::weak_ptr<GLTimerQuery::State> const weak = tq->state;
|
||||||
uint32_t const cookie = query->gl.query;
|
|
||||||
push([&platform = mPlatform, fence, weak, cookie]() {
|
// FIXME: this implementation of beginTimeElapsedQuery is usually wrong; it ends up
|
||||||
auto emulation = weak.lock();
|
// measuring the current CPU time because the fence signals immediately (usually there is
|
||||||
if (emulation) {
|
// 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 state = weak.lock();
|
||||||
|
if (state) {
|
||||||
platform.waitFence(fence, FENCE_WAIT_FOR_EVER);
|
platform.waitFence(fence, FENCE_WAIT_FOR_EVER);
|
||||||
int64_t const then = clock::now().time_since_epoch().count();
|
int64_t const then = clock::now().time_since_epoch().count();
|
||||||
emulation->elapsed.store(-then, std::memory_order_relaxed);
|
state->elapsed.store(-then, std::memory_order_relaxed);
|
||||||
SYSTRACE_CONTEXT();
|
SYSTRACE_CONTEXT();
|
||||||
SYSTRACE_ASYNC_BEGIN("OpenGLTimerQueryFence", cookie);
|
SYSTRACE_ASYNC_BEGIN("OpenGLTimerQueryFence", intptr_t(state.get()));
|
||||||
(void)cookie;
|
|
||||||
}
|
}
|
||||||
platform.destroyFence(fence);
|
platform.destroyFence(fence);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLTimerQueryFence::endTimeElapsedQuery(GLTimerQuery* query) {
|
void OpenGLTimerQueryFence::endTimeElapsedQuery(GLTimerQuery* tq) {
|
||||||
|
assert_invariant(tq->state);
|
||||||
Platform::Fence* fence = mPlatform.createFence();
|
Platform::Fence* fence = mPlatform.createFence();
|
||||||
std::weak_ptr<GLTimerQuery::State> const weak = query->gl.emulation;
|
std::weak_ptr<GLTimerQuery::State> const weak = tq->state;
|
||||||
uint32_t const cookie = query->gl.query;
|
|
||||||
push([&platform = mPlatform, fence, weak, cookie]() {
|
push([&platform = mPlatform, fence, weak]() {
|
||||||
auto emulation = weak.lock();
|
auto state = weak.lock();
|
||||||
if (emulation) {
|
if (state) {
|
||||||
platform.waitFence(fence, FENCE_WAIT_FOR_EVER);
|
platform.waitFence(fence, FENCE_WAIT_FOR_EVER);
|
||||||
int64_t const now = clock::now().time_since_epoch().count();
|
int64_t const now = clock::now().time_since_epoch().count();
|
||||||
int64_t const then = emulation->elapsed.load(std::memory_order_relaxed);
|
int64_t const then = state->elapsed.load(std::memory_order_relaxed);
|
||||||
assert_invariant(then < 0);
|
assert_invariant(then < 0);
|
||||||
emulation->elapsed.store(now + then, std::memory_order_relaxed);
|
state->elapsed.store(now + then, std::memory_order_relaxed);
|
||||||
SYSTRACE_CONTEXT();
|
SYSTRACE_CONTEXT();
|
||||||
SYSTRACE_ASYNC_END("OpenGLTimerQueryFence", cookie);
|
SYSTRACE_ASYNC_END("OpenGLTimerQueryFence", intptr_t(state.get()));
|
||||||
(void)cookie;
|
|
||||||
}
|
}
|
||||||
platform.destroyFence(fence);
|
platform.destroyFence(fence);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLTimerQueryFence::queryResultAvailable(GLTimerQuery* query) {
|
|
||||||
return query->gl.emulation->elapsed.load(std::memory_order_relaxed) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t OpenGLTimerQueryFence::queryResult(GLTimerQuery* query) {
|
|
||||||
int64_t const result = query->gl.emulation->elapsed.load(std::memory_order_relaxed);
|
|
||||||
return result > 0 ? result : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
TimerQueryFallback::TimerQueryFallback() = default;
|
TimerQueryFallback::TimerQueryFallback() = default;
|
||||||
|
|
||||||
TimerQueryFallback::~TimerQueryFallback() = default;
|
TimerQueryFallback::~TimerQueryFallback() = default;
|
||||||
|
|
||||||
void TimerQueryFallback::beginTimeElapsedQuery(OpenGLTimerQueryInterface::GLTimerQuery* query) {
|
void TimerQueryFallback::createTimerQuery(GLTimerQuery* tq) {
|
||||||
if (!query->gl.emulation) {
|
if (UTILS_UNLIKELY(!tq->state)) {
|
||||||
query->gl.emulation = std::make_shared<GLTimerQuery::State>();
|
tq->state = std::make_shared<GLTimerQuery::State>();
|
||||||
}
|
}
|
||||||
// this implementation clearly doesn't work at all, but we have no h/w support
|
}
|
||||||
|
|
||||||
|
void TimerQueryFallback::destroyTimerQuery(GLTimerQuery* tq) {
|
||||||
|
assert_invariant(tq->state);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
int64_t const then = clock::now().time_since_epoch().count();
|
||||||
query->gl.emulation->elapsed.store(-then, std::memory_order_relaxed);
|
tq->state->elapsed.store(-then, std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimerQueryFallback::endTimeElapsedQuery(OpenGLTimerQueryInterface::GLTimerQuery* query) {
|
void TimerQueryFallback::endTimeElapsedQuery(OpenGLTimerQueryInterface::GLTimerQuery* tq) {
|
||||||
// this implementation clearly doesn't work at all, but we have no h/w support
|
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 now = clock::now().time_since_epoch().count();
|
||||||
int64_t const then = query->gl.emulation->elapsed.load(std::memory_order_relaxed);
|
int64_t const then = tq->state->elapsed.load(std::memory_order_relaxed);
|
||||||
assert_invariant(then < 0);
|
assert_invariant(then < 0);
|
||||||
query->gl.emulation->elapsed.store(now + then, std::memory_order_relaxed);
|
tq->state->elapsed.store(now + then, std::memory_order_relaxed);
|
||||||
}
|
|
||||||
|
|
||||||
bool TimerQueryFallback::queryResultAvailable(OpenGLTimerQueryInterface::GLTimerQuery* query) {
|
|
||||||
return query->gl.emulation->elapsed.load(std::memory_order_relaxed) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t TimerQueryFallback::queryResult(OpenGLTimerQueryInterface::GLTimerQuery* query) {
|
|
||||||
int64_t const result = query->gl.emulation->elapsed.load(std::memory_order_relaxed);
|
|
||||||
return result > 0 ? result : 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace filament::backend
|
} // namespace filament::backend
|
||||||
|
|||||||
@@ -20,20 +20,36 @@
|
|||||||
#include "OpenGLDriver.h"
|
#include "OpenGLDriver.h"
|
||||||
|
|
||||||
#include <utils/Condition.h>
|
#include <utils/Condition.h>
|
||||||
|
#include <utils/Mutex.h>
|
||||||
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace filament::backend {
|
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
|
* on some gpu disjoint_timer_query/arb_timer_query is much less accurate than
|
||||||
* using fences.
|
* using fences.
|
||||||
*
|
*
|
||||||
* These classes implement the various strategies...
|
* 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 {
|
class OpenGLTimerQueryInterface {
|
||||||
protected:
|
protected:
|
||||||
using GLTimerQuery = OpenGLDriver::GLTimerQuery;
|
using GLTimerQuery = OpenGLDriver::GLTimerQuery;
|
||||||
@@ -41,24 +57,26 @@ protected:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
virtual ~OpenGLTimerQueryInterface();
|
virtual ~OpenGLTimerQueryInterface();
|
||||||
|
virtual void createTimerQuery(GLTimerQuery* query) = 0;
|
||||||
|
virtual void destroyTimerQuery(GLTimerQuery* query) = 0;
|
||||||
virtual void beginTimeElapsedQuery(GLTimerQuery* query) = 0;
|
virtual void beginTimeElapsedQuery(GLTimerQuery* query) = 0;
|
||||||
virtual void endTimeElapsedQuery(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)
|
#if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_disjoint_timer_query)
|
||||||
|
|
||||||
class TimerQueryNative : public OpenGLTimerQueryInterface {
|
class TimerQueryNative : public OpenGLTimerQueryInterface {
|
||||||
public:
|
public:
|
||||||
explicit TimerQueryNative(OpenGLContext& context);
|
explicit TimerQueryNative(OpenGLDriver& driver);
|
||||||
~TimerQueryNative() override;
|
~TimerQueryNative() override;
|
||||||
private:
|
private:
|
||||||
|
void createTimerQuery(GLTimerQuery* query) override;
|
||||||
|
void destroyTimerQuery(GLTimerQuery* query) override;
|
||||||
void beginTimeElapsedQuery(GLTimerQuery* query) override;
|
void beginTimeElapsedQuery(GLTimerQuery* query) override;
|
||||||
void endTimeElapsedQuery(GLTimerQuery* query) override;
|
void endTimeElapsedQuery(GLTimerQuery* query) override;
|
||||||
bool queryResultAvailable(GLTimerQuery* query) override;
|
OpenGLDriver& mDriver;
|
||||||
uint64_t queryResult(GLTimerQuery* query) override;
|
|
||||||
OpenGLContext& mContext;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -69,12 +87,12 @@ public:
|
|||||||
~OpenGLTimerQueryFence() override;
|
~OpenGLTimerQueryFence() override;
|
||||||
private:
|
private:
|
||||||
using Job = std::function<void()>;
|
using Job = std::function<void()>;
|
||||||
void beginTimeElapsedQuery(GLTimerQuery* query) override;
|
void createTimerQuery(GLTimerQuery* query) override;
|
||||||
void endTimeElapsedQuery(GLTimerQuery* query) override;
|
void destroyTimerQuery(GLTimerQuery* query) override;
|
||||||
bool queryResultAvailable(GLTimerQuery* query) override;
|
void beginTimeElapsedQuery(GLTimerQuery* tq) override;
|
||||||
uint64_t queryResult(GLTimerQuery* query) override;
|
void endTimeElapsedQuery(GLTimerQuery* tq) override;
|
||||||
void enqueue(Job&& job);
|
|
||||||
|
|
||||||
|
void enqueue(Job&& job);
|
||||||
template<typename CALLABLE, typename ... ARGS>
|
template<typename CALLABLE, typename ... ARGS>
|
||||||
void push(CALLABLE&& func, ARGS&& ... args) {
|
void push(CALLABLE&& func, ARGS&& ... args) {
|
||||||
enqueue(Job(std::bind(std::forward<CALLABLE>(func), std::forward<ARGS>(args)...)));
|
enqueue(Job(std::bind(std::forward<CALLABLE>(func), std::forward<ARGS>(args)...)));
|
||||||
@@ -93,10 +111,10 @@ public:
|
|||||||
explicit TimerQueryFallback();
|
explicit TimerQueryFallback();
|
||||||
~TimerQueryFallback() override;
|
~TimerQueryFallback() override;
|
||||||
private:
|
private:
|
||||||
|
void createTimerQuery(GLTimerQuery* query) override;
|
||||||
|
void destroyTimerQuery(GLTimerQuery* query) override;
|
||||||
void beginTimeElapsedQuery(GLTimerQuery* query) override;
|
void beginTimeElapsedQuery(GLTimerQuery* query) override;
|
||||||
void endTimeElapsedQuery(GLTimerQuery* query) override;
|
void endTimeElapsedQuery(GLTimerQuery* query) override;
|
||||||
bool queryResultAvailable(GLTimerQuery* query) override;
|
|
||||||
uint64_t queryResult(GLTimerQuery* query) override;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace filament::backend
|
} // namespace filament::backend
|
||||||
|
|||||||
@@ -32,7 +32,6 @@
|
|||||||
#include <utils/Systrace.h>
|
#include <utils/Systrace.h>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <future>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
@@ -64,17 +63,18 @@ static inline std::string to_string(float f) noexcept {
|
|||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
struct ShaderCompilerService::ProgramToken {
|
struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
|
||||||
struct ProgramBinary {
|
struct ProgramData {
|
||||||
GLenum format{};
|
|
||||||
GLuint program{};
|
GLuint program{};
|
||||||
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders{};
|
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders{};
|
||||||
std::vector<char> blob;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ProgramToken(ShaderCompilerService& compiler, utils::CString const& name) noexcept
|
~OpenGLProgramToken() override;
|
||||||
|
|
||||||
|
OpenGLProgramToken(ShaderCompilerService& compiler, utils::CString const& name) noexcept
|
||||||
: compiler(compiler), name(name) {
|
: compiler(compiler), name(name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ShaderCompilerService& compiler;
|
ShaderCompilerService& compiler;
|
||||||
utils::CString const& name;
|
utils::CString const& name;
|
||||||
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> attributes;
|
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> attributes;
|
||||||
@@ -85,11 +85,44 @@ struct ShaderCompilerService::ProgramToken {
|
|||||||
GLuint program = 0;
|
GLuint program = 0;
|
||||||
} gl; // 12 bytes
|
} 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;
|
BlobCacheKey key;
|
||||||
std::future<ProgramBinary> binary;
|
mutable utils::Mutex lock;
|
||||||
bool canceled = false;
|
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 {
|
void ShaderCompilerService::setUserData(const program_token_t& token, void* user) noexcept {
|
||||||
token->user = user;
|
token->user = user;
|
||||||
}
|
}
|
||||||
@@ -100,297 +133,181 @@ void* ShaderCompilerService::getUserData(const program_token_t& token) noexcept
|
|||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
ShaderCompilerService::CompilerThreadPool::CompilerThreadPool() noexcept = default;
|
|
||||||
|
|
||||||
ShaderCompilerService::CompilerThreadPool::~CompilerThreadPool() noexcept {
|
|
||||||
assert_invariant(mCompilerThreads.empty());
|
|
||||||
assert_invariant(mQueues[0].empty());
|
|
||||||
assert_invariant(mQueues[1].empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderCompilerService::CompilerThreadPool::init(
|
|
||||||
bool useSharedContexts, uint32_t threadCount, OpenGLPlatform& platform) noexcept {
|
|
||||||
|
|
||||||
for (size_t i = 0; i < threadCount; i++) {
|
|
||||||
mCompilerThreads.emplace_back([this, useSharedContexts, &platform]() {
|
|
||||||
// give the thread a name
|
|
||||||
JobSystem::setThreadName("CompilerThreadPool");
|
|
||||||
|
|
||||||
// create a gl context current to this thread
|
|
||||||
platform.createContext(useSharedContexts);
|
|
||||||
|
|
||||||
// process jobs from the queue until we're asked to exit
|
|
||||||
while (!mExitRequested) {
|
|
||||||
std::unique_lock lock(mQueueLock);
|
|
||||||
mQueueCondition.wait(lock, [this]() {
|
|
||||||
return mExitRequested ||
|
|
||||||
mUrgentJob ||
|
|
||||||
(!std::all_of( std::begin(mQueues), std::end(mQueues),
|
|
||||||
[](auto&& q) { return q.empty(); }));
|
|
||||||
});
|
|
||||||
if (!mExitRequested) {
|
|
||||||
Job job{ std::move(mUrgentJob) };
|
|
||||||
if (!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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto ShaderCompilerService::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 ShaderCompilerService::CompilerThreadPool::dequeue(program_token_t const& token) -> Job {
|
|
||||||
Job job;
|
|
||||||
auto&& [q, pos] = find(token);
|
|
||||||
if (pos != q.end()) {
|
|
||||||
std::swap(job, pos->second);
|
|
||||||
q.erase(pos);
|
|
||||||
}
|
|
||||||
return job;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderCompilerService::CompilerThreadPool::makeUrgent(program_token_t const& token) {
|
|
||||||
std::unique_lock const lock(mQueueLock);
|
|
||||||
assert_invariant(!mUrgentJob);
|
|
||||||
Job job{ dequeue(token) };
|
|
||||||
std::swap(job, mUrgentJob);
|
|
||||||
mQueueCondition.notify_one();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShaderCompilerService::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 ShaderCompilerService::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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
ShaderCompilerService::ShaderCompilerService(OpenGLDriver& driver)
|
ShaderCompilerService::ShaderCompilerService(OpenGLDriver& driver)
|
||||||
: mDriver(driver),
|
: mDriver(driver),
|
||||||
|
mCallbackManager(driver),
|
||||||
KHR_parallel_shader_compile(driver.getContext().ext.KHR_parallel_shader_compile) {
|
KHR_parallel_shader_compile(driver.getContext().ext.KHR_parallel_shader_compile) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ShaderCompilerService::~ShaderCompilerService() noexcept = default;
|
ShaderCompilerService::~ShaderCompilerService() noexcept = default;
|
||||||
|
|
||||||
|
bool ShaderCompilerService::isParallelShaderCompileSupported() const noexcept {
|
||||||
|
return KHR_parallel_shader_compile || mShaderCompilerThreadCount;
|
||||||
|
}
|
||||||
|
|
||||||
void ShaderCompilerService::init() noexcept {
|
void ShaderCompilerService::init() noexcept {
|
||||||
// If we have KHR_parallel_shader_compile, we always use it, it should be more resource
|
// If we have KHR_parallel_shader_compile, we always use it, it should be more resource
|
||||||
// friendly.
|
// friendly.
|
||||||
if (!KHR_parallel_shader_compile) {
|
if (!KHR_parallel_shader_compile) {
|
||||||
// - on Adreno there is a single compiler object. We can't use a pool > 1
|
// - on Adreno there is a single compiler object. We can't use a pool > 1
|
||||||
// also glProgramBinary blocks if other threads are compiling.
|
// also glProgramBinary blocks if other threads are compiling.
|
||||||
// - on Mali shader compilation can be multithreaded, but program linking happens on
|
// - 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.
|
// 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
|
// - 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.
|
// a shared context, so parallel shader compilation yields no benefit.
|
||||||
// - on windows/linux we could use more threads, tbd.
|
// - on windows/linux we could use more threads, tbd.
|
||||||
if (mDriver.mPlatform.isExtraContextSupported()) {
|
if (mDriver.mPlatform.isExtraContextSupported()) {
|
||||||
mShaderCompilerThreadCount = 1;
|
// By default, we use one thread at the same priority as the gl thread. This is the
|
||||||
mCompilerThreadPool.init(mUseSharedContext,
|
// safest choice that avoids priority inversions.
|
||||||
mShaderCompilerThreadCount, mDriver.mPlatform);
|
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 {
|
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();
|
mCompilerThreadPool.terminate();
|
||||||
|
|
||||||
// We could have some pending callbacks here, we need to execute them
|
|
||||||
for (auto&& op: mRunAtNextTickOps) {
|
|
||||||
Job const& job = std::get<2>(op);
|
|
||||||
if (job.callback) {
|
|
||||||
mDriver.scheduleCallback(job.handler, job.user, job.callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mRunAtNextTickOps.clear();
|
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(
|
ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||||
utils::CString const& name, Program&& program) {
|
utils::CString const& name, Program&& program) {
|
||||||
auto& gl = mDriver.getContext();
|
auto& gl = mDriver.getContext();
|
||||||
|
|
||||||
auto token = std::make_shared<ProgramToken>(*this, name);
|
auto token = std::make_shared<OpenGLProgramToken>(*this, name);
|
||||||
|
|
||||||
if (UTILS_UNLIKELY(gl.isES2())) {
|
if (UTILS_UNLIKELY(gl.isES2())) {
|
||||||
token->attributes = std::move(program.getAttributes());
|
token->attributes = std::move(program.getAttributes());
|
||||||
}
|
}
|
||||||
|
|
||||||
token->gl.program = OpenGLBlobCache::retrieve(&token->key, mDriver.mPlatform, program);
|
token->gl.program = OpenGLBlobCache::retrieve(&token->key, mDriver.mPlatform, program);
|
||||||
if (!token->gl.program) {
|
if (token->gl.program) {
|
||||||
CompilerPriorityQueue const priorityQueue = program.getPriorityQueue();
|
return token;
|
||||||
if (mShaderCompilerThreadCount) {
|
}
|
||||||
// set the future in the token and pass the promise to the worker thread
|
|
||||||
std::promise<ProgramToken::ProgramBinary> promise;
|
|
||||||
token->binary = promise.get_future();
|
|
||||||
// queue a compile job
|
|
||||||
mCompilerThreadPool.queue(priorityQueue, token,
|
|
||||||
[this, &gl, promise = std::move(promise),
|
|
||||||
program = std::move(program), token]() mutable {
|
|
||||||
|
|
||||||
// compile the shaders
|
token->handle = mCallbackManager.get();
|
||||||
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
|
CompilerPriorityQueue const priorityQueue = program.getPriorityQueue();
|
||||||
GLuint const glProgram = linkProgram(gl, shaders, token->attributes);
|
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);
|
||||||
|
|
||||||
ProgramToken::ProgramBinary binary;
|
// link the program
|
||||||
binary.shaders = shaders;
|
GLuint const glProgram = linkProgram(gl, shaders, token->attributes);
|
||||||
|
|
||||||
if (UTILS_LIKELY(mUseSharedContext)) {
|
OpenGLProgramToken::ProgramData programData;
|
||||||
// We need to query the link status here to guarantee that the
|
programData.shaders = shaders;
|
||||||
// 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);
|
|
||||||
binary.program = glProgram;
|
|
||||||
if (token->key) {
|
|
||||||
// Attempt to cache. This calls glGetProgramBinary.
|
|
||||||
OpenGLBlobCache::insert(mDriver.mPlatform, token->key, glProgram);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
|
||||||
else {
|
|
||||||
// retrieve the program binary
|
|
||||||
GLsizei programBinarySize = 0;
|
|
||||||
glGetProgramiv(glProgram, GL_PROGRAM_BINARY_LENGTH, &programBinarySize);
|
|
||||||
assert_invariant(programBinarySize);
|
|
||||||
if (programBinarySize) {
|
|
||||||
binary.blob.resize(programBinarySize);
|
|
||||||
glGetProgramBinary(glProgram, programBinarySize,
|
|
||||||
&programBinarySize, &binary.format, binary.blob.data());
|
|
||||||
}
|
|
||||||
// and we can destroy the program
|
|
||||||
glDeleteProgram(glProgram);
|
|
||||||
if (token->key) {
|
|
||||||
// attempt to cache
|
|
||||||
OpenGLBlobCache::insert(mDriver.mPlatform, token->key,
|
|
||||||
binary.format,
|
|
||||||
binary.blob.data(), GLsizei(binary.blob.size()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
// we don't need to check for success here, it'll be done on the
|
|
||||||
// main thread side.
|
|
||||||
promise.set_value(binary);
|
|
||||||
});
|
|
||||||
} 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);
|
|
||||||
|
|
||||||
}
|
// 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&) {
|
runAtNextTick(priorityQueue, token, [this, token](Job const&) {
|
||||||
if (mShaderCompilerThreadCount) {
|
if (KHR_parallel_shader_compile) {
|
||||||
if (!token->gl.program) {
|
// don't attempt to link this program if all shaders are not done compiling
|
||||||
// TODO: see if we could completely eliminate this callback here
|
GLint status;
|
||||||
// and instead just rely on token->gl.program being atomically
|
if (token->gl.program) {
|
||||||
// set by the compiler thread.
|
glGetProgramiv(token->gl.program, GL_COMPLETION_STATUS, &status);
|
||||||
assert_invariant(token->binary.valid());
|
if (status == GL_FALSE) {
|
||||||
// we're using the compiler thread, check if the program is ready, no-op if not.
|
|
||||||
using namespace std::chrono_literals;
|
|
||||||
if (token->binary.wait_for(0s) != std::future_status::ready) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// program binary is ready, retrieve it without blocking
|
} else {
|
||||||
ShaderCompilerService::getProgramFromCompilerPool(
|
for (auto shader: token->gl.shaders) {
|
||||||
const_cast<program_token_t&>(token));
|
if (shader) {
|
||||||
}
|
glGetShaderiv(shader, GL_COMPLETION_STATUS, &status);
|
||||||
} else {
|
if (status == GL_FALSE) {
|
||||||
if (KHR_parallel_shader_compile) {
|
return false;
|
||||||
// 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) {
|
if (!token->gl.program) {
|
||||||
// link the program, this also cannot fail because status is checked later.
|
// link the program, this also cannot fail because status is checked later.
|
||||||
token->gl.program = linkProgram(mDriver.getContext(),
|
token->gl.program = linkProgram(mDriver.getContext(),
|
||||||
token->gl.shaders, token->attributes);
|
token->gl.shaders, token->attributes);
|
||||||
if (KHR_parallel_shader_compile) {
|
if (KHR_parallel_shader_compile) {
|
||||||
// wait until the link finishes...
|
// wait until the link finishes...
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_invariant(token->gl.program);
|
assert_invariant(token->gl.program);
|
||||||
|
|
||||||
if (token->key && !mShaderCompilerThreadCount) {
|
mCallbackManager.put(token->handle);
|
||||||
|
|
||||||
|
if (token->key) {
|
||||||
// TODO: technically we don't have to cache right now. Is it advantageous to
|
// TODO: technically we don't have to cache right now. Is it advantageous to
|
||||||
// do this later, maybe depending on CPU usage?
|
// do this later, maybe depending on CPU usage?
|
||||||
// attempt to cache if we don't have a thread pool (otherwise it's done
|
// attempt to cache if we don't have a thread pool (otherwise it's done
|
||||||
@@ -405,31 +322,12 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
|||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShaderCompilerService::isProgramReady(
|
|
||||||
const ShaderCompilerService::program_token_t& token) const noexcept {
|
|
||||||
|
|
||||||
assert_invariant(token);
|
|
||||||
|
|
||||||
if (!token->gl.program) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (KHR_parallel_shader_compile) {
|
|
||||||
GLint status = GL_FALSE;
|
|
||||||
glGetProgramiv(token->gl.program, GL_COMPLETION_STATUS, &status);
|
|
||||||
return (bool)status;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If gl.program is set, this means the program was linked. Some drivers may defer the link
|
|
||||||
// in which case we might block in getProgram() when we check the program status.
|
|
||||||
// Unfortunately, this is nothing we can do about that.
|
|
||||||
return bool(token->gl.program);
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t& token) {
|
GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t& token) {
|
||||||
GLuint const program = initialize(token);
|
GLuint const program = initialize(token);
|
||||||
assert_invariant(token == nullptr);
|
assert_invariant(token == nullptr);
|
||||||
|
#ifndef FILAMENT_ENABLE_MATDBG
|
||||||
assert_invariant(program);
|
assert_invariant(program);
|
||||||
|
#endif
|
||||||
return program;
|
return program;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,78 +351,30 @@ GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t&
|
|||||||
glDeleteProgram(token->gl.program);
|
glDeleteProgram(token->gl.program);
|
||||||
}
|
}
|
||||||
|
|
||||||
token = nullptr;
|
token.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShaderCompilerService::tick() {
|
void ShaderCompilerService::tick() {
|
||||||
executeTickOps();
|
// we don't need to run executeTickOps() if we're using the thread-pool
|
||||||
|
if (UTILS_UNLIKELY(!mShaderCompilerThreadCount)) {
|
||||||
|
executeTickOps();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShaderCompilerService::notifyWhenAllProgramsAreReady(CompilerPriorityQueue priority,
|
void ShaderCompilerService::notifyWhenAllProgramsAreReady(
|
||||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||||
|
if (callback) {
|
||||||
if (KHR_parallel_shader_compile || mShaderCompilerThreadCount) {
|
mCallbackManager.setCallback(handler, callback, user);
|
||||||
// list all programs up to this point, both low and high priority
|
|
||||||
|
|
||||||
using TokenVector = utils::FixedCapacityVector<
|
|
||||||
program_token_t, std::allocator<program_token_t>, false>;
|
|
||||||
TokenVector tokens{ TokenVector::with_capacity(mRunAtNextTickOps.size()) };
|
|
||||||
|
|
||||||
for (auto& [itemPriority, token, job] : mRunAtNextTickOps) {
|
|
||||||
if (token && job.fn && itemPriority == priority) {
|
|
||||||
tokens.push_back(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
runAtNextTick(priority, nullptr, {
|
|
||||||
[this, tokens = std::move(tokens)](Job const& job) {
|
|
||||||
for (auto const& token : tokens) {
|
|
||||||
assert_invariant(token);
|
|
||||||
if (!isProgramReady(token)) {
|
|
||||||
// one of the program is not ready, try next time
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (job.callback) {
|
|
||||||
// all programs are ready, we can call the callbacks
|
|
||||||
mDriver.scheduleCallback(job.handler, job.user, job.callback);
|
|
||||||
}
|
|
||||||
// and we're done
|
|
||||||
return true;
|
|
||||||
}, handler, user, callback });
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// we don't have KHR_parallel_shader_compile
|
|
||||||
|
|
||||||
runAtNextTick(priority, nullptr, {[this](Job const& job) {
|
|
||||||
mDriver.scheduleCallback(job.handler, job.user, job.callback);
|
|
||||||
return true;
|
|
||||||
}, handler, user, callback });
|
|
||||||
|
|
||||||
// TODO: we could spread the compiles over several frames, the tick() below then is not
|
|
||||||
// needed here. We keep it for now as to not change the current behavior too much.
|
|
||||||
// this will block until all programs are linked
|
|
||||||
tick();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
void ShaderCompilerService::getProgramFromCompilerPool(program_token_t& token) noexcept {
|
void ShaderCompilerService::getProgramFromCompilerPool(program_token_t& token) noexcept {
|
||||||
ProgramToken::ProgramBinary const binary{ token->binary.get() };
|
OpenGLProgramToken::ProgramData const& programData{ token->get() };
|
||||||
if (!token->canceled) {
|
if (!token->canceled) {
|
||||||
token->gl.shaders = binary.shaders;
|
token->gl.shaders = programData.shaders;
|
||||||
if (UTILS_LIKELY(mUseSharedContext)) {
|
token->gl.program = programData.program;
|
||||||
token->gl.program = binary.program;
|
|
||||||
}
|
|
||||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
|
||||||
else {
|
|
||||||
token->gl.program = glCreateProgram();
|
|
||||||
glProgramBinary(token->gl.program, binary.format,
|
|
||||||
binary.blob.data(), GLsizei(binary.blob.size()));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,24 +382,36 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
|
|||||||
SYSTRACE_CALL();
|
SYSTRACE_CALL();
|
||||||
if (!token->gl.program) {
|
if (!token->gl.program) {
|
||||||
if (mShaderCompilerThreadCount) {
|
if (mShaderCompilerThreadCount) {
|
||||||
// Block until the program is ready. This could take a very long time.
|
// we need this program right now, remove it from the queue
|
||||||
assert_invariant(token->binary.valid());
|
auto job = mCompilerThreadPool.dequeue(token);
|
||||||
|
if (job) {
|
||||||
// we need this program right now, so move it to the head of the queue.
|
// if we were able to remove it, we execute the job now, otherwise it means
|
||||||
mCompilerThreadPool.makeUrgent(token);
|
// it's being executed right now.
|
||||||
|
job();
|
||||||
|
}
|
||||||
|
|
||||||
if (!token->canceled) {
|
if (!token->canceled) {
|
||||||
token->compiler.cancelTickOp(token);
|
token->compiler.cancelTickOp(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
// block until we get the program from the pool
|
// 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);
|
ShaderCompilerService::getProgramFromCompilerPool(token);
|
||||||
} else if (KHR_parallel_shader_compile) {
|
} else if (KHR_parallel_shader_compile) {
|
||||||
// we force the program link -- which might stall, either here or below in
|
// 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.
|
// checkProgramStatus(), but we don't have a choice, we need to use the program now.
|
||||||
token->compiler.cancelTickOp(token);
|
token->compiler.cancelTickOp(token);
|
||||||
|
|
||||||
token->gl.program = linkProgram(mDriver.getContext(),
|
token->gl.program = linkProgram(mDriver.getContext(),
|
||||||
token->gl.shaders, token->attributes);
|
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 {
|
} else {
|
||||||
// if we don't have a program yet, block until we get it.
|
// if we don't have a program yet, block until we get it.
|
||||||
tick();
|
tick();
|
||||||
@@ -803,8 +665,7 @@ void ShaderCompilerService::runAtNextTick(CompilerPriorityQueue priority,
|
|||||||
void ShaderCompilerService::cancelTickOp(program_token_t token) noexcept {
|
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.
|
// We do a linear search here, but this is rare, and we know the list is pretty small.
|
||||||
auto& ops = mRunAtNextTickOps;
|
auto& ops = mRunAtNextTickOps;
|
||||||
auto pos = std::find_if(ops.begin(), ops.end(),
|
auto pos = std::find_if(ops.begin(), ops.end(), [&](const auto& item) {
|
||||||
[&](const auto& item) {
|
|
||||||
return std::get<1>(item) == token;
|
return std::get<1>(item) == token;
|
||||||
});
|
});
|
||||||
if (pos != ops.end()) {
|
if (pos != ops.end()) {
|
||||||
|
|||||||
@@ -19,12 +19,16 @@
|
|||||||
|
|
||||||
#include "gl_headers.h"
|
#include "gl_headers.h"
|
||||||
|
|
||||||
|
#include "CallbackManager.h"
|
||||||
|
#include "CompilerThreadPool.h"
|
||||||
|
|
||||||
#include <backend/CallbackHandler.h>
|
#include <backend/CallbackHandler.h>
|
||||||
#include <backend/Program.h>
|
#include <backend/Program.h>
|
||||||
|
|
||||||
#include <utils/CString.h>
|
#include <utils/CString.h>
|
||||||
#include <utils/Invocable.h>
|
|
||||||
#include <utils/FixedCapacityVector.h>
|
#include <utils/FixedCapacityVector.h>
|
||||||
|
#include <utils/Invocable.h>
|
||||||
|
#include <utils/JobSystem.h>
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
@@ -48,10 +52,10 @@ class CallbackHandler;
|
|||||||
* A class handling shader compilation that supports asynchronous compilation.
|
* A class handling shader compilation that supports asynchronous compilation.
|
||||||
*/
|
*/
|
||||||
class ShaderCompilerService {
|
class ShaderCompilerService {
|
||||||
struct ProgramToken;
|
struct OpenGLProgramToken;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using program_token_t = std::shared_ptr<ProgramToken>;
|
using program_token_t = std::shared_ptr<OpenGLProgramToken>;
|
||||||
|
|
||||||
explicit ShaderCompilerService(OpenGLDriver& driver);
|
explicit ShaderCompilerService(OpenGLDriver& driver);
|
||||||
|
|
||||||
@@ -62,16 +66,14 @@ public:
|
|||||||
|
|
||||||
~ShaderCompilerService() noexcept;
|
~ShaderCompilerService() noexcept;
|
||||||
|
|
||||||
|
bool isParallelShaderCompileSupported() const noexcept;
|
||||||
|
|
||||||
void init() noexcept;
|
void init() noexcept;
|
||||||
void terminate() noexcept;
|
void terminate() noexcept;
|
||||||
|
|
||||||
// creates a program (compile + link) asynchronously if supported
|
// creates a program (compile + link) asynchronously if supported
|
||||||
program_token_t createProgram(utils::CString const& name, Program&& program);
|
program_token_t createProgram(utils::CString const& name, Program&& program);
|
||||||
|
|
||||||
// Returns true if the program is linked (successfully or not). Guarantees that
|
|
||||||
// getProgram() won't block. Does not block.
|
|
||||||
bool isProgramReady(const program_token_t& token) const noexcept;
|
|
||||||
|
|
||||||
// Return the GL program, blocks if necessary. The Token is destroyed and becomes invalid.
|
// Return the GL program, blocks if necessary. The Token is destroyed and becomes invalid.
|
||||||
GLuint getProgram(program_token_t& token);
|
GLuint getProgram(program_token_t& token);
|
||||||
|
|
||||||
@@ -88,42 +90,17 @@ public:
|
|||||||
static void* getUserData(const program_token_t& token) noexcept;
|
static void* getUserData(const program_token_t& token) noexcept;
|
||||||
|
|
||||||
// call the callback when all active programs are ready
|
// call the callback when all active programs are ready
|
||||||
void notifyWhenAllProgramsAreReady(CompilerPriorityQueue priority,
|
void notifyWhenAllProgramsAreReady(
|
||||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user);
|
CallbackHandler* handler, CallbackHandler::Callback callback, void* user);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class CompilerThreadPool {
|
|
||||||
public:
|
|
||||||
CompilerThreadPool() noexcept;
|
|
||||||
~CompilerThreadPool() noexcept;
|
|
||||||
using Job = utils::Invocable<void()>;
|
|
||||||
void init(bool useSharedContexts, uint32_t threadCount, OpenGLPlatform& platform) noexcept;
|
|
||||||
void terminate() noexcept;
|
|
||||||
void queue(CompilerPriorityQueue priorityQueue, program_token_t const& token, Job&& job);
|
|
||||||
void makeUrgent(program_token_t const& token);
|
|
||||||
|
|
||||||
private:
|
|
||||||
using Queue = std::deque<std::pair<program_token_t, Job>>;
|
|
||||||
std::vector<std::thread> mCompilerThreads;
|
|
||||||
std::atomic_bool mExitRequested{ false };
|
|
||||||
std::mutex mQueueLock;
|
|
||||||
std::condition_variable mQueueCondition;
|
|
||||||
std::array<Queue, 2> mQueues;
|
|
||||||
Job mUrgentJob; // needs mQueueLock as well
|
|
||||||
Job dequeue(program_token_t const& token); // lock must be held
|
|
||||||
std::pair<Queue&, Queue::iterator> find(program_token_t const& token);
|
|
||||||
};
|
|
||||||
|
|
||||||
OpenGLDriver& mDriver;
|
OpenGLDriver& mDriver;
|
||||||
|
CallbackManager mCallbackManager;
|
||||||
CompilerThreadPool mCompilerThreadPool;
|
CompilerThreadPool mCompilerThreadPool;
|
||||||
|
|
||||||
const bool KHR_parallel_shader_compile;
|
const bool KHR_parallel_shader_compile;
|
||||||
uint32_t mShaderCompilerThreadCount = 0u;
|
uint32_t mShaderCompilerThreadCount = 0u;
|
||||||
|
|
||||||
// For now, we assume shared contexts are supported everywhere. If they are not,
|
|
||||||
// we don't use the shader compiler pool. However, the code supports it.
|
|
||||||
static constexpr bool mUseSharedContext = true;
|
|
||||||
|
|
||||||
GLuint initialize(ShaderCompilerService::program_token_t& token) noexcept;
|
GLuint initialize(ShaderCompilerService::program_token_t& token) noexcept;
|
||||||
|
|
||||||
static void getProgramFromCompilerPool(program_token_t& token) noexcept;
|
static void getProgramFromCompilerPool(program_token_t& token) noexcept;
|
||||||
|
|||||||
@@ -115,9 +115,14 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon
|
|||||||
|
|
||||||
auto extensions = GLUtils::split(eglQueryString(mEGLDisplay, EGL_EXTENSIONS));
|
auto extensions = GLUtils::split(eglQueryString(mEGLDisplay, EGL_EXTENSIONS));
|
||||||
ext.egl.ANDROID_recordable = extensions.has("EGL_ANDROID_recordable");
|
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_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_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");
|
eglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC) eglGetProcAddress("eglCreateSyncKHR");
|
||||||
eglDestroySyncKHR = (PFNEGLDESTROYSYNCKHRPROC) eglGetProcAddress("eglDestroySyncKHR");
|
eglDestroySyncKHR = (PFNEGLDESTROYSYNCKHRPROC) eglGetProcAddress("eglDestroySyncKHR");
|
||||||
@@ -181,13 +186,6 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon
|
|||||||
eglConfig = mEGLConfig;
|
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++) {
|
for (size_t tries = 0; tries < 3; tries++) {
|
||||||
mEGLContext = eglCreateContext(mEGLDisplay, eglConfig,
|
mEGLContext = eglCreateContext(mEGLDisplay, eglConfig,
|
||||||
(EGLContext)sharedContext, contextAttribs.data());
|
(EGLContext)sharedContext, contextAttribs.data());
|
||||||
@@ -220,6 +218,26 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon
|
|||||||
goto error;
|
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))) {
|
if (UTILS_UNLIKELY(!makeCurrent(mEGLDummySurface, mEGLDummySurface))) {
|
||||||
// eglMakeCurrent failed
|
// eglMakeCurrent failed
|
||||||
logEglError("eglMakeCurrent");
|
logEglError("eglMakeCurrent");
|
||||||
@@ -255,11 +273,13 @@ error:
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool PlatformEGL::isExtraContextSupported() const noexcept {
|
bool PlatformEGL::isExtraContextSupported() const noexcept {
|
||||||
return ext.egl.KHR_no_config_context;
|
return ext.egl.KHR_surfaceless_context;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlatformEGL::createContext(bool shared) {
|
void PlatformEGL::createContext(bool shared) {
|
||||||
EGLContext context = eglCreateContext(mEGLDisplay, EGL_NO_CONFIG_KHR,
|
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());
|
shared ? mEGLContext : EGL_NO_CONTEXT, mContextAttribs.data());
|
||||||
|
|
||||||
if (UTILS_UNLIKELY(context == EGL_NO_CONTEXT)) {
|
if (UTILS_UNLIKELY(context == EGL_NO_CONTEXT)) {
|
||||||
@@ -274,6 +294,22 @@ void PlatformEGL::createContext(bool shared) {
|
|||||||
mAdditionalContexts.push_back(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 {
|
EGLBoolean PlatformEGL::makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) noexcept {
|
||||||
if (UTILS_UNLIKELY((drawSurface != mCurrentDrawSurface || readSurface != mCurrentReadSurface))) {
|
if (UTILS_UNLIKELY((drawSurface != mCurrentDrawSurface || readSurface != mCurrentReadSurface))) {
|
||||||
mCurrentDrawSurface = drawSurface;
|
mCurrentDrawSurface = drawSurface;
|
||||||
@@ -284,8 +320,11 @@ EGLBoolean PlatformEGL::makeCurrent(EGLSurface drawSurface, EGLSurface readSurfa
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PlatformEGL::terminate() noexcept {
|
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);
|
eglMakeCurrent(mEGLDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||||
eglDestroySurface(mEGLDisplay, mEGLDummySurface);
|
if (mEGLDummySurface) {
|
||||||
|
eglDestroySurface(mEGLDisplay, mEGLDummySurface);
|
||||||
|
}
|
||||||
eglDestroyContext(mEGLDisplay, mEGLContext);
|
eglDestroyContext(mEGLDisplay, mEGLContext);
|
||||||
for (auto context : mAdditionalContexts) {
|
for (auto context : mAdditionalContexts) {
|
||||||
eglDestroyContext(mEGLDisplay, context);
|
eglDestroyContext(mEGLDisplay, context);
|
||||||
|
|||||||
@@ -16,7 +16,10 @@
|
|||||||
|
|
||||||
#include "VulkanBlitter.h"
|
#include "VulkanBlitter.h"
|
||||||
#include "VulkanContext.h"
|
#include "VulkanContext.h"
|
||||||
|
#include "VulkanFboCache.h"
|
||||||
#include "VulkanHandles.h"
|
#include "VulkanHandles.h"
|
||||||
|
#include "VulkanSamplerCache.h"
|
||||||
|
#include "VulkanTexture.h"
|
||||||
|
|
||||||
#include <utils/FixedCapacityVector.h>
|
#include <utils/FixedCapacityVector.h>
|
||||||
#include <utils/Panic.h>
|
#include <utils/Panic.h>
|
||||||
@@ -134,16 +137,20 @@ void VulkanBlitter::blitColor(BlitArgs args) {
|
|||||||
VkFormatProperties info;
|
VkFormatProperties info;
|
||||||
vkGetPhysicalDeviceFormatProperties(gpu, src.getFormat(), &info);
|
vkGetPhysicalDeviceFormatProperties(gpu, src.getFormat(), &info);
|
||||||
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT,
|
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;
|
return;
|
||||||
}
|
}
|
||||||
vkGetPhysicalDeviceFormatProperties(gpu, dst.getFormat(), &info);
|
vkGetPhysicalDeviceFormatProperties(gpu, dst.getFormat(), &info);
|
||||||
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT,
|
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;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#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,
|
blitFast(cmdbuffer, aspect, args.filter, args.srcTarget->getExtent(), src, dst,
|
||||||
args.srcRectPair, args.dstRectPair);
|
args.srcRectPair, args.dstRectPair);
|
||||||
}
|
}
|
||||||
@@ -158,12 +165,12 @@ void VulkanBlitter::blitDepth(BlitArgs args) {
|
|||||||
VkFormatProperties info;
|
VkFormatProperties info;
|
||||||
vkGetPhysicalDeviceFormatProperties(gpu, src.getFormat(), &info);
|
vkGetPhysicalDeviceFormatProperties(gpu, src.getFormat(), &info);
|
||||||
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT,
|
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;
|
return;
|
||||||
}
|
}
|
||||||
vkGetPhysicalDeviceFormatProperties(gpu, dst.getFormat(), &info);
|
vkGetPhysicalDeviceFormatProperties(gpu, dst.getFormat(), &info);
|
||||||
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT,
|
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;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -175,7 +182,11 @@ void VulkanBlitter::blitDepth(BlitArgs args) {
|
|||||||
args.dstRectPair);
|
args.dstRectPair);
|
||||||
return;
|
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,
|
blitFast(cmdbuffer, aspect, args.filter, args.srcTarget->getExtent(), src, dst, args.srcRectPair,
|
||||||
args.dstRectPair);
|
args.dstRectPair);
|
||||||
}
|
}
|
||||||
@@ -245,13 +256,16 @@ void VulkanBlitter::lazyInit() noexcept {
|
|||||||
+1.0f, +1.0f,
|
+1.0f, +1.0f,
|
||||||
};
|
};
|
||||||
|
|
||||||
mTriangleBuffer = new VulkanBuffer(mAllocator, mCommands, mStagePool,
|
VulkanCommandBuffer& commands = mCommands->get();
|
||||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, sizeof(kTriangleVertices));
|
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,
|
mTriangleBuffer->loadFromCpu(cmdbuffer, kTriangleVertices, 0, sizeof(kTriangleVertices));
|
||||||
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, sizeof(BlitterUniforms));
|
|
||||||
|
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:
|
// At a high level, the procedure for resolving depth looks like this:
|
||||||
@@ -263,11 +277,16 @@ void VulkanBlitter::blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, V
|
|||||||
VulkanAttachment dst, const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]) {
|
VulkanAttachment dst, const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]) {
|
||||||
lazyInit();
|
lazyInit();
|
||||||
|
|
||||||
|
VulkanCommandBuffer* commands = &mCommands->get();
|
||||||
|
VkCommandBuffer const cmdbuffer = commands->cmdbuffer;
|
||||||
|
commands->acquire(src.texture);
|
||||||
|
commands->acquire(dst.texture);
|
||||||
|
|
||||||
BlitterUniforms const uniforms = {
|
BlitterUniforms const uniforms = {
|
||||||
.sampleCount = src.texture->samples,
|
.sampleCount = src.texture->samples,
|
||||||
.inverseSampleCount = 1.0f / float(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;
|
VkImageAspectFlags const aspect = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||||
|
|
||||||
@@ -314,8 +333,6 @@ void VulkanBlitter::blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, V
|
|||||||
renderPassInfo.renderArea.extent.width = dstRect[1].x - dstRect[0].x;
|
renderPassInfo.renderArea.extent.width = dstRect[1].x - dstRect[0].x;
|
||||||
renderPassInfo.renderArea.extent.height = dstRect[1].y - dstRect[0].y;
|
renderPassInfo.renderArea.extent.height = dstRect[1].y - dstRect[0].y;
|
||||||
|
|
||||||
const VkCommandBuffer cmdbuffer = mCommands->get().cmdbuffer;
|
|
||||||
|
|
||||||
// We need to transition the source into a sampler since it'll be sampled in the shader.
|
// We need to transition the source into a sampler since it'll be sampled in the shader.
|
||||||
const VkImageSubresourceRange srcRange = {
|
const VkImageSubresourceRange srcRange = {
|
||||||
.aspectMask = aspect,
|
.aspectMask = aspect,
|
||||||
@@ -376,6 +393,7 @@ void VulkanBlitter::blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, V
|
|||||||
VkSampler vksampler = mSamplerCache.getSampler({});
|
VkSampler vksampler = mSamplerCache.getSampler({});
|
||||||
|
|
||||||
VkDescriptorImageInfo samplers[VulkanPipelineCache::SAMPLER_BINDING_COUNT];
|
VkDescriptorImageInfo samplers[VulkanPipelineCache::SAMPLER_BINDING_COUNT];
|
||||||
|
VulkanTexture* textures[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {nullptr};
|
||||||
for (auto& sampler : samplers) {
|
for (auto& sampler : samplers) {
|
||||||
sampler = {
|
sampler = {
|
||||||
.sampler = vksampler,
|
.sampler = vksampler,
|
||||||
@@ -389,8 +407,9 @@ void VulkanBlitter::blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, V
|
|||||||
.imageView = src.getImageView(VK_IMAGE_ASPECT_DEPTH_BIT),
|
.imageView = src.getImageView(VK_IMAGE_ASPECT_DEPTH_BIT),
|
||||||
.imageLayout = ImgUtil::getVkLayout(samplerLayout),
|
.imageLayout = ImgUtil::getVkLayout(samplerLayout),
|
||||||
};
|
};
|
||||||
|
textures[0] = src.texture;
|
||||||
|
|
||||||
mPipelineCache.bindSamplers(samplers,
|
mPipelineCache.bindSamplers(samplers, textures,
|
||||||
VulkanPipelineCache::getUsageFlags(0, ShaderStageFlags::FRAGMENT));
|
VulkanPipelineCache::getUsageFlags(0, ShaderStageFlags::FRAGMENT));
|
||||||
|
|
||||||
auto previousUbo = mPipelineCache.getUniformBufferBinding(0);
|
auto previousUbo = mPipelineCache.getUniformBufferBinding(0);
|
||||||
@@ -407,7 +426,7 @@ void VulkanBlitter::blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, V
|
|||||||
|
|
||||||
mPipelineCache.bindScissor(cmdbuffer, scissor);
|
mPipelineCache.bindScissor(cmdbuffer, scissor);
|
||||||
|
|
||||||
if (!mPipelineCache.bindPipeline(cmdbuffer)) {
|
if (!mPipelineCache.bindPipeline(commands)) {
|
||||||
assert_invariant(false);
|
assert_invariant(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,11 @@ using namespace bluevk;
|
|||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
VulkanBuffer::VulkanBuffer(VmaAllocator allocator, VulkanCommands* commands,
|
VulkanBuffer::VulkanBuffer(VmaAllocator allocator, VulkanStagePool& stagePool,
|
||||||
VulkanStagePool& stagePool, VkBufferUsageFlags usage, uint32_t numBytes)
|
VkBufferUsageFlags usage, uint32_t numBytes)
|
||||||
: mAllocator(allocator), mCommands(commands), mStagePool(stagePool), mUsage(usage) {
|
: mAllocator(allocator),
|
||||||
|
mStagePool(stagePool),
|
||||||
|
mUsage(usage) {
|
||||||
|
|
||||||
// for now make sure that only 1 bit is set in usage
|
// for now make sure that only 1 bit is set in usage
|
||||||
// (because loadFromCpu() assumes that somewhat)
|
// (because loadFromCpu() assumes that somewhat)
|
||||||
@@ -53,7 +55,8 @@ void VulkanBuffer::terminate() {
|
|||||||
mGpuBuffer = VK_NULL_HANDLE;
|
mGpuBuffer = VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
assert_invariant(byteOffset == 0);
|
||||||
VulkanStage const* stage = mStagePool.acquireStage(numBytes);
|
VulkanStage const* stage = mStagePool.acquireStage(numBytes);
|
||||||
void* mapped;
|
void* mapped;
|
||||||
@@ -62,10 +65,8 @@ void VulkanBuffer::loadFromCpu(const void* cpuData, uint32_t byteOffset, uint32_
|
|||||||
vmaUnmapMemory(mAllocator, stage->memory);
|
vmaUnmapMemory(mAllocator, stage->memory);
|
||||||
vmaFlushAllocation(mAllocator, stage->memory, byteOffset, numBytes);
|
vmaFlushAllocation(mAllocator, stage->memory, byteOffset, numBytes);
|
||||||
|
|
||||||
VkCommandBuffer const cmdbuffer = mCommands->get(true).cmdbuffer;
|
|
||||||
|
|
||||||
VkBufferCopy region{ .size = numBytes };
|
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.
|
// 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)
|
// Secondly, in case the user decides to upload another chunk (without ever using the first one)
|
||||||
@@ -99,7 +100,7 @@ void VulkanBuffer::loadFromCpu(const void* cpuData, uint32_t byteOffset, uint32_
|
|||||||
.size = VK_WHOLE_SIZE,
|
.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);
|
&barrier, 0, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,15 +25,18 @@ namespace filament::backend {
|
|||||||
// Encapsulates a Vulkan buffer, its attached DeviceMemory and a staging area.
|
// Encapsulates a Vulkan buffer, its attached DeviceMemory and a staging area.
|
||||||
class VulkanBuffer {
|
class VulkanBuffer {
|
||||||
public:
|
public:
|
||||||
VulkanBuffer(VmaAllocator allocator, VulkanCommands* commands, VulkanStagePool& stagePool,
|
VulkanBuffer(VmaAllocator allocator, VulkanStagePool& stagePool, VkBufferUsageFlags usage,
|
||||||
VkBufferUsageFlags usage, uint32_t numBytes);
|
uint32_t numBytes);
|
||||||
~VulkanBuffer();
|
~VulkanBuffer();
|
||||||
void terminate();
|
void terminate();
|
||||||
void loadFromCpu(const void* cpuData, uint32_t byteOffset, uint32_t numBytes) const;
|
void loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint32_t byteOffset,
|
||||||
VkBuffer getGpuBuffer() const { return mGpuBuffer; }
|
uint32_t numBytes) const;
|
||||||
|
VkBuffer getGpuBuffer() const {
|
||||||
|
return mGpuBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
VmaAllocator mAllocator;
|
VmaAllocator mAllocator;
|
||||||
VulkanCommands* mCommands;
|
|
||||||
VulkanStagePool& mStagePool;
|
VulkanStagePool& mStagePool;
|
||||||
|
|
||||||
VmaAllocation mGpuMemory = VK_NULL_HANDLE;
|
VmaAllocation mGpuMemory = VK_NULL_HANDLE;
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
|
|
||||||
#include "VulkanConstants.h"
|
#include "VulkanConstants.h"
|
||||||
#include "VulkanContext.h"
|
#include "VulkanContext.h"
|
||||||
#include "VulkanDriver.h"
|
|
||||||
|
|
||||||
#include <utils/Log.h>
|
#include <utils/Log.h>
|
||||||
#include <utils/Panic.h>
|
#include <utils/Panic.h>
|
||||||
@@ -69,22 +68,35 @@ static VkCommandPool createPool(VkDevice device, uint32_t queueFamilyIndex) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanGroupMarkers::push(std::string const& marker, Timestamp start) noexcept {
|
void VulkanGroupMarkers::push(std::string const& marker, Timestamp start) noexcept {
|
||||||
mMarkers.push(marker);
|
mMarkers.push_back(marker);
|
||||||
#if FILAMENT_VULKAN_VERBOSE
|
#if FILAMENT_VULKAN_VERBOSE
|
||||||
mTimestamps.push(start.time_since_epoch().count() > 0.0
|
mTimestamps.push_back(start.time_since_epoch().count() > 0.0
|
||||||
? start
|
? start
|
||||||
: std::chrono::high_resolution_clock::now());
|
: std::chrono::high_resolution_clock::now());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<std::string, Timestamp> VulkanGroupMarkers::pop() noexcept {
|
std::pair<std::string, Timestamp> VulkanGroupMarkers::pop() noexcept {
|
||||||
auto const marker = mMarkers.top();
|
auto const marker = mMarkers.back();
|
||||||
mMarkers.pop();
|
mMarkers.pop_back();
|
||||||
|
|
||||||
#if FILAMENT_VULKAN_VERBOSE
|
#if FILAMENT_VULKAN_VERBOSE
|
||||||
auto const topTimestamp = mTimestamps.top();
|
auto const timestamp = mTimestamps.back();
|
||||||
mTimestamps.pop();
|
mTimestamps.pop_back();
|
||||||
return std::make_pair(marker, topTimestamp);
|
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
|
#else
|
||||||
return std::make_pair(marker, Timestamp{});
|
return std::make_pair(marker, Timestamp{});
|
||||||
#endif
|
#endif
|
||||||
@@ -92,7 +104,7 @@ std::pair<std::string, Timestamp> VulkanGroupMarkers::pop() noexcept {
|
|||||||
|
|
||||||
std::pair<std::string, Timestamp> VulkanGroupMarkers::top() const {
|
std::pair<std::string, Timestamp> VulkanGroupMarkers::top() const {
|
||||||
assert_invariant(!empty());
|
assert_invariant(!empty());
|
||||||
auto const marker = mMarkers.top();
|
auto const marker = mMarkers.back();
|
||||||
#if FILAMENT_VULKAN_VERBOSE
|
#if FILAMENT_VULKAN_VERBOSE
|
||||||
auto const topTimestamp = mTimestamps.top();
|
auto const topTimestamp = mTimestamps.top();
|
||||||
return std::make_pair(marker, topTimestamp);
|
return std::make_pair(marker, topTimestamp);
|
||||||
@@ -106,15 +118,20 @@ bool VulkanGroupMarkers::empty() const noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
VulkanCommands::VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex,
|
VulkanCommands::VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex,
|
||||||
VulkanContext* context)
|
VulkanContext* context, VulkanResourceAllocator* allocator)
|
||||||
: mDevice(device),
|
: mDevice(device),
|
||||||
mQueue(queue),
|
mQueue(queue),
|
||||||
mPool(createPool(mDevice, queueFamilyIndex)),
|
mPool(createPool(mDevice, queueFamilyIndex)),
|
||||||
mContext(context) {
|
mContext(context),
|
||||||
|
mStorage(CAPACITY) {
|
||||||
VkSemaphoreCreateInfo sci{.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
|
VkSemaphoreCreateInfo sci{.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
|
||||||
for (auto& semaphore: mSubmissionSignals) {
|
for (auto& semaphore: mSubmissionSignals) {
|
||||||
vkCreateSemaphore(mDevice, &sci, nullptr, &semaphore);
|
vkCreateSemaphore(mDevice, &sci, nullptr, &semaphore);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < CAPACITY; ++i) {
|
||||||
|
mStorage[i] = std::make_unique<VulkanCommandBuffer>(allocator);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VulkanCommands::~VulkanCommands() {
|
VulkanCommands::~VulkanCommands() {
|
||||||
@@ -126,10 +143,9 @@ VulkanCommands::~VulkanCommands() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VulkanCommandBuffer const& VulkanCommands::get(bool blockOnGC) {
|
VulkanCommandBuffer& VulkanCommands::get() {
|
||||||
if (mCurrent) {
|
if (mCurrentCommandBufferIndex >= 0) {
|
||||||
mCurrent->blockOnGC = mCurrent->blockOnGC || blockOnGC;
|
return *mStorage[mCurrentCommandBufferIndex].get();
|
||||||
return *mCurrent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we ran out of available command buffers, stall until one finishes. This is very rare.
|
// If we ran out of available command buffers, stall until one finishes. This is very rare.
|
||||||
@@ -145,15 +161,18 @@ VulkanCommandBuffer const& VulkanCommands::get(bool blockOnGC) {
|
|||||||
gc();
|
gc();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VulkanCommandBuffer* currentbuf = nullptr;
|
||||||
// Find an available slot.
|
// Find an available slot.
|
||||||
for (VulkanCommandBuffer& wrapper : mStorage) {
|
for (size_t i = 0; i < CAPACITY; ++i) {
|
||||||
if (wrapper.cmdbuffer == VK_NULL_HANDLE) {
|
auto wrapper = mStorage[i].get();
|
||||||
mCurrent = &wrapper;
|
if (wrapper->cmdbuffer == VK_NULL_HANDLE) {
|
||||||
|
mCurrentCommandBufferIndex = static_cast<int8_t>(i);
|
||||||
|
currentbuf = wrapper;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_invariant(mCurrent);
|
assert_invariant(currentbuf);
|
||||||
--mAvailableCount;
|
--mAvailableCount;
|
||||||
|
|
||||||
// Create the low-level command buffer.
|
// Create the low-level command buffer.
|
||||||
@@ -163,47 +182,46 @@ VulkanCommandBuffer const& VulkanCommands::get(bool blockOnGC) {
|
|||||||
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||||
.commandBufferCount = 1
|
.commandBufferCount = 1
|
||||||
};
|
};
|
||||||
vkAllocateCommandBuffers(mDevice, &allocateInfo, &mCurrent->cmdbuffer);
|
vkAllocateCommandBuffers(mDevice, &allocateInfo, ¤tbuf->cmdbuffer);
|
||||||
|
|
||||||
mCurrent->blockOnGC = blockOnGC;
|
|
||||||
|
|
||||||
// Note that the fence wrapper uses shared_ptr because a DriverAPI fence can also have ownership
|
// 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
|
// over it. The destruction of the low-level fence occurs either in VulkanCommands::gc(), or in
|
||||||
// VulkanDriver::destroyFence(), both of which are safe spots.
|
// 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.
|
// Begin writing into the command buffer.
|
||||||
const VkCommandBufferBeginInfo binfo {
|
const VkCommandBufferBeginInfo binfo {
|
||||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||||
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
.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.
|
// Notify the observer that a new command buffer has been activated.
|
||||||
if (mObserver) {
|
if (mObserver) {
|
||||||
mObserver->onCommandBuffer(*mCurrent);
|
mObserver->onCommandBuffer(*currentbuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We push the current markers onto a temporary stack. This must be placed after mCurrent is set
|
// We push the current markers onto a temporary stack. This must be placed after currentbuf is
|
||||||
// to the new command buffer since pushGroupMarker also calls get().
|
// set to the new command buffer since pushGroupMarker also calls get().
|
||||||
while (mCarriedOverMarkers && !mCarriedOverMarkers->empty()) {
|
while (mCarriedOverMarkers && !mCarriedOverMarkers->empty()) {
|
||||||
auto [marker, time] = mCarriedOverMarkers->pop();
|
auto [marker, time] = mCarriedOverMarkers->pop();
|
||||||
pushGroupMarker(marker.c_str(), time);
|
pushGroupMarker(marker.c_str(), time);
|
||||||
}
|
}
|
||||||
|
|
||||||
return *mCurrent;
|
return *currentbuf;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VulkanCommands::flush() {
|
bool VulkanCommands::flush() {
|
||||||
// It's perfectly fine to call flush when no commands have been written.
|
// It's perfectly fine to call flush when no commands have been written.
|
||||||
if (mCurrent == nullptr) {
|
if (mCurrentCommandBufferIndex < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int64_t index = mCurrent - &mStorage[0];
|
int8_t const index = mCurrentCommandBufferIndex;
|
||||||
VkSemaphore renderingFinished = mSubmissionSignals[index];
|
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,
|
// 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
|
// it is sometimes fine to start executing commands anyway, as along as we stall the GPU at the
|
||||||
@@ -227,7 +245,7 @@ bool VulkanCommands::flush() {
|
|||||||
.pWaitSemaphores = signals,
|
.pWaitSemaphores = signals,
|
||||||
.pWaitDstStageMask = waitDestStageMasks,
|
.pWaitDstStageMask = waitDestStageMasks,
|
||||||
.commandBufferCount = 1,
|
.commandBufferCount = 1,
|
||||||
.pCommandBuffers = &mCurrent->cmdbuffer,
|
.pCommandBuffers = ¤tbuf->cmdbuffer,
|
||||||
.signalSemaphoreCount = 1u,
|
.signalSemaphoreCount = 1u,
|
||||||
.pSignalSemaphores = &renderingFinished,
|
.pSignalSemaphores = &renderingFinished,
|
||||||
};
|
};
|
||||||
@@ -240,13 +258,18 @@ bool VulkanCommands::flush() {
|
|||||||
signals[submitInfo.waitSemaphoreCount++] = mInjectedSignal;
|
signals[submitInfo.waitSemaphoreCount++] = mInjectedSignal;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FILAMENT_VULKAN_VERBOSE) {
|
// To address a validation warning.
|
||||||
slog.i << "Submitting cmdbuffer=" << mCurrent->cmdbuffer
|
if (submitInfo.waitSemaphoreCount == 0) {
|
||||||
<< " wait=(" << signals[0] << ", " << signals[1] << ") "
|
submitInfo.pWaitSemaphores = VK_NULL_HANDLE;
|
||||||
<< " signal=" << renderingFinished
|
|
||||||
<< io::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#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.
|
// Before actually submitting, we need to pop any leftover group markers.
|
||||||
while (mGroupMarkers && !mGroupMarkers->empty()) {
|
while (mGroupMarkers && !mGroupMarkers->empty()) {
|
||||||
if (!mCarriedOverMarkers) {
|
if (!mCarriedOverMarkers) {
|
||||||
@@ -258,44 +281,51 @@ bool VulkanCommands::flush() {
|
|||||||
popGroupMarker();
|
popGroupMarker();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& cmdfence = mCurrent->fence;
|
auto& cmdfence = currentbuf->fence;
|
||||||
std::unique_lock<utils::Mutex> lock(cmdfence->mutex);
|
std::unique_lock<utils::Mutex> lock(cmdfence->mutex);
|
||||||
cmdfence->status.store(VK_NOT_READY);
|
cmdfence->status.store(VK_NOT_READY);
|
||||||
UTILS_UNUSED_IN_RELEASE VkResult result = vkQueueSubmit(mQueue, 1, &submitInfo, cmdfence->fence);
|
UTILS_UNUSED_IN_RELEASE VkResult result = vkQueueSubmit(mQueue, 1, &submitInfo, cmdfence->fence);
|
||||||
cmdfence->condition.notify_all();
|
cmdfence->condition.notify_all();
|
||||||
lock.unlock();
|
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);
|
assert_invariant(result == VK_SUCCESS);
|
||||||
|
|
||||||
mSubmissionSignal = renderingFinished;
|
mSubmissionSignal = renderingFinished;
|
||||||
mInjectedSignal = VK_NULL_HANDLE;
|
mInjectedSignal = VK_NULL_HANDLE;
|
||||||
mCurrent = nullptr;
|
mCurrentCommandBufferIndex = -1;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
VkSemaphore VulkanCommands::acquireFinishedSignal() {
|
VkSemaphore VulkanCommands::acquireFinishedSignal() {
|
||||||
VkSemaphore semaphore = mSubmissionSignal;
|
VkSemaphore semaphore = mSubmissionSignal;
|
||||||
mSubmissionSignal = VK_NULL_HANDLE;
|
mSubmissionSignal = VK_NULL_HANDLE;
|
||||||
if (FILAMENT_VULKAN_VERBOSE) {
|
#if FILAMENT_VULKAN_VERBOSE
|
||||||
slog.i << "Acquiring " << semaphore << " (e.g. for vkQueuePresentKHR)" << io::endl;
|
slog.i << "Acquiring " << semaphore << " (e.g. for vkQueuePresentKHR)" << io::endl;
|
||||||
}
|
#endif
|
||||||
return semaphore;
|
return semaphore;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanCommands::injectDependency(VkSemaphore next) {
|
void VulkanCommands::injectDependency(VkSemaphore next) {
|
||||||
assert_invariant(mInjectedSignal == VK_NULL_HANDLE);
|
assert_invariant(mInjectedSignal == VK_NULL_HANDLE);
|
||||||
mInjectedSignal = next;
|
mInjectedSignal = next;
|
||||||
if (FILAMENT_VULKAN_VERBOSE) {
|
#if FILAMENT_VULKAN_VERBOSE
|
||||||
slog.i << "Injecting " << next << " (e.g. due to vkAcquireNextImageKHR)" << io::endl;
|
slog.i << "Injecting " << next << " (e.g. due to vkAcquireNextImageKHR)" << io::endl;
|
||||||
}
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanCommands::wait() {
|
void VulkanCommands::wait() {
|
||||||
VkFence fences[CAPACITY];
|
VkFence fences[CAPACITY];
|
||||||
uint32_t count = 0;
|
size_t count = 0;
|
||||||
for (auto& wrapper : mStorage) {
|
for (size_t i = 0; i < CAPACITY; i++) {
|
||||||
if (wrapper.cmdbuffer != VK_NULL_HANDLE && mCurrent != &wrapper) {
|
auto wrapper = mStorage[i].get();
|
||||||
fences[count++] = wrapper.fence->fence;
|
if (wrapper->cmdbuffer != VK_NULL_HANDLE
|
||||||
|
&& mCurrentCommandBufferIndex != static_cast<int8_t>(i)) {
|
||||||
|
fences[count++] = wrapper->fence->fence;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
@@ -304,26 +334,34 @@ void VulkanCommands::wait() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanCommands::gc() {
|
void VulkanCommands::gc() {
|
||||||
for (auto& wrapper : mStorage) {
|
VkCommandBuffer buffers[CAPACITY];
|
||||||
if (wrapper.cmdbuffer != VK_NULL_HANDLE) {
|
size_t count = 0;
|
||||||
uint64_t const timeout = wrapper.blockOnGC ? UINT64_MAX : 0;
|
for (size_t i = 0; i < CAPACITY; i++) {
|
||||||
VkResult const result
|
auto wrapper = mStorage[i].get();
|
||||||
= vkWaitForFences(mDevice, 1, &wrapper.fence->fence, VK_TRUE, timeout);
|
if (wrapper->cmdbuffer == VK_NULL_HANDLE) {
|
||||||
if (result == VK_SUCCESS) {
|
continue;
|
||||||
vkFreeCommandBuffers(mDevice, mPool, 1, &wrapper.cmdbuffer);
|
|
||||||
wrapper.cmdbuffer = VK_NULL_HANDLE;
|
|
||||||
wrapper.fence->status.store(VK_SUCCESS);
|
|
||||||
wrapper.fence.reset();
|
|
||||||
++mAvailableCount;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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() {
|
void VulkanCommands::updateFences() {
|
||||||
for (auto& wrapper : mStorage) {
|
for (size_t i = 0; i < CAPACITY; i++) {
|
||||||
if (wrapper.cmdbuffer != VK_NULL_HANDLE) {
|
auto wrapper = mStorage[i].get();
|
||||||
VulkanCmdFence* fence = wrapper.fence.get();
|
if (wrapper->cmdbuffer != VK_NULL_HANDLE) {
|
||||||
|
VulkanCmdFence* fence = wrapper->fence.get();
|
||||||
if (fence) {
|
if (fence) {
|
||||||
VkResult status = vkGetFenceStatus(mDevice, fence->fence);
|
VkResult status = vkGetFenceStatus(mDevice, fence->fence);
|
||||||
// This is either VK_SUCCESS, VK_NOT_READY, or VK_ERROR_DEVICE_LOST.
|
// This is either VK_SUCCESS, VK_NOT_READY, or VK_ERROR_DEVICE_LOST.
|
||||||
@@ -389,8 +427,9 @@ void VulkanCommands::popGroupMarker() {
|
|||||||
}
|
}
|
||||||
} else if (mCarriedOverMarkers && !mCarriedOverMarkers->empty()) {
|
} else if (mCarriedOverMarkers && !mCarriedOverMarkers->empty()) {
|
||||||
// It could be that pop is called between flush() and get() (new command buffer), in which
|
// 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
|
// case the marker is in "carried over" state, we'd just remove that. Since the
|
||||||
mCarriedOverMarkers->pop();
|
// mCarriedOverMarkers is in the opposite order, we pop the bottom instead of the top.
|
||||||
|
mCarriedOverMarkers->pop_bottom();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,15 +19,19 @@
|
|||||||
|
|
||||||
#include <bluevk/BlueVK.h>
|
#include <bluevk/BlueVK.h>
|
||||||
|
|
||||||
|
#include "DriverBase.h"
|
||||||
|
|
||||||
#include "VulkanConstants.h"
|
#include "VulkanConstants.h"
|
||||||
|
#include "VulkanResources.h"
|
||||||
|
|
||||||
#include <utils/Condition.h>
|
#include <utils/Condition.h>
|
||||||
|
#include <utils/FixedCapacityVector.h>
|
||||||
#include <utils/Mutex.h>
|
#include <utils/Mutex.h>
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <stack>
|
#include <list>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
@@ -41,13 +45,14 @@ public:
|
|||||||
|
|
||||||
void push(std::string const& marker, Timestamp start = {}) noexcept;
|
void push(std::string const& marker, Timestamp start = {}) noexcept;
|
||||||
std::pair<std::string, Timestamp> pop() noexcept;
|
std::pair<std::string, Timestamp> pop() noexcept;
|
||||||
|
std::pair<std::string, Timestamp> pop_bottom() noexcept;
|
||||||
std::pair<std::string, Timestamp> top() const;
|
std::pair<std::string, Timestamp> top() const;
|
||||||
bool empty() const noexcept;
|
bool empty() const noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::stack<std::string> mMarkers;
|
std::list<std::string> mMarkers;
|
||||||
#if FILAMENT_VULKAN_VERBOSE
|
#if FILAMENT_VULKAN_VERBOSE
|
||||||
std::stack<Timestamp> mTimestamps;
|
std::list<Timestamp> mTimestamps;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -66,12 +71,28 @@ struct VulkanCmdFence {
|
|||||||
// DriverApi fence object and should not be destroyed until both the DriverApi object is freed and
|
// 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.
|
// we're done waiting on the most recent submission of the given command buffer.
|
||||||
struct VulkanCommandBuffer {
|
struct VulkanCommandBuffer {
|
||||||
VulkanCommandBuffer() {}
|
VulkanCommandBuffer(VulkanResourceAllocator* allocator)
|
||||||
|
: mResourceManager(allocator) {}
|
||||||
|
|
||||||
VulkanCommandBuffer(VulkanCommandBuffer const&) = delete;
|
VulkanCommandBuffer(VulkanCommandBuffer const&) = delete;
|
||||||
VulkanCommandBuffer& operator=(VulkanCommandBuffer const&) = delete;
|
VulkanCommandBuffer& operator=(VulkanCommandBuffer const&) = delete;
|
||||||
VkCommandBuffer cmdbuffer = VK_NULL_HANDLE;
|
VkCommandBuffer cmdbuffer = VK_NULL_HANDLE;
|
||||||
std::shared_ptr<VulkanCmdFence> fence;
|
std::shared_ptr<VulkanCmdFence> fence;
|
||||||
bool blockOnGC = false;
|
|
||||||
|
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.
|
// Allows classes to be notified after a new command buffer has been activated.
|
||||||
@@ -110,14 +131,11 @@ public:
|
|||||||
class VulkanCommands {
|
class VulkanCommands {
|
||||||
public:
|
public:
|
||||||
VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex,
|
VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex,
|
||||||
VulkanContext* context);
|
VulkanContext* context, VulkanResourceAllocator* allocator);
|
||||||
~VulkanCommands();
|
~VulkanCommands();
|
||||||
|
|
||||||
// Creates a "current" command buffer if none exists, otherwise returns the current one.
|
// Creates a "current" command buffer if none exists, otherwise returns the current one.
|
||||||
// `blockOnGC` guarrantees that this buffer will be waited on when gc() is called on it so
|
VulkanCommandBuffer& get();
|
||||||
// that dependent resources can be gc'd safetly after the buffer is sumbitted, completed,
|
|
||||||
// and gc'd.
|
|
||||||
VulkanCommandBuffer const& get(bool blockOnGC = false);
|
|
||||||
|
|
||||||
// Submits the current command buffer if it exists, then sets "current" to null.
|
// 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.
|
// If there are no outstanding commands then nothing happens and this returns false.
|
||||||
@@ -160,10 +178,12 @@ class VulkanCommands {
|
|||||||
VkCommandPool const mPool;
|
VkCommandPool const mPool;
|
||||||
VulkanContext const* mContext;
|
VulkanContext const* mContext;
|
||||||
|
|
||||||
VulkanCommandBuffer* mCurrent = nullptr;
|
// int8 only goes up to 127, therefore capacity must be less than that.
|
||||||
|
static_assert(CAPACITY < 128);
|
||||||
|
int8_t mCurrentCommandBufferIndex = -1;
|
||||||
VkSemaphore mSubmissionSignal = {};
|
VkSemaphore mSubmissionSignal = {};
|
||||||
VkSemaphore mInjectedSignal = {};
|
VkSemaphore mInjectedSignal = {};
|
||||||
VulkanCommandBuffer mStorage[CAPACITY] = {};
|
utils::FixedCapacityVector<std::unique_ptr<VulkanCommandBuffer>> mStorage;
|
||||||
VkSemaphore mSubmissionSignals[CAPACITY] = {};
|
VkSemaphore mSubmissionSignals[CAPACITY] = {};
|
||||||
size_t mAvailableCount = CAPACITY;
|
size_t mAvailableCount = CAPACITY;
|
||||||
CommandBufferObserver* mObserver = nullptr;
|
CommandBufferObserver* mObserver = nullptr;
|
||||||
|
|||||||
@@ -97,8 +97,7 @@ public:
|
|||||||
}
|
}
|
||||||
flags >>= 1;
|
flags >>= 1;
|
||||||
}
|
}
|
||||||
ASSERT_POSTCONDITION(false, "Unable to find a memory type that meets requirements.");
|
return (uint32_t) VK_MAX_MEMORY_TYPES;
|
||||||
return (uint32_t) ~0ul;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline VkFormat getDepthFormat() const {
|
inline VkFormat getDepthFormat() const {
|
||||||
|
|||||||
@@ -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::terminate() 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 terminate() 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
|
|
||||||
@@ -94,7 +94,7 @@ VulkanTexture* createEmptyTexture(VkDevice device, VkPhysicalDevice physicalDevi
|
|||||||
VulkanTexture* emptyTexture = new VulkanTexture(device, physicalDevice, context, allocator,
|
VulkanTexture* emptyTexture = new VulkanTexture(device, physicalDevice, context, allocator,
|
||||||
commands, SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, 1, 1, 1,
|
commands, SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, 1, 1, 1,
|
||||||
TextureUsage::DEFAULT | TextureUsage::COLOR_ATTACHMENT | TextureUsage::SUBPASS_INPUT,
|
TextureUsage::DEFAULT | TextureUsage::COLOR_ATTACHMENT | TextureUsage::SUBPASS_INPUT,
|
||||||
stagePool);
|
stagePool, true /* heap allocated */);
|
||||||
uint32_t black = 0;
|
uint32_t black = 0;
|
||||||
PixelBufferDescriptor pbd(&black, 4, PixelDataFormat::RGBA, PixelDataType::UBYTE);
|
PixelBufferDescriptor pbd(&black, 4, PixelDataFormat::RGBA, PixelDataType::UBYTE);
|
||||||
emptyTexture->updateImage(pbd, 1, 1, 1, 0, 0, 0, 0);
|
emptyTexture->updateImage(pbd, 1, 1, 1, 0, 0, 0, 0);
|
||||||
@@ -108,6 +108,10 @@ VKAPI_ATTR VkBool32 VKAPI_CALL debugReportCallback(VkDebugReportFlagsEXT flags,
|
|||||||
if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) {
|
if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) {
|
||||||
utils::slog.e << "VULKAN ERROR: (" << pLayerPrefix << ") " << pMessage << utils::io::endl;
|
utils::slog.e << "VULKAN ERROR: (" << pLayerPrefix << ") " << pMessage << utils::io::endl;
|
||||||
} else {
|
} else {
|
||||||
|
// TODO: emit best practices warnings about aggressive pipeline barriers.
|
||||||
|
if (strstr(pMessage, "ALL_GRAPHICS_BIT") || strstr(pMessage, "ALL_COMMANDS_BIT")) {
|
||||||
|
return VK_FALSE;
|
||||||
|
}
|
||||||
utils::slog.w << "VULKAN WARNING: (" << pLayerPrefix << ") " << pMessage << utils::io::endl;
|
utils::slog.w << "VULKAN WARNING: (" << pLayerPrefix << ") " << pMessage << utils::io::endl;
|
||||||
}
|
}
|
||||||
utils::slog.e << utils::io::endl;
|
utils::slog.e << utils::io::endl;
|
||||||
@@ -122,8 +126,8 @@ VKAPI_ATTR VkBool32 VKAPI_CALL debugUtilsCallback(VkDebugUtilsMessageSeverityFla
|
|||||||
<< utils::io::endl;
|
<< utils::io::endl;
|
||||||
} else {
|
} else {
|
||||||
// TODO: emit best practices warnings about aggressive pipeline barriers.
|
// TODO: emit best practices warnings about aggressive pipeline barriers.
|
||||||
if (strstr(cbdata->pMessage, "ALL_GRAPHICS_BIT") ||
|
if (strstr(cbdata->pMessage, "ALL_GRAPHICS_BIT")
|
||||||
strstr(cbdata->pMessage, "ALL_COMMANDS_BIT")) {
|
|| strstr(cbdata->pMessage, "ALL_COMMANDS_BIT")) {
|
||||||
return VK_FALSE;
|
return VK_FALSE;
|
||||||
}
|
}
|
||||||
utils::slog.w << "VULKAN WARNING: (" << cbdata->pMessageIdName << ") " << cbdata->pMessage
|
utils::slog.w << "VULKAN WARNING: (" << cbdata->pMessageIdName << ") " << cbdata->pMessage
|
||||||
@@ -148,7 +152,10 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
|
|||||||
mAllocator(createAllocator(mPlatform->getInstance(), mPlatform->getPhysicalDevice(),
|
mAllocator(createAllocator(mPlatform->getInstance(), mPlatform->getPhysicalDevice(),
|
||||||
mPlatform->getDevice())),
|
mPlatform->getDevice())),
|
||||||
mContext(context),
|
mContext(context),
|
||||||
mHandleAllocator("Handles", driverConfig.handleArenaSize),
|
mResourceAllocator(driverConfig.handleArenaSize),
|
||||||
|
mResourceManager(&mResourceAllocator),
|
||||||
|
mThreadSafeResourceManager(&mResourceAllocator),
|
||||||
|
mPipelineCache(&mResourceAllocator),
|
||||||
mBlitter(mStagePool, mPipelineCache, mFramebufferCache, mSamplerCache),
|
mBlitter(mStagePool, mPipelineCache, mFramebufferCache, mSamplerCache),
|
||||||
mReadPixels(mPlatform->getDevice()) {
|
mReadPixels(mPlatform->getDevice()) {
|
||||||
|
|
||||||
@@ -183,7 +190,8 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
|
|||||||
#endif
|
#endif
|
||||||
mTimestamps = std::make_unique<VulkanTimestamps>(mPlatform->getDevice());
|
mTimestamps = std::make_unique<VulkanTimestamps>(mPlatform->getDevice());
|
||||||
mCommands = std::make_unique<VulkanCommands>(mPlatform->getDevice(),
|
mCommands = std::make_unique<VulkanCommands>(mPlatform->getDevice(),
|
||||||
mPlatform->getGraphicsQueue(), mPlatform->getGraphicsQueueFamilyIndex(), &mContext);
|
mPlatform->getGraphicsQueue(), mPlatform->getGraphicsQueueFamilyIndex(), &mContext,
|
||||||
|
&mResourceAllocator);
|
||||||
mCommands->setObserver(&mPipelineCache);
|
mCommands->setObserver(&mPipelineCache);
|
||||||
mPipelineCache.setDevice(mPlatform->getDevice(), mAllocator);
|
mPipelineCache.setDevice(mPlatform->getDevice(), mAllocator);
|
||||||
|
|
||||||
@@ -230,9 +238,8 @@ void VulkanDriver::terminate() {
|
|||||||
mBlitter.terminate();
|
mBlitter.terminate();
|
||||||
mReadPixels.terminate();
|
mReadPixels.terminate();
|
||||||
|
|
||||||
// Allow the stage pool and disposer to clean up.
|
// Allow the stage pool to clean up.
|
||||||
mStagePool.gc();
|
mStagePool.gc();
|
||||||
mDisposer.terminate();
|
|
||||||
|
|
||||||
mStagePool.terminate();
|
mStagePool.terminate();
|
||||||
mPipelineCache.terminate();
|
mPipelineCache.terminate();
|
||||||
@@ -261,10 +268,20 @@ void VulkanDriver::tick(int) {
|
|||||||
void VulkanDriver::collectGarbage() {
|
void VulkanDriver::collectGarbage() {
|
||||||
// Command buffers need to be submitted and completed before other resources can be gc'd. And
|
// Command buffers need to be submitted and completed before other resources can be gc'd. And
|
||||||
// its gc() function carrys out the *wait*.
|
// its gc() function carrys out the *wait*.
|
||||||
|
SYSTRACE_CONTEXT();
|
||||||
|
SYSTRACE_NAME_BEGIN("gc");
|
||||||
|
|
||||||
mCommands->gc();
|
mCommands->gc();
|
||||||
|
|
||||||
|
SYSTRACE_NAME_BEGIN("stagepool-gc");
|
||||||
mStagePool.gc();
|
mStagePool.gc();
|
||||||
|
SYSTRACE_NAME_END();
|
||||||
|
|
||||||
|
SYSTRACE_NAME_BEGIN("framebuffercache-gc");
|
||||||
mFramebufferCache.gc();
|
mFramebufferCache.gc();
|
||||||
mDisposer.gc();
|
SYSTRACE_NAME_END();
|
||||||
|
|
||||||
|
SYSTRACE_NAME_END(); // "gc"
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::beginFrame(int64_t monotonic_clock_ns, uint32_t frameId) {
|
void VulkanDriver::beginFrame(int64_t monotonic_clock_ns, uint32_t frameId) {
|
||||||
@@ -276,7 +293,7 @@ void VulkanDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
|
void VulkanDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
|
||||||
FrameCompletedCallback callback, void* user) {
|
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::setPresentationTime(int64_t monotonic_clock_ns) {
|
void VulkanDriver::setPresentationTime(int64_t monotonic_clock_ns) {
|
||||||
@@ -300,85 +317,84 @@ void VulkanDriver::finish(int dummy) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::createSamplerGroupR(Handle<HwSamplerGroup> sbh, uint32_t count) {
|
void VulkanDriver::createSamplerGroupR(Handle<HwSamplerGroup> sbh, uint32_t count) {
|
||||||
construct<VulkanSamplerGroup>(sbh, count);
|
auto sg = mResourceAllocator.construct<VulkanSamplerGroup>(sbh, count);
|
||||||
|
mResourceManager.acquire(sg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph,
|
void VulkanDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph,
|
||||||
Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh,
|
Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh,
|
||||||
PrimitiveType pt, uint32_t offset,
|
PrimitiveType pt, uint32_t offset,
|
||||||
uint32_t minIndex, uint32_t maxIndex, uint32_t count) {
|
uint32_t minIndex, uint32_t maxIndex, uint32_t count) {
|
||||||
construct<VulkanRenderPrimitive>(rph);
|
auto rp = mResourceAllocator.construct<VulkanRenderPrimitive>(rph, &mResourceAllocator);
|
||||||
|
mResourceManager.acquire(rp);
|
||||||
VulkanDriver::setRenderPrimitiveBuffer(rph, vbh, ibh);
|
VulkanDriver::setRenderPrimitiveBuffer(rph, vbh, ibh);
|
||||||
VulkanDriver::setRenderPrimitiveRange(rph, pt, offset, minIndex, maxIndex, count);
|
VulkanDriver::setRenderPrimitiveRange(rph, pt, offset, minIndex, maxIndex, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
void VulkanDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||||
if (rph) {
|
if (!rph) {
|
||||||
destruct<VulkanRenderPrimitive>(rph);
|
return;
|
||||||
}
|
}
|
||||||
|
auto rp = mResourceAllocator.handle_cast<VulkanRenderPrimitive*>(rph);
|
||||||
|
mResourceManager.release(rp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::createVertexBufferR(Handle<HwVertexBuffer> vbh, uint8_t bufferCount,
|
void VulkanDriver::createVertexBufferR(Handle<HwVertexBuffer> vbh, uint8_t bufferCount,
|
||||||
uint8_t attributeCount, uint32_t elementCount, AttributeArray attributes) {
|
uint8_t attributeCount, uint32_t elementCount, AttributeArray attributes) {
|
||||||
auto vertexBuffer = construct<VulkanVertexBuffer>(vbh, mContext, mStagePool,
|
auto vertexBuffer = mResourceAllocator.construct<VulkanVertexBuffer>(vbh, mContext, mStagePool,
|
||||||
bufferCount, attributeCount, elementCount, attributes);
|
&mResourceAllocator, bufferCount, attributeCount, elementCount, attributes);
|
||||||
mDisposer.createDisposable(vertexBuffer, [this, vbh] () {
|
mResourceManager.acquire(vertexBuffer);
|
||||||
destruct<VulkanVertexBuffer>(vbh);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::destroyVertexBuffer(Handle<HwVertexBuffer> vbh) {
|
void VulkanDriver::destroyVertexBuffer(Handle<HwVertexBuffer> vbh) {
|
||||||
if (vbh) {
|
if (!vbh) {
|
||||||
auto vertexBuffer = handle_cast<VulkanVertexBuffer*>(vbh);
|
return;
|
||||||
mDisposer.removeReference(vertexBuffer);
|
|
||||||
}
|
}
|
||||||
|
auto vertexBuffer = mResourceAllocator.handle_cast<VulkanVertexBuffer*>(vbh);
|
||||||
|
mResourceManager.release(vertexBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::createIndexBufferR(Handle<HwIndexBuffer> ibh, ElementType elementType,
|
void VulkanDriver::createIndexBufferR(Handle<HwIndexBuffer> ibh, ElementType elementType,
|
||||||
uint32_t indexCount, BufferUsage usage) {
|
uint32_t indexCount, BufferUsage usage) {
|
||||||
auto elementSize = (uint8_t) getElementTypeSize(elementType);
|
auto elementSize = (uint8_t) getElementTypeSize(elementType);
|
||||||
auto indexBuffer = construct<VulkanIndexBuffer>(ibh, mAllocator, mCommands.get(), mStagePool,
|
auto indexBuffer = mResourceAllocator.construct<VulkanIndexBuffer>(ibh, mAllocator, mStagePool,
|
||||||
elementSize, indexCount);
|
elementSize, indexCount);
|
||||||
mDisposer.createDisposable(indexBuffer,
|
mResourceManager.acquire(indexBuffer);
|
||||||
[this, ibh]() { destructBuffer<VulkanIndexBuffer>(ibh); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::destroyIndexBuffer(Handle<HwIndexBuffer> ibh) {
|
void VulkanDriver::destroyIndexBuffer(Handle<HwIndexBuffer> ibh) {
|
||||||
if (ibh) {
|
if (!ibh) {
|
||||||
auto indexBuffer = handle_cast<VulkanIndexBuffer*>(ibh);
|
return;
|
||||||
mDisposer.removeReference(indexBuffer);
|
|
||||||
}
|
}
|
||||||
|
auto indexBuffer = mResourceAllocator.handle_cast<VulkanIndexBuffer*>(ibh);
|
||||||
|
mResourceManager.release(indexBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byteCount,
|
void VulkanDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byteCount,
|
||||||
BufferObjectBinding bindingType, BufferUsage usage) {
|
BufferObjectBinding bindingType, BufferUsage usage) {
|
||||||
auto bufferObject = construct<VulkanBufferObject>(boh, mAllocator, mCommands.get(), mStagePool,
|
auto bufferObject = mResourceAllocator.construct<VulkanBufferObject>(boh, mAllocator,
|
||||||
byteCount, bindingType, usage);
|
mStagePool, byteCount, bindingType, usage);
|
||||||
mDisposer.createDisposable(bufferObject,
|
mResourceManager.acquire(bufferObject);
|
||||||
[this, boh]() { destructBuffer<VulkanBufferObject>(boh); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::destroyBufferObject(Handle<HwBufferObject> boh) {
|
void VulkanDriver::destroyBufferObject(Handle<HwBufferObject> boh) {
|
||||||
if (boh) {
|
if (!boh) {
|
||||||
auto bufferObject = handle_cast<VulkanBufferObject*>(boh);
|
return;
|
||||||
if (bufferObject->bindingType == BufferObjectBinding::UNIFORM) {
|
|
||||||
mPipelineCache.unbindUniformBuffer(bufferObject->buffer.getGpuBuffer());
|
|
||||||
// Decrement the refcount of the uniform buffer, but schedule it for destruction a few
|
|
||||||
// frames in the future. To be safe, we need to assume that the current command buffer is
|
|
||||||
// still using it somewhere.
|
|
||||||
mDisposer.acquire(bufferObject);
|
|
||||||
}
|
|
||||||
mDisposer.removeReference(bufferObject);
|
|
||||||
}
|
}
|
||||||
|
auto bufferObject = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
|
||||||
|
if (bufferObject->bindingType == BufferObjectBinding::UNIFORM) {
|
||||||
|
mPipelineCache.unbindUniformBuffer(bufferObject->buffer.getGpuBuffer());
|
||||||
|
}
|
||||||
|
mResourceManager.release(bufferObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint8_t levels,
|
void VulkanDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint8_t levels,
|
||||||
TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
|
TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
|
||||||
TextureUsage usage) {
|
TextureUsage usage) {
|
||||||
auto vktexture = construct<VulkanTexture>(th, mPlatform->getDevice(),
|
auto vktexture = mResourceAllocator.construct<VulkanTexture>(th, mPlatform->getDevice(),
|
||||||
mPlatform->getPhysicalDevice(), mContext, mAllocator, mCommands.get(), target, levels,
|
mPlatform->getPhysicalDevice(), mContext, mAllocator, mCommands.get(), target, levels,
|
||||||
format, samples, w, h, depth, usage, mStagePool);
|
format, samples, w, h, depth, usage, mStagePool);
|
||||||
mDisposer.createDisposable(vktexture, [this, th]() { destruct<VulkanTexture>(th); });
|
mResourceManager.acquire(vktexture);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::createTextureSwizzledR(Handle<HwTexture> th, SamplerType target, uint8_t levels,
|
void VulkanDriver::createTextureSwizzledR(Handle<HwTexture> th, SamplerType target, uint8_t levels,
|
||||||
@@ -387,12 +403,10 @@ void VulkanDriver::createTextureSwizzledR(Handle<HwTexture> th, SamplerType targ
|
|||||||
TextureSwizzle r, TextureSwizzle g, TextureSwizzle b, TextureSwizzle a) {
|
TextureSwizzle r, TextureSwizzle g, TextureSwizzle b, TextureSwizzle a) {
|
||||||
TextureSwizzle swizzleArray[] = {r, g, b, a};
|
TextureSwizzle swizzleArray[] = {r, g, b, a};
|
||||||
const VkComponentMapping swizzleMap = getSwizzleMap(swizzleArray);
|
const VkComponentMapping swizzleMap = getSwizzleMap(swizzleArray);
|
||||||
auto vktexture = construct<VulkanTexture>(th, mPlatform->getDevice(),
|
auto vktexture = mResourceAllocator.construct<VulkanTexture>(th, mPlatform->getDevice(),
|
||||||
mPlatform->getPhysicalDevice(), mContext, mAllocator, mCommands.get(), target, levels,
|
mPlatform->getPhysicalDevice(), mContext, mAllocator, mCommands.get(), target, levels,
|
||||||
format, samples, w, h, depth, usage, mStagePool, swizzleMap);
|
format, samples, w, h, depth, usage, mStagePool, false /*heap allocated */, swizzleMap);
|
||||||
mDisposer.createDisposable(vktexture, [this, th]() {
|
mResourceManager.acquire(vktexture);
|
||||||
destruct<VulkanTexture>(th);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
|
void VulkanDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
|
||||||
@@ -403,33 +417,31 @@ void VulkanDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::destroyTexture(Handle<HwTexture> th) {
|
void VulkanDriver::destroyTexture(Handle<HwTexture> th) {
|
||||||
if (th) {
|
if (!th) {
|
||||||
auto texture = handle_cast<VulkanTexture*>(th);
|
return;
|
||||||
mPipelineCache.unbindImageView(texture->getPrimaryImageView());
|
|
||||||
mDisposer.removeReference(texture);
|
|
||||||
}
|
}
|
||||||
|
auto texture = mResourceAllocator.handle_cast<VulkanTexture*>(th);
|
||||||
|
mResourceManager.release(texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
|
void VulkanDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
|
||||||
auto vkprogram = construct<VulkanProgram>(ph, mPlatform->getDevice(), program);
|
auto vkprogram = mResourceAllocator.construct<VulkanProgram>(ph, mPlatform->getDevice(), program);
|
||||||
mDisposer.createDisposable(vkprogram, [this, ph] () {
|
mResourceManager.acquire(vkprogram);
|
||||||
destruct<VulkanProgram>(ph);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::destroyProgram(Handle<HwProgram> ph) {
|
void VulkanDriver::destroyProgram(Handle<HwProgram> ph) {
|
||||||
if (ph) {
|
if (!ph) {
|
||||||
mDisposer.removeReference(handle_cast<VulkanProgram*>(ph));
|
return;
|
||||||
}
|
}
|
||||||
|
auto vkprogram = mResourceAllocator.handle_cast<VulkanProgram*>(ph);
|
||||||
|
mResourceManager.release(vkprogram);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int) {
|
void VulkanDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int) {
|
||||||
assert_invariant(mDefaultRenderTarget == nullptr);
|
assert_invariant(mDefaultRenderTarget == nullptr);
|
||||||
VulkanRenderTarget* renderTarget = construct<VulkanRenderTarget>(rth);
|
VulkanRenderTarget* renderTarget = mResourceAllocator.construct<VulkanRenderTarget>(rth);
|
||||||
mDefaultRenderTarget = renderTarget;
|
mDefaultRenderTarget = renderTarget;
|
||||||
mDisposer.createDisposable(renderTarget, [this, rth] () {
|
mResourceManager.acquire(renderTarget);
|
||||||
destruct<VulkanRenderTarget>(rth);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
||||||
@@ -443,7 +455,7 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
|||||||
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
|
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
|
||||||
if (color[i].handle) {
|
if (color[i].handle) {
|
||||||
colorTargets[i] = {
|
colorTargets[i] = {
|
||||||
.texture = handle_cast<VulkanTexture*>(color[i].handle),
|
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(color[i].handle),
|
||||||
.level = color[i].level,
|
.level = color[i].level,
|
||||||
.layer = color[i].layer,
|
.layer = color[i].layer,
|
||||||
};
|
};
|
||||||
@@ -457,7 +469,7 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
|||||||
VulkanAttachment depthStencil[2] = {};
|
VulkanAttachment depthStencil[2] = {};
|
||||||
if (depth.handle) {
|
if (depth.handle) {
|
||||||
depthStencil[0] = {
|
depthStencil[0] = {
|
||||||
.texture = handle_cast<VulkanTexture*>(depth.handle),
|
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(depth.handle),
|
||||||
.level = depth.level,
|
.level = depth.level,
|
||||||
.layer = depth.layer,
|
.layer = depth.layer,
|
||||||
};
|
};
|
||||||
@@ -469,7 +481,7 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
|||||||
|
|
||||||
if (stencil.handle) {
|
if (stencil.handle) {
|
||||||
depthStencil[1] = {
|
depthStencil[1] = {
|
||||||
.texture = handle_cast<VulkanTexture*>(stencil.handle),
|
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(stencil.handle),
|
||||||
.level = stencil.level,
|
.level = stencil.level,
|
||||||
.layer = stencil.layer,
|
.layer = stencil.layer,
|
||||||
};
|
};
|
||||||
@@ -485,36 +497,37 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
|||||||
assert_invariant(tmin == tmax);
|
assert_invariant(tmin == tmax);
|
||||||
assert_invariant(tmin.x >= width && tmin.y >= height);
|
assert_invariant(tmin.x >= width && tmin.y >= height);
|
||||||
|
|
||||||
auto renderTarget = construct<VulkanRenderTarget>(rth, mPlatform->getDevice(),
|
auto renderTarget = mResourceAllocator.construct<VulkanRenderTarget>(rth, mPlatform->getDevice(),
|
||||||
mPlatform->getPhysicalDevice(), mContext, mAllocator, mCommands.get(), width, height,
|
mPlatform->getPhysicalDevice(), mContext, mAllocator, mCommands.get(), width, height,
|
||||||
samples, colorTargets, depthStencil, mStagePool);
|
samples, colorTargets, depthStencil, mStagePool);
|
||||||
mDisposer.createDisposable(renderTarget, [this, rth]() { destruct<VulkanRenderTarget>(rth); });
|
mResourceManager.acquire(renderTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
|
void VulkanDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
|
||||||
if (rth) {
|
if (!rth) {
|
||||||
VulkanRenderTarget* rt = handle_cast<VulkanRenderTarget*>(rth);
|
return;
|
||||||
if (UTILS_UNLIKELY(rt == mDefaultRenderTarget)) {
|
|
||||||
mDefaultRenderTarget = nullptr;
|
|
||||||
}
|
|
||||||
mDisposer.removeReference(rt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VulkanRenderTarget* rt = mResourceAllocator.handle_cast<VulkanRenderTarget*>(rth);
|
||||||
|
if (UTILS_UNLIKELY(rt == mDefaultRenderTarget)) {
|
||||||
|
mDefaultRenderTarget = nullptr;
|
||||||
|
}
|
||||||
|
mResourceManager.release(rt);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::createFenceR(Handle<HwFence> fh, int) {
|
void VulkanDriver::createFenceR(Handle<HwFence> fh, int) {
|
||||||
VulkanCommandBuffer const& commandBuffer = mCommands->get();
|
VulkanCommandBuffer const& commandBuffer = mCommands->get();
|
||||||
construct<VulkanFence>(fh, commandBuffer.fence);
|
mResourceAllocator.construct<VulkanFence>(fh, commandBuffer.fence);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
|
void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
|
||||||
if ((flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0 &&
|
if ((flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0 && !isSRGBSwapChainSupported()) {
|
||||||
!isSRGBSwapChainSupported()) {
|
|
||||||
utils::slog.w << "sRGB swapchain requested, but Platform does not support it"
|
utils::slog.w << "sRGB swapchain requested, but Platform does not support it"
|
||||||
<< utils::io::endl;
|
<< utils::io::endl;
|
||||||
flags = flags | ~(backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE);
|
flags = flags | ~(backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE);
|
||||||
}
|
}
|
||||||
construct<VulkanSwapChain>(sch, mPlatform, mContext, mAllocator, mCommands.get(), mStagePool,
|
mResourceAllocator.construct<VulkanSwapChain>(sch, mPlatform, mContext, mAllocator,
|
||||||
nativeWindow, flags);
|
mCommands.get(), mStagePool, nativeWindow, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t width,
|
void VulkanDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t width,
|
||||||
@@ -526,8 +539,8 @@ void VulkanDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t wi
|
|||||||
flags = flags | ~(backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE);
|
flags = flags | ~(backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE);
|
||||||
}
|
}
|
||||||
assert_invariant(width > 0 && height > 0 && "Vulkan requires non-zero swap chain dimensions.");
|
assert_invariant(width > 0 && height > 0 && "Vulkan requires non-zero swap chain dimensions.");
|
||||||
construct<VulkanSwapChain>(sch, mPlatform, mContext, mAllocator, mCommands.get(), mStagePool,
|
mResourceAllocator.construct<VulkanSwapChain>(sch, mPlatform, mContext, mAllocator,
|
||||||
nullptr, flags, VkExtent2D{width, height});
|
mCommands.get(), mStagePool, nullptr, flags, VkExtent2D{width, height});
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
|
void VulkanDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
|
||||||
@@ -535,105 +548,108 @@ void VulkanDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwVertexBuffer> VulkanDriver::createVertexBufferS() noexcept {
|
Handle<HwVertexBuffer> VulkanDriver::createVertexBufferS() noexcept {
|
||||||
return allocHandle<VulkanVertexBuffer>();
|
return mResourceAllocator.allocHandle<VulkanVertexBuffer>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwIndexBuffer> VulkanDriver::createIndexBufferS() noexcept {
|
Handle<HwIndexBuffer> VulkanDriver::createIndexBufferS() noexcept {
|
||||||
return allocHandle<VulkanIndexBuffer>();
|
return mResourceAllocator.allocHandle<VulkanIndexBuffer>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwBufferObject> VulkanDriver::createBufferObjectS() noexcept {
|
Handle<HwBufferObject> VulkanDriver::createBufferObjectS() noexcept {
|
||||||
return allocHandle<VulkanBufferObject>();
|
return mResourceAllocator.allocHandle<VulkanBufferObject>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwTexture> VulkanDriver::createTextureS() noexcept {
|
Handle<HwTexture> VulkanDriver::createTextureS() noexcept {
|
||||||
return allocHandle<VulkanTexture>();
|
return mResourceAllocator.allocHandle<VulkanTexture>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwTexture> VulkanDriver::createTextureSwizzledS() noexcept {
|
Handle<HwTexture> VulkanDriver::createTextureSwizzledS() noexcept {
|
||||||
return allocHandle<VulkanTexture>();
|
return mResourceAllocator.allocHandle<VulkanTexture>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwTexture> VulkanDriver::importTextureS() noexcept {
|
Handle<HwTexture> VulkanDriver::importTextureS() noexcept {
|
||||||
return allocHandle<VulkanTexture>();
|
return mResourceAllocator.allocHandle<VulkanTexture>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwSamplerGroup> VulkanDriver::createSamplerGroupS() noexcept {
|
Handle<HwSamplerGroup> VulkanDriver::createSamplerGroupS() noexcept {
|
||||||
return allocHandle<VulkanSamplerGroup>();
|
return mResourceAllocator.allocHandle<VulkanSamplerGroup>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwRenderPrimitive> VulkanDriver::createRenderPrimitiveS() noexcept {
|
Handle<HwRenderPrimitive> VulkanDriver::createRenderPrimitiveS() noexcept {
|
||||||
return allocHandle<VulkanRenderPrimitive>();
|
return mResourceAllocator.allocHandle<VulkanRenderPrimitive>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwProgram> VulkanDriver::createProgramS() noexcept {
|
Handle<HwProgram> VulkanDriver::createProgramS() noexcept {
|
||||||
return allocHandle<VulkanProgram>();
|
return mResourceAllocator.allocHandle<VulkanProgram>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwRenderTarget> VulkanDriver::createDefaultRenderTargetS() noexcept {
|
Handle<HwRenderTarget> VulkanDriver::createDefaultRenderTargetS() noexcept {
|
||||||
return allocHandle<VulkanRenderTarget>();
|
return mResourceAllocator.allocHandle<VulkanRenderTarget>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwRenderTarget> VulkanDriver::createRenderTargetS() noexcept {
|
Handle<HwRenderTarget> VulkanDriver::createRenderTargetS() noexcept {
|
||||||
return allocHandle<VulkanRenderTarget>();
|
return mResourceAllocator.allocHandle<VulkanRenderTarget>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwFence> VulkanDriver::createFenceS() noexcept {
|
Handle<HwFence> VulkanDriver::createFenceS() noexcept {
|
||||||
return initHandle<VulkanFence>();
|
return mResourceAllocator.initHandle<VulkanFence>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwSwapChain> VulkanDriver::createSwapChainS() noexcept {
|
Handle<HwSwapChain> VulkanDriver::createSwapChainS() noexcept {
|
||||||
return allocHandle<VulkanSwapChain>();
|
return mResourceAllocator.allocHandle<VulkanSwapChain>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwSwapChain> VulkanDriver::createSwapChainHeadlessS() noexcept {
|
Handle<HwSwapChain> VulkanDriver::createSwapChainHeadlessS() noexcept {
|
||||||
return allocHandle<VulkanSwapChain>();
|
return mResourceAllocator.allocHandle<VulkanSwapChain>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwTimerQuery> VulkanDriver::createTimerQueryS() noexcept {
|
Handle<HwTimerQuery> VulkanDriver::createTimerQueryS() noexcept {
|
||||||
// The handle must be constructed here, as a synchronous call to getTimerQueryValue might happen
|
// The handle must be constructed here, as a synchronous call to getTimerQueryValue might happen
|
||||||
// before createTimerQueryR is executed.
|
// before createTimerQueryR is executed.
|
||||||
Handle<HwTimerQuery> tqh = initHandle<VulkanTimerQuery>(mTimestamps->getNextQuery());
|
Handle<HwTimerQuery> tqh
|
||||||
auto query = handle_cast<VulkanTimerQuery*>(tqh);
|
= mResourceAllocator.initHandle<VulkanTimerQuery>(mTimestamps->getNextQuery());
|
||||||
mDisposer.createDisposable(query, [this, tqh] () {
|
auto query = mResourceAllocator.handle_cast<VulkanTimerQuery*>(tqh);
|
||||||
destruct<VulkanTimerQuery>(tqh);
|
mThreadSafeResourceManager.acquire(query);
|
||||||
});
|
|
||||||
return tqh;
|
return tqh;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::destroySamplerGroup(Handle<HwSamplerGroup> sbh) {
|
void VulkanDriver::destroySamplerGroup(Handle<HwSamplerGroup> sbh) {
|
||||||
if (sbh) {
|
if (!sbh) {
|
||||||
// Unlike most of the other "Hw" handles, the sampler buffer is an abstract concept and does
|
return;
|
||||||
// not map to any Vulkan objects. To handle destruction, the only thing we need to do is
|
|
||||||
// ensure that the next draw call doesn't try to access a zombie sampler buffer. Therefore,
|
|
||||||
// simply replace all weak references with null.
|
|
||||||
auto* hwsb = handle_cast<VulkanSamplerGroup*>(sbh);
|
|
||||||
for (auto& binding : mSamplerBindings) {
|
|
||||||
if (binding == hwsb) {
|
|
||||||
binding = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
destruct<VulkanSamplerGroup>(sbh);
|
|
||||||
}
|
}
|
||||||
|
// Unlike most of the other "Hw" handles, the sampler buffer is an abstract concept and does
|
||||||
|
// not map to any Vulkan objects. To handle destruction, the only thing we need to do is
|
||||||
|
// ensure that the next draw call doesn't try to access a zombie sampler buffer. Therefore,
|
||||||
|
// simply replace all weak references with null.
|
||||||
|
auto* hwsb = mResourceAllocator.handle_cast<VulkanSamplerGroup*>(sbh);
|
||||||
|
for (auto& binding : mSamplerBindings) {
|
||||||
|
if (binding == hwsb) {
|
||||||
|
binding = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mResourceManager.release(hwsb);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::destroySwapChain(Handle<HwSwapChain> sch) {
|
void VulkanDriver::destroySwapChain(Handle<HwSwapChain> sch) {
|
||||||
if (sch) {
|
if (!sch) {
|
||||||
VulkanSwapChain& swapChain = *handle_cast<VulkanSwapChain*>(sch);
|
return;
|
||||||
if (mCurrentSwapChain == &swapChain) {
|
|
||||||
mCurrentSwapChain = nullptr;
|
|
||||||
}
|
|
||||||
destruct<VulkanSwapChain>(sch);
|
|
||||||
}
|
}
|
||||||
|
VulkanSwapChain* swapChain = mResourceAllocator.handle_cast<VulkanSwapChain*>(sch);
|
||||||
|
if (mCurrentSwapChain == swapChain) {
|
||||||
|
mCurrentSwapChain = nullptr;
|
||||||
|
}
|
||||||
|
mResourceManager.release(swapChain);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::destroyStream(Handle<HwStream> sh) {
|
void VulkanDriver::destroyStream(Handle<HwStream> sh) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
|
void VulkanDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||||
if (tqh) {
|
if (!tqh) {
|
||||||
mDisposer.removeReference(handle_cast<VulkanTimerQuery*>(tqh));
|
return;
|
||||||
}
|
}
|
||||||
|
auto vtq = mResourceAllocator.handle_cast<VulkanTimerQuery*>(tqh);
|
||||||
|
mThreadSafeResourceManager.release(vtq);
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwStream> VulkanDriver::createStreamNative(void* nativeStream) {
|
Handle<HwStream> VulkanDriver::createStreamNative(void* nativeStream) {
|
||||||
@@ -659,11 +675,11 @@ void VulkanDriver::updateStreams(CommandStream* driver) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::destroyFence(Handle<HwFence> fh) {
|
void VulkanDriver::destroyFence(Handle<HwFence> fh) {
|
||||||
destruct<VulkanFence>(fh);
|
mResourceAllocator.destruct<VulkanFence>(fh);
|
||||||
}
|
}
|
||||||
|
|
||||||
FenceStatus VulkanDriver::wait(Handle<HwFence> fh, uint64_t timeout) {
|
FenceStatus VulkanDriver::getFenceStatus(Handle<HwFence> fh) {
|
||||||
auto& cmdfence = handle_cast<VulkanFence*>(fh)->fence;
|
auto& cmdfence = mResourceAllocator.handle_cast<VulkanFence*>(fh)->fence;
|
||||||
if (!cmdfence) {
|
if (!cmdfence) {
|
||||||
// If wait is called before a fence actually exists, we return timeout. This matches the
|
// If wait is called before a fence actually exists, we return timeout. This matches the
|
||||||
// current behavior in OpenGLDriver, but we should eventually reconsider a different error
|
// current behavior in OpenGLDriver, but we should eventually reconsider a different error
|
||||||
@@ -682,7 +698,7 @@ FenceStatus VulkanDriver::wait(Handle<HwFence> fh, uint64_t timeout) {
|
|||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
VkResult result =
|
VkResult result =
|
||||||
vkWaitForFences(mPlatform->getDevice(), 1, &cmdfence->fence, VK_TRUE, timeout);
|
vkWaitForFences(mPlatform->getDevice(), 1, &cmdfence->fence, VK_TRUE, 0);
|
||||||
return result == VK_SUCCESS ? FenceStatus::CONDITION_SATISFIED : FenceStatus::TIMEOUT_EXPIRED;
|
return result == VK_SUCCESS ? FenceStatus::CONDITION_SATISFIED : FenceStatus::TIMEOUT_EXPIRED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -753,6 +769,14 @@ bool VulkanDriver::isSRGBSwapChainSupported() {
|
|||||||
return mPlatform->isSRGBSwapChainSupported();
|
return mPlatform->isSRGBSwapChainSupported();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool VulkanDriver::isStereoSupported() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VulkanDriver::isParallelShaderCompileSupported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool VulkanDriver::isWorkaroundNeeded(Workaround workaround) {
|
bool VulkanDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||||
switch (workaround) {
|
switch (workaround) {
|
||||||
case Workaround::SPLIT_EASU: {
|
case Workaround::SPLIT_EASU: {
|
||||||
@@ -836,34 +860,42 @@ size_t VulkanDriver::getMaxUniformBufferSize() {
|
|||||||
|
|
||||||
void VulkanDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t index,
|
void VulkanDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t index,
|
||||||
Handle<HwBufferObject> boh) {
|
Handle<HwBufferObject> boh) {
|
||||||
auto& vb = *handle_cast<VulkanVertexBuffer*>(vbh);
|
auto vb = mResourceAllocator.handle_cast<VulkanVertexBuffer*>(vbh);
|
||||||
auto& bo = *handle_cast<VulkanBufferObject*>(boh);
|
auto bo = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
|
||||||
assert_invariant(bo.bindingType == BufferObjectBinding::VERTEX);
|
assert_invariant(bo->bindingType == BufferObjectBinding::VERTEX);
|
||||||
vb.buffers[index] = &bo.buffer;
|
|
||||||
|
vb->setBuffer(bo, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&& p,
|
void VulkanDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&& p,
|
||||||
uint32_t byteOffset) {
|
uint32_t byteOffset) {
|
||||||
auto ib = handle_cast<VulkanIndexBuffer*>(ibh);
|
VulkanCommandBuffer& commands = mCommands->get();
|
||||||
ib->buffer.loadFromCpu(p.buffer, byteOffset, p.size);
|
auto ib = mResourceAllocator.handle_cast<VulkanIndexBuffer*>(ibh);
|
||||||
mDisposer.acquire(ib);
|
commands.acquire(ib);
|
||||||
|
ib->buffer.loadFromCpu(commands.cmdbuffer, p.buffer, byteOffset, p.size);
|
||||||
|
|
||||||
scheduleDestroy(std::move(p));
|
scheduleDestroy(std::move(p));
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescriptor&& bd,
|
void VulkanDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescriptor&& bd,
|
||||||
uint32_t byteOffset) {
|
uint32_t byteOffset) {
|
||||||
auto bo = handle_cast<VulkanBufferObject*>(boh);
|
VulkanCommandBuffer& commands = mCommands->get();
|
||||||
bo->buffer.loadFromCpu(bd.buffer, byteOffset, bd.size);
|
|
||||||
mDisposer.acquire(bo);
|
auto bo = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
|
||||||
|
commands.acquire(bo);
|
||||||
|
bo->buffer.loadFromCpu(commands.cmdbuffer, bd.buffer, byteOffset, bd.size);
|
||||||
|
|
||||||
scheduleDestroy(std::move(bd));
|
scheduleDestroy(std::move(bd));
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> boh,
|
void VulkanDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> boh,
|
||||||
BufferDescriptor&& bd, uint32_t byteOffset) {
|
BufferDescriptor&& bd, uint32_t byteOffset) {
|
||||||
auto bo = handle_cast<VulkanBufferObject*>(boh);
|
VulkanCommandBuffer& commands = mCommands->get();
|
||||||
|
auto bo = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
|
||||||
|
commands.acquire(bo);
|
||||||
// TODO: implement unsynchronized version
|
// TODO: implement unsynchronized version
|
||||||
bo->buffer.loadFromCpu(bd.buffer, byteOffset, bd.size);
|
bo->buffer.loadFromCpu(commands.cmdbuffer, bd.buffer, byteOffset, bd.size);
|
||||||
mDisposer.acquire(bo);
|
mResourceManager.acquire(bo);
|
||||||
scheduleDestroy(std::move(bd));
|
scheduleDestroy(std::move(bd));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -877,15 +909,13 @@ void VulkanDriver::resetBufferObject(Handle<HwBufferObject> boh) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::setMinMaxLevels(Handle<HwTexture> th, uint32_t minLevel, uint32_t maxLevel) {
|
void VulkanDriver::setMinMaxLevels(Handle<HwTexture> th, uint32_t minLevel, uint32_t maxLevel) {
|
||||||
handle_cast<VulkanTexture*>(th)->setPrimaryRange(minLevel, maxLevel);
|
mResourceAllocator.handle_cast<VulkanTexture*>(th)->setPrimaryRange(minLevel, maxLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::update3DImage(
|
void VulkanDriver::update3DImage(Handle<HwTexture> th, uint32_t level, uint32_t xoffset,
|
||||||
Handle<HwTexture> th,
|
uint32_t yoffset, uint32_t zoffset, uint32_t width, uint32_t height, uint32_t depth,
|
||||||
uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset,
|
|
||||||
uint32_t width, uint32_t height, uint32_t depth,
|
|
||||||
PixelBufferDescriptor&& data) {
|
PixelBufferDescriptor&& data) {
|
||||||
handle_cast<VulkanTexture*>(th)->updateImage(data, width, height, depth,
|
mResourceAllocator.handle_cast<VulkanTexture*>(th)->updateImage(data, width, height, depth,
|
||||||
xoffset, yoffset, zoffset, level);
|
xoffset, yoffset, zoffset, level);
|
||||||
scheduleDestroy(std::move(data));
|
scheduleDestroy(std::move(data));
|
||||||
}
|
}
|
||||||
@@ -894,7 +924,7 @@ void VulkanDriver::setupExternalImage(void* image) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool VulkanDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsedTime) {
|
bool VulkanDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsedTime) {
|
||||||
VulkanTimerQuery* vtq = handle_cast<VulkanTimerQuery*>(tqh);
|
VulkanTimerQuery* vtq = mResourceAllocator.handle_cast<VulkanTimerQuery*>(tqh);
|
||||||
if (!vtq->isCompleted()) {
|
if (!vtq->isCompleted()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -938,7 +968,7 @@ bool VulkanDriver::canGenerateMipmaps() {
|
|||||||
|
|
||||||
void VulkanDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
|
void VulkanDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
|
||||||
BufferDescriptor&& data) {
|
BufferDescriptor&& data) {
|
||||||
auto* sb = handle_cast<VulkanSamplerGroup*>(sbh);
|
auto* sb = mResourceAllocator.handle_cast<VulkanSamplerGroup*>(sbh);
|
||||||
|
|
||||||
// FIXME: we shouldn't be using SamplerGroup here, instead the backend should create
|
// FIXME: we shouldn't be using SamplerGroup here, instead the backend should create
|
||||||
// a descriptor or any internal data-structure that represents the textures/samplers.
|
// a descriptor or any internal data-structure that represents the textures/samplers.
|
||||||
@@ -960,7 +990,7 @@ void VulkanDriver::compilePrograms(CompilerPriorityQueue priority,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassParams& params) {
|
void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassParams& params) {
|
||||||
VulkanRenderTarget* const rt = handle_cast<VulkanRenderTarget*>(rth);
|
VulkanRenderTarget* const rt = mResourceAllocator.handle_cast<VulkanRenderTarget*>(rth);
|
||||||
const VkExtent2D extent = rt->getExtent();
|
const VkExtent2D extent = rt->getExtent();
|
||||||
assert_invariant(extent.width > 0 && extent.height > 0);
|
assert_invariant(extent.width > 0 && extent.height > 0);
|
||||||
|
|
||||||
@@ -989,7 +1019,8 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
|||||||
// more general layout. Otherwise, we prefer the DEPTH_ATTACHMENT layout, which is optimal for
|
// more general layout. Otherwise, we prefer the DEPTH_ATTACHMENT layout, which is optimal for
|
||||||
// the non-sampling case.
|
// the non-sampling case.
|
||||||
bool samplingDepthAttachment = false;
|
bool samplingDepthAttachment = false;
|
||||||
VkCommandBuffer const cmdbuffer = mCommands->get().cmdbuffer;
|
VulkanCommandBuffer& commands = mCommands->get();
|
||||||
|
VkCommandBuffer const cmdbuffer = commands.cmdbuffer;
|
||||||
|
|
||||||
UTILS_NOUNROLL
|
UTILS_NOUNROLL
|
||||||
for (uint8_t samplerGroupIdx = 0; samplerGroupIdx < Program::SAMPLER_BINDING_COUNT;
|
for (uint8_t samplerGroupIdx = 0; samplerGroupIdx < Program::SAMPLER_BINDING_COUNT;
|
||||||
@@ -1002,7 +1033,8 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
|||||||
for (size_t i = 0; i < sb->getSize(); i++) {
|
for (size_t i = 0; i < sb->getSize(); i++) {
|
||||||
SamplerDescriptor const* boundSampler = sb->data() + i;
|
SamplerDescriptor const* boundSampler = sb->data() + i;
|
||||||
if (UTILS_LIKELY(boundSampler->t)) {
|
if (UTILS_LIKELY(boundSampler->t)) {
|
||||||
VulkanTexture* texture = handle_cast<VulkanTexture*>(boundSampler->t);
|
VulkanTexture* texture
|
||||||
|
= mResourceAllocator.handle_cast<VulkanTexture*>(boundSampler->t);
|
||||||
if (!any(texture->usage & TextureUsage::DEPTH_ATTACHMENT)) {
|
if (!any(texture->usage & TextureUsage::DEPTH_ATTACHMENT)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1018,6 +1050,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
|||||||
.baseArrayLayer = 0,
|
.baseArrayLayer = 0,
|
||||||
.layerCount = texture->depth,
|
.layerCount = texture->depth,
|
||||||
};
|
};
|
||||||
|
commands.acquire(texture);
|
||||||
texture->transitionLayout(cmdbuffer, subresources, VulkanLayout::DEPTH_SAMPLER);
|
texture->transitionLayout(cmdbuffer, subresources, VulkanLayout::DEPTH_SAMPLER);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1136,10 +1169,14 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
|||||||
|
|
||||||
// The current command buffer now owns a reference to the render target and its attachments.
|
// The current command buffer now owns a reference to the render target and its attachments.
|
||||||
// Note that we must acquire parent textures, not sidecars.
|
// Note that we must acquire parent textures, not sidecars.
|
||||||
mDisposer.acquire(rt);
|
commands.acquire(rt);
|
||||||
mDisposer.acquire((VulkanTexture const*) rt->getDepth().texture);
|
if (depth.texture) {
|
||||||
|
commands.acquire((VulkanTexture*) depth.texture);
|
||||||
|
}
|
||||||
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
|
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
|
||||||
mDisposer.acquire((VulkanTexture const*) rt->getColor(i).texture);
|
if (rt->getColor(i).texture) {
|
||||||
|
commands.acquire(rt->getColor(i).texture);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate the structures required for vkCmdBeginRenderPass.
|
// Populate the structures required for vkCmdBeginRenderPass.
|
||||||
@@ -1208,7 +1245,8 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::endRenderPass(int) {
|
void VulkanDriver::endRenderPass(int) {
|
||||||
VkCommandBuffer cmdbuffer = mCommands->get().cmdbuffer;
|
VulkanCommandBuffer& commands = mCommands->get();
|
||||||
|
VkCommandBuffer cmdbuffer = commands.cmdbuffer;
|
||||||
vkCmdEndRenderPass(cmdbuffer);
|
vkCmdEndRenderPass(cmdbuffer);
|
||||||
|
|
||||||
VulkanRenderTarget* rt = mCurrentRenderPass.renderTarget;
|
VulkanRenderTarget* rt = mCurrentRenderPass.renderTarget;
|
||||||
@@ -1279,15 +1317,15 @@ void VulkanDriver::nextSubpass(int) {
|
|||||||
|
|
||||||
void VulkanDriver::setRenderPrimitiveBuffer(Handle<HwRenderPrimitive> rph,
|
void VulkanDriver::setRenderPrimitiveBuffer(Handle<HwRenderPrimitive> rph,
|
||||||
Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh) {
|
Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh) {
|
||||||
auto primitive = handle_cast<VulkanRenderPrimitive*>(rph);
|
auto primitive = mResourceAllocator.handle_cast<VulkanRenderPrimitive*>(rph);
|
||||||
primitive->setBuffers(handle_cast<VulkanVertexBuffer*>(vbh),
|
primitive->setBuffers(mResourceAllocator.handle_cast<VulkanVertexBuffer*>(vbh),
|
||||||
handle_cast<VulkanIndexBuffer*>(ibh));
|
mResourceAllocator.handle_cast<VulkanIndexBuffer*>(ibh));
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::setRenderPrimitiveRange(Handle<HwRenderPrimitive> rph,
|
void VulkanDriver::setRenderPrimitiveRange(Handle<HwRenderPrimitive> rph,
|
||||||
PrimitiveType pt, uint32_t offset,
|
PrimitiveType pt, uint32_t offset,
|
||||||
uint32_t minIndex, uint32_t maxIndex, uint32_t count) {
|
uint32_t minIndex, uint32_t maxIndex, uint32_t count) {
|
||||||
auto& primitive = *handle_cast<VulkanRenderPrimitive*>(rph);
|
auto& primitive = *mResourceAllocator.handle_cast<VulkanRenderPrimitive*>(rph);
|
||||||
primitive.setPrimitiveType(pt);
|
primitive.setPrimitiveType(pt);
|
||||||
primitive.offset = offset * primitive.indexBuffer->elementSize;
|
primitive.offset = offset * primitive.indexBuffer->elementSize;
|
||||||
primitive.count = count;
|
primitive.count = count;
|
||||||
@@ -1297,8 +1335,9 @@ void VulkanDriver::setRenderPrimitiveRange(Handle<HwRenderPrimitive> rph,
|
|||||||
|
|
||||||
void VulkanDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain> readSch) {
|
void VulkanDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain> readSch) {
|
||||||
ASSERT_PRECONDITION_NON_FATAL(drawSch == readSch,
|
ASSERT_PRECONDITION_NON_FATAL(drawSch == readSch,
|
||||||
"Vulkan driver does not support distinct draw/read swap chains.");
|
"Vulkan driver does not support distinct draw/read swap chains.");
|
||||||
VulkanSwapChain* swapChain = mCurrentSwapChain = handle_cast<VulkanSwapChain*>(drawSch);
|
VulkanSwapChain* swapChain = mCurrentSwapChain
|
||||||
|
= mResourceAllocator.handle_cast<VulkanSwapChain*>(drawSch);
|
||||||
|
|
||||||
bool resized = false;
|
bool resized = false;
|
||||||
swapChain->acquire(resized);
|
swapChain->acquire(resized);
|
||||||
@@ -1313,7 +1352,7 @@ void VulkanDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::commit(Handle<HwSwapChain> sch) {
|
void VulkanDriver::commit(Handle<HwSwapChain> sch) {
|
||||||
VulkanSwapChain* swapChain = handle_cast<VulkanSwapChain*>(sch);
|
VulkanSwapChain* swapChain = mResourceAllocator.handle_cast<VulkanSwapChain*>(sch);
|
||||||
|
|
||||||
if (mCommands->flush()) {
|
if (mCommands->flush()) {
|
||||||
collectGarbage();
|
collectGarbage();
|
||||||
@@ -1324,10 +1363,10 @@ void VulkanDriver::commit(Handle<HwSwapChain> sch) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::bindUniformBuffer(uint32_t index, Handle<HwBufferObject> boh) {
|
void VulkanDriver::bindUniformBuffer(uint32_t index, Handle<HwBufferObject> boh) {
|
||||||
auto* bo = handle_cast<VulkanBufferObject*>(boh);
|
auto* bo = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
|
||||||
const VkDeviceSize offset = 0;
|
const VkDeviceSize offset = 0;
|
||||||
const VkDeviceSize size = VK_WHOLE_SIZE;
|
const VkDeviceSize size = VK_WHOLE_SIZE;
|
||||||
mPipelineCache.bindUniformBuffer((uint32_t) index, bo->buffer.getGpuBuffer(), offset, size);
|
mPipelineCache.bindUniformBufferObject((uint32_t) index, bo, offset, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t index,
|
void VulkanDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t index,
|
||||||
@@ -1338,8 +1377,8 @@ void VulkanDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t ind
|
|||||||
|
|
||||||
// TODO: implement BufferObjectBinding::SHADER_STORAGE case
|
// TODO: implement BufferObjectBinding::SHADER_STORAGE case
|
||||||
|
|
||||||
auto* bo = handle_cast<VulkanBufferObject*>(boh);
|
auto* bo = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
|
||||||
mPipelineCache.bindUniformBuffer((uint32_t)index, bo->buffer.getGpuBuffer(), offset, size);
|
mPipelineCache.bindUniformBufferObject((uint32_t)index, bo, offset, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index) {
|
void VulkanDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index) {
|
||||||
@@ -1347,7 +1386,7 @@ void VulkanDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
|
void VulkanDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
|
||||||
auto* hwsb = handle_cast<VulkanSamplerGroup*>(sbh);
|
auto* hwsb = mResourceAllocator.handle_cast<VulkanSamplerGroup*>(sbh);
|
||||||
mSamplerBindings[index] = hwsb;
|
mSamplerBindings[index] = hwsb;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1378,12 +1417,13 @@ void VulkanDriver::stopCapture(int) {}
|
|||||||
|
|
||||||
void VulkanDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y, uint32_t width,
|
void VulkanDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y, uint32_t width,
|
||||||
uint32_t height, PixelBufferDescriptor&& pbd) {
|
uint32_t height, PixelBufferDescriptor&& pbd) {
|
||||||
VulkanRenderTarget* srcTarget = handle_cast<VulkanRenderTarget*>(src);
|
VulkanRenderTarget* srcTarget = mResourceAllocator.handle_cast<VulkanRenderTarget*>(src);
|
||||||
mCommands->flush();
|
mCommands->flush();
|
||||||
mReadPixels.run(
|
mReadPixels.run(
|
||||||
srcTarget, x, y, width, height, mPlatform->getGraphicsQueueFamilyIndex(),
|
srcTarget, x, y, width, height, mPlatform->getGraphicsQueueFamilyIndex(),
|
||||||
std::move(pbd),
|
std::move(pbd),
|
||||||
[&context = mContext](uint32_t reqs, VkFlags flags) {
|
[&context = mContext](uint32_t reqs, VkFlags flags) {
|
||||||
|
utils::slog.d <<"read pixels: reqs=" << reqs <<" flags=" << flags << utils::io::endl;
|
||||||
return context.selectMemoryType(reqs, flags);
|
return context.selectMemoryType(reqs, flags);
|
||||||
},
|
},
|
||||||
[this](PixelBufferDescriptor&& pbd) {
|
[this](PixelBufferDescriptor&& pbd) {
|
||||||
@@ -1410,8 +1450,8 @@ void VulkanDriver::blit(TargetBufferFlags buffers, Handle<HwRenderTarget> dst, V
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
VulkanRenderTarget* dstTarget = handle_cast<VulkanRenderTarget*>(dst);
|
VulkanRenderTarget* dstTarget = mResourceAllocator.handle_cast<VulkanRenderTarget*>(dst);
|
||||||
VulkanRenderTarget* srcTarget = handle_cast<VulkanRenderTarget*>(src);
|
VulkanRenderTarget* srcTarget = mResourceAllocator.handle_cast<VulkanRenderTarget*>(src);
|
||||||
|
|
||||||
VkFilter vkfilter = filter == SamplerMagFilter::NEAREST ? VK_FILTER_NEAREST : VK_FILTER_LINEAR;
|
VkFilter vkfilter = filter == SamplerMagFilter::NEAREST ? VK_FILTER_NEAREST : VK_FILTER_LINEAR;
|
||||||
|
|
||||||
@@ -1442,19 +1482,19 @@ void VulkanDriver::blit(TargetBufferFlags buffers, Handle<HwRenderTarget> dst, V
|
|||||||
|
|
||||||
void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> rph,
|
void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> rph,
|
||||||
const uint32_t instanceCount) {
|
const uint32_t instanceCount) {
|
||||||
VulkanCommandBuffer const* commands = &mCommands->get();
|
VulkanCommandBuffer* commands = &mCommands->get();
|
||||||
VkCommandBuffer cmdbuffer = commands->cmdbuffer;
|
VkCommandBuffer cmdbuffer = commands->cmdbuffer;
|
||||||
const VulkanRenderPrimitive& prim = *handle_cast<VulkanRenderPrimitive*>(rph);
|
const VulkanRenderPrimitive& prim = *mResourceAllocator.handle_cast<VulkanRenderPrimitive*>(rph);
|
||||||
|
|
||||||
Handle<HwProgram> programHandle = pipelineState.program;
|
Handle<HwProgram> programHandle = pipelineState.program;
|
||||||
RasterState rasterState = pipelineState.rasterState;
|
RasterState rasterState = pipelineState.rasterState;
|
||||||
PolygonOffset depthOffset = pipelineState.polygonOffset;
|
PolygonOffset depthOffset = pipelineState.polygonOffset;
|
||||||
const Viewport& scissorBox = pipelineState.scissor;
|
Viewport const& scissorBox = pipelineState.scissor;
|
||||||
|
|
||||||
auto* program = handle_cast<VulkanProgram*>(programHandle);
|
auto* program = mResourceAllocator.handle_cast<VulkanProgram*>(programHandle);
|
||||||
mDisposer.acquire(program);
|
commands->acquire(program);
|
||||||
mDisposer.acquire(prim.indexBuffer);
|
commands->acquire(prim.indexBuffer);
|
||||||
mDisposer.acquire(prim.vertexBuffer);
|
commands->acquire(prim.vertexBuffer);
|
||||||
|
|
||||||
// If this is a debug build, validate the current shader.
|
// If this is a debug build, validate the current shader.
|
||||||
#if !defined(NDEBUG)
|
#if !defined(NDEBUG)
|
||||||
@@ -1544,6 +1584,8 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
|
|||||||
// Filament concept used to form groups of samplers.
|
// Filament concept used to form groups of samplers.
|
||||||
|
|
||||||
VkDescriptorImageInfo samplerInfo[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {};
|
VkDescriptorImageInfo samplerInfo[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {};
|
||||||
|
VulkanTexture* samplerTextures[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {nullptr};
|
||||||
|
|
||||||
VulkanPipelineCache::UsageFlags usage;
|
VulkanPipelineCache::UsageFlags usage;
|
||||||
|
|
||||||
UTILS_NOUNROLL
|
UTILS_NOUNROLL
|
||||||
@@ -1565,8 +1607,7 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
|
|||||||
samplerIdx++;
|
samplerIdx++;
|
||||||
|
|
||||||
if (UTILS_LIKELY(boundSampler->t)) {
|
if (UTILS_LIKELY(boundSampler->t)) {
|
||||||
VulkanTexture* texture = handle_cast<VulkanTexture*>(boundSampler->t);
|
VulkanTexture* texture = mResourceAllocator.handle_cast<VulkanTexture*>(boundSampler->t);
|
||||||
mDisposer.acquire(texture);
|
|
||||||
|
|
||||||
// TODO: can this uninitialized check be checked in a higher layer?
|
// TODO: can this uninitialized check be checked in a higher layer?
|
||||||
// This fallback path is very flaky because the dummy texture might not have
|
// This fallback path is very flaky because the dummy texture might not have
|
||||||
@@ -1577,7 +1618,6 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
|
|||||||
utils::slog.w << " in material '" << program->name.c_str() << "'";
|
utils::slog.w << " in material '" << program->name.c_str() << "'";
|
||||||
utils::slog.w << " at binding point " << +sampler.binding << utils::io::endl;
|
utils::slog.w << " at binding point " << +sampler.binding << utils::io::endl;
|
||||||
#endif
|
#endif
|
||||||
// Calling get() won't leak here since `texture` is local.
|
|
||||||
texture = mEmptyTexture.get();
|
texture = mEmptyTexture.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1591,11 +1631,12 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
|
|||||||
.imageView = texture->getPrimaryImageView(),
|
.imageView = texture->getPrimaryImageView(),
|
||||||
.imageLayout = ImgUtil::getVkLayout(texture->getPrimaryImageLayout())
|
.imageLayout = ImgUtil::getVkLayout(texture->getPrimaryImageLayout())
|
||||||
};
|
};
|
||||||
|
samplerTextures[sampler.binding] = texture;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mPipelineCache.bindSamplers(samplerInfo, usage);
|
mPipelineCache.bindSamplers(samplerInfo, samplerTextures, usage);
|
||||||
|
|
||||||
// Bind new descriptor sets if they need to change.
|
// Bind new descriptor sets if they need to change.
|
||||||
// If descriptor set allocation failed, skip the draw call and bail. No need to emit an error
|
// If descriptor set allocation failed, skip the draw call and bail. No need to emit an error
|
||||||
@@ -1628,7 +1669,7 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
|
|||||||
// Bind a new pipeline if the pipeline state changed.
|
// Bind a new pipeline if the pipeline state changed.
|
||||||
// If allocation failed, skip the draw call and bail. We do not emit an error since the
|
// If allocation failed, skip the draw call and bail. We do not emit an error since the
|
||||||
// validation layer will already do so.
|
// validation layer will already do so.
|
||||||
if (!mPipelineCache.bindPipeline(cmdbuffer)) {
|
if (!mPipelineCache.bindPipeline(commands)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1644,6 +1685,7 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
|
|||||||
const uint32_t firstIndex = prim.offset / prim.indexBuffer->elementSize;
|
const uint32_t firstIndex = prim.offset / prim.indexBuffer->elementSize;
|
||||||
const int32_t vertexOffset = 0;
|
const int32_t vertexOffset = 0;
|
||||||
const uint32_t firstInstId = 0;
|
const uint32_t firstInstId = 0;
|
||||||
|
|
||||||
vkCmdDrawIndexed(cmdbuffer, indexCount, instanceCount, firstIndex, vertexOffset, firstInstId);
|
vkCmdDrawIndexed(cmdbuffer, indexCount, instanceCount, firstIndex, vertexOffset, firstInstId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1652,12 +1694,12 @@ void VulkanDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGr
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
|
void VulkanDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||||
VulkanTimerQuery* vtq = handle_cast<VulkanTimerQuery*>(tqh);
|
VulkanTimerQuery* vtq = mResourceAllocator.handle_cast<VulkanTimerQuery*>(tqh);
|
||||||
mTimestamps->beginQuery(&(mCommands->get()), vtq);
|
mTimestamps->beginQuery(&(mCommands->get()), vtq);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
|
void VulkanDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||||
VulkanTimerQuery* vtq = handle_cast<VulkanTimerQuery*>(tqh);
|
VulkanTimerQuery* vtq = mResourceAllocator.handle_cast<VulkanTimerQuery*>(tqh);
|
||||||
mTimestamps->endQuery(&(mCommands->get()), vtq);
|
mTimestamps->endQuery(&(mCommands->get()), vtq);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,23 +17,23 @@
|
|||||||
#ifndef TNT_FILAMENT_BACKEND_VULKANDRIVER_H
|
#ifndef TNT_FILAMENT_BACKEND_VULKANDRIVER_H
|
||||||
#define TNT_FILAMENT_BACKEND_VULKANDRIVER_H
|
#define TNT_FILAMENT_BACKEND_VULKANDRIVER_H
|
||||||
|
|
||||||
#include "VulkanPipelineCache.h"
|
|
||||||
#include "VulkanBlitter.h"
|
#include "VulkanBlitter.h"
|
||||||
#include "VulkanDisposer.h"
|
|
||||||
#include "VulkanConstants.h"
|
#include "VulkanConstants.h"
|
||||||
#include "VulkanContext.h"
|
#include "VulkanContext.h"
|
||||||
#include "VulkanFboCache.h"
|
#include "VulkanFboCache.h"
|
||||||
|
#include "VulkanHandles.h"
|
||||||
|
#include "VulkanPipelineCache.h"
|
||||||
#include "VulkanReadPixels.h"
|
#include "VulkanReadPixels.h"
|
||||||
|
#include "VulkanResourceAllocator.h"
|
||||||
#include "VulkanSamplerCache.h"
|
#include "VulkanSamplerCache.h"
|
||||||
#include "VulkanStagePool.h"
|
#include "VulkanStagePool.h"
|
||||||
#include "VulkanUtility.h"
|
#include "VulkanUtility.h"
|
||||||
|
|
||||||
#include "private/backend/Driver.h"
|
|
||||||
#include "private/backend/HandleAllocator.h"
|
|
||||||
#include "DriverBase.h"
|
#include "DriverBase.h"
|
||||||
|
#include "private/backend/Driver.h"
|
||||||
|
|
||||||
#include <utils/compiler.h>
|
|
||||||
#include <utils/Allocator.h>
|
#include <utils/Allocator.h>
|
||||||
|
#include <utils/compiler.h>
|
||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
@@ -46,8 +46,8 @@ public:
|
|||||||
Platform::DriverConfig const& driverConfig) noexcept;
|
Platform::DriverConfig const& driverConfig) noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void debugCommandBegin(CommandStream* cmds, bool synchronous,
|
||||||
void debugCommandBegin(CommandStream* cmds, bool synchronous, const char* methodName) noexcept override;
|
const char* methodName) noexcept override;
|
||||||
|
|
||||||
inline VulkanDriver(VulkanPlatform* platform, VulkanContext const& context,
|
inline VulkanDriver(VulkanPlatform* platform, VulkanContext const& context,
|
||||||
Platform::DriverConfig const& driverConfig) noexcept;
|
Platform::DriverConfig const& driverConfig) noexcept;
|
||||||
@@ -61,77 +61,24 @@ private:
|
|||||||
template<typename T>
|
template<typename T>
|
||||||
friend class ConcreteDispatcher;
|
friend class ConcreteDispatcher;
|
||||||
|
|
||||||
#define DECL_DRIVER_API(methodName, paramsDecl, params) \
|
#define DECL_DRIVER_API(methodName, paramsDecl, params) \
|
||||||
UTILS_ALWAYS_INLINE inline void methodName(paramsDecl);
|
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;
|
RetType methodName(paramsDecl) override;
|
||||||
|
|
||||||
#define DECL_DRIVER_API_RETURN(RetType, methodName, paramsDecl, params) \
|
#define DECL_DRIVER_API_RETURN(RetType, methodName, paramsDecl, params) \
|
||||||
RetType methodName##S() noexcept override; \
|
RetType methodName##S() noexcept override; \
|
||||||
UTILS_ALWAYS_INLINE inline void methodName##R(RetType, paramsDecl);
|
UTILS_ALWAYS_INLINE inline void methodName##R(RetType, paramsDecl);
|
||||||
|
|
||||||
#include "private/backend/DriverAPI.inc"
|
#include "private/backend/DriverAPI.inc"
|
||||||
|
|
||||||
VulkanDriver(VulkanDriver const&) = delete;
|
VulkanDriver(VulkanDriver const&) = delete;
|
||||||
VulkanDriver& operator = (VulkanDriver const&) = delete;
|
VulkanDriver& operator=(VulkanDriver const&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
inline void setRenderPrimitiveBuffer(Handle<HwRenderPrimitive> rph, Handle<HwVertexBuffer> vbh,
|
||||||
template<typename D, typename ... ARGS>
|
Handle<HwIndexBuffer> ibh);
|
||||||
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 setRenderPrimitiveRange(Handle<HwRenderPrimitive> rph, PrimitiveType pt,
|
inline void setRenderPrimitiveRange(Handle<HwRenderPrimitive> rph, PrimitiveType pt,
|
||||||
uint32_t offset, uint32_t minIndex, uint32_t maxIndex, uint32_t count);
|
uint32_t offset, uint32_t minIndex, uint32_t maxIndex, uint32_t count);
|
||||||
@@ -151,9 +98,14 @@ private:
|
|||||||
VkDebugUtilsMessengerEXT mDebugMessenger = VK_NULL_HANDLE;
|
VkDebugUtilsMessengerEXT mDebugMessenger = VK_NULL_HANDLE;
|
||||||
|
|
||||||
VulkanContext mContext = {};
|
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;
|
VulkanPipelineCache mPipelineCache;
|
||||||
VulkanDisposer mDisposer;
|
|
||||||
VulkanStagePool mStagePool;
|
VulkanStagePool mStagePool;
|
||||||
VulkanFboCache mFramebufferCache;
|
VulkanFboCache mFramebufferCache;
|
||||||
VulkanSamplerCache mSamplerCache;
|
VulkanSamplerCache mSamplerCache;
|
||||||
|
|||||||
@@ -46,10 +46,12 @@ static void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeig
|
|||||||
rect->extent.height = std::max(top - y, 0);
|
rect->extent.height = std::max(top - y, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
VulkanProgram::VulkanProgram(VkDevice device, const Program& builder) noexcept :
|
VulkanProgram::VulkanProgram(VkDevice device, const Program& builder) noexcept
|
||||||
HwProgram(builder.getName()), mDevice(device) {
|
: HwProgram(builder.getName()),
|
||||||
|
VulkanResource(VulkanResourceType::PROGRAM),
|
||||||
|
mDevice(device) {
|
||||||
auto const& blobs = builder.getShadersSource();
|
auto const& blobs = builder.getShadersSource();
|
||||||
VkShaderModule* modules[2] = { &bundle.vertex, &bundle.fragment };
|
VkShaderModule* modules[2] = {&bundle.vertex, &bundle.fragment};
|
||||||
// TODO: handle compute shaders.
|
// TODO: handle compute shaders.
|
||||||
for (size_t i = 0; i < 2; i++) {
|
for (size_t i = 0; i < 2; i++) {
|
||||||
const auto& blob = blobs[i];
|
const auto& blob = blobs[i];
|
||||||
@@ -113,8 +115,9 @@ VulkanProgram::VulkanProgram(VkDevice device, const Program& builder) noexcept :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VulkanProgram::VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs) noexcept :
|
VulkanProgram::VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs) noexcept
|
||||||
mDevice(device) {
|
: VulkanResource(VulkanResourceType::PROGRAM),
|
||||||
|
mDevice(device) {
|
||||||
bundle.vertex = vs;
|
bundle.vertex = vs;
|
||||||
bundle.fragment = fs;
|
bundle.fragment = fs;
|
||||||
}
|
}
|
||||||
@@ -126,7 +129,10 @@ VulkanProgram::~VulkanProgram() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Creates a special "default" render target (i.e. associated with the swap chain)
|
// 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) {
|
void VulkanRenderTarget::bindToSwapChain(VulkanSwapChain& swapChain) {
|
||||||
assert_invariant(!mOffscreen);
|
assert_invariant(!mOffscreen);
|
||||||
@@ -138,11 +144,14 @@ void VulkanRenderTarget::bindToSwapChain(VulkanSwapChain& swapChain) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physicalDevice,
|
VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||||
VulkanContext const& context, VmaAllocator allocator,
|
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
|
||||||
VulkanCommands* commands, uint32_t width, uint32_t height, uint8_t samples,
|
uint32_t width, uint32_t height, uint8_t samples,
|
||||||
VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
|
VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
|
||||||
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool)
|
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++) {
|
for (int index = 0; index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; index++) {
|
||||||
mColor[index] = color[index];
|
mColor[index] = color[index];
|
||||||
}
|
}
|
||||||
@@ -166,10 +175,11 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica
|
|||||||
if (texture && texture->samples == 1) {
|
if (texture && texture->samples == 1) {
|
||||||
auto msTexture = texture->getSidecar();
|
auto msTexture = texture->getSidecar();
|
||||||
if (UTILS_UNLIKELY(!msTexture)) {
|
if (UTILS_UNLIKELY(!msTexture)) {
|
||||||
msTexture = new VulkanTexture(device, physicalDevice, context,
|
// TODO: This should be allocated with the ResourceAllocator.
|
||||||
allocator, commands, texture->target,
|
msTexture = new VulkanTexture(device, physicalDevice, context, allocator, commands,
|
||||||
((VulkanTexture const*) texture)->levels, texture->format, samples,
|
texture->target, ((VulkanTexture const*) texture)->levels, texture->format,
|
||||||
texture->width, texture->height, texture->depth, texture->usage, stagePool);
|
samples, texture->width, texture->height, texture->depth, texture->usage,
|
||||||
|
stagePool, true /* heap allocated */);
|
||||||
texture->setSidecar(msTexture);
|
texture->setSidecar(msTexture);
|
||||||
}
|
}
|
||||||
mMsaaAttachments[index] = {.texture = msTexture};
|
mMsaaAttachments[index] = {.texture = msTexture};
|
||||||
@@ -198,7 +208,7 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica
|
|||||||
msTexture = new VulkanTexture(device, physicalDevice, context, allocator,
|
msTexture = new VulkanTexture(device, physicalDevice, context, allocator,
|
||||||
commands, depthTexture->target, msLevel, depthTexture->format, samples,
|
commands, depthTexture->target, msLevel, depthTexture->format, samples,
|
||||||
depthTexture->width, depthTexture->height, depthTexture->depth, depthTexture->usage,
|
depthTexture->width, depthTexture->height, depthTexture->depth, depthTexture->usage,
|
||||||
stagePool);
|
stagePool, true /* heap allocated */);
|
||||||
depthTexture->setSidecar(msTexture);
|
depthTexture->setSidecar(msTexture);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,16 +267,23 @@ uint8_t VulkanRenderTarget::getColorTargetCount(const VulkanRenderPass& pass) co
|
|||||||
}
|
}
|
||||||
|
|
||||||
VulkanVertexBuffer::VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool,
|
VulkanVertexBuffer::VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool,
|
||||||
uint8_t bufferCount, uint8_t attributeCount,
|
VulkanResourceAllocator* allocator, uint8_t bufferCount, uint8_t attributeCount,
|
||||||
uint32_t elementCount, AttributeArray const& attribs) :
|
uint32_t elementCount, AttributeArray const& attribs)
|
||||||
HwVertexBuffer(bufferCount, attributeCount, elementCount, attribs),
|
: HwVertexBuffer(bufferCount, attributeCount, elementCount, attribs),
|
||||||
buffers(bufferCount, nullptr) {}
|
VulkanResource(VulkanResourceType::VERTEX_BUFFER),
|
||||||
|
buffers(bufferCount, nullptr),
|
||||||
|
mResources(allocator) {}
|
||||||
|
|
||||||
VulkanBufferObject::VulkanBufferObject(VmaAllocator allocator,
|
void VulkanVertexBuffer::setBuffer(VulkanBufferObject* bufferObject, uint32_t index) {
|
||||||
VulkanCommands* commands, VulkanStagePool& stagePool, uint32_t byteCount,
|
buffers[index] = &bufferObject->buffer;
|
||||||
BufferObjectBinding bindingType, BufferUsage usage)
|
mResources.acquire(bufferObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
VulkanBufferObject::VulkanBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool,
|
||||||
|
uint32_t byteCount, BufferObjectBinding bindingType, BufferUsage usage)
|
||||||
: HwBufferObject(byteCount),
|
: HwBufferObject(byteCount),
|
||||||
buffer(allocator, commands, stagePool, getBufferObjectUsage(bindingType), byteCount),
|
VulkanResource(VulkanResourceType::BUFFER_OBJECT),
|
||||||
|
buffer(allocator, stagePool, getBufferObjectUsage(bindingType), byteCount),
|
||||||
bindingType(bindingType) {}
|
bindingType(bindingType) {}
|
||||||
|
|
||||||
void VulkanRenderPrimitive::setPrimitiveType(PrimitiveType pt) {
|
void VulkanRenderPrimitive::setPrimitiveType(PrimitiveType pt) {
|
||||||
@@ -294,10 +311,14 @@ void VulkanRenderPrimitive::setBuffers(VulkanVertexBuffer* vertexBuffer,
|
|||||||
VulkanIndexBuffer* indexBuffer) {
|
VulkanIndexBuffer* indexBuffer) {
|
||||||
this->vertexBuffer = vertexBuffer;
|
this->vertexBuffer = vertexBuffer;
|
||||||
this->indexBuffer = indexBuffer;
|
this->indexBuffer = indexBuffer;
|
||||||
|
mResources.acquire(vertexBuffer);
|
||||||
|
mResources.acquire(indexBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
VulkanTimerQuery::VulkanTimerQuery(std::tuple<uint32_t, uint32_t> indices)
|
VulkanTimerQuery::VulkanTimerQuery(std::tuple<uint32_t, uint32_t> indices)
|
||||||
: mStartingQueryIndex(std::get<0>(indices)), mStoppingQueryIndex(std::get<1>(indices)) {}
|
: VulkanThreadSafeResource(VulkanResourceType::TIMER_QUERY),
|
||||||
|
mStartingQueryIndex(std::get<0>(indices)),
|
||||||
|
mStoppingQueryIndex(std::get<1>(indices)) {}
|
||||||
|
|
||||||
void VulkanTimerQuery::setFence(std::shared_ptr<VulkanCmdFence> fence) noexcept {
|
void VulkanTimerQuery::setFence(std::shared_ptr<VulkanCmdFence> fence) noexcept {
|
||||||
std::unique_lock<utils::Mutex> lock(mFenceMutex);
|
std::unique_lock<utils::Mutex> lock(mFenceMutex);
|
||||||
|
|||||||
@@ -14,25 +14,28 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef TNT_FILAMENT_BACKEND_VULKANHANDLES_H
|
#ifndef TNT_FILAMENT_BACKEND_VULKANHANDLES_H
|
||||||
#define 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 "VulkanBuffer.h"
|
||||||
|
#include "VulkanPipelineCache.h"
|
||||||
|
#include "VulkanResources.h"
|
||||||
#include "VulkanSwapChain.h"
|
#include "VulkanSwapChain.h"
|
||||||
#include "VulkanTexture.h"
|
#include "VulkanTexture.h"
|
||||||
#include "VulkanUtility.h"
|
#include "VulkanUtility.h"
|
||||||
|
|
||||||
#include "private/backend/SamplerGroup.h"
|
#include "private/backend/SamplerGroup.h"
|
||||||
|
|
||||||
#include "utils/Mutex.h"
|
#include <utils/Mutex.h>
|
||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
class VulkanTimestamps;
|
class VulkanTimestamps;
|
||||||
|
|
||||||
struct VulkanProgram : public HwProgram {
|
struct VulkanProgram : public HwProgram, VulkanResource {
|
||||||
VulkanProgram(VkDevice device, const Program& builder) noexcept;
|
VulkanProgram(VkDevice device, const Program& builder) noexcept;
|
||||||
VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs) noexcept;
|
VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs) noexcept;
|
||||||
~VulkanProgram();
|
~VulkanProgram();
|
||||||
@@ -51,7 +54,7 @@ private:
|
|||||||
//
|
//
|
||||||
// We use private inheritance to shield clients from the width / height fields in HwRenderTarget,
|
// 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.
|
// 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.
|
// Creates an offscreen render target.
|
||||||
VulkanRenderTarget(VkDevice device, VkPhysicalDevice physicalDevice,
|
VulkanRenderTarget(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||||
VulkanContext const& context, VmaAllocator allocator,
|
VulkanContext const& context, VmaAllocator allocator,
|
||||||
@@ -84,29 +87,43 @@ private:
|
|||||||
uint8_t mSamples : 7;
|
uint8_t mSamples : 7;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VulkanVertexBuffer : public HwVertexBuffer {
|
struct VulkanBufferObject;
|
||||||
|
|
||||||
|
struct VulkanVertexBuffer : public HwVertexBuffer, VulkanResource {
|
||||||
VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool,
|
VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool,
|
||||||
uint8_t bufferCount, uint8_t attributeCount, uint32_t elementCount,
|
VulkanResourceAllocator* allocator, uint8_t bufferCount, uint8_t attributeCount,
|
||||||
AttributeArray const& attributes);
|
uint32_t elementCount, AttributeArray const& attributes);
|
||||||
|
|
||||||
|
void setBuffer(VulkanBufferObject* bufferObject, uint32_t index);
|
||||||
|
|
||||||
|
inline void terminate() {
|
||||||
|
mResources.clear();
|
||||||
|
}
|
||||||
|
|
||||||
utils::FixedCapacityVector<VulkanBuffer const*> buffers;
|
utils::FixedCapacityVector<VulkanBuffer const*> buffers;
|
||||||
|
|
||||||
|
private:
|
||||||
|
FixedSizeVulkanResourceManager mResources;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VulkanIndexBuffer : public HwIndexBuffer {
|
struct VulkanIndexBuffer : public HwIndexBuffer, VulkanResource {
|
||||||
VulkanIndexBuffer(VmaAllocator allocator, VulkanCommands* commands,
|
VulkanIndexBuffer(VmaAllocator allocator, VulkanStagePool& stagePool, uint8_t elementSize,
|
||||||
VulkanStagePool& stagePool, uint8_t elementSize, uint32_t indexCount)
|
uint32_t indexCount)
|
||||||
: HwIndexBuffer(elementSize, indexCount),
|
: HwIndexBuffer(elementSize, indexCount),
|
||||||
buffer(allocator, commands, stagePool, VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
|
VulkanResource(VulkanResourceType::INDEX_BUFFER),
|
||||||
elementSize * indexCount),
|
buffer(allocator, stagePool, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, elementSize * indexCount),
|
||||||
indexType(elementSize == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32) {}
|
indexType(elementSize == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32) {}
|
||||||
void terminate() { buffer.terminate(); }
|
|
||||||
|
void terminate() {
|
||||||
|
buffer.terminate();
|
||||||
|
}
|
||||||
VulkanBuffer buffer;
|
VulkanBuffer buffer;
|
||||||
const VkIndexType indexType;
|
const VkIndexType indexType;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VulkanBufferObject : public HwBufferObject {
|
struct VulkanBufferObject : public HwBufferObject, VulkanResource {
|
||||||
VulkanBufferObject(VmaAllocator allocator, VulkanCommands* commands,
|
VulkanBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool, uint32_t byteCount,
|
||||||
VulkanStagePool& stagePool, uint32_t byteCount, BufferObjectBinding bindingType,
|
BufferObjectBinding bindingType, BufferUsage usage);
|
||||||
BufferUsage usage);
|
|
||||||
void terminate() {
|
void terminate() {
|
||||||
buffer.terminate();
|
buffer.terminate();
|
||||||
}
|
}
|
||||||
@@ -114,28 +131,45 @@ struct VulkanBufferObject : public HwBufferObject {
|
|||||||
const BufferObjectBinding bindingType;
|
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
|
// 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
|
std::unique_ptr<SamplerGroup> sb;// FIXME: this shouldn't depend on filament::SamplerGroup
|
||||||
explicit VulkanSamplerGroup(size_t size) noexcept : sb(new SamplerGroup(size)) { }
|
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 setPrimitiveType(PrimitiveType pt);
|
||||||
void setBuffers(VulkanVertexBuffer* vertexBuffer, VulkanIndexBuffer* indexBuffer);
|
void setBuffers(VulkanVertexBuffer* vertexBuffer, VulkanIndexBuffer* indexBuffer);
|
||||||
VulkanVertexBuffer* vertexBuffer = nullptr;
|
VulkanVertexBuffer* vertexBuffer = nullptr;
|
||||||
VulkanIndexBuffer* indexBuffer = nullptr;
|
VulkanIndexBuffer* indexBuffer = nullptr;
|
||||||
VkPrimitiveTopology primitiveTopology;
|
VkPrimitiveTopology primitiveTopology;
|
||||||
|
|
||||||
|
private:
|
||||||
|
FixedSizeVulkanResourceManager mResources;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VulkanFence : public HwFence {
|
struct VulkanFence : public HwFence, VulkanResource {
|
||||||
VulkanFence() = default;
|
VulkanFence()
|
||||||
explicit VulkanFence(std::shared_ptr<VulkanCmdFence> fence) : fence(fence) {}
|
: VulkanResource(VulkanResourceType::FENCE) {}
|
||||||
|
|
||||||
|
explicit VulkanFence(std::shared_ptr<VulkanCmdFence> fence)
|
||||||
|
: VulkanResource(VulkanResourceType::FENCE),
|
||||||
|
fence(fence) {}
|
||||||
|
|
||||||
std::shared_ptr<VulkanCmdFence> fence;
|
std::shared_ptr<VulkanCmdFence> fence;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VulkanTimerQuery : public HwTimerQuery {
|
struct VulkanTimerQuery : public HwTimerQuery, VulkanThreadSafeResource {
|
||||||
explicit VulkanTimerQuery(std::tuple<uint32_t, uint32_t> indices);
|
explicit VulkanTimerQuery(std::tuple<uint32_t, uint32_t> indices);
|
||||||
~VulkanTimerQuery();
|
~VulkanTimerQuery();
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include "VulkanConstants.h"
|
#include "VulkanConstants.h"
|
||||||
#include "VulkanHandles.h"
|
#include "VulkanHandles.h"
|
||||||
|
#include "VulkanTexture.h"
|
||||||
#include "VulkanUtility.h"
|
#include "VulkanUtility.h"
|
||||||
|
|
||||||
// Vulkan functions often immediately dereference pointers, so it's fine to pass in a pointer
|
// Vulkan functions often immediately dereference pointers, so it's fine to pass in a pointer
|
||||||
@@ -64,7 +65,10 @@ VulkanPipelineCache::getUsageFlags(uint16_t binding, ShaderStageFlags flags, Usa
|
|||||||
return src;
|
return src;
|
||||||
}
|
}
|
||||||
|
|
||||||
VulkanPipelineCache::VulkanPipelineCache() : mCurrentRasterState(createDefaultRasterState()) {
|
VulkanPipelineCache::VulkanPipelineCache(VulkanResourceAllocator* allocator)
|
||||||
|
: mCurrentRasterState(createDefaultRasterState()),
|
||||||
|
mResourceAllocator(allocator),
|
||||||
|
mPipelineBoundResources(allocator) {
|
||||||
mDummyBufferWriteInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
mDummyBufferWriteInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||||
mDummyBufferWriteInfo.pNext = nullptr;
|
mDummyBufferWriteInfo.pNext = nullptr;
|
||||||
mDummyBufferWriteInfo.dstArrayElement = 0;
|
mDummyBufferWriteInfo.dstArrayElement = 0;
|
||||||
@@ -144,6 +148,15 @@ bool VulkanPipelineCache::bindDescriptors(VkCommandBuffer cmdbuffer) noexcept {
|
|||||||
|
|
||||||
cacheEntry->lastUsed = mCurrentTime;
|
cacheEntry->lastUsed = mCurrentTime;
|
||||||
mBoundDescriptor = mDescriptorRequirements;
|
mBoundDescriptor = mDescriptorRequirements;
|
||||||
|
// This passes the currently "bound" uniform buffer objects to pipeline that will be used in the
|
||||||
|
// draw call.
|
||||||
|
auto resourceEntry = mDescriptorResources.find(cacheEntry->id);
|
||||||
|
if (resourceEntry == mDescriptorResources.end()) {
|
||||||
|
mDescriptorResources[cacheEntry->id]
|
||||||
|
= std::make_unique<VulkanAcquireOnlyResourceManager>(mResourceAllocator);
|
||||||
|
resourceEntry = mDescriptorResources.find(cacheEntry->id);
|
||||||
|
}
|
||||||
|
resourceEntry->second->acquire(&mPipelineBoundResources);
|
||||||
|
|
||||||
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||||
getOrCreatePipelineLayout()->handle, 0, VulkanPipelineCache::DESCRIPTOR_TYPE_COUNT,
|
getOrCreatePipelineLayout()->handle, 0, VulkanPipelineCache::DESCRIPTOR_TYPE_COUNT,
|
||||||
@@ -152,7 +165,9 @@ bool VulkanPipelineCache::bindDescriptors(VkCommandBuffer cmdbuffer) noexcept {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VulkanPipelineCache::bindPipeline(VkCommandBuffer cmdbuffer) noexcept {
|
bool VulkanPipelineCache::bindPipeline(VulkanCommandBuffer* commands) noexcept {
|
||||||
|
VkCommandBuffer const cmdbuffer = commands->cmdbuffer;
|
||||||
|
|
||||||
PipelineMap::iterator pipelineIter = mPipelines.find(mPipelineRequirements);
|
PipelineMap::iterator pipelineIter = mPipelines.find(mPipelineRequirements);
|
||||||
|
|
||||||
// Check if the required pipeline is already bound.
|
// Check if the required pipeline is already bound.
|
||||||
@@ -191,7 +206,10 @@ void VulkanPipelineCache::bindScissor(VkCommandBuffer cmdbuffer, VkRect2D scisso
|
|||||||
VulkanPipelineCache::DescriptorCacheEntry* VulkanPipelineCache::createDescriptorSets() noexcept {
|
VulkanPipelineCache::DescriptorCacheEntry* VulkanPipelineCache::createDescriptorSets() noexcept {
|
||||||
PipelineLayoutCacheEntry* layoutCacheEntry = getOrCreatePipelineLayout();
|
PipelineLayoutCacheEntry* layoutCacheEntry = getOrCreatePipelineLayout();
|
||||||
|
|
||||||
DescriptorCacheEntry descriptorCacheEntry = { .pipelineLayout = mPipelineRequirements.layout };
|
DescriptorCacheEntry descriptorCacheEntry = {
|
||||||
|
.pipelineLayout = mPipelineRequirements.layout,
|
||||||
|
.id = mDescriptorCacheEntryCount++,
|
||||||
|
};
|
||||||
|
|
||||||
// Each of the arenas for this particular layout are guaranteed to have the same size. Check
|
// Each of the arenas for this particular layout are guaranteed to have the same size. Check
|
||||||
// the first arena to see if any descriptor sets are available that can be re-claimed. If not,
|
// the first arena to see if any descriptor sets are available that can be re-claimed. If not,
|
||||||
@@ -602,13 +620,19 @@ void VulkanPipelineCache::unbindImageView(VkImageView imageView) noexcept {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanPipelineCache::bindUniformBuffer(uint32_t bindingIndex, VkBuffer uniformBuffer,
|
void VulkanPipelineCache::bindUniformBufferObject(uint32_t bindingIndex,
|
||||||
|
VulkanBufferObject* bufferObject, VkDeviceSize offset, VkDeviceSize size) noexcept {
|
||||||
|
bindUniformBuffer(bindingIndex, bufferObject->buffer.getGpuBuffer(), offset, size);
|
||||||
|
mPipelineBoundResources.acquire(bufferObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanPipelineCache::bindUniformBuffer(uint32_t bindingIndex, VkBuffer buffer,
|
||||||
VkDeviceSize offset, VkDeviceSize size) noexcept {
|
VkDeviceSize offset, VkDeviceSize size) noexcept {
|
||||||
ASSERT_POSTCONDITION(bindingIndex < UBUFFER_BINDING_COUNT,
|
ASSERT_POSTCONDITION(bindingIndex < UBUFFER_BINDING_COUNT,
|
||||||
"Uniform bindings overflow: index = %d, capacity = %d.",
|
"Uniform bindings overflow: index = %d, capacity = %d.", bindingIndex,
|
||||||
bindingIndex, UBUFFER_BINDING_COUNT);
|
UBUFFER_BINDING_COUNT);
|
||||||
auto& key = mDescriptorRequirements;
|
auto& key = mDescriptorRequirements;
|
||||||
key.uniformBuffers[bindingIndex] = uniformBuffer;
|
key.uniformBuffers[bindingIndex] = buffer;
|
||||||
|
|
||||||
if (size == VK_WHOLE_SIZE) {
|
if (size == VK_WHOLE_SIZE) {
|
||||||
size = WHOLE_SIZE;
|
size = WHOLE_SIZE;
|
||||||
@@ -622,9 +646,12 @@ void VulkanPipelineCache::bindUniformBuffer(uint32_t bindingIndex, VkBuffer unif
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VulkanPipelineCache::bindSamplers(VkDescriptorImageInfo samplers[SAMPLER_BINDING_COUNT],
|
void VulkanPipelineCache::bindSamplers(VkDescriptorImageInfo samplers[SAMPLER_BINDING_COUNT],
|
||||||
UsageFlags flags) noexcept {
|
VulkanTexture* textures[SAMPLER_BINDING_COUNT], UsageFlags flags) noexcept {
|
||||||
for (uint32_t bindingIndex = 0; bindingIndex < SAMPLER_BINDING_COUNT; bindingIndex++) {
|
for (uint32_t bindingIndex = 0; bindingIndex < SAMPLER_BINDING_COUNT; bindingIndex++) {
|
||||||
mDescriptorRequirements.samplers[bindingIndex] = samplers[bindingIndex];
|
mDescriptorRequirements.samplers[bindingIndex] = samplers[bindingIndex];
|
||||||
|
if (textures[bindingIndex]) {
|
||||||
|
mPipelineBoundResources.acquire(textures[bindingIndex]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mPipelineRequirements.layout = flags;
|
mPipelineRequirements.layout = flags;
|
||||||
}
|
}
|
||||||
@@ -643,6 +670,7 @@ void VulkanPipelineCache::terminate() noexcept {
|
|||||||
for (auto& iter : mPipelines) {
|
for (auto& iter : mPipelines) {
|
||||||
vkDestroyPipeline(mDevice, iter.second.handle, VKALLOC);
|
vkDestroyPipeline(mDevice, iter.second.handle, VKALLOC);
|
||||||
}
|
}
|
||||||
|
mPipelineBoundResources.clear();
|
||||||
mPipelines.clear();
|
mPipelines.clear();
|
||||||
mBoundPipeline = {};
|
mBoundPipeline = {};
|
||||||
vmaDestroyBuffer(mAllocator, mDummyBuffer, mDummyMemory);
|
vmaDestroyBuffer(mAllocator, mDummyBuffer, mDummyMemory);
|
||||||
@@ -676,6 +704,7 @@ void VulkanPipelineCache::onCommandBuffer(const VulkanCommandBuffer& cmdbuffer)
|
|||||||
arenas[i].push_back(cacheEntry.handles[i]);
|
arenas[i].push_back(cacheEntry.handles[i]);
|
||||||
}
|
}
|
||||||
++mDescriptorArenasCount;
|
++mDescriptorArenasCount;
|
||||||
|
mDescriptorResources.erase(cacheEntry.id);
|
||||||
iter = mDescriptorSets.erase(iter);
|
iter = mDescriptorSets.erase(iter);
|
||||||
} else {
|
} else {
|
||||||
++iter;
|
++iter;
|
||||||
@@ -736,6 +765,10 @@ void VulkanPipelineCache::onCommandBuffer(const VulkanCommandBuffer& cmdbuffer)
|
|||||||
vkDestroyDescriptorPool(mDevice, pool, VKALLOC);
|
vkDestroyDescriptorPool(mDevice, pool, VKALLOC);
|
||||||
}
|
}
|
||||||
mExtinctDescriptorPools.clear();
|
mExtinctDescriptorPools.clear();
|
||||||
|
|
||||||
|
for (auto const& entry : mExtinctDescriptorBundles) {
|
||||||
|
mDescriptorResources.erase(entry.id);
|
||||||
|
}
|
||||||
mExtinctDescriptorBundles.clear();
|
mExtinctDescriptorBundles.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -798,6 +831,10 @@ void VulkanPipelineCache::destroyLayoutsAndDescriptors() noexcept {
|
|||||||
mExtinctDescriptorPools.clear();
|
mExtinctDescriptorPools.clear();
|
||||||
mExtinctDescriptorBundles.clear();
|
mExtinctDescriptorBundles.clear();
|
||||||
|
|
||||||
|
// Both mDescriptorSets and mExtinctDescriptorBundles have been cleared, so it's safe to call
|
||||||
|
// clear() on mDescriptorResources.
|
||||||
|
mDescriptorResources.clear();
|
||||||
|
|
||||||
mBoundDescriptor = {};
|
mBoundDescriptor = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,9 +28,11 @@
|
|||||||
#include <utils/compiler.h>
|
#include <utils/compiler.h>
|
||||||
#include <utils/Hash.h>
|
#include <utils/Hash.h>
|
||||||
|
|
||||||
|
#include <list>
|
||||||
#include <tsl/robin_map.h>
|
#include <tsl/robin_map.h>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "VulkanCommands.h"
|
#include "VulkanCommands.h"
|
||||||
|
|
||||||
@@ -41,6 +43,9 @@ VK_DEFINE_HANDLE(VmaPool)
|
|||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
struct VulkanProgram;
|
struct VulkanProgram;
|
||||||
|
struct VulkanBufferObject;
|
||||||
|
struct VulkanTexture;
|
||||||
|
class VulkanResourceAllocator;
|
||||||
|
|
||||||
// VulkanPipelineCache manages a cache of descriptor sets and pipelines.
|
// VulkanPipelineCache manages a cache of descriptor sets and pipelines.
|
||||||
//
|
//
|
||||||
@@ -131,7 +136,7 @@ public:
|
|||||||
|
|
||||||
// Upon construction, the pipeCache initializes some internal state but does not make any Vulkan
|
// Upon construction, the pipeCache initializes some internal state but does not make any Vulkan
|
||||||
// calls. On destruction it will free any cached Vulkan objects that haven't already been freed.
|
// calls. On destruction it will free any cached Vulkan objects that haven't already been freed.
|
||||||
VulkanPipelineCache();
|
VulkanPipelineCache(VulkanResourceAllocator* allocator);
|
||||||
~VulkanPipelineCache();
|
~VulkanPipelineCache();
|
||||||
void setDevice(VkDevice device, VmaAllocator allocator);
|
void setDevice(VkDevice device, VmaAllocator allocator);
|
||||||
|
|
||||||
@@ -141,7 +146,7 @@ public:
|
|||||||
|
|
||||||
// Creates a new pipeline if necessary and binds it using vkCmdBindPipeline.
|
// Creates a new pipeline if necessary and binds it using vkCmdBindPipeline.
|
||||||
// Returns false if an error occurred.
|
// Returns false if an error occurred.
|
||||||
bool bindPipeline(VkCommandBuffer cmdbuffer) noexcept;
|
bool bindPipeline(VulkanCommandBuffer* commands) noexcept;
|
||||||
|
|
||||||
// Sets up a new scissor rectangle if it has been dirtied.
|
// Sets up a new scissor rectangle if it has been dirtied.
|
||||||
void bindScissor(VkCommandBuffer cmdbuffer, VkRect2D scissor) noexcept;
|
void bindScissor(VkCommandBuffer cmdbuffer, VkRect2D scissor) noexcept;
|
||||||
@@ -151,9 +156,12 @@ public:
|
|||||||
void bindRasterState(const RasterState& rasterState) noexcept;
|
void bindRasterState(const RasterState& rasterState) noexcept;
|
||||||
void bindRenderPass(VkRenderPass renderPass, int subpassIndex) noexcept;
|
void bindRenderPass(VkRenderPass renderPass, int subpassIndex) noexcept;
|
||||||
void bindPrimitiveTopology(VkPrimitiveTopology topology) noexcept;
|
void bindPrimitiveTopology(VkPrimitiveTopology topology) noexcept;
|
||||||
void bindUniformBuffer(uint32_t bindingIndex, VkBuffer uniformBuffer,
|
void bindUniformBufferObject(uint32_t bindingIndex, VulkanBufferObject* bufferObject,
|
||||||
VkDeviceSize offset = 0, VkDeviceSize size = VK_WHOLE_SIZE) noexcept;
|
VkDeviceSize offset = 0, VkDeviceSize size = VK_WHOLE_SIZE) noexcept;
|
||||||
void bindSamplers(VkDescriptorImageInfo samplers[SAMPLER_BINDING_COUNT], UsageFlags flags) noexcept;
|
void bindUniformBuffer(uint32_t bindingIndex, VkBuffer buffer,
|
||||||
|
VkDeviceSize offset = 0, VkDeviceSize size = VK_WHOLE_SIZE) noexcept;
|
||||||
|
void bindSamplers(VkDescriptorImageInfo samplers[SAMPLER_BINDING_COUNT],
|
||||||
|
VulkanTexture* textures[SAMPLER_BINDING_COUNT], UsageFlags flags) noexcept;
|
||||||
void bindInputAttachment(uint32_t bindingIndex, VkDescriptorImageInfo imageInfo) noexcept;
|
void bindInputAttachment(uint32_t bindingIndex, VkDescriptorImageInfo imageInfo) noexcept;
|
||||||
void bindVertexArray(const VertexArray& varray) noexcept;
|
void bindVertexArray(const VertexArray& varray) noexcept;
|
||||||
|
|
||||||
@@ -185,17 +193,22 @@ public:
|
|||||||
mDummyTargetInfo.imageView = imageView;
|
mDummyTargetInfo.imageView = imageView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Acquires a resource to be bound to the current pipeline. The ownership of the resource
|
||||||
|
// will be transferred to the corresponding pipeline when pipeline is bound.
|
||||||
|
void acquireResource(VulkanResource* resource) {
|
||||||
|
mPipelineBoundResources.acquire(resource);
|
||||||
|
}
|
||||||
|
|
||||||
inline RasterState getCurrentRasterState() const noexcept {
|
inline RasterState getCurrentRasterState() const noexcept {
|
||||||
return mCurrentRasterState;
|
return mCurrentRasterState;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to update this outside of bindRasterState due to VulkanDriver::draw.
|
// We need to update this outside of bindRasterState due to VulkanDriver::draw.
|
||||||
inline void setCurrentRasterState(RasterState const& rasterState) noexcept {
|
inline void setCurrentRasterState(RasterState const& rasterState) noexcept {
|
||||||
mCurrentRasterState = rasterState;
|
mCurrentRasterState = rasterState;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
// PIPELINE LAYOUT CACHE KEY
|
// PIPELINE LAYOUT CACHE KEY
|
||||||
// -------------------------
|
// -------------------------
|
||||||
|
|
||||||
@@ -337,7 +350,10 @@ private:
|
|||||||
std::array<VkDescriptorSet, DESCRIPTOR_TYPE_COUNT> handles;
|
std::array<VkDescriptorSet, DESCRIPTOR_TYPE_COUNT> handles;
|
||||||
Timestamp lastUsed;
|
Timestamp lastUsed;
|
||||||
PipelineLayoutKey pipelineLayout;
|
PipelineLayoutKey pipelineLayout;
|
||||||
|
uint32_t id;
|
||||||
};
|
};
|
||||||
|
uint32_t mDescriptorCacheEntryCount = 0;
|
||||||
|
|
||||||
|
|
||||||
struct PipelineCacheEntry {
|
struct PipelineCacheEntry {
|
||||||
VkPipeline handle;
|
VkPipeline handle;
|
||||||
@@ -372,12 +388,15 @@ private:
|
|||||||
PipelineLayoutKeyHashFn, PipelineLayoutKeyEqual>;
|
PipelineLayoutKeyHashFn, PipelineLayoutKeyEqual>;
|
||||||
using PipelineMap = tsl::robin_map<PipelineKey, PipelineCacheEntry,
|
using PipelineMap = tsl::robin_map<PipelineKey, PipelineCacheEntry,
|
||||||
PipelineHashFn, PipelineEqual>;
|
PipelineHashFn, PipelineEqual>;
|
||||||
using DescriptorMap = tsl::robin_map<DescriptorKey, DescriptorCacheEntry,
|
using DescriptorMap
|
||||||
DescHashFn, DescEqual>;
|
= tsl::robin_map<DescriptorKey, DescriptorCacheEntry, DescHashFn, DescEqual>;
|
||||||
|
using DescriptorResourceMap
|
||||||
|
= std::unordered_map<uint32_t, std::unique_ptr<VulkanAcquireOnlyResourceManager>>;
|
||||||
|
|
||||||
PipelineLayoutMap mPipelineLayouts;
|
PipelineLayoutMap mPipelineLayouts;
|
||||||
PipelineMap mPipelines;
|
PipelineMap mPipelines;
|
||||||
DescriptorMap mDescriptorSets;
|
DescriptorMap mDescriptorSets;
|
||||||
|
DescriptorResourceMap mDescriptorResources;
|
||||||
|
|
||||||
// These helpers all return unstable pointers that should not be stored.
|
// These helpers all return unstable pointers that should not be stored.
|
||||||
DescriptorCacheEntry* createDescriptorSets() noexcept;
|
DescriptorCacheEntry* createDescriptorSets() noexcept;
|
||||||
@@ -425,8 +444,8 @@ private:
|
|||||||
// After a growth event (i.e. when the VkDescriptorPool is replaced with a bigger version), all
|
// After a growth event (i.e. when the VkDescriptorPool is replaced with a bigger version), all
|
||||||
// currently used descriptors are moved into the "extinct" sets so that they can be safely
|
// currently used descriptors are moved into the "extinct" sets so that they can be safely
|
||||||
// destroyed a few frames later.
|
// destroyed a few frames later.
|
||||||
std::vector<VkDescriptorPool> mExtinctDescriptorPools;
|
std::list<VkDescriptorPool> mExtinctDescriptorPools;
|
||||||
std::vector<DescriptorCacheEntry> mExtinctDescriptorBundles;
|
std::list<DescriptorCacheEntry> mExtinctDescriptorBundles;
|
||||||
|
|
||||||
VkDescriptorBufferInfo mDummyBufferInfo = {};
|
VkDescriptorBufferInfo mDummyBufferInfo = {};
|
||||||
VkWriteDescriptorSet mDummyBufferWriteInfo = {};
|
VkWriteDescriptorSet mDummyBufferWriteInfo = {};
|
||||||
@@ -435,6 +454,9 @@ private:
|
|||||||
|
|
||||||
VkBuffer mDummyBuffer;
|
VkBuffer mDummyBuffer;
|
||||||
VmaAllocation mDummyMemory;
|
VmaAllocation mDummyMemory;
|
||||||
|
|
||||||
|
VulkanResourceAllocator* mResourceAllocator;
|
||||||
|
VulkanAcquireOnlyResourceManager mPipelineBoundResources;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace filament::backend
|
} // namespace filament::backend
|
||||||
|
|||||||
@@ -176,12 +176,28 @@ void VulkanReadPixels::run(VulkanRenderTarget const* srcTarget, uint32_t const x
|
|||||||
VkMemoryRequirements memReqs;
|
VkMemoryRequirements memReqs;
|
||||||
VkDeviceMemory stagingMemory;
|
VkDeviceMemory stagingMemory;
|
||||||
vkGetImageMemoryRequirements(device, stagingImage, &memReqs);
|
vkGetImageMemoryRequirements(device, stagingImage, &memReqs);
|
||||||
|
|
||||||
|
uint32_t memoryTypeIndex = selectMemoryFunc(memReqs.memoryTypeBits,
|
||||||
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
|
||||||
|
| VK_MEMORY_PROPERTY_HOST_CACHED_BIT);
|
||||||
|
|
||||||
|
// If VK_MEMORY_PROPERTY_HOST_CACHED_BIT is not supported, we try only
|
||||||
|
// HOST_VISIBLE+HOST_COHERENT. HOST_CACHED helps a lot with readpixels performance.
|
||||||
|
if (memoryTypeIndex >= VK_MAX_MEMORY_TYPES) {
|
||||||
|
memoryTypeIndex = selectMemoryFunc(memReqs.memoryTypeBits,
|
||||||
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
|
||||||
|
utils::slog.w
|
||||||
|
<< "readPixels is slow because VK_MEMORY_PROPERTY_HOST_CACHED_BIT is not available"
|
||||||
|
<< utils::io::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_POSTCONDITION(memoryTypeIndex < VK_MAX_MEMORY_TYPES,
|
||||||
|
"VulkanReadPixels: unable to find a memory type that meets requirements.");
|
||||||
|
|
||||||
VkMemoryAllocateInfo const allocInfo = {
|
VkMemoryAllocateInfo const allocInfo = {
|
||||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||||
.allocationSize = memReqs.size,
|
.allocationSize = memReqs.size,
|
||||||
.memoryTypeIndex = selectMemoryFunc(memReqs.memoryTypeBits,
|
.memoryTypeIndex = memoryTypeIndex,
|
||||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
|
|
||||||
| VK_MEMORY_PROPERTY_HOST_CACHED_BIT),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
vkAllocateMemory(device, &allocInfo, VKALLOC, &stagingMemory);
|
vkAllocateMemory(device, &allocInfo, VKALLOC, &stagingMemory);
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
/*
|
||||||
|
* 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_VULKANRESOURCEALLOCATOR_H
|
||||||
|
#define TNT_FILAMENT_BACKEND_VULKANRESOURCEALLOCATOR_H
|
||||||
|
|
||||||
|
#include "VulkanHandles.h"
|
||||||
|
|
||||||
|
#include <private/backend/HandleAllocator.h>
|
||||||
|
|
||||||
|
#include <utils/FixedCapacityVector.h>
|
||||||
|
#include <utils/Log.h>
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
namespace filament::backend {
|
||||||
|
|
||||||
|
// RESOURCE_TYPE_COUNT matches the count of enum VulkanResourceType.
|
||||||
|
#define RESOURCE_TYPE_COUNT 12
|
||||||
|
#define DEBUG_RESOURCE_LEAKS 0
|
||||||
|
|
||||||
|
#if DEBUG_RESOURCE_LEAKS
|
||||||
|
#define TRACK_INCREMENT() \
|
||||||
|
if (!IS_MANAGED_TYPE(obj->mType)) { \
|
||||||
|
mDebugOnlyResourceCount[static_cast<size_t>(obj->mType)]++; \
|
||||||
|
}
|
||||||
|
#define TRACK_DECREMENT() \
|
||||||
|
if (!IS_MANAGED_TYPE(obj->mType)) { \
|
||||||
|
mDebugOnlyResourceCount[static_cast<size_t>(obj->mType)]--; \
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// No-op
|
||||||
|
#define TRACK_INCREMENT()
|
||||||
|
#define TRACK_DECREMENT()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class VulkanResourceAllocator {
|
||||||
|
|
||||||
|
public:
|
||||||
|
VulkanResourceAllocator(size_t arenaSize)
|
||||||
|
: mHandleAllocatorImpl("Handles", arenaSize)
|
||||||
|
#if DEBUG_RESOURCE_LEAKS
|
||||||
|
, mDebugOnlyResourceCount(RESOURCE_TYPE_COUNT) {
|
||||||
|
std::memset(mDebugOnlyResourceCount.data(), 0, sizeof(size_t) * RESOURCE_TYPE_COUNT);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
{}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template<typename D, typename... ARGS>
|
||||||
|
inline Handle<D> initHandle(ARGS&&... args) noexcept {
|
||||||
|
auto handle = mHandleAllocatorImpl.allocateAndConstruct<D>(std::forward<ARGS>(args)...);
|
||||||
|
auto obj = handle_cast<D*>(handle);
|
||||||
|
obj->initResource(handle.getId());
|
||||||
|
TRACK_INCREMENT();
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename D>
|
||||||
|
inline Handle<D> allocHandle() noexcept {
|
||||||
|
return mHandleAllocatorImpl.allocate<D>();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename D, typename B, typename... ARGS>
|
||||||
|
inline typename std::enable_if<std::is_base_of<B, D>::value, D>::type* construct(
|
||||||
|
Handle<B> const& handle, ARGS&&... args) noexcept {
|
||||||
|
auto obj = mHandleAllocatorImpl.construct<D, B>(handle, std::forward<ARGS>(args)...);
|
||||||
|
obj->initResource(handle.getId());
|
||||||
|
TRACK_INCREMENT();
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
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>& handle) noexcept {
|
||||||
|
return mHandleAllocatorImpl.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 mHandleAllocatorImpl.handle_cast<Dp, B>(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename D, typename B>
|
||||||
|
inline void destruct(Handle<B> handle) noexcept {
|
||||||
|
auto obj = handle_cast<D*>(handle);
|
||||||
|
if constexpr (std::is_base_of_v<VulkanIndexBuffer, D>
|
||||||
|
|| std::is_base_of_v<VulkanBufferObject, D>) {
|
||||||
|
obj->terminate();
|
||||||
|
}
|
||||||
|
TRACK_DECREMENT();
|
||||||
|
mHandleAllocatorImpl.deallocate(handle, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
HandleAllocatorVK mHandleAllocatorImpl;
|
||||||
|
|
||||||
|
#if DEBUG_RESOURCE_LEAKS
|
||||||
|
public:
|
||||||
|
void print() {
|
||||||
|
utils::slog.d << "Resource Allocator state (debug only)" << utils::io::endl;
|
||||||
|
for (size_t i = 0; i < RESOURCE_TYPE_COUNT; i++) {
|
||||||
|
utils::slog.d << "[" << i << "]=" << mDebugOnlyResourceCount[i] << utils::io::endl;
|
||||||
|
}
|
||||||
|
utils::slog.d << "+++++++++++++++++++++++++++++++++++++" << utils::io::endl;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
utils::FixedCapacityVector<size_t> mDebugOnlyResourceCount;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#undef TRACK_INCREMENT
|
||||||
|
#undef TRACK_DECREMENT
|
||||||
|
#undef DEBUG_RESOURCE_LEAKS
|
||||||
|
|
||||||
|
} // namespace filament::backend
|
||||||
|
|
||||||
|
#endif // TNT_FILAMENT_BACKEND_VULKANRESOURCEALLOCATOR_H
|
||||||
@@ -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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "VulkanResources.h"
|
||||||
|
#include "VulkanHandles.h"
|
||||||
|
#include "VulkanResourceAllocator.h"
|
||||||
|
|
||||||
|
namespace filament::backend {
|
||||||
|
|
||||||
|
void deallocateResource(VulkanResourceAllocator* allocator, VulkanResourceType type,
|
||||||
|
HandleBase::HandleId id) {
|
||||||
|
|
||||||
|
if (IS_HEAP_ALLOC_TYPE(type)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case VulkanResourceType::BUFFER_OBJECT:
|
||||||
|
allocator->destruct<VulkanBufferObject>(Handle<HwBufferObject>(id));
|
||||||
|
break;
|
||||||
|
case VulkanResourceType::INDEX_BUFFER:
|
||||||
|
allocator->destruct<VulkanIndexBuffer>(Handle<HwIndexBuffer>(id));
|
||||||
|
break;
|
||||||
|
case VulkanResourceType::PROGRAM:
|
||||||
|
allocator->destruct<VulkanProgram>(Handle<HwProgram>(id));
|
||||||
|
break;
|
||||||
|
case VulkanResourceType::RENDER_TARGET:
|
||||||
|
allocator->destruct<VulkanRenderTarget>(Handle<HwRenderTarget>(id));
|
||||||
|
break;
|
||||||
|
case VulkanResourceType::SAMPLER_GROUP:
|
||||||
|
allocator->destruct<VulkanSamplerGroup>(Handle<HwSamplerGroup>(id));
|
||||||
|
break;
|
||||||
|
case VulkanResourceType::SWAP_CHAIN:
|
||||||
|
allocator->destruct<VulkanSwapChain>(Handle<HwSwapChain>(id));
|
||||||
|
break;
|
||||||
|
case VulkanResourceType::TEXTURE:
|
||||||
|
allocator->destruct<VulkanTexture>(Handle<HwTexture>(id));
|
||||||
|
break;
|
||||||
|
case VulkanResourceType::TIMER_QUERY:
|
||||||
|
allocator->destruct<VulkanTimerQuery>(Handle<HwTimerQuery>(id));
|
||||||
|
break;
|
||||||
|
case VulkanResourceType::VERTEX_BUFFER:
|
||||||
|
allocator->destruct<VulkanVertexBuffer>(Handle<HwVertexBuffer>(id));
|
||||||
|
break;
|
||||||
|
case VulkanResourceType::RENDER_PRIMITIVE:
|
||||||
|
allocator->destruct<VulkanRenderPrimitive>(Handle<VulkanRenderPrimitive>(id));
|
||||||
|
break;
|
||||||
|
// If the resource is heap allocated, then the resource manager just skip refcounted
|
||||||
|
// destruction.
|
||||||
|
case VulkanResourceType::FENCE:
|
||||||
|
case VulkanResourceType::HEAP_ALLOCATED:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace filament::backend
|
||||||
@@ -0,0 +1,337 @@
|
|||||||
|
/*
|
||||||
|
* 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_VULKANRESOURCES_H
|
||||||
|
#define TNT_FILAMENT_BACKEND_VULKANRESOURCES_H
|
||||||
|
|
||||||
|
#include <backend/DriverEnums.h>// For MAX_VERTEX_BUFFER_COUNT
|
||||||
|
#include <backend/Handle.h>
|
||||||
|
|
||||||
|
#include <tsl/robin_set.h>
|
||||||
|
#include <utils/Mutex.h>
|
||||||
|
#include <utils/Panic.h>
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
namespace filament::backend {
|
||||||
|
|
||||||
|
class VulkanResourceAllocator;
|
||||||
|
struct VulkanThreadSafeResource;
|
||||||
|
|
||||||
|
// Subclasses of VulkanResource must provide this enum in their construction.
|
||||||
|
enum class VulkanResourceType : uint8_t {
|
||||||
|
BUFFER_OBJECT,
|
||||||
|
INDEX_BUFFER,
|
||||||
|
PROGRAM,
|
||||||
|
RENDER_TARGET,
|
||||||
|
SAMPLER_GROUP,
|
||||||
|
SWAP_CHAIN,
|
||||||
|
RENDER_PRIMITIVE,
|
||||||
|
TEXTURE,
|
||||||
|
TIMER_QUERY,
|
||||||
|
VERTEX_BUFFER,
|
||||||
|
|
||||||
|
// Below are resources that are managed manually (i.e. not ref counted).
|
||||||
|
FENCE,
|
||||||
|
HEAP_ALLOCATED,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define IS_HEAP_ALLOC_TYPE(f) \
|
||||||
|
(f == VulkanResourceType::FENCE || f == VulkanResourceType::HEAP_ALLOCATED)
|
||||||
|
|
||||||
|
|
||||||
|
// This is a ref-counting base class that tracks how many references of this resource exist. This
|
||||||
|
// class is paired with VulkanResourceManagerImpl which is responsible for incrementing or
|
||||||
|
// decrementing the count. Once mRefCount == 0, VulkanResourceManagerImpl will also call the
|
||||||
|
// appropriate destructor. VulkanCommandBuffer, VulkanDriver, and composite structure like
|
||||||
|
// VulkanRenderPrimitive are owners of VulkanResourceManagerImpl instances.
|
||||||
|
struct VulkanResourceBase {
|
||||||
|
protected:
|
||||||
|
explicit VulkanResourceBase(VulkanResourceType type)
|
||||||
|
: mRefCount(IS_HEAP_ALLOC_TYPE(type) ? 1 : 0),
|
||||||
|
mType(type),
|
||||||
|
mHandleId(0) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
inline VulkanResourceType getType() {
|
||||||
|
return mType;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline HandleBase::HandleId getId() {
|
||||||
|
return mHandleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void initResource(HandleBase::HandleId id) noexcept {
|
||||||
|
mHandleId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ref() noexcept {
|
||||||
|
if (IS_HEAP_ALLOC_TYPE(mType)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
++mRefCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void deref() noexcept {
|
||||||
|
if (IS_HEAP_ALLOC_TYPE(mType)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
--mRefCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t refcount() noexcept {
|
||||||
|
return mRefCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t mRefCount = 0;
|
||||||
|
VulkanResourceType mType = VulkanResourceType::BUFFER_OBJECT;
|
||||||
|
HandleBase::HandleId mHandleId;
|
||||||
|
|
||||||
|
friend struct VulkanThreadSafeResource;
|
||||||
|
friend class VulkanResourceAllocator;
|
||||||
|
|
||||||
|
template<typename RT, typename ST>
|
||||||
|
friend class VulkanResourceManagerImpl;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VulkanThreadSafeResource {
|
||||||
|
protected:
|
||||||
|
explicit VulkanThreadSafeResource(VulkanResourceType type)
|
||||||
|
: mImpl(type) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
inline VulkanResourceType getType() {
|
||||||
|
return mImpl.getType();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline HandleBase::HandleId getId() {
|
||||||
|
return mImpl.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void initResource(HandleBase::HandleId id) noexcept {
|
||||||
|
std::unique_lock<utils::Mutex> lock(mMutex);
|
||||||
|
mImpl.initResource(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ref() noexcept {
|
||||||
|
std::unique_lock<utils::Mutex> lock(mMutex);
|
||||||
|
mImpl.ref();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void deref() noexcept {
|
||||||
|
std::unique_lock<utils::Mutex> lock(mMutex);
|
||||||
|
mImpl.deref();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t refcount() noexcept {
|
||||||
|
std::unique_lock<utils::Mutex> lock(mMutex);
|
||||||
|
return mImpl.refcount();
|
||||||
|
}
|
||||||
|
|
||||||
|
utils::Mutex mMutex;
|
||||||
|
VulkanResourceBase mImpl;
|
||||||
|
|
||||||
|
friend class VulkanResourceAllocator;
|
||||||
|
template<typename RT, typename ST>
|
||||||
|
friend class VulkanResourceManagerImpl;
|
||||||
|
};
|
||||||
|
|
||||||
|
using VulkanResource = VulkanResourceBase;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// When the size of the resource set is known to be small, (for example for VulkanRenderPrimitive),
|
||||||
|
// we just use a std::array to back the set.
|
||||||
|
class FixedCapacityResourceSet {
|
||||||
|
private:
|
||||||
|
constexpr static size_t const SIZE = MAX_VERTEX_BUFFER_COUNT;
|
||||||
|
using FixedSizeArray = std::array<VulkanResource*, SIZE>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using const_iterator = FixedSizeArray::const_iterator;
|
||||||
|
|
||||||
|
inline const_iterator begin() {
|
||||||
|
if (mInd == 0) {
|
||||||
|
return mArray.cend();
|
||||||
|
}
|
||||||
|
return mArray.cbegin();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const_iterator end() {
|
||||||
|
if (mInd == 0) {
|
||||||
|
return mArray.cend();
|
||||||
|
}
|
||||||
|
if (mInd < SIZE) {
|
||||||
|
return mArray.begin() + mInd;
|
||||||
|
}
|
||||||
|
return mArray.cend();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const_iterator find(VulkanResource* resource) {
|
||||||
|
return std::find(mArray.begin(), mArray.end(), resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void insert(VulkanResource* resource) {
|
||||||
|
assert_invariant(mInd < SIZE);
|
||||||
|
mArray[mInd++] = resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void erase(VulkanResource* resource) {
|
||||||
|
assert_invariant(false && "FixedCapacityResourceSet::erase should not be called");
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void clear() {
|
||||||
|
if (mInd == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mInd = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
FixedSizeArray mArray{nullptr};
|
||||||
|
size_t mInd = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// robin_set/map are useful for sets that are acquire only and the set will be iterated when the set
|
||||||
|
// is cleared.
|
||||||
|
using FastIterationResourceSet = tsl::robin_set<VulkanResource*>;
|
||||||
|
|
||||||
|
// unoredered_set is used in the general case where insert/erase can occur at will. This is useful
|
||||||
|
// for the basic object ownership count - i.e. VulkanDriver.
|
||||||
|
using ResourceSet = std::unordered_set<VulkanResource*>;
|
||||||
|
|
||||||
|
using ThreadSafeResourceSet = std::unordered_set<VulkanThreadSafeResource*>;
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
class VulkanResourceAllocator;
|
||||||
|
|
||||||
|
#define LOCK_IF_NEEDED() \
|
||||||
|
if constexpr (std::is_base_of_v<VulkanThreadSafeResource, ResourceType>) { \
|
||||||
|
mMutex->lock(); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define UNLOCK_IF_NEEDED() \
|
||||||
|
if constexpr (std::is_base_of_v<VulkanThreadSafeResource, ResourceType>) { \
|
||||||
|
mMutex->unlock(); \
|
||||||
|
}
|
||||||
|
|
||||||
|
void deallocateResource(VulkanResourceAllocator* allocator, VulkanResourceType type,
|
||||||
|
HandleBase::HandleId id);
|
||||||
|
|
||||||
|
template<typename ResourceType, typename SetType>
|
||||||
|
class VulkanResourceManagerImpl {
|
||||||
|
public:
|
||||||
|
explicit VulkanResourceManagerImpl(VulkanResourceAllocator* allocator)
|
||||||
|
: mAllocator(allocator) {
|
||||||
|
if constexpr (std::is_base_of_v<VulkanThreadSafeResource, ResourceType>) {
|
||||||
|
mMutex = std::make_unique<utils::Mutex>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VulkanResourceManagerImpl(const VulkanResourceManagerImpl& other) = delete;
|
||||||
|
void operator=(const VulkanResourceManagerImpl& other) = delete;
|
||||||
|
VulkanResourceManagerImpl(const VulkanResourceManagerImpl&& other) = delete;
|
||||||
|
void operator=(const VulkanResourceManagerImpl&& other) = delete;
|
||||||
|
|
||||||
|
~VulkanResourceManagerImpl() {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void acquire(ResourceType* resource) {
|
||||||
|
if (IS_HEAP_ALLOC_TYPE(resource->getType())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOCK_IF_NEEDED();
|
||||||
|
if (mResources.find(resource) != mResources.end()) {
|
||||||
|
UNLOCK_IF_NEEDED();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mResources.insert(resource);
|
||||||
|
UNLOCK_IF_NEEDED();
|
||||||
|
resource->ref();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfers ownership from one resource set to another
|
||||||
|
inline void acquire(VulkanResourceManagerImpl<ResourceType, SetType>* srcResources) {
|
||||||
|
LOCK_IF_NEEDED();
|
||||||
|
for (auto iter = srcResources->mResources.begin(); iter != srcResources->mResources.end();
|
||||||
|
iter++) {
|
||||||
|
acquire(*iter);
|
||||||
|
}
|
||||||
|
UNLOCK_IF_NEEDED();
|
||||||
|
srcResources->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void release(ResourceType* resource) {
|
||||||
|
if (IS_HEAP_ALLOC_TYPE(resource->getType())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOCK_IF_NEEDED();
|
||||||
|
auto resItr = mResources.find(resource);
|
||||||
|
if (resItr == mResources.end()) {
|
||||||
|
UNLOCK_IF_NEEDED();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mResources.erase(resItr);
|
||||||
|
UNLOCK_IF_NEEDED();
|
||||||
|
derefImpl(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void clear() {
|
||||||
|
LOCK_IF_NEEDED();
|
||||||
|
for (auto iter = mResources.begin(); iter != mResources.end(); iter++) {
|
||||||
|
derefImpl(*iter);
|
||||||
|
}
|
||||||
|
mResources.clear();
|
||||||
|
UNLOCK_IF_NEEDED();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t size() {
|
||||||
|
return mResources.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
inline void derefImpl(ResourceType* resource) {
|
||||||
|
resource->deref();
|
||||||
|
if (resource->refcount() != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deallocateResource(mAllocator, resource->getType(), resource->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
VulkanResourceAllocator* mAllocator;
|
||||||
|
SetType mResources;
|
||||||
|
std::unique_ptr<utils::Mutex> mMutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
using VulkanAcquireOnlyResourceManager
|
||||||
|
= VulkanResourceManagerImpl<VulkanResource, FastIterationResourceSet>;
|
||||||
|
using VulkanResourceManager = VulkanResourceManagerImpl<VulkanResource, ResourceSet>;
|
||||||
|
using FixedSizeVulkanResourceManager
|
||||||
|
= VulkanResourceManagerImpl<VulkanResource, FixedCapacityResourceSet>;
|
||||||
|
using VulkanThreadSafeResourceManager
|
||||||
|
= VulkanResourceManagerImpl<VulkanThreadSafeResource, ThreadSafeResourceSet>;
|
||||||
|
|
||||||
|
#undef LOCK_IF_NEEDED
|
||||||
|
#undef UNLOCK_IF_NEEDED
|
||||||
|
|
||||||
|
} // namespace filament::backend
|
||||||
|
|
||||||
|
#endif // TNT_FILAMENT_BACKEND_VULKANRESOURCES_H
|
||||||
@@ -87,8 +87,7 @@ constexpr inline float getMaxLod(SamplerMinFilter filter) noexcept {
|
|||||||
case SamplerMinFilter::LINEAR_MIPMAP_NEAREST:
|
case SamplerMinFilter::LINEAR_MIPMAP_NEAREST:
|
||||||
case SamplerMinFilter::NEAREST_MIPMAP_LINEAR:
|
case SamplerMinFilter::NEAREST_MIPMAP_LINEAR:
|
||||||
case SamplerMinFilter::LINEAR_MIPMAP_LINEAR:
|
case SamplerMinFilter::LINEAR_MIPMAP_LINEAR:
|
||||||
// Assuming our maximum texture size is 4k, we'll never need more than 12 miplevels.
|
return VK_LOD_CLAMP_NONE;
|
||||||
return 12.0f;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +98,7 @@ constexpr inline VkBool32 getCompareEnable(SamplerCompareMode mode) noexcept {
|
|||||||
void VulkanSamplerCache::initialize(VkDevice device) { mDevice = device; }
|
void VulkanSamplerCache::initialize(VkDevice device) { mDevice = device; }
|
||||||
|
|
||||||
VkSampler VulkanSamplerCache::getSampler(SamplerParams params) noexcept {
|
VkSampler VulkanSamplerCache::getSampler(SamplerParams params) noexcept {
|
||||||
auto iter = mCache.find(params.u);
|
auto iter = mCache.find(params);
|
||||||
if (UTILS_LIKELY(iter != mCache.end())) {
|
if (UTILS_LIKELY(iter != mCache.end())) {
|
||||||
return iter->second;
|
return iter->second;
|
||||||
}
|
}
|
||||||
@@ -123,7 +122,7 @@ VkSampler VulkanSamplerCache::getSampler(SamplerParams params) noexcept {
|
|||||||
VkSampler sampler;
|
VkSampler sampler;
|
||||||
VkResult error = vkCreateSampler(mDevice, &samplerInfo, VKALLOC, &sampler);
|
VkResult error = vkCreateSampler(mDevice, &samplerInfo, VKALLOC, &sampler);
|
||||||
ASSERT_POSTCONDITION(!error, "Unable to create sampler.");
|
ASSERT_POSTCONDITION(!error, "Unable to create sampler.");
|
||||||
mCache.insert({params.u, sampler});
|
mCache.insert({params, sampler});
|
||||||
return sampler;
|
return sampler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public:
|
|||||||
void terminate() noexcept;
|
void terminate() noexcept;
|
||||||
private:
|
private:
|
||||||
VkDevice mDevice;
|
VkDevice mDevice;
|
||||||
tsl::robin_map<uint32_t, VkSampler> mCache;
|
tsl::robin_map<SamplerParams, VkSampler, SamplerParams::Hasher, SamplerParams::EqualTo> mCache;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace filament::backend
|
} // namespace filament::backend
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ namespace filament::backend {
|
|||||||
VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& context,
|
VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& context,
|
||||||
VmaAllocator allocator, VulkanCommands* commands, VulkanStagePool& stagePool,
|
VmaAllocator allocator, VulkanCommands* commands, VulkanStagePool& stagePool,
|
||||||
void* nativeWindow, uint64_t flags, VkExtent2D extent)
|
void* nativeWindow, uint64_t flags, VkExtent2D extent)
|
||||||
: mPlatform(platform),
|
: VulkanResource(VulkanResourceType::SWAP_CHAIN),
|
||||||
|
mPlatform(platform),
|
||||||
mCommands(commands),
|
mCommands(commands),
|
||||||
mAllocator(allocator),
|
mAllocator(allocator),
|
||||||
mStagePool(stagePool),
|
mStagePool(stagePool),
|
||||||
@@ -67,11 +68,11 @@ void VulkanSwapChain::update() {
|
|||||||
for (auto const color: bundle.colors) {
|
for (auto const color: bundle.colors) {
|
||||||
mColors.push_back(std::make_unique<VulkanTexture>(device, mAllocator, mCommands, color,
|
mColors.push_back(std::make_unique<VulkanTexture>(device, mAllocator, mCommands, color,
|
||||||
bundle.colorFormat, 1, bundle.extent.width, bundle.extent.height,
|
bundle.colorFormat, 1, bundle.extent.width, bundle.extent.height,
|
||||||
TextureUsage::COLOR_ATTACHMENT, mStagePool));
|
TextureUsage::COLOR_ATTACHMENT, mStagePool, true /* heap allocated */));
|
||||||
}
|
}
|
||||||
mDepth = std::make_unique<VulkanTexture>(device, mAllocator, mCommands, bundle.depth,
|
mDepth = std::make_unique<VulkanTexture>(device, mAllocator, mCommands, bundle.depth,
|
||||||
bundle.depthFormat, 1, bundle.extent.width, bundle.extent.height,
|
bundle.depthFormat, 1, bundle.extent.width, bundle.extent.height,
|
||||||
TextureUsage::DEPTH_ATTACHMENT, mStagePool);
|
TextureUsage::DEPTH_ATTACHMENT, mStagePool, true /* heap allocated */);
|
||||||
|
|
||||||
mExtent = bundle.extent;
|
mExtent = bundle.extent;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,10 @@
|
|||||||
#ifndef TNT_FILAMENT_BACKEND_VULKANSWAPCHAIN_H
|
#ifndef TNT_FILAMENT_BACKEND_VULKANSWAPCHAIN_H
|
||||||
#define TNT_FILAMENT_BACKEND_VULKANSWAPCHAIN_H
|
#define TNT_FILAMENT_BACKEND_VULKANSWAPCHAIN_H
|
||||||
|
|
||||||
|
#include "DriverBase.h"
|
||||||
|
|
||||||
#include "VulkanContext.h"
|
#include "VulkanContext.h"
|
||||||
#include "VulkanDriver.h"
|
#include "VulkanResources.h"
|
||||||
|
|
||||||
#include <backend/platforms/VulkanPlatform.h>
|
#include <backend/platforms/VulkanPlatform.h>
|
||||||
|
|
||||||
@@ -35,7 +37,7 @@ struct VulkanHeadlessSwapChain;
|
|||||||
struct VulkanSurfaceSwapChain;
|
struct VulkanSurfaceSwapChain;
|
||||||
|
|
||||||
// A wrapper around the platform implementation of swapchain.
|
// A wrapper around the platform implementation of swapchain.
|
||||||
struct VulkanSwapChain : public HwSwapChain {
|
struct VulkanSwapChain : public HwSwapChain, VulkanResource {
|
||||||
VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& context, VmaAllocator allocator,
|
VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& context, VmaAllocator allocator,
|
||||||
VulkanCommands* commands, VulkanStagePool& stagePool,
|
VulkanCommands* commands, VulkanStagePool& stagePool,
|
||||||
void* nativeWindow, uint64_t flags, VkExtent2D extent = {0, 0});
|
void* nativeWindow, uint64_t flags, VkExtent2D extent = {0, 0});
|
||||||
|
|||||||
@@ -29,12 +29,16 @@ using namespace bluevk;
|
|||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
using ImgUtil = VulkanImageUtility;
|
using ImgUtil = VulkanImageUtility;
|
||||||
VulkanTexture::VulkanTexture(VkDevice device, VmaAllocator allocator,
|
VulkanTexture::VulkanTexture(VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
|
||||||
VulkanCommands* commands, VkImage image, VkFormat format, uint8_t samples,
|
VkImage image, VkFormat format, uint8_t samples, uint32_t width, uint32_t height,
|
||||||
uint32_t width, uint32_t height, TextureUsage tusage, VulkanStagePool& stagePool)
|
TextureUsage tusage, VulkanStagePool& stagePool, bool heapAllocated)
|
||||||
: HwTexture(SamplerType::SAMPLER_2D, 1, samples, width, height, 1, TextureFormat::UNUSED,
|
: HwTexture(SamplerType::SAMPLER_2D, 1, samples, width, height, 1, TextureFormat::UNUSED,
|
||||||
tusage),
|
tusage),
|
||||||
mVkFormat(format), mViewType(ImgUtil::getViewType(target)), mSwizzle({}),
|
VulkanResource(
|
||||||
|
heapAllocated ? VulkanResourceType::HEAP_ALLOCATED : VulkanResourceType::TEXTURE),
|
||||||
|
mVkFormat(format),
|
||||||
|
mViewType(ImgUtil::getViewType(target)),
|
||||||
|
mSwizzle({}),
|
||||||
mTextureImage(image),
|
mTextureImage(image),
|
||||||
mPrimaryViewRange{
|
mPrimaryViewRange{
|
||||||
.aspectMask = getImageAspect(),
|
.aspectMask = getImageAspect(),
|
||||||
@@ -43,19 +47,28 @@ VulkanTexture::VulkanTexture(VkDevice device, VmaAllocator allocator,
|
|||||||
.baseArrayLayer = 0,
|
.baseArrayLayer = 0,
|
||||||
.layerCount = 1,
|
.layerCount = 1,
|
||||||
},
|
},
|
||||||
mStagePool(stagePool), mDevice(device), mAllocator(allocator), mCommands(commands) {}
|
mStagePool(stagePool),
|
||||||
|
mDevice(device),
|
||||||
|
mAllocator(allocator),
|
||||||
|
mCommands(commands) {}
|
||||||
|
|
||||||
VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||||
VulkanContext const& context, VmaAllocator allocator,
|
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
|
||||||
VulkanCommands* commands, SamplerType target, uint8_t levels,
|
SamplerType target, uint8_t levels, TextureFormat tformat, uint8_t samples, uint32_t w,
|
||||||
TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
|
uint32_t h, uint32_t depth, TextureUsage tusage, VulkanStagePool& stagePool,
|
||||||
TextureUsage tusage, VulkanStagePool& stagePool, VkComponentMapping swizzle)
|
bool heapAllocated, VkComponentMapping swizzle)
|
||||||
: HwTexture(target, levels, samples, w, h, depth, tformat, tusage),
|
: HwTexture(target, levels, samples, w, h, depth, tformat, tusage),
|
||||||
|
VulkanResource(
|
||||||
|
heapAllocated ? VulkanResourceType::HEAP_ALLOCATED : VulkanResourceType::TEXTURE),
|
||||||
// Vulkan does not support 24-bit depth, use the official fallback format.
|
// Vulkan does not support 24-bit depth, use the official fallback format.
|
||||||
mVkFormat(tformat == TextureFormat::DEPTH24 ? context.getDepthFormat()
|
mVkFormat(tformat == TextureFormat::DEPTH24 ? context.getDepthFormat()
|
||||||
: backend::getVkFormat(tformat)),
|
: backend::getVkFormat(tformat)),
|
||||||
mViewType(ImgUtil::getViewType(target)), mSwizzle(swizzle), mStagePool(stagePool),
|
mViewType(ImgUtil::getViewType(target)),
|
||||||
mDevice(device), mAllocator(allocator), mCommands(commands) {
|
mSwizzle(swizzle),
|
||||||
|
mStagePool(stagePool),
|
||||||
|
mDevice(device),
|
||||||
|
mAllocator(allocator),
|
||||||
|
mCommands(commands) {
|
||||||
|
|
||||||
// Create an appropriately-sized device-only VkImage, but do not fill it yet.
|
// Create an appropriately-sized device-only VkImage, but do not fill it yet.
|
||||||
VkImageCreateInfo imageInfo{.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
VkImageCreateInfo imageInfo{.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||||
@@ -154,7 +167,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
|||||||
<< "handle = " << utils::io::hex << mTextureImage << utils::io::dec << ", "
|
<< "handle = " << utils::io::hex << mTextureImage << utils::io::dec << ", "
|
||||||
<< "extent = " << w << "x" << h << "x"<< depth << ", "
|
<< "extent = " << w << "x" << h << "x"<< depth << ", "
|
||||||
<< "mipLevels = " << int(levels) << ", "
|
<< "mipLevels = " << int(levels) << ", "
|
||||||
<< "TextureUsage = " << static_cast<int>(usage) << ", "
|
<< "TextureUsage = " << static_cast<int>(usage) << ", "
|
||||||
<< "usage = " << imageInfo.usage << ", "
|
<< "usage = " << imageInfo.usage << ", "
|
||||||
<< "samples = " << imageInfo.samples << ", "
|
<< "samples = " << imageInfo.samples << ", "
|
||||||
<< "type = " << imageInfo.imageType << ", "
|
<< "type = " << imageInfo.imageType << ", "
|
||||||
@@ -167,11 +180,17 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
|||||||
// Allocate memory for the VkImage and bind it.
|
// Allocate memory for the VkImage and bind it.
|
||||||
VkMemoryRequirements memReqs = {};
|
VkMemoryRequirements memReqs = {};
|
||||||
vkGetImageMemoryRequirements(mDevice, mTextureImage, &memReqs);
|
vkGetImageMemoryRequirements(mDevice, mTextureImage, &memReqs);
|
||||||
|
|
||||||
|
uint32_t memoryTypeIndex
|
||||||
|
= context.selectMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||||
|
|
||||||
|
ASSERT_POSTCONDITION(memoryTypeIndex < VK_MAX_MEMORY_TYPES,
|
||||||
|
"VulkanTexture: unable to find a memory type that meets requirements.");
|
||||||
|
|
||||||
VkMemoryAllocateInfo allocInfo = {
|
VkMemoryAllocateInfo allocInfo = {
|
||||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||||
.allocationSize = memReqs.size,
|
.allocationSize = memReqs.size,
|
||||||
.memoryTypeIndex = context.selectMemoryType(memReqs.memoryTypeBits,
|
.memoryTypeIndex = memoryTypeIndex,
|
||||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)
|
|
||||||
};
|
};
|
||||||
error = vkAllocateMemory(mDevice, &allocInfo, nullptr, &mTextureImageMemory);
|
error = vkAllocateMemory(mDevice, &allocInfo, nullptr, &mTextureImageMemory);
|
||||||
ASSERT_POSTCONDITION(!error, "Unable to allocate image memory.");
|
ASSERT_POSTCONDITION(!error, "Unable to allocate image memory.");
|
||||||
@@ -206,7 +225,11 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
|||||||
| VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)) {
|
| VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)) {
|
||||||
const uint32_t layers = mPrimaryViewRange.layerCount;
|
const uint32_t layers = mPrimaryViewRange.layerCount;
|
||||||
VkImageSubresourceRange range = { getImageAspect(), 0, levels, 0, layers };
|
VkImageSubresourceRange range = { getImageAspect(), 0, levels, 0, layers };
|
||||||
VkCommandBuffer cmdbuf = mCommands->get().cmdbuffer;
|
|
||||||
|
VulkanCommandBuffer& commands = mCommands->get();
|
||||||
|
VkCommandBuffer const cmdbuf = commands.cmdbuffer;
|
||||||
|
commands.acquire(this);
|
||||||
|
|
||||||
transitionLayout(cmdbuf, range, ImgUtil::getDefaultLayout(imageInfo.usage));
|
transitionLayout(cmdbuf, range, ImgUtil::getDefaultLayout(imageInfo.usage));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -256,7 +279,9 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
|
|||||||
vmaUnmapMemory(mAllocator, stage->memory);
|
vmaUnmapMemory(mAllocator, stage->memory);
|
||||||
vmaFlushAllocation(mAllocator, stage->memory, 0, hostData->size);
|
vmaFlushAllocation(mAllocator, stage->memory, 0, hostData->size);
|
||||||
|
|
||||||
const VkCommandBuffer cmdbuf = mCommands->get(true).cmdbuffer;
|
VulkanCommandBuffer& commands = mCommands->get();
|
||||||
|
VkCommandBuffer const cmdbuf = commands.cmdbuffer;
|
||||||
|
commands.acquire(this);
|
||||||
|
|
||||||
VkBufferImageCopy copyRegion = {
|
VkBufferImageCopy copyRegion = {
|
||||||
.bufferOffset = {},
|
.bufferOffset = {},
|
||||||
@@ -317,7 +342,9 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, u
|
|||||||
vmaUnmapMemory(mAllocator, stage->memory);
|
vmaUnmapMemory(mAllocator, stage->memory);
|
||||||
vmaFlushAllocation(mAllocator, stage->memory, 0, hostData.size);
|
vmaFlushAllocation(mAllocator, stage->memory, 0, hostData.size);
|
||||||
|
|
||||||
const VkCommandBuffer cmdbuf = mCommands->get().cmdbuffer;
|
VulkanCommandBuffer& commands = mCommands->get();
|
||||||
|
VkCommandBuffer const cmdbuf = commands.cmdbuffer;
|
||||||
|
commands.acquire(this);
|
||||||
|
|
||||||
// TODO: support blit-based format conversion for 3D images and cubemaps.
|
// TODO: support blit-based format conversion for 3D images and cubemaps.
|
||||||
const int layer = 0;
|
const int layer = 0;
|
||||||
|
|||||||
@@ -17,26 +17,29 @@
|
|||||||
#ifndef TNT_FILAMENT_BACKEND_VULKANTEXTURE_H
|
#ifndef TNT_FILAMENT_BACKEND_VULKANTEXTURE_H
|
||||||
#define TNT_FILAMENT_BACKEND_VULKANTEXTURE_H
|
#define TNT_FILAMENT_BACKEND_VULKANTEXTURE_H
|
||||||
|
|
||||||
#include "VulkanDriver.h"
|
#include "DriverBase.h"
|
||||||
|
|
||||||
#include "VulkanBuffer.h"
|
#include "VulkanBuffer.h"
|
||||||
|
#include "VulkanResources.h"
|
||||||
#include "VulkanImageUtility.h"
|
#include "VulkanImageUtility.h"
|
||||||
|
|
||||||
#include <utils/RangeMap.h>
|
#include <utils/RangeMap.h>
|
||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
struct VulkanTexture : public HwTexture {
|
struct VulkanTexture : public HwTexture, VulkanResource {
|
||||||
// Standard constructor for user-facing textures.
|
// Standard constructor for user-facing textures.
|
||||||
VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context,
|
VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context,
|
||||||
VmaAllocator allocator, VulkanCommands* commands, SamplerType target, uint8_t levels,
|
VmaAllocator allocator, VulkanCommands* commands, SamplerType target, uint8_t levels,
|
||||||
TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
|
TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
|
||||||
TextureUsage tusage, VulkanStagePool& stagePool, VkComponentMapping swizzle = {});
|
TextureUsage tusage, VulkanStagePool& stagePool, bool heapAllocated = false,
|
||||||
|
VkComponentMapping swizzle = {});
|
||||||
|
|
||||||
// Specialized constructor for internally created textures (e.g. from a swap chain)
|
// Specialized constructor for internally created textures (e.g. from a swap chain)
|
||||||
// The texture will never destroy the given VkImage, but it does manages its subresources.
|
// The texture will never destroy the given VkImage, but it does manages its subresources.
|
||||||
VulkanTexture(VkDevice device, VmaAllocator allocator, VulkanCommands* commands, VkImage image,
|
VulkanTexture(VkDevice device, VmaAllocator allocator, VulkanCommands* commands, VkImage image,
|
||||||
VkFormat format, uint8_t samples, uint32_t width, uint32_t height, TextureUsage tusage,
|
VkFormat format, uint8_t samples, uint32_t width, uint32_t height, TextureUsage tusage,
|
||||||
VulkanStagePool& stagePool);
|
VulkanStagePool& stagePool, bool heapAllocated = false);
|
||||||
|
|
||||||
~VulkanTexture();
|
~VulkanTexture();
|
||||||
|
|
||||||
|
|||||||
@@ -57,11 +57,17 @@ std::tuple<VkImage, VkDeviceMemory> createImageAndMemory(VulkanContext const& co
|
|||||||
VkDeviceMemory imageMemory;
|
VkDeviceMemory imageMemory;
|
||||||
VkMemoryRequirements memReqs;
|
VkMemoryRequirements memReqs;
|
||||||
vkGetImageMemoryRequirements(device, image, &memReqs);
|
vkGetImageMemoryRequirements(device, image, &memReqs);
|
||||||
|
|
||||||
|
uint32_t memoryTypeIndex
|
||||||
|
= context.selectMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||||
|
|
||||||
|
ASSERT_POSTCONDITION(memoryTypeIndex < VK_MAX_MEMORY_TYPES,
|
||||||
|
"VulkanPlatformSwapChainImpl: unable to find a memory type that meets requirements.");
|
||||||
|
|
||||||
VkMemoryAllocateInfo allocInfo = {
|
VkMemoryAllocateInfo allocInfo = {
|
||||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||||
.allocationSize = memReqs.size,
|
.allocationSize = memReqs.size,
|
||||||
.memoryTypeIndex
|
.memoryTypeIndex = memoryTypeIndex,
|
||||||
= context.selectMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT),
|
|
||||||
};
|
};
|
||||||
result = vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory);
|
result = vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory);
|
||||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to allocate image memory.");
|
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to allocate image memory.");
|
||||||
|
|||||||
@@ -85,13 +85,10 @@ void BackendTest::executeCommands() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BackendTest::flushAndWait(uint64_t timeout) {
|
void BackendTest::flushAndWait() {
|
||||||
auto& api = getDriverApi();
|
auto& api = getDriverApi();
|
||||||
auto fence = api.createFence();
|
|
||||||
api.finish();
|
api.finish();
|
||||||
executeCommands();
|
executeCommands();
|
||||||
api.wait(fence, timeout);
|
|
||||||
api.destroyFence(fence);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<HwSwapChain> BackendTest::createSwapChain() {
|
Handle<HwSwapChain> BackendTest::createSwapChain() {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ protected:
|
|||||||
|
|
||||||
void initializeDriver();
|
void initializeDriver();
|
||||||
void executeCommands();
|
void executeCommands();
|
||||||
void flushAndWait(uint64_t timeout = 1000);
|
void flushAndWait();
|
||||||
|
|
||||||
filament::backend::Handle<filament::backend::HwSwapChain> createSwapChain();
|
filament::backend::Handle<filament::backend::HwSwapChain> createSwapChain();
|
||||||
|
|
||||||
|
|||||||
@@ -87,11 +87,8 @@ void ComputeTest::executeCommands() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ComputeTest::flushAndWait(uint64_t timeout) {
|
void ComputeTest::flushAndWait() {
|
||||||
auto& api = getDriverApi();
|
auto& api = getDriverApi();
|
||||||
auto fence = api.createFence();
|
|
||||||
api.finish();
|
api.finish();
|
||||||
executeCommands();
|
executeCommands();
|
||||||
api.wait(fence, timeout);
|
|
||||||
api.destroyFence(fence);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ protected:
|
|||||||
void TearDown() override;
|
void TearDown() override;
|
||||||
|
|
||||||
void executeCommands();
|
void executeCommands();
|
||||||
void flushAndWait(uint64_t timeout = 1000);
|
void flushAndWait();
|
||||||
filament::backend::DriverApi& getDriverApi() { return *commandStream; }
|
filament::backend::DriverApi& getDriverApi() { return *commandStream; }
|
||||||
filament::backend::Driver& getDriver() { return *driver; }
|
filament::backend::Driver& getDriver() { return *driver; }
|
||||||
|
|
||||||
|
|||||||
@@ -34,10 +34,13 @@ class Entity;
|
|||||||
namespace filament {
|
namespace filament {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Camera represents the eye through which the scene is viewed.
|
* Camera represents the eye(s) through which the scene is viewed.
|
||||||
*
|
*
|
||||||
* A Camera has a position and orientation and controls the projection and exposure parameters.
|
* A Camera has a position and orientation and controls the projection and exposure parameters.
|
||||||
*
|
*
|
||||||
|
* For stereoscopic rendering, a Camera maintains two separate "eyes": Eye 0 and Eye 1. These are
|
||||||
|
* arbitrary and don't necessarily need to correspond to "left" and "right".
|
||||||
|
*
|
||||||
* Creation and destruction
|
* Creation and destruction
|
||||||
* ========================
|
* ========================
|
||||||
*
|
*
|
||||||
@@ -140,6 +143,18 @@ namespace filament {
|
|||||||
* intensity and the Camera exposure interact to produce the final scene's brightness.
|
* intensity and the Camera exposure interact to produce the final scene's brightness.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
|
* Stereoscopic rendering
|
||||||
|
* ======================
|
||||||
|
*
|
||||||
|
* The Camera's transform (as set by setModelMatrix or via TransformManager) defines a "head" space,
|
||||||
|
* which typically corresponds to the location of the viewer's head. Each eye's transform is set
|
||||||
|
* relative to this head space by setEyeModelMatrix.
|
||||||
|
*
|
||||||
|
* Each eye also maintains its own projection matrix. These can be set with setCustomEyeProjection.
|
||||||
|
* Care must be taken to correctly set the projectionForCulling matrix, as well as its corresponding
|
||||||
|
* near and far values. The projectionForCulling matrix must define a frustum (in head space) that
|
||||||
|
* bounds the frustums of both eyes. Alternatively, culling may be disabled with
|
||||||
|
* View::setFrustumCullingEnabled.
|
||||||
*
|
*
|
||||||
* \see Frustum, View
|
* \see Frustum, View
|
||||||
*/
|
*/
|
||||||
@@ -234,6 +249,24 @@ public:
|
|||||||
*/
|
*/
|
||||||
void setCustomProjection(math::mat4 const& projection, double near, double far) noexcept;
|
void setCustomProjection(math::mat4 const& projection, double near, double far) noexcept;
|
||||||
|
|
||||||
|
/** Sets a custom projection matrix for each eye.
|
||||||
|
*
|
||||||
|
* The projectionForCulling, near, and far parameters establish a "culling frustum" which must
|
||||||
|
* encompass anything either eye can see.
|
||||||
|
*
|
||||||
|
* @param projection an array of projection matrices, only the first
|
||||||
|
* CONFIG_STEREOSCOPIC_EYES (2) are read
|
||||||
|
* @param count size of the projection matrix array to set, must be
|
||||||
|
* >= CONFIG_STEREOSCOPIC_EYES (2)
|
||||||
|
* @param projectionForCulling custom projection matrix for culling, must encompass both eyes
|
||||||
|
* @param near distance in world units from the camera to the culling near plane. \p near > 0.
|
||||||
|
* @param far distance in world units from the camera to the culling far plane. \p far > \p
|
||||||
|
* near.
|
||||||
|
* @see setCustomProjection
|
||||||
|
*/
|
||||||
|
void setCustomEyeProjection(math::mat4 const* projection, size_t count,
|
||||||
|
math::mat4 const& projectionForCulling, double near, double far);
|
||||||
|
|
||||||
/** Sets the projection matrix.
|
/** Sets the projection matrix.
|
||||||
*
|
*
|
||||||
* The projection matrices must be of one of the following form:
|
* The projection matrices must be of one of the following form:
|
||||||
@@ -309,11 +342,14 @@ public:
|
|||||||
* The projection matrix used for rendering always has its far plane set to infinity. This
|
* The projection matrix used for rendering always has its far plane set to infinity. This
|
||||||
* is why it may differ from the matrix set through setProjection() or setLensProjection().
|
* is why it may differ from the matrix set through setProjection() or setLensProjection().
|
||||||
*
|
*
|
||||||
|
* @param eyeId the index of the eye to return the projection matrix for, must be <
|
||||||
|
* CONFIG_STEREOSCOPIC_EYES (2)
|
||||||
* @return The projection matrix used for rendering
|
* @return The projection matrix used for rendering
|
||||||
*
|
*
|
||||||
* @see setProjection, setLensProjection, setCustomProjection, getCullingProjectionMatrix
|
* @see setProjection, setLensProjection, setCustomProjection, getCullingProjectionMatrix,
|
||||||
|
* setCustomEyeProjection
|
||||||
*/
|
*/
|
||||||
math::mat4 getProjectionMatrix() const noexcept;
|
math::mat4 getProjectionMatrix(uint8_t eyeId = 0) const;
|
||||||
|
|
||||||
|
|
||||||
/** Returns the projection matrix used for culling (far plane is finite).
|
/** Returns the projection matrix used for culling (far plane is finite).
|
||||||
@@ -350,6 +386,26 @@ public:
|
|||||||
void setModelMatrix(const math::mat4& model) noexcept;
|
void setModelMatrix(const math::mat4& model) noexcept;
|
||||||
void setModelMatrix(const math::mat4f& model) noexcept; //!< @overload
|
void setModelMatrix(const math::mat4f& model) noexcept; //!< @overload
|
||||||
|
|
||||||
|
/** Set the position of an eye relative to this Camera (head).
|
||||||
|
*
|
||||||
|
* By default, both eyes' model matrices are identity matrices.
|
||||||
|
*
|
||||||
|
* For example, to position Eye 0 3cm leftwards and Eye 1 3cm rightwards:
|
||||||
|
* ~~~~~~~~~~~{.cpp}
|
||||||
|
* const mat4 leftEye = mat4::translation(double3{-0.03, 0.0, 0.0});
|
||||||
|
* const mat4 rightEye = mat4::translation(double3{ 0.03, 0.0, 0.0});
|
||||||
|
* camera.setEyeModelMatrix(0, leftEye);
|
||||||
|
* camera.setEyeModelMatrix(1, rightEye);
|
||||||
|
* ~~~~~~~~~~~
|
||||||
|
*
|
||||||
|
* This method is not intended to be called every frame. Instead, to update the position of the
|
||||||
|
* head, use Camera::setModelMatrix.
|
||||||
|
*
|
||||||
|
* @param eyeId the index of the eye to set, must be < CONFIG_STEREOSCOPIC_EYES (2)
|
||||||
|
* @param model the model matrix for an individual eye
|
||||||
|
*/
|
||||||
|
void setEyeModelMatrix(uint8_t eyeId, math::mat4 const& model);
|
||||||
|
|
||||||
/** Sets the camera's model matrix
|
/** Sets the camera's model matrix
|
||||||
*
|
*
|
||||||
* @param eye The position of the camera in world space.
|
* @param eye The position of the camera in world space.
|
||||||
@@ -448,7 +504,9 @@ public:
|
|||||||
//! returns this camera's sensitivity in ISO
|
//! returns this camera's sensitivity in ISO
|
||||||
float getSensitivity() const noexcept;
|
float getSensitivity() const noexcept;
|
||||||
|
|
||||||
//! returns the focal length in meters [m] for a 35mm camera
|
/** Returns the focal length in meters [m] for a 35mm camera.
|
||||||
|
* Eye 0's projection matrix is used to compute the focal length.
|
||||||
|
*/
|
||||||
double getFocalLength() const noexcept;
|
double getFocalLength() const noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -513,6 +513,14 @@ public:
|
|||||||
*/
|
*/
|
||||||
size_t getMaxAutomaticInstances() const noexcept;
|
size_t getMaxAutomaticInstances() const noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries the device and platform for instanced stereo rendering support.
|
||||||
|
*
|
||||||
|
* @return true if stereo rendering is supported, false otherwise
|
||||||
|
* @see View::setStereoscopicOptions
|
||||||
|
*/
|
||||||
|
bool isStereoSupported() const noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return EntityManager used by filament
|
* @return EntityManager used by filament
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -28,11 +28,7 @@
|
|||||||
namespace filament {
|
namespace filament {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fence is used to synchronize rendering operations together, with the CPU or with compute.
|
* Fence is used to synchronize the application main thread with filament's rendering thread.
|
||||||
*
|
|
||||||
* \note
|
|
||||||
* Currently Fence only provide client-side synchronization.
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
class UTILS_PUBLIC Fence : public FilamentAPI {
|
class UTILS_PUBLIC Fence : public FilamentAPI {
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -180,6 +180,11 @@ public:
|
|||||||
* is guaranteed to be invalid (either because it's been destroyed by the user already, or,
|
* is guaranteed to be invalid (either because it's been destroyed by the user already, or,
|
||||||
* because it's been cleaned-up by the Engine).
|
* because it's been cleaned-up by the Engine).
|
||||||
*
|
*
|
||||||
|
* UserVariantFilterMask::ALL should be used with caution. Only variants that an application
|
||||||
|
* needs should be included in the variants argument. For example, the STE variant is only used
|
||||||
|
* for stereoscopic rendering. If an application is not planning to render in stereo, this bit
|
||||||
|
* should be turned off to avoid unnecessary material compilations.
|
||||||
|
*
|
||||||
* @param priority Which priority queue to use, LOW or HIGH.
|
* @param priority Which priority queue to use, LOW or HIGH.
|
||||||
* @param variants Variants to include to the compile command.
|
* @param variants Variants to include to the compile command.
|
||||||
* @param handler Handler to dispatch the callback or nullptr for the default handler
|
* @param handler Handler to dispatch the callback or nullptr for the default handler
|
||||||
|
|||||||
@@ -133,9 +133,9 @@ struct BloomOptions {
|
|||||||
Texture* dirt = nullptr; //!< user provided dirt texture %codegen_skip_json% %codegen_skip_javascript%
|
Texture* dirt = nullptr; //!< user provided dirt texture %codegen_skip_json% %codegen_skip_javascript%
|
||||||
float dirtStrength = 0.2f; //!< strength of the dirt texture %codegen_skip_json% %codegen_skip_javascript%
|
float dirtStrength = 0.2f; //!< strength of the dirt texture %codegen_skip_json% %codegen_skip_javascript%
|
||||||
float strength = 0.10f; //!< bloom's strength between 0.0 and 1.0
|
float strength = 0.10f; //!< bloom's strength between 0.0 and 1.0
|
||||||
uint32_t resolution = 360; //!< resolution of vertical axis (2^levels to 2048)
|
uint32_t resolution = 384; //!< resolution of vertical axis (2^levels to 2048)
|
||||||
float anamorphism = 1.0f; //!< bloom x/y aspect-ratio (1/32 to 32)
|
float anamorphism = 1.0f; //!< bloom x/y aspect-ratio (1/32 to 32)
|
||||||
uint8_t levels = 6; //!< number of blur levels (3 to 11)
|
uint8_t levels = 6; //!< number of blur levels (1 to 11)
|
||||||
BlendMode blendMode = BlendMode::ADD; //!< how the bloom effect is applied
|
BlendMode blendMode = BlendMode::ADD; //!< how the bloom effect is applied
|
||||||
bool threshold = true; //!< whether to threshold the source
|
bool threshold = true; //!< whether to threshold the source
|
||||||
bool enabled = false; //!< enable or disable bloom
|
bool enabled = false; //!< enable or disable bloom
|
||||||
@@ -541,6 +541,13 @@ struct SoftShadowOptions {
|
|||||||
float penumbraRatioScale = 1.0f;
|
float penumbraRatioScale = 1.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for stereoscopic (multi-eye) rendering.
|
||||||
|
*/
|
||||||
|
struct StereoscopicOptions {
|
||||||
|
bool enabled = false;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace filament
|
} // namespace filament
|
||||||
|
|
||||||
#endif //TNT_FILAMENT_OPTIONS_H
|
#endif //TNT_FILAMENT_OPTIONS_H
|
||||||
|
|||||||
@@ -91,8 +91,6 @@ public:
|
|||||||
/**
|
/**
|
||||||
* Sets a texture to a given attachment point.
|
* Sets a texture to a given attachment point.
|
||||||
*
|
*
|
||||||
* All RenderTargets must have a non-null COLOR attachment.
|
|
||||||
*
|
|
||||||
* When using a DEPTH attachment, it is important to always disable post-processing
|
* When using a DEPTH attachment, it is important to always disable post-processing
|
||||||
* in the View. Failing to do so will cause the DEPTH attachment to be ignored in most
|
* in the View. Failing to do so will cause the DEPTH attachment to be ignored in most
|
||||||
* cases.
|
* cases.
|
||||||
|
|||||||
@@ -18,10 +18,13 @@
|
|||||||
#define TNT_FILAMENT_SWAPCHAIN_H
|
#define TNT_FILAMENT_SWAPCHAIN_H
|
||||||
|
|
||||||
#include <filament/FilamentAPI.h>
|
#include <filament/FilamentAPI.h>
|
||||||
|
|
||||||
|
#include <backend/CallbackHandler.h>
|
||||||
#include <backend/DriverEnums.h>
|
#include <backend/DriverEnums.h>
|
||||||
#include <backend/PresentCallable.h>
|
#include <backend/PresentCallable.h>
|
||||||
|
|
||||||
#include <utils/compiler.h>
|
#include <utils/compiler.h>
|
||||||
|
#include <utils/Invocable.h>
|
||||||
|
|
||||||
namespace filament {
|
namespace filament {
|
||||||
|
|
||||||
@@ -148,7 +151,7 @@ class Engine;
|
|||||||
class UTILS_PUBLIC SwapChain : public FilamentAPI {
|
class UTILS_PUBLIC SwapChain : public FilamentAPI {
|
||||||
public:
|
public:
|
||||||
using FrameScheduledCallback = backend::FrameScheduledCallback;
|
using FrameScheduledCallback = backend::FrameScheduledCallback;
|
||||||
using FrameCompletedCallback = backend::FrameCompletedCallback;
|
using FrameCompletedCallback = utils::Invocable<void(SwapChain*)>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests a SwapChain with an alpha channel.
|
* Requests a SwapChain with an alpha channel.
|
||||||
@@ -241,17 +244,23 @@ public:
|
|||||||
* contents have completed rendering on the GPU.
|
* contents have completed rendering on the GPU.
|
||||||
*
|
*
|
||||||
* Use SwapChain::setFrameCompletedCallback to set a callback on an individual SwapChain. Each
|
* Use SwapChain::setFrameCompletedCallback to set a callback on an individual SwapChain. Each
|
||||||
* time a frame completes GPU rendering, the callback will be called with optional user data.
|
* time a frame completes GPU rendering, the callback will be called.
|
||||||
*
|
*
|
||||||
* The FrameCompletedCallback is guaranteed to be called on the main Filament thread.
|
* If handler is nullptr, the callback is guaranteed to be called on the main Filament thread.
|
||||||
*
|
*
|
||||||
* @param callback A callback, or nullptr to unset.
|
* Use \c setFrameCompletedCallback() (with default arguments) to unset the callback.
|
||||||
* @param user An optional pointer to user data passed to the callback function.
|
*
|
||||||
|
* @param handler Handler to dispatch the callback or nullptr for the default handler.
|
||||||
|
* @param callback Callback called when each frame completes.
|
||||||
*
|
*
|
||||||
* @remark Only Filament's Metal backend supports frame callbacks. Other backends ignore the
|
* @remark Only Filament's Metal backend supports frame callbacks. Other backends ignore the
|
||||||
* callback (which will never be called) and proceed normally.
|
* callback (which will never be called) and proceed normally.
|
||||||
|
*
|
||||||
|
* @see CallbackHandler
|
||||||
*/
|
*/
|
||||||
void setFrameCompletedCallback(FrameCompletedCallback callback, void* user = nullptr);
|
void setFrameCompletedCallback(backend::CallbackHandler* handler = nullptr,
|
||||||
|
FrameCompletedCallback&& callback = {}) noexcept;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace filament
|
} // namespace filament
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
TextureSampler() noexcept = default;
|
TextureSampler() noexcept = default;
|
||||||
|
|
||||||
|
explicit TextureSampler(backend::SamplerParams params) noexcept : mSamplerParams(params) { }
|
||||||
|
|
||||||
TextureSampler(const TextureSampler& rhs) noexcept = default;
|
TextureSampler(const TextureSampler& rhs) noexcept = default;
|
||||||
TextureSampler& operator=(const TextureSampler& rhs) noexcept = default;
|
TextureSampler& operator=(const TextureSampler& rhs) noexcept = default;
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ public:
|
|||||||
using SoftShadowOptions = SoftShadowOptions;
|
using SoftShadowOptions = SoftShadowOptions;
|
||||||
using ScreenSpaceReflectionsOptions = ScreenSpaceReflectionsOptions;
|
using ScreenSpaceReflectionsOptions = ScreenSpaceReflectionsOptions;
|
||||||
using GuardBandOptions = GuardBandOptions;
|
using GuardBandOptions = GuardBandOptions;
|
||||||
|
using StereoscopicOptions = StereoscopicOptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the View's name. Only useful for debugging.
|
* Sets the View's name. Only useful for debugging.
|
||||||
@@ -676,6 +677,32 @@ public:
|
|||||||
*/
|
*/
|
||||||
bool isStencilBufferEnabled() const noexcept;
|
bool isStencilBufferEnabled() const noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the stereoscopic rendering options for this view.
|
||||||
|
*
|
||||||
|
* Currently, only one type of stereoscopic rendering is supported: side-by-side.
|
||||||
|
* Side-by-side stereo rendering splits the viewport into two halves: a left and right half.
|
||||||
|
* Eye 0 will render to the left half, while Eye 1 will render into the right half.
|
||||||
|
*
|
||||||
|
* Currently, the following features are not supported with stereoscopic rendering:
|
||||||
|
* - post-processing
|
||||||
|
* - shadowing
|
||||||
|
* - punctual lights
|
||||||
|
*
|
||||||
|
* Stereo rendering depends on device and platform support. To check if stereo rendering is
|
||||||
|
* supported, use Engine::isStereoSupported().
|
||||||
|
*
|
||||||
|
* @param options The stereoscopic options to use on this view
|
||||||
|
*/
|
||||||
|
void setStereoscopicOptions(StereoscopicOptions const& options);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the stereoscopic options associated with this View.
|
||||||
|
*
|
||||||
|
* @return value set by setStereoscopicOptions().
|
||||||
|
*/
|
||||||
|
StereoscopicOptions const& getStereoscopicOptions() const noexcept;
|
||||||
|
|
||||||
// for debugging...
|
// for debugging...
|
||||||
|
|
||||||
//! debugging: allows to entirely disable frustum culling. (culling enabled by default).
|
//! debugging: allows to entirely disable frustum culling. (culling enabled by default).
|
||||||
|
|||||||
+11
-2
@@ -67,6 +67,15 @@ mat4 Camera::inverseProjection(const mat4 & p) noexcept {
|
|||||||
return filament::inverseProjection(p);
|
return filament::inverseProjection(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Camera::setEyeModelMatrix(uint8_t eyeId, math::mat4 const& model) {
|
||||||
|
downcast(this)->setEyeModelMatrix(eyeId, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Camera::setCustomEyeProjection(math::mat4 const* projection, size_t count,
|
||||||
|
math::mat4 const& projectionForCulling, double near, double far) {
|
||||||
|
downcast(this)->setCustomEyeProjection(projection, count, projectionForCulling, near, far);
|
||||||
|
}
|
||||||
|
|
||||||
void Camera::setProjection(Camera::Projection projection, double left, double right, double bottom,
|
void Camera::setProjection(Camera::Projection projection, double left, double right, double bottom,
|
||||||
double top, double near, double far) {
|
double top, double near, double far) {
|
||||||
downcast(this)->setProjection(projection, left, right, bottom, top, near, far);
|
downcast(this)->setProjection(projection, left, right, bottom, top, near, far);
|
||||||
@@ -99,8 +108,8 @@ void Camera::setShift(double2 shift) noexcept {
|
|||||||
downcast(this)->setShift(shift);
|
downcast(this)->setShift(shift);
|
||||||
}
|
}
|
||||||
|
|
||||||
mat4 Camera::getProjectionMatrix() const noexcept {
|
mat4 Camera::getProjectionMatrix(uint8_t eyeId) const {
|
||||||
return downcast(this)->getUserProjectionMatrix();
|
return downcast(this)->getUserProjectionMatrix(eyeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
mat4 Camera::getCullingProjectionMatrix() const noexcept {
|
mat4 Camera::getCullingProjectionMatrix() const noexcept {
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ void Engine::destroyCameraComponent(utils::Entity entity) noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Fence* Engine::createFence() noexcept {
|
Fence* Engine::createFence() noexcept {
|
||||||
return downcast(this)->createFence(FFence::Type::SOFT);
|
return downcast(this)->createFence();
|
||||||
}
|
}
|
||||||
|
|
||||||
SwapChain* Engine::createSwapChain(void* nativeWindow, uint64_t flags) noexcept {
|
SwapChain* Engine::createSwapChain(void* nativeWindow, uint64_t flags) noexcept {
|
||||||
@@ -327,6 +327,10 @@ size_t Engine::getMaxAutomaticInstances() const noexcept {
|
|||||||
return downcast(this)->getMaxAutomaticInstances();
|
return downcast(this)->getMaxAutomaticInstances();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Engine::isStereoSupported() const noexcept {
|
||||||
|
return downcast(this)->isStereoSupported();
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(__EMSCRIPTEN__)
|
#if defined(__EMSCRIPTEN__)
|
||||||
void Engine::resetBackendState() noexcept {
|
void Engine::resetBackendState() noexcept {
|
||||||
downcast(this)->resetBackendState();
|
downcast(this)->resetBackendState();
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ bool FrameSkipper::beginFrame(DriverApi& driver) noexcept {
|
|||||||
auto& fences = mDelayedFences;
|
auto& fences = mDelayedFences;
|
||||||
auto fence = fences.front();
|
auto fence = fences.front();
|
||||||
if (fence) {
|
if (fence) {
|
||||||
auto status = driver.wait(fence, 0);
|
auto status = driver.getFenceStatus(fence);
|
||||||
if (status == FenceStatus::TIMEOUT_EXPIRED) {
|
if (status == FenceStatus::TIMEOUT_EXPIRED) {
|
||||||
// Sync not ready, skip frame
|
// Sync not ready, skip frame
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -81,6 +81,21 @@ struct Froxelizer::FroxelThreadData :
|
|||||||
public std::array<LightGroupType, FROXEL_BUFFER_MAX_ENTRY_COUNT> {
|
public std::array<LightGroupType, FROXEL_BUFFER_MAX_ENTRY_COUNT> {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Returns false if the two matrices are different. May return false if they're the
|
||||||
|
// same, with some elements only differing by +0 or -0. Behaviour is undefined with NaNs.
|
||||||
|
static bool fuzzyEqual(mat4f const& UTILS_RESTRICT l, mat4f const& UTILS_RESTRICT r) noexcept {
|
||||||
|
auto const li = reinterpret_cast<uint32_t const*>( reinterpret_cast<char const*>(&l) );
|
||||||
|
auto const ri = reinterpret_cast<uint32_t const*>( reinterpret_cast<char const*>(&r) );
|
||||||
|
uint32_t result = 0;
|
||||||
|
for (size_t i = 0; i < sizeof(mat4f) / sizeof(uint32_t); i++) {
|
||||||
|
// clang fully vectorizes this
|
||||||
|
result |= li[i] ^ ri[i];
|
||||||
|
}
|
||||||
|
return result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Froxelizer::Froxelizer(FEngine& engine)
|
Froxelizer::Froxelizer(FEngine& engine)
|
||||||
: mArena("froxel", PER_FROXELDATA_ARENA_SIZE),
|
: mArena("froxel", PER_FROXELDATA_ARENA_SIZE),
|
||||||
mZLightNear(FROXEL_FIRST_SLICE_DEPTH),
|
mZLightNear(FROXEL_FIRST_SLICE_DEPTH),
|
||||||
@@ -144,9 +159,8 @@ void Froxelizer::setViewport(filament::Viewport const& viewport) noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Froxelizer::setProjection(const mat4f& projection,
|
void Froxelizer::setProjection(const mat4f& projection,
|
||||||
float near,
|
float near, UTILS_UNUSED float far) noexcept {
|
||||||
UTILS_UNUSED float far) noexcept {
|
if (UTILS_UNLIKELY(!fuzzyEqual(mProjection, projection))) {
|
||||||
if (UTILS_UNLIKELY(mat4f::fuzzyEqual(mProjection, projection))) {
|
|
||||||
mProjection = projection;
|
mProjection = projection;
|
||||||
mNear = near;
|
mNear = near;
|
||||||
mDirtyFlags |= PROJECTION_CHANGED;
|
mDirtyFlags |= PROJECTION_CHANGED;
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ void PerShadowMapUniforms::prepareCamera(Transaction const& transaction,
|
|||||||
s.worldFromViewMatrix = worldFromView; // model
|
s.worldFromViewMatrix = worldFromView; // model
|
||||||
s.clipFromViewMatrix = clipFromView; // projection
|
s.clipFromViewMatrix = clipFromView; // projection
|
||||||
s.viewFromClipMatrix = viewFromClip; // 1/projection
|
s.viewFromClipMatrix = viewFromClip; // 1/projection
|
||||||
s.clipFromWorldMatrix = clipFromWorld; // projection * view
|
s.clipFromWorldMatrix[0] = clipFromWorld; // projection * view
|
||||||
s.worldFromClipMatrix = worldFromClip; // 1/(projection * view)
|
s.worldFromClipMatrix = worldFromClip; // 1/(projection * view)
|
||||||
s.userWorldFromWorldMatrix = mat4f(inverse(camera.worldOrigin));
|
s.userWorldFromWorldMatrix = mat4f(inverse(camera.worldOrigin));
|
||||||
s.clipTransform = camera.clipTransfrom;
|
s.clipTransform = camera.clipTransfrom;
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ void PerViewUniforms::prepareCamera(FEngine& engine, const CameraInfo& camera) n
|
|||||||
mat4f const& clipFromView = camera.projection;
|
mat4f const& clipFromView = camera.projection;
|
||||||
|
|
||||||
const mat4f viewFromClip{ inverse((mat4)camera.projection) };
|
const mat4f viewFromClip{ inverse((mat4)camera.projection) };
|
||||||
const mat4f clipFromWorld{ highPrecisionMultiply(clipFromView, viewFromWorld) };
|
|
||||||
const mat4f worldFromClip{ highPrecisionMultiply(worldFromView, viewFromClip) };
|
const mat4f worldFromClip{ highPrecisionMultiply(worldFromView, viewFromClip) };
|
||||||
|
|
||||||
auto& s = mUniforms.edit();
|
auto& s = mUniforms.edit();
|
||||||
@@ -74,7 +73,6 @@ void PerViewUniforms::prepareCamera(FEngine& engine, const CameraInfo& camera) n
|
|||||||
s.worldFromViewMatrix = worldFromView; // model
|
s.worldFromViewMatrix = worldFromView; // model
|
||||||
s.clipFromViewMatrix = clipFromView; // projection
|
s.clipFromViewMatrix = clipFromView; // projection
|
||||||
s.viewFromClipMatrix = viewFromClip; // 1/projection
|
s.viewFromClipMatrix = viewFromClip; // 1/projection
|
||||||
s.clipFromWorldMatrix = clipFromWorld; // projection * view
|
|
||||||
s.worldFromClipMatrix = worldFromClip; // 1/(projection * view)
|
s.worldFromClipMatrix = worldFromClip; // 1/(projection * view)
|
||||||
s.userWorldFromWorldMatrix = mat4f(inverse(camera.worldOrigin));
|
s.userWorldFromWorldMatrix = mat4f(inverse(camera.worldOrigin));
|
||||||
s.clipTransform = camera.clipTransfrom;
|
s.clipTransform = camera.clipTransfrom;
|
||||||
@@ -82,6 +80,15 @@ void PerViewUniforms::prepareCamera(FEngine& engine, const CameraInfo& camera) n
|
|||||||
s.oneOverFarMinusNear = 1.0f / (camera.zf - camera.zn);
|
s.oneOverFarMinusNear = 1.0f / (camera.zf - camera.zn);
|
||||||
s.nearOverFarMinusNear = camera.zn / (camera.zf - camera.zn);
|
s.nearOverFarMinusNear = camera.zn / (camera.zf - camera.zn);
|
||||||
|
|
||||||
|
mat4f const& headFromWorld = camera.view;
|
||||||
|
for (uint8_t i = 0; i < CONFIG_STEREOSCOPIC_EYES; i++) {
|
||||||
|
mat4f const& eyeFromHead = camera.eyeFromView[i]; // identity for monoscopic rendering
|
||||||
|
mat4f const& clipFromEye = camera.eyeProjection[i];
|
||||||
|
// clipFromEye * eyeFromHead * headFromWorld
|
||||||
|
s.clipFromWorldMatrix[i] = highPrecisionMultiply(
|
||||||
|
clipFromEye, highPrecisionMultiply(eyeFromHead, headFromWorld));
|
||||||
|
}
|
||||||
|
|
||||||
// with a clip-space of [-w, w] ==> z' = -z
|
// with a clip-space of [-w, w] ==> z' = -z
|
||||||
// with a clip-space of [0, w] ==> z' = (w - z)/2
|
// with a clip-space of [0, w] ==> z' = (w - z)/2
|
||||||
s.clipControl = engine.getDriverApi().getClipSpaceParams();
|
s.clipControl = engine.getDriverApi().getClipSpaceParams();
|
||||||
|
|||||||
@@ -2132,7 +2132,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::flarePass(FrameGraph& fg,
|
|||||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
||||||
});
|
});
|
||||||
|
|
||||||
mi->setParameter("level", 1.0f); // adjust with resolution
|
mi->setParameter("level", 0.0f); // adjust with resolution
|
||||||
mi->setParameter("aspectRatio", float2{ aspectRatio, 1.0f / aspectRatio });
|
mi->setParameter("aspectRatio", float2{ aspectRatio, 1.0f / aspectRatio });
|
||||||
mi->setParameter("threshold",
|
mi->setParameter("threshold",
|
||||||
float2{ bloomOptions.ghostThreshold, bloomOptions.haloThreshold });
|
float2{ bloomOptions.ghostThreshold, bloomOptions.haloThreshold });
|
||||||
|
|||||||
@@ -458,6 +458,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(uint32_t extraFlags,
|
|||||||
|
|
||||||
const bool hasShadowing = renderFlags & HAS_SHADOWING;
|
const bool hasShadowing = renderFlags & HAS_SHADOWING;
|
||||||
const bool viewInverseFrontFaces = renderFlags & HAS_INVERSE_FRONT_FACES;
|
const bool viewInverseFrontFaces = renderFlags & HAS_INVERSE_FRONT_FACES;
|
||||||
|
const bool hasInstancedStereo = renderFlags & IS_STEREOSCOPIC;
|
||||||
|
|
||||||
Command cmdColor;
|
Command cmdColor;
|
||||||
|
|
||||||
@@ -522,6 +523,15 @@ RenderPass::Command* RenderPass::generateCommandsImpl(uint32_t extraFlags,
|
|||||||
soaInstanceInfo[i].count | PrimitiveInfo::USER_INSTANCE_MASK;
|
soaInstanceInfo[i].count | PrimitiveInfo::USER_INSTANCE_MASK;
|
||||||
cmdColor.primitive.instanceBufferHandle = soaInstanceInfo[i].handle;
|
cmdColor.primitive.instanceBufferHandle = soaInstanceInfo[i].handle;
|
||||||
|
|
||||||
|
// soaInstanceInfo[i].count is the number of instances the user has requested, either for
|
||||||
|
// manual or hybrid instancing. Instanced stereo multiplies the number of instances by the
|
||||||
|
// eye count.
|
||||||
|
if (UTILS_UNLIKELY(hasInstancedStereo)) {
|
||||||
|
cmdColor.primitive.instanceCount =
|
||||||
|
(soaInstanceInfo[i].count * CONFIG_STEREOSCOPIC_EYES) |
|
||||||
|
PrimitiveInfo::USER_INSTANCE_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
// if we are already an SSR variant, the SRE bit is already set,
|
// if we are already an SSR variant, the SRE bit is already set,
|
||||||
// there is no harm setting it again
|
// there is no harm setting it again
|
||||||
static_assert(Variant::SPECIAL_SSR & Variant::SRE);
|
static_assert(Variant::SPECIAL_SSR & Variant::SRE);
|
||||||
@@ -541,6 +551,12 @@ RenderPass::Command* RenderPass::generateCommandsImpl(uint32_t extraFlags,
|
|||||||
cmdDepth.primitive.instanceBufferHandle = soaInstanceInfo[i].handle;
|
cmdDepth.primitive.instanceBufferHandle = soaInstanceInfo[i].handle;
|
||||||
cmdDepth.primitive.materialVariant.setSkinning(hasSkinningOrMorphing);
|
cmdDepth.primitive.materialVariant.setSkinning(hasSkinningOrMorphing);
|
||||||
cmdDepth.primitive.rasterState.inverseFrontFaces = inverseFrontFaces;
|
cmdDepth.primitive.rasterState.inverseFrontFaces = inverseFrontFaces;
|
||||||
|
|
||||||
|
if (UTILS_UNLIKELY(hasInstancedStereo)) {
|
||||||
|
cmdColor.primitive.instanceCount =
|
||||||
|
(soaInstanceInfo[i].count * CONFIG_STEREOSCOPIC_EYES) |
|
||||||
|
PrimitiveInfo::USER_INSTANCE_MASK;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if constexpr (isColorPass) {
|
if constexpr (isColorPass) {
|
||||||
renderableVariant.setFog(soaVisibility[i].fog && Variant::isFogVariant(variant));
|
renderableVariant.setFog(soaVisibility[i].fog && Variant::isFogVariant(variant));
|
||||||
|
|||||||
@@ -264,6 +264,7 @@ public:
|
|||||||
using RenderFlags = uint8_t;
|
using RenderFlags = uint8_t;
|
||||||
static constexpr RenderFlags HAS_SHADOWING = 0x01;
|
static constexpr RenderFlags HAS_SHADOWING = 0x01;
|
||||||
static constexpr RenderFlags HAS_INVERSE_FRONT_FACES = 0x02;
|
static constexpr RenderFlags HAS_INVERSE_FRONT_FACES = 0x02;
|
||||||
|
static constexpr RenderFlags IS_STEREOSCOPIC = 0x04;
|
||||||
|
|
||||||
// Arena used for commands
|
// Arena used for commands
|
||||||
using Arena = utils::Arena<
|
using Arena = utils::Arena<
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user