Compare commits

...
Author SHA1 Message Date
Benjamin Doherty 031cd302dd Capture command callstacks for debugging 2023-10-30 14:42:24 -07:00
Benjamin Doherty 87351097ad Adjust NEW_RELEASE_NOTES to reflect cherry-picks 2023-08-30 16:30:10 -07:00
Ben Doherty 694766682f Update FrameCompletedCallback using directive (#7128) 2023-08-30 16:26:15 -07:00
Ben Doherty c946ebd1e6 Make destroyFence asynchronous (#7127) 2023-08-30 16:22:37 -07:00
Romain Guy 25a8291101 Don't force masked blending for transmission/volume materials (#7126)
* Don't force masked blending for transmission/volume materials

glTF lets you choose your own alpha mode when using the transmission
and volume material extensions. We were forcing the masked mode which
was incorrect, except to pass the standard tests.

* Update release notes
2023-08-30 13:34:49 -07:00
Mathias Agopian 60c689688d attempt to fix remote ui (#7120)
fixes #7116
2023-08-30 08:46:53 -07:00
Romain Guy 763bc1f34a Fix possible NPE when updating fog options (#7123) 2023-08-30 08:44:39 -07:00
Romain Guy ef07638eef Properly apply emissive to masked materials (#7122)
* Properly apply emissive to masked materials

The emissive property should not be multiplied by the color alpha
in masked materials. The alpha is treated as a coverage value in
that case, not an opacity value.

* Update release notes
2023-08-30 08:43:36 -07:00
Ben Doherty 0aa0efe159 Transition setFrameCompletedCallback to take a CallbackHandler (#7103) 2023-08-28 10:27:38 -07:00
Powei Feng ef7bcd1e19 vulkan: refactor resource garbage collection (#7110) 2023-08-28 10:20:37 -07:00
Powei Feng 702ceda82a vulkan: fix debug marker pop (#7112) 2023-08-25 21:27:30 -07:00
Mathias Agopian 66b78074de Revert "workaround another PowerVR compiler bug "
This reverts commit 58f96be2c4.

This caused material files to increase in size significantly. It turns
out that glslang has to generate a copy for each parameter that is
passed to a function as a non-const parameter.


This revert will break IMG devices again, but that should be the case
only on debug builds. Release builds lose the const qualifier by 
virtue of going through spirv. We'll try to address this some other 
way later.
2023-08-25 15:31:00 -07:00
Mathias Agopian 8d440cea17 Update/Improve ViewerGUI
- separate out the settings for bloom, ssao and ssr
- update webgl binaries

- change default bloom resolution to 384 from 360 to have up to 7 
mipmap levels vertically
2023-08-25 09:53:52 -07:00
Mathias Agopian 3ab8e4d725 fix lenseflare effect
we were accessing an uninitialized LOD.
2023-08-25 09:53:33 -07:00
Mathias Agopian 97f20afdd7 remove the anonymous union in SamplerParams
- don't rely on it being 32-bits
- update the jni code to store SamplerParams in a long (64 bits)
  instead of a int. This gives us some future-proofing of the java side.
2023-08-24 21:36:42 -07:00
Mathias Agopian 42989e76d7 fix possible NPE crasher in timerquery
fixes #7106
2023-08-24 21:36:21 -07:00
Powei Feng ad45cc9092 Release Filament 1.42.0 2023-08-22 13:36:35 -07:00
Ben Doherty 04669f6ab9 Add Engine query for stereoscopic support (#7086) 2023-08-22 12:41:56 -07:00
Powei Feng c3c0dde82f vulkan: fix crashing Pixel 4xl adreno (#7087)
Adreno doesn't seem to like defining the size of arrays using a
`const int`.
2023-08-22 12:36:45 -07:00
Powei Feng ecd5b681d0 Update MaterialEnums.h (#7098) 2023-08-21 10:49:44 -07:00
Romain Guy 66081e6cc1 Add fields used by JNI to proguard rules (#7096) 2023-08-21 10:39:34 -07:00
Jacob Su aa6e94a128 Fix Mat cofactor UT error on Mac M2 chip machine. 2023-08-18 10:28:51 -07:00
Mathias AgopianandPowei Feng 098be2e115 rework how we initialize the gl context (#7085)
* rework how we initialize the gl context

- early initialization is now implemented with static methods so that
  it's very clear which state they need.

- the version number is no longer used outside of initialization,
  instead we use the feature level.

- ES3.0 Adreno devices are downgraded to feature level 0

* Update filament/backend/src/opengl/OpenGLContext.cpp

Co-authored-by: Powei Feng <powei@google.com>

---------

Co-authored-by: Powei Feng <powei@google.com>
2023-08-18 10:25:10 -07:00
Mathias Agopian 17caf6cae9 improvements to CompilerThreadPool and OpenGLPlatform
CompilerThreadPool:
- it now supports a thread cleanup function
- some initialization is moved to the setup function

OpenGLPlatform:
- now cleans-up the thread pool threads upon exit
2023-08-17 20:11:57 -07:00
Mathias Agopian 26952631a3 only attempt to compile shaders in parallel if supported
It can be extremely counter productive to attempt to do this if not
supported.
2023-08-17 20:10:57 -07:00
Mathias Agopian fc7b6447b7 make sure to not assert when matdbg is enabled 2023-08-17 20:09:55 -07:00
Powei Feng 6c0db37919 vulkan: fix readPixels selectMemory (#7084)
readPixels requests staging memory to be host-visible/coherent/cached.
But "cached" is not supported on Mali (Pixel 6pro).  We make it a
preferrable but optional bit.
2023-08-17 15:19:43 -07:00
Mathias Agopian 69f78dbcbe better fix for calls to eglMakeCurrent
turns out that KHR_surfaceless_context is implied for ES3.0 when 
KHR_create_context is present. However, Adreno 306 fails even if
it advertises it. So, we now reset the value of KHR_surfaceless_context
based on actually calling eglMakeCurrent(EGL_NO_SURFACE).
2023-08-16 22:46:36 -07:00
Mathias Agopian c0389ac54c rework ShaderCompilerService to improve performance
- remove support for non-shared contextes parallel compilation.
  this wasn't used. we can always revive it later if we need to.

- rework how callbacks work so that we don't have to use a work list
  executed at each tick() in the shared context case (common case).
  this improves performance significantly on low-end devices, by
  not having to go through the list to check if all programs are
  compiled, multiple times per frame.

The new CallbackManager handles scheduling the callbacks after all
previous programs are compiled.
2023-08-16 22:46:13 -07:00
Mathias Agopian c0db909c13 don't use eglMakeCurrent with EGL_NO_SURFACE unless we're allowed
EGL_KHR_surfaceless_context is needed to be able to use eglMakeCurrent
without an EGLSurface.
2023-08-16 21:35:29 -07:00
Ben Doherty 46e4e966b9 Fix assert with matdbg enabled (#7079) 2023-08-16 14:23:24 -07:00
Powei Feng 288b59a348 Fix missing createFence (#7076)
Continuing from #7072
2023-08-16 12:18:15 -07:00
Mathias Agopian 1c7293db8d fix fuzzyEqual
- the return value was inverted
- fuzzyEqual could generate alignment faults
- move it out of mat4 and mat2 because it was
  only used in one place.
2023-08-16 10:40:13 -07:00
161 changed files with 2956 additions and 1845 deletions
+6
View File
@@ -7,3 +7,9 @@ for next branch cut* header.
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut
- Fix possible NPE when updating fog options from Java/Kotlin
- The `emissive` property was not applied properly to `MASKED` materials, and could cause
dark fringes to appear (recompile materials)
- Allow glTF materials with transmission/volume extensions to choose their alpha mode
instead of forcing `MASKED`
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.41.0'
implementation 'com.google.android.filament:filament-android:1.42.0'
}
```
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```
pod 'Filament', '~> 1.41.0'
pod 'Filament', '~> 1.42.0'
```
### Snapshots
+2
View File
@@ -7,6 +7,8 @@ A new header is inserted each time a *tag* is created.
Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.42.1
## v1.42.0
- engine: add preliminary support for instanced stereoscopic rendering [⚠️ **Recompile materials**]
@@ -246,17 +246,21 @@ Java_com_google_android_filament_MaterialInstance_nSetFloatParameterArray(JNIEnv
env->ReleaseStringUTFChars(name_, name);
}
// defined in TextureSampler.cpp
namespace filament::JniUtils {
TextureSampler from_long(jlong params) noexcept;
} // TextureSamplerJniUtils
extern "C"
JNIEXPORT void JNICALL
Java_com_google_android_filament_MaterialInstance_nSetParameterTexture(
JNIEnv *env, jclass, jlong nativeMaterialInstance, jstring name_,
jlong nativeTexture, jint sampler_) {
jlong nativeTexture, jlong sampler_) {
MaterialInstance* instance = (MaterialInstance*) nativeMaterialInstance;
Texture* texture = (Texture*) nativeTexture;
TextureSampler& sampler = reinterpret_cast<TextureSampler&>(sampler_);
const char *name = env->GetStringUTFChars(name_, 0);
instance->setParameter(name, texture, sampler);
instance->setParameter(name, texture, JniUtils::from_long(sampler_));
env->ReleaseStringUTFChars(name_, name);
}
@@ -27,11 +27,10 @@ extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_SwapChain_nSetFrameCompletedCallback(JNIEnv* env, jclass,
jlong nativeSwapChain, jobject handler, jobject runnable) {
SwapChain* swapChain = (SwapChain*) nativeSwapChain;
auto *callback = JniCallback::make(env, handler, runnable);
swapChain->setFrameCompletedCallback([](void* user) {
JniCallback* callback = (JniCallback*)user;
auto* callback = JniCallback::make(env, handler, runnable);
swapChain->setFrameCompletedCallback(nullptr, [callback](SwapChain* swapChain) {
JniCallback::postToJavaAndDestroy(callback);
}, callback);
});
}
extern "C" JNIEXPORT jboolean JNICALL
@@ -18,142 +18,139 @@
#include <filament/TextureSampler.h>
#include <utils/algorithm.h>
using namespace filament;
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_TextureSampler_nCreateSampler(JNIEnv *env, jclass type, jint min,
namespace filament::JniUtils {
jlong to_long(TextureSampler const& sampler) noexcept {
return jlong(utils::bit_cast<uint32_t>(sampler.getSamplerParams()));
}
TextureSampler from_long(jlong params) noexcept {
return TextureSampler{
utils::bit_cast<backend::SamplerParams>(
static_cast<uint32_t>(params))};
}
} // namespace filament::JniUtils
using namespace JniUtils;
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_TextureSampler_nCreateSampler(JNIEnv *, jclass, jint min,
jint max, jint s, jint t, jint r) {
return TextureSampler(static_cast<TextureSampler::MinFilter>(min),
static_cast<TextureSampler::MagFilter>(max), static_cast<TextureSampler::WrapMode>(s),
static_cast<TextureSampler::WrapMode>(t),
static_cast<TextureSampler::WrapMode>(r)).getSamplerParams().u;
TextureSampler sampler(static_cast<TextureSampler::MinFilter>(min),
static_cast<TextureSampler::MagFilter>(max),
static_cast<TextureSampler::WrapMode>(s),
static_cast<TextureSampler::WrapMode>(t),
static_cast<TextureSampler::WrapMode>(r));
return to_long(sampler);
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_TextureSampler_nCreateCompareSampler(JNIEnv *env, jclass type,
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_TextureSampler_nCreateCompareSampler(JNIEnv *, jclass,
jint mode, jint function) {
return TextureSampler(static_cast<TextureSampler::CompareMode>(mode),
static_cast<TextureSampler::CompareFunc>(function)).getSamplerParams().u;
TextureSampler sampler(static_cast<TextureSampler::CompareMode>(mode),
static_cast<TextureSampler::CompareFunc>(function));
return to_long(sampler);
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_TextureSampler_nGetMinFilter(JNIEnv *env, jclass type,
jint sampler_) {
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
return static_cast<jint>(sampler.getMinFilter());
Java_com_google_android_filament_TextureSampler_nGetMinFilter(JNIEnv *, jclass, jlong sampler) {
return static_cast<jint>(from_long(sampler).getMinFilter());
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_TextureSampler_nSetMinFilter(JNIEnv *env, jclass type,
jint sampler_, jint filter) {
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_TextureSampler_nSetMinFilter(JNIEnv *, jclass, jlong sampler_, jint filter) {
TextureSampler sampler{from_long(sampler_)};
sampler.setMinFilter(static_cast<TextureSampler::MinFilter>(filter));
return sampler.getSamplerParams().u;
return to_long(sampler);
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_TextureSampler_nGetMagFilter(JNIEnv *env, jclass type,
jint sampler_) {
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
return static_cast<jint>(sampler.getMagFilter());
Java_com_google_android_filament_TextureSampler_nGetMagFilter(JNIEnv *, jclass, jlong sampler) {
return static_cast<jint>(from_long(sampler).getMagFilter());
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_TextureSampler_nSetMagFilter(JNIEnv *env, jclass type,
jint sampler_, jint filter) {
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_TextureSampler_nSetMagFilter(JNIEnv *, jclass, jlong sampler_, jint filter) {
TextureSampler sampler{from_long(sampler_)};
sampler.setMagFilter(static_cast<TextureSampler::MagFilter>(filter));
return sampler.getSamplerParams().u;
return to_long(sampler);
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_TextureSampler_nGetWrapModeS(JNIEnv *env, jclass type,
jint sampler_) {
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
return static_cast<jint>(sampler.getWrapModeS());
Java_com_google_android_filament_TextureSampler_nGetWrapModeS(JNIEnv *, jclass, jlong sampler) {
return static_cast<jint>(from_long(sampler).getWrapModeS());
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_TextureSampler_nSetWrapModeS(JNIEnv *env, jclass type,
jint sampler_, jint mode) {
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_TextureSampler_nSetWrapModeS(JNIEnv *, jclass, jlong sampler_, jint mode) {
TextureSampler sampler{from_long(sampler_)};
sampler.setWrapModeS(static_cast<TextureSampler::WrapMode>(mode));
return sampler.getSamplerParams().u;
return to_long(sampler);
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_TextureSampler_nGetWrapModeT(JNIEnv *env, jclass type,
jint sampler_) {
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
return static_cast<jint>(sampler.getWrapModeT());
Java_com_google_android_filament_TextureSampler_nGetWrapModeT(JNIEnv *, jclass, jlong sampler) {
return static_cast<jint>(from_long(sampler).getWrapModeT());
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_TextureSampler_nSetWrapModeT(JNIEnv *env, jclass type,
jint sampler_, jint mode) {
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_TextureSampler_nSetWrapModeT(JNIEnv *, jclass, jlong sampler_, jint mode) {
TextureSampler sampler{from_long(sampler_)};
sampler.setWrapModeT(static_cast<TextureSampler::WrapMode>(mode));
return sampler.getSamplerParams().u;
return to_long(sampler);
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_TextureSampler_nGetWrapModeR(JNIEnv *env, jclass type,
jint sampler_) {
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
return static_cast<jint>(sampler.getWrapModeR());
Java_com_google_android_filament_TextureSampler_nGetWrapModeR(JNIEnv *, jclass, jlong sampler) {
return static_cast<jint>(from_long(sampler).getWrapModeR());
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_TextureSampler_nSetWrapModeR(JNIEnv *env, jclass type,
jint sampler_, jint mode) {
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_TextureSampler_nSetWrapModeR(JNIEnv *, jclass, jlong sampler_, jint mode) {
TextureSampler sampler{from_long(sampler_)};
sampler.setWrapModeR(static_cast<TextureSampler::WrapMode>(mode));
return sampler.getSamplerParams().u;
return to_long(sampler);
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_TextureSampler_nGetCompareMode(JNIEnv *env, jclass type,
jint sampler_) {
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
return static_cast<jint>(sampler.getCompareMode());
Java_com_google_android_filament_TextureSampler_nGetCompareMode(JNIEnv *, jclass, jlong sampler) {
return static_cast<jint>(from_long(sampler).getCompareMode());
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_TextureSampler_nSetCompareMode(JNIEnv *env, jclass type,
jint sampler_, jint mode) {
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_TextureSampler_nSetCompareMode(JNIEnv *, jclass, jlong sampler_, jint mode) {
TextureSampler sampler{from_long(sampler_)};
sampler.setCompareMode(static_cast<TextureSampler::CompareMode>(mode),
sampler.getCompareFunc());
return sampler.getSamplerParams().u;
return to_long(sampler);
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_TextureSampler_nGetCompareFunction(JNIEnv *env, jclass type,
jint sampler_) {
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
return static_cast<jint>(sampler.getCompareFunc());
Java_com_google_android_filament_TextureSampler_nGetCompareFunction(JNIEnv *, jclass, jlong sampler) {
return static_cast<jint>(from_long(sampler).getCompareFunc());
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_TextureSampler_nSetCompareFunction(JNIEnv *env, jclass type,
jint sampler_, jint function) {
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_TextureSampler_nSetCompareFunction(JNIEnv *, jclass, jlong sampler_, jint function) {
TextureSampler sampler{from_long(sampler_)};
sampler.setCompareMode(sampler.getCompareMode(),
static_cast<TextureSampler::CompareFunc>(function));
return sampler.getSamplerParams().u;
return to_long(sampler);
}
extern "C" JNIEXPORT jfloat JNICALL
Java_com_google_android_filament_TextureSampler_nGetAnisotropy(JNIEnv *env, jclass type,
jint sampler_) {
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
return sampler.getAnisotropy();
Java_com_google_android_filament_TextureSampler_nGetAnisotropy(JNIEnv *, jclass, jlong sampler) {
return from_long(sampler).getAnisotropy();
}
extern "C" JNIEXPORT jint JNICALL
Java_com_google_android_filament_TextureSampler_nSetAnisotropy(JNIEnv *env, jclass type,
jint sampler_, jfloat anisotropy) {
TextureSampler &sampler = reinterpret_cast<TextureSampler &>(sampler_);
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_TextureSampler_nSetAnisotropy(JNIEnv *, jclass, jlong sampler_, jfloat anisotropy) {
TextureSampler sampler{from_long(sampler_)};
sampler.setAnisotropy(anisotropy);
return sampler.getSamplerParams().u;
return to_long(sampler);
}
@@ -900,7 +900,7 @@ public class MaterialInstance {
@IntRange(from = 0) int offset, @IntRange(from = 1) int count);
private static native void nSetParameterTexture(long nativeMaterialInstance,
@NonNull String name, long nativeTexture, int sampler);
@NonNull String name, long nativeTexture, long sampler);
private static native void nSetScissor(long nativeMaterialInstance,
@IntRange(from = 0) int left, @IntRange(from = 0) int bottom,
@@ -137,10 +137,6 @@ public class SwapChain {
* </p>
*
* <p>
* The FrameCompletedCallback is guaranteed to be called on the main Filament thread.
* </p>
*
* <p>
* Warning: Only Filament's Metal backend supports frame callbacks. Other backends ignore the
* callback (which will never be called) and proceed normally.
* </p>
@@ -126,7 +126,7 @@ public class TextureSampler {
NEVER
}
int mSampler = 0; // bit field used by native
long mSampler = 0; // bit field used by native
/**
* Initializes the <code>TextureSampler</code> with default values.
@@ -342,26 +342,26 @@ public class TextureSampler {
}
}
private static native int nCreateSampler(int min, int max, int s, int t, int r);
private static native int nCreateCompareSampler(int mode, int function);
private static native long nCreateSampler(int min, int max, int s, int t, int r);
private static native long nCreateCompareSampler(int mode, int function);
private static native int nGetMinFilter(int sampler);
private static native int nSetMinFilter(int sampler, int filter);
private static native int nGetMagFilter(int sampler);
private static native int nSetMagFilter(int sampler, int filter);
private static native int nGetMinFilter(long sampler);
private static native long nSetMinFilter(long sampler, int filter);
private static native int nGetMagFilter(long sampler);
private static native long nSetMagFilter(long sampler, int filter);
private static native int nGetWrapModeS(int sampler);
private static native int nSetWrapModeS(int sampler, int mode);
private static native int nGetWrapModeT(int sampler);
private static native int nSetWrapModeT(int sampler, int mode);
private static native int nGetWrapModeR(int sampler);
private static native int nSetWrapModeR(int sampler, int mode);
private static native int nGetWrapModeS(long sampler);
private static native long nSetWrapModeS(long sampler, int mode);
private static native int nGetWrapModeT(long sampler);
private static native long nSetWrapModeT(long sampler, int mode);
private static native int nGetWrapModeR(long sampler);
private static native long nSetWrapModeR(long sampler, int mode);
private static native int nGetCompareMode(int sampler);
private static native int nSetCompareMode(int sampler, int mode);
private static native int nGetCompareFunction(int sampler);
private static native int nSetCompareFunction(int sampler, int function);
private static native int nGetCompareMode(long sampler);
private static native long nSetCompareMode(long sampler, int mode);
private static native int nGetCompareFunction(long sampler);
private static native long nSetCompareFunction(long sampler, int function);
private static native float nGetAnisotropy(int sampler);
private static native int nSetAnisotropy(int sampler, float anisotropy);
private static native float nGetAnisotropy(long sampler);
private static native long nSetAnisotropy(long sampler, float anisotropy);
}
@@ -27,6 +27,8 @@ import static com.google.android.filament.Asserts.assertFloat3In;
import static com.google.android.filament.Asserts.assertFloat4In;
import static com.google.android.filament.Colors.LinearColor;
import com.google.android.filament.proguard.UsedByNative;
/**
* Encompasses all the state needed for rendering a {@link Scene}.
*
@@ -965,7 +967,8 @@ public class View {
options.heightFalloff, options.cutOffDistance,
options.color[0], options.color[1], options.color[2],
options.density, options.inScatteringStart, options.inScatteringSize,
options.fogColorFromIbl, options.skyColor.getNativeObject(),
options.fogColorFromIbl,
options.skyColor == null ? 0 : options.skyColor.getNativeObject(),
options.enabled);
}
@@ -1095,10 +1098,29 @@ public class View {
nPick(getNativeObject(), x, y, handler, internalCallback);
}
@UsedByNative("View.cpp")
private static class InternalOnPickCallback implements Runnable {
private final OnPickCallback mUserCallback;
private final PickingQueryResult mPickingQueryResult = new PickingQueryResult();
@UsedByNative("View.cpp")
@Entity
int mRenderable;
@UsedByNative("View.cpp")
float mDepth;
@UsedByNative("View.cpp")
float mFragCoordsX;
@UsedByNative("View.cpp")
float mFragCoordsY;
@UsedByNative("View.cpp")
float mFragCoordsZ;
public InternalOnPickCallback(OnPickCallback mUserCallback) {
this.mUserCallback = mUserCallback;
}
@Override
public void run() {
mPickingQueryResult.renderable = mRenderable;
@@ -1108,13 +1130,6 @@ public class View {
mPickingQueryResult.fragCoords[2] = mFragCoordsZ;
mUserCallback.onPick(mPickingQueryResult);
}
private final OnPickCallback mUserCallback;
private final PickingQueryResult mPickingQueryResult = new PickingQueryResult();
@Entity int mRenderable;
float mDepth;
float mFragCoordsX;
float mFragCoordsY;
float mFragCoordsZ;
}
/**
@@ -1377,13 +1392,13 @@ public class View {
/**
* resolution of vertical axis (2^levels to 2048)
*/
public int resolution = 360;
public int resolution = 384;
/**
* bloom x/y aspect-ratio (1/32 to 32)
*/
public float anamorphism = 1.0f;
/**
* number of blur levels (3 to 11)
* number of blur levels (1 to 11)
*/
public int levels = 6;
/**
@@ -1971,4 +1986,11 @@ public class View {
*/
public float penumbraRatioScale = 1.0f;
}
/**
* Options for stereoscopic (multi-eye) rendering.
*/
public static class StereoscopicOptions {
public boolean enabled = false;
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.41.0
VERSION_NAME=1.42.0
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
@@ -118,9 +118,10 @@ class MainActivity : Activity() {
}
private fun setupView() {
val ssaoOptions = view.ambientOcclusionOptions
ssaoOptions.enabled = true
view.ambientOcclusionOptions = ssaoOptions
// ambient occlusion is the cheapest effect that adds a lot of quality
view.ambientOcclusionOptions = view.ambientOcclusionOptions.apply {
enabled = true
}
// NOTE: Try to disable post-processing (tone-mapping, etc.) to see the difference
// view.isPostProcessingEnabled = false
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.
+5 -3
View File
@@ -27,7 +27,6 @@ set(SRCS
src/BackendUtils.cpp
src/BlobCacheKey.cpp
src/Callable.cpp
src/CallbackHandler.cpp
src/CircularBuffer.cpp
src/CommandBufferQueue.cpp
src/CommandStream.cpp
@@ -68,6 +67,8 @@ set(PRIVATE_HDRS
if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3 AND NOT FILAMENT_USE_SWIFTSHADER)
list(APPEND SRCS
include/backend/platforms/OpenGLPlatform.h
src/opengl/CallbackManager.h
src/opengl/CallbackManager.cpp
src/opengl/gl_headers.cpp
src/opengl/gl_headers.h
src/opengl/GLUtils.cpp
@@ -176,8 +177,6 @@ if (FILAMENT_SUPPORTS_VULKAN)
src/vulkan/VulkanConstants.h
src/vulkan/VulkanContext.cpp
src/vulkan/VulkanContext.h
src/vulkan/VulkanDisposer.cpp
src/vulkan/VulkanDisposer.h
src/vulkan/VulkanDriver.cpp
src/vulkan/VulkanDriver.h
src/vulkan/VulkanDriverFactory.h
@@ -199,6 +198,9 @@ if (FILAMENT_SUPPORTS_VULKAN)
src/vulkan/VulkanSwapChain.h
src/vulkan/VulkanReadPixels.cpp
src/vulkan/VulkanReadPixels.h
src/vulkan/VulkanResourceAllocator.h
src/vulkan/VulkanResources.cpp
src/vulkan/VulkanResources.h
src/vulkan/VulkanTexture.cpp
src/vulkan/VulkanTexture.h
src/vulkan/VulkanUtility.cpp
@@ -66,7 +66,7 @@ public:
virtual void post(void* user, Callback callback) = 0;
protected:
virtual ~CallbackHandler();
virtual ~CallbackHandler() = default;
};
} // namespace filament::backend
+39 -20
View File
@@ -796,32 +796,53 @@ enum class SamplerCompareFunc : uint8_t {
//! Sampler parameters
struct SamplerParams { // NOLINT
union {
struct {
SamplerMagFilter filterMag : 1; //!< magnification filter (NEAREST)
SamplerMinFilter filterMin : 3; //!< minification filter (NEAREST)
SamplerWrapMode wrapS : 2; //!< s-coordinate wrap mode (CLAMP_TO_EDGE)
SamplerWrapMode wrapT : 2; //!< t-coordinate wrap mode (CLAMP_TO_EDGE)
SamplerMagFilter filterMag : 1; //!< magnification filter (NEAREST)
SamplerMinFilter filterMin : 3; //!< minification filter (NEAREST)
SamplerWrapMode wrapS : 2; //!< s-coordinate wrap mode (CLAMP_TO_EDGE)
SamplerWrapMode wrapT : 2; //!< t-coordinate wrap mode (CLAMP_TO_EDGE)
SamplerWrapMode wrapR : 2; //!< r-coordinate wrap mode (CLAMP_TO_EDGE)
uint8_t anisotropyLog2 : 3; //!< anisotropy level (0)
SamplerCompareMode compareMode : 1; //!< sampler compare mode (NONE)
uint8_t padding0 : 2; //!< reserved. must be 0.
SamplerWrapMode wrapR : 2; //!< r-coordinate wrap mode (CLAMP_TO_EDGE)
uint8_t anisotropyLog2 : 3; //!< anisotropy level (0)
SamplerCompareMode compareMode : 1; //!< sampler compare mode (NONE)
uint8_t padding0 : 2; //!< reserved. must be 0.
SamplerCompareFunc compareFunc : 3; //!< sampler comparison function (LE)
uint8_t padding1 : 5; //!< reserved. must be 0.
SamplerCompareFunc compareFunc : 3; //!< sampler comparison function (LE)
uint8_t padding1 : 5; //!< reserved. must be 0.
uint8_t padding2 : 8; //!< reserved. must be 0.
uint8_t padding2 : 8; //!< reserved. must be 0.
};
uint32_t u;
struct Hasher {
size_t operator()(SamplerParams p) const noexcept {
// we don't use std::hash<> here, so we don't have to include <functional>
return *reinterpret_cast<uint64_t const*>(reinterpret_cast<char const*>(&p));
}
};
struct EqualTo {
bool operator()(SamplerParams lhs, SamplerParams rhs) const noexcept {
auto* pLhs = reinterpret_cast<uint64_t const*>(reinterpret_cast<char const*>(&lhs));
auto* pRhs = reinterpret_cast<uint64_t const*>(reinterpret_cast<char const*>(&rhs));
return *pLhs == *pRhs;
}
};
struct LessThan {
bool operator()(SamplerParams lhs, SamplerParams rhs) const noexcept {
auto* pLhs = reinterpret_cast<uint64_t const*>(reinterpret_cast<char const*>(&lhs));
auto* pRhs = reinterpret_cast<uint64_t const*>(reinterpret_cast<char const*>(&rhs));
return *pLhs == *pRhs;
}
};
private:
friend inline bool operator < (SamplerParams lhs, SamplerParams rhs) {
return lhs.u < rhs.u;
friend inline bool operator < (SamplerParams lhs, SamplerParams rhs) noexcept {
return SamplerParams::LessThan{}(lhs, rhs);
}
};
static_assert(sizeof(SamplerParams) == sizeof(uint32_t), "SamplerParams must be 32 bits");
// The limitation to 64-bits max comes from how we store a SamplerParams in our JNI code
// see android/.../TextureSampler.cpp
static_assert(sizeof(SamplerParams) <= sizeof(uint64_t),
"SamplerParams must be no more than 64 bits");
//! blending equation function
enum class BlendEquation : uint8_t {
@@ -1126,8 +1147,6 @@ static_assert(sizeof(StencilState) == 12u,
using FrameScheduledCallback = void(*)(PresentCallable callable, void* user);
using FrameCompletedCallback = void(*)(void* user);
enum class Workaround : uint16_t {
// The EASU pass must split because shader compiler flattens early-exit branch
SPLIT_EASU,
@@ -288,6 +288,12 @@ public:
* @see terminate()
*/
virtual void createContext(bool shared);
/**
* Detach and destroy the current context if any and releases all resources associated to
* this thread.
*/
virtual void releaseContext() noexcept;
};
} // namespace filament
@@ -40,6 +40,7 @@ public:
PlatformEGL() noexcept;
bool isExtraContextSupported() const noexcept override;
void createContext(bool shared) override;
void releaseContext() noexcept override;
protected:
@@ -139,6 +140,7 @@ protected:
bool KHR_create_context = false;
bool KHR_gl_colorspace = false;
bool KHR_no_config_context = false;
bool KHR_surfaceless_context = false;
} egl;
} ext;
@@ -73,14 +73,32 @@ public:
// a cost here (writing and reading the stack at each iteration), in the end it's
// probably better to pay the cost at just one location.
intptr_t next;
driver.mCurrentExecutingCommand = this;
mExecute(driver, this, &next);
return reinterpret_cast<CommandBase*>(reinterpret_cast<intptr_t>(this) + next);
}
inline void captureCallstack() noexcept {
auto c = utils::CallStack::unwind(4);
size_t i = 0;
for (; i < c.getFrameCount() && i < 16; i++) {
mCallstack[i] = c[i];
}
for (; i < 16; i++) {
mCallstack[i] = 0;
}
}
void printCallstack() noexcept {
auto c = utils::CallStack(mCallstack);
utils::slog.d << c << utils::io::endl;
}
inline ~CommandBase() noexcept = default;
private:
Execute mExecute;
std::array<intptr_t, 16> mCallstack = {0};
};
// ------------------------------------------------------------------------------------------------
@@ -218,6 +236,7 @@ public:
using Cmd = COMMAND_TYPE(methodName); \
void* const p = allocateCommand(CommandBase::align(sizeof(Cmd))); \
new(p) Cmd(mDispatcher.methodName##_, APPLY(std::move, params)); \
((Cmd*)p)->captureCallstack(); \
DEBUG_COMMAND_END(methodName, false); \
}
@@ -237,6 +256,7 @@ public:
using Cmd = COMMAND_TYPE(methodName##R); \
void* const p = allocateCommand(CommandBase::align(sizeof(Cmd))); \
new(p) Cmd(mDispatcher.methodName##_, RetType(result), APPLY(std::move, params)); \
((Cmd*)p)->captureCallstack(); \
DEBUG_COMMAND_END(methodName, false); \
return result; \
}
@@ -53,6 +53,7 @@ template<typename T>
class ConcreteDispatcher;
class Dispatcher;
class CommandStream;
class CommandBase;
class Driver {
public:
@@ -83,6 +84,8 @@ public:
virtual void debugCommandEnd(CommandStream* cmds,
bool synchronous, const char* methodName) noexcept = 0;
CommandBase* mCurrentExecutingCommand = nullptr;
/*
* Asynchronous calls here only to provide a type to CommandStream. They must be non-virtual
* so that calling the concrete implementation won't go through a vtable.
@@ -142,7 +142,8 @@ DECL_DRIVER_API_N(setFrameScheduledCallback,
DECL_DRIVER_API_N(setFrameCompletedCallback,
backend::SwapChainHandle, sch,
backend::FrameCompletedCallback, callback,
backend::CallbackHandler*, handler,
backend::CallbackHandler::Callback, callback,
void*, user)
DECL_DRIVER_API_N(setPresentationTime,
@@ -273,6 +274,7 @@ DECL_DRIVER_API_N(destroyRenderTarget, backend::RenderTargetHandle, rth)
DECL_DRIVER_API_N(destroySwapChain, backend::SwapChainHandle, sch)
DECL_DRIVER_API_N(destroyStream, backend::StreamHandle, sh)
DECL_DRIVER_API_N(destroyTimerQuery, backend::TimerQueryHandle, sh)
DECL_DRIVER_API_N(destroyFence, backend::FenceHandle, fh)
/*
* Synchronous APIs
@@ -286,7 +288,6 @@ DECL_DRIVER_API_SYNCHRONOUS_N(void, setAcquiredImage, backend::StreamHandle, str
DECL_DRIVER_API_SYNCHRONOUS_N(void, setStreamDimensions, backend::StreamHandle, stream, uint32_t, width, uint32_t, height)
DECL_DRIVER_API_SYNCHRONOUS_N(int64_t, getStreamTimestamp, backend::StreamHandle, stream)
DECL_DRIVER_API_SYNCHRONOUS_N(void, updateStreams, backend::DriverApi*, driver)
DECL_DRIVER_API_SYNCHRONOUS_N(void, destroyFence, backend::FenceHandle, fh)
DECL_DRIVER_API_SYNCHRONOUS_N(backend::FenceStatus, getFenceStatus, backend::FenceHandle, fh)
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isTextureFormatSupported, backend::TextureFormat, format)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isTextureSwizzleSupported)
@@ -297,6 +298,8 @@ DECL_DRIVER_API_SYNCHRONOUS_0(bool, isFrameBufferFetchMultiSampleSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isFrameTimeSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isAutoDepthResolveSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isSRGBSwapChainSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isStereoSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isParallelShaderCompileSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(uint8_t, getMaxDrawBuffers)
DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getMaxUniformBufferSize)
DECL_DRIVER_API_SYNCHRONOUS_0(math::float2, getClipSpaceParams)
-23
View File
@@ -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
+14 -8
View File
@@ -16,6 +16,8 @@
#include "CompilerThreadPool.h"
#include <utils/Systrace.h>
#include <memory>
namespace filament::backend {
@@ -32,16 +34,14 @@ CompilerThreadPool::~CompilerThreadPool() noexcept {
assert_invariant(mQueues[1].empty());
}
void CompilerThreadPool::init(uint32_t threadCount, JobSystem::Priority priority,
ThreadSetup&& threadSetup) noexcept {
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, priority, setup]() {
// give the thread a name
JobSystem::setThreadName("CompilerThreadPool");
// run at a slightly lower priority than other filament threads
JobSystem::setThreadPriority(priority);
mCompilerThreads.emplace_back([this, setup, cleanup]() {
SYSTRACE_CONTEXT();
(*setup)();
@@ -53,7 +53,11 @@ void CompilerThreadPool::init(uint32_t threadCount, JobSystem::Priority priority
(!std::all_of( std::begin(mQueues), std::end(mQueues),
[](auto&& q) { return q.empty(); }));
});
if (!mExitRequested) {
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& {
@@ -73,6 +77,8 @@ void CompilerThreadPool::init(uint32_t threadCount, JobSystem::Priority priority
job();
}
}
(*cleanup)();
});
}
+9 -7
View File
@@ -20,12 +20,13 @@
#include <backend/DriverEnums.h>
#include <utils/Invocable.h>
#include <utils/JobSystem.h>
#include <utils/Mutex.h>
#include <utils/Condition.h>
#include <array>
#include <atomic>
#include <deque>
#include <memory>
#include <thread>
#include <utility>
#include <vector>
@@ -45,8 +46,9 @@ public:
~CompilerThreadPool() noexcept;
using Job = utils::Invocable<void()>;
using ThreadSetup = utils::Invocable<void()>;
void init(uint32_t threadCount, utils::JobSystem::Priority priority,
ThreadSetup&& threadSetup) noexcept;
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);
@@ -54,9 +56,9 @@ public:
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;
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);
+7 -7
View File
@@ -165,13 +165,6 @@ public:
void purge() noexcept final;
// --------------------------------------------------------------------------------------------
// Privates
// --------------------------------------------------------------------------------------------
protected:
class CallbackDataDetails;
// Helpers...
struct CallbackData {
CallbackData(CallbackData const &) = delete;
@@ -202,6 +195,13 @@ protected:
void scheduleCallback(CallbackHandler* handler, void* user, CallbackHandler::Callback callback);
// --------------------------------------------------------------------------------------------
// Privates
// --------------------------------------------------------------------------------------------
protected:
class CallbackDataDetails;
inline void scheduleDestroy(BufferDescriptor&& buffer) noexcept {
if (buffer.hasCallback()) {
scheduleDestroySlow(std::move(buffer));
+10 -2
View File
@@ -176,9 +176,9 @@ void MetalDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
}
void MetalDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
FrameCompletedCallback callback, void* user) {
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
auto* swapChain = handle_cast<MetalSwapChain>(sch);
swapChain->setFrameCompletedCallback(callback, user);
swapChain->setFrameCompletedCallback(handler, callback, user);
}
void MetalDriver::execute(std::function<void(void)> const& fn) noexcept {
@@ -696,6 +696,14 @@ bool MetalDriver::isSRGBSwapChainSupported() {
return false;
}
bool MetalDriver::isStereoSupported() {
return true;
}
bool MetalDriver::isParallelShaderCompileSupported() {
return false;
}
bool MetalDriver::isWorkaroundNeeded(Workaround workaround) {
switch (workaround) {
case Workaround::SPLIT_EASU:
+7 -3
View File
@@ -70,7 +70,8 @@ public:
void releaseDrawable();
void setFrameScheduledCallback(FrameScheduledCallback callback, void* user);
void setFrameCompletedCallback(FrameCompletedCallback callback, void* user);
void setFrameCompletedCallback(CallbackHandler* handler,
CallbackHandler::Callback callback, void* user);
// For CAMetalLayer-backed SwapChains, presents the drawable or schedules a
// FrameScheduledCallback.
@@ -112,8 +113,11 @@ private:
FrameScheduledCallback frameScheduledCallback = nullptr;
void* frameScheduledUserData = nullptr;
FrameCompletedCallback frameCompletedCallback = nullptr;
void* frameCompletedUserData = nullptr;
struct {
CallbackHandler* handler = nullptr;
CallbackHandler::Callback callback = {};
void* user = nullptr;
} frameCompleted;
};
class MetalBufferObject : public HwBufferObject {
+13 -24
View File
@@ -194,13 +194,15 @@ void MetalSwapChain::setFrameScheduledCallback(FrameScheduledCallback callback,
frameScheduledUserData = user;
}
void MetalSwapChain::setFrameCompletedCallback(FrameCompletedCallback callback, void* user) {
frameCompletedCallback = callback;
frameCompletedUserData = user;
void MetalSwapChain::setFrameCompletedCallback(CallbackHandler* handler,
CallbackHandler::Callback callback, void* user) {
frameCompleted.handler = handler;
frameCompleted.callback = callback;
frameCompleted.user = user;
}
void MetalSwapChain::present() {
if (frameCompletedCallback) {
if (frameCompleted.callback) {
scheduleFrameCompletedCallback();
}
if (drawable) {
@@ -244,30 +246,17 @@ void MetalSwapChain::scheduleFrameScheduledCallback() {
}
void MetalSwapChain::scheduleFrameCompletedCallback() {
if (!frameCompletedCallback) {
if (!frameCompleted.callback) {
return;
}
FrameCompletedCallback callback = frameCompletedCallback;
void* userData = frameCompletedUserData;
[getPendingCommandBuffer(&context) addCompletedHandler:^(id<MTLCommandBuffer> cb) {
struct CallbackData {
void* userData;
FrameCompletedCallback callback;
};
CallbackData* data = new CallbackData();
data->userData = userData;
data->callback = callback;
CallbackHandler* handler = frameCompleted.handler;
void* user = frameCompleted.user;
CallbackHandler::Callback callback = frameCompleted.callback;
// Instantiate a BufferDescriptor with a callback for the sole purpose of passing it to
// scheduleDestroy. This forces the BufferDescriptor callback (and thus the
// FrameCompletedCallback) to be called on the user thread.
BufferDescriptor b(nullptr, 0u, [](void* buffer, size_t size, void* user) {
CallbackData* data = (CallbackData*) user;
data->callback(data->userData);
free(data);
}, data);
context.driver->scheduleDestroy(std::move(b));
MetalDriver* driver = context.driver;
[getPendingCommandBuffer(&context) addCompletedHandler:^(id<MTLCommandBuffer> cb) {
driver->scheduleCallback(handler, user, callback);
}];
}
+1 -1
View File
@@ -34,7 +34,7 @@ namespace filament {
namespace backend {
inline bool operator==(const SamplerParams& lhs, const SamplerParams& rhs) {
return lhs.u == rhs.u;
return SamplerParams::EqualTo{}(lhs, rhs);
}
// Rasterization Bindings
+9 -1
View File
@@ -58,7 +58,7 @@ void NoopDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
}
void NoopDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
FrameCompletedCallback callback, void* user) {
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
}
@@ -174,6 +174,14 @@ bool NoopDriver::isSRGBSwapChainSupported() {
return false;
}
bool NoopDriver::isStereoSupported() {
return false;
}
bool NoopDriver::isParallelShaderCompileSupported() {
return false;
}
bool NoopDriver::isWorkaroundNeeded(Workaround) {
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
+409 -325
View File
@@ -49,6 +49,7 @@ bool OpenGLContext::queryOpenGLVersion(GLint* major, GLint* minor) noexcept {
}
OpenGLContext::OpenGLContext() noexcept {
state.vao.p = &mDefaultVAO;
// These queries work with all GL/GLES versions!
@@ -61,265 +62,74 @@ OpenGLContext::OpenGLContext() noexcept {
"[" << state.version << "], [" << state.shader << "]" << io::endl;
/*
* Figure out GL / GLES version and available features
* Figure out GL / GLES version, extensions and capabilities we need to
* determine the feature level
*/
queryOpenGLVersion(&state.major, &state.minor);
glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &gets.max_renderbuffer_size);
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &gets.max_texture_image_units);
glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &gets.max_combined_texture_image_units);
OpenGLContext::initExtensions(&ext, state.major, state.minor);
if (state.major > 2) { // this check works for both GL and GLES, but is intended for GLES
OpenGLContext::initProcs(&procs, ext, state.major, state.minor);
OpenGLContext::initBugs(&bugs, ext, state.major, state.minor,
state.vendor, state.renderer, state.version, state.shader);
glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &gets.max_renderbuffer_size);
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &gets.max_texture_image_units);
glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &gets.max_combined_texture_image_units);
mFeatureLevel = OpenGLContext::resolveFeatureLevel(state.major, state.minor, ext, gets, bugs);
#ifdef BACKEND_OPENGL_VERSION_GLES
mShaderModel = ShaderModel::MOBILE;
#else
mShaderModel = ShaderModel::DESKTOP;
#endif
#ifdef BACKEND_OPENGL_VERSION_GLES
if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_2) {
features.multisample_texture = true;
}
#else
if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1) {
features.multisample_texture = true;
}
#endif
if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1) {
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &gets.max_uniform_block_size);
glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &gets.max_uniform_buffer_bindings);
glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &gets.uniform_buffer_offset_alignment);
glGetIntegerv(GL_MAX_SAMPLES, &gets.max_samples);
glGetIntegerv(GL_MAX_DRAW_BUFFERS, &gets.max_draw_buffers);
glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE,
&gets.max_uniform_block_size);
glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS,
&gets.max_uniform_buffer_bindings);
glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT,
&gets.uniform_buffer_offset_alignment);
glGetIntegerv(GL_MAX_SAMPLES,
&gets.max_samples);
glGetIntegerv(GL_MAX_DRAW_BUFFERS,
&gets.max_draw_buffers);
glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,
&gets.max_transform_feedback_separate_attribs);
#ifdef GL_EXT_texture_filter_anisotropic
if (ext.EXT_texture_filter_anisotropic) {
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gets.max_anisotropy);
}
#endif
} else {
#endif
}
#ifdef BACKEND_OPENGL_VERSION_GLES
else {
gets.max_uniform_block_size = 0;
gets.max_uniform_buffer_bindings = 0;
gets.uniform_buffer_offset_alignment = 0;
gets.max_samples = 1;
gets.max_draw_buffers = 1;
gets.max_transform_feedback_separate_attribs = 0;
}
constexpr auto const caps3 = FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_3];
constexpr GLint MAX_VERTEX_SAMPLER_COUNT = caps3.MAX_VERTEX_SAMPLER_COUNT;
constexpr GLint MAX_FRAGMENT_SAMPLER_COUNT = caps3.MAX_FRAGMENT_SAMPLER_COUNT;
// default procs that can be overridden based on runtime version
#ifdef BACKEND_OPENGL_LEVEL_GLES30
procs.genVertexArrays = glGenVertexArrays;
procs.bindVertexArray = glBindVertexArray;
procs.deleteVertexArrays = glDeleteVertexArrays;
// these are core in GL and GLES 3.x
procs.genQueries = glGenQueries;
procs.deleteQueries = glDeleteQueries;
procs.beginQuery = glBeginQuery;
procs.endQuery = glEndQuery;
procs.getQueryObjectuiv = glGetQueryObjectuiv;
# ifdef BACKEND_OPENGL_VERSION_GL
procs.getQueryObjectui64v = glGetQueryObjectui64v; // only core in GL
# elif defined(GL_EXT_disjoint_timer_query)
procs.getQueryObjectui64v = glGetQueryObjectui64vEXT;
# endif // BACKEND_OPENGL_VERSION_GL
// core in ES 3.0 and GL 4.3
procs.invalidateFramebuffer = glInvalidateFramebuffer;
#endif // BACKEND_OPENGL_LEVEL_GLES30
// 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);
gets.max_anisotropy = 1;
}
#endif
/*
* Figure out which driver bugs we need to workaround
*/
const bool isAngle = strstr(state.renderer, "ANGLE");
if (!isAngle) {
if (strstr(state.renderer, "Adreno")) {
// Qualcomm GPU
bugs.invalidate_end_only_if_invalidate_start = true;
// On Adreno (As of 3/20) timer query seem to return the CPU time, not the GPU time.
bugs.dont_use_timer_query = true;
// Blits to texture arrays are failing
// This bug continues to reproduce, though at times we've seen it appear to "go away".
// The standalone sample app that was written to show this problem still reproduces.
// The working 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
// 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(state.renderer, "Apple")) {
// Apple GPU
} else if (strstr(state.renderer, "Tegra") ||
strstr(state.renderer, "GeForce") ||
strstr(state.renderer, "NV")) {
// NVIDIA GPU
} else if (strstr(state.renderer, "Vivante")) {
// Vivante GPU
} else if (strstr(state.renderer, "AMD") ||
strstr(state.renderer, "ATI")) {
// AMD/ATI GPU
} else if (strstr(state.renderer, "Mozilla")) {
bugs.disable_invalidate_framebuffer = true;
}
} else {
// When running under ANGLE, it's a different set of workaround that we need.
if (strstr(state.renderer, "Adreno")) {
// Qualcomm GPU
// early exit condition is flattened in EASU code
// (that should be regardless of ANGLE, but we should double-check)
bugs.split_easu = true;
}
// TODO: see if we could use `bugs.allow_read_only_ancillary_feedback_loop = true`
}
slog.v << "Feature level: " << +mFeatureLevel << '\n';
slog.v << "Active workarounds: " << '\n';
@@ -345,14 +155,14 @@ OpenGLContext::OpenGLContext() noexcept {
#endif
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
assert_invariant(state.major <= 2 || gets.max_draw_buffers >= 4); // minspec
assert_invariant(mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0 || gets.max_draw_buffers >= 4); // minspec
#endif
setDefaultState();
#ifdef GL_EXT_texture_filter_anisotropic
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
if (state.major > 2 && ext.EXT_texture_filter_anisotropic) {
if (mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1 && ext.EXT_texture_filter_anisotropic) {
// make sure we don't have any error flag
while (glGetError() != GL_NO_ERROR) { }
@@ -458,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
void OpenGLContext::initExtensionsGLES() noexcept {
void OpenGLContext::initExtensionsGLES(Extensions* ext, GLint major, GLint minor) noexcept {
const char * const extensions = (const char*)glGetString(GL_EXTENSIONS);
GLUtils::unordered_string_set const exts = GLUtils::split(extensions);
if constexpr (DEBUG_PRINT_EXTENSIONS) {
@@ -472,51 +560,50 @@ void OpenGLContext::initExtensionsGLES() noexcept {
// figure out and initialize the extensions we need
using namespace std::literals;
ext.APPLE_color_buffer_packed_float = exts.has("GL_APPLE_color_buffer_packed_float"sv);
ext.EXT_clip_control = exts.has("GL_EXT_clip_control"sv);
ext.EXT_clip_cull_distance = exts.has("GL_EXT_clip_cull_distance"sv);
ext.EXT_color_buffer_float = exts.has("GL_EXT_color_buffer_float"sv);
ext.EXT_color_buffer_half_float = exts.has("GL_EXT_color_buffer_half_float"sv);
ext.EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
ext.EXT_discard_framebuffer = exts.has("GL_EXT_discard_framebuffer"sv);
ext.EXT_disjoint_timer_query = exts.has("GL_EXT_disjoint_timer_query"sv);
ext.EXT_multisampled_render_to_texture = exts.has("GL_EXT_multisampled_render_to_texture"sv);
ext.EXT_multisampled_render_to_texture2 = exts.has("GL_EXT_multisampled_render_to_texture2"sv);
ext.EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv);
ext->APPLE_color_buffer_packed_float = exts.has("GL_APPLE_color_buffer_packed_float"sv);
ext->EXT_clip_control = exts.has("GL_EXT_clip_control"sv);
ext->EXT_clip_cull_distance = exts.has("GL_EXT_clip_cull_distance"sv);
ext->EXT_color_buffer_float = exts.has("GL_EXT_color_buffer_float"sv);
ext->EXT_color_buffer_half_float = exts.has("GL_EXT_color_buffer_half_float"sv);
ext->EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
ext->EXT_discard_framebuffer = exts.has("GL_EXT_discard_framebuffer"sv);
ext->EXT_disjoint_timer_query = exts.has("GL_EXT_disjoint_timer_query"sv);
ext->EXT_multisampled_render_to_texture = exts.has("GL_EXT_multisampled_render_to_texture"sv);
ext->EXT_multisampled_render_to_texture2 = exts.has("GL_EXT_multisampled_render_to_texture2"sv);
ext->EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv);
#if !defined(__EMSCRIPTEN__)
ext.EXT_texture_compression_etc2 = true;
ext->EXT_texture_compression_etc2 = true;
#endif
ext.EXT_texture_compression_s3tc = exts.has("GL_EXT_texture_compression_s3tc"sv);
ext.EXT_texture_compression_s3tc_srgb = exts.has("GL_EXT_texture_compression_s3tc_srgb"sv);
ext.EXT_texture_compression_rgtc = exts.has("GL_EXT_texture_compression_rgtc"sv);
ext.EXT_texture_compression_bptc = exts.has("GL_EXT_texture_compression_bptc"sv);
ext.EXT_texture_cube_map_array = exts.has("GL_EXT_texture_cube_map_array"sv) || exts.has("GL_OES_texture_cube_map_array"sv);
ext.GOOGLE_cpp_style_line_directive = exts.has("GL_GOOGLE_cpp_style_line_directive"sv);
ext.KHR_debug = exts.has("GL_KHR_debug"sv);
ext.KHR_parallel_shader_compile = exts.has("GL_KHR_parallel_shader_compile"sv);
ext.KHR_texture_compression_astc_hdr = exts.has("GL_KHR_texture_compression_astc_hdr"sv);
ext.KHR_texture_compression_astc_ldr = exts.has("GL_KHR_texture_compression_astc_ldr"sv);
ext.OES_depth_texture = exts.has("GL_OES_depth_texture"sv);
ext.OES_depth24 = exts.has("GL_OES_depth24"sv);
ext.OES_packed_depth_stencil = exts.has("GL_OES_packed_depth_stencil"sv);
ext.OES_EGL_image_external_essl3 = exts.has("GL_OES_EGL_image_external_essl3"sv);
ext.OES_rgb8_rgba8 = exts.has("GL_OES_rgb8_rgba8"sv);
ext.OES_standard_derivatives = exts.has("GL_OES_standard_derivatives"sv);
ext.OES_texture_npot = exts.has("GL_OES_texture_npot"sv);
ext.OES_vertex_array_object = exts.has("GL_OES_vertex_array_object"sv);
ext.WEBGL_compressed_texture_etc = exts.has("WEBGL_compressed_texture_etc"sv);
ext.WEBGL_compressed_texture_s3tc = exts.has("WEBGL_compressed_texture_s3tc"sv);
ext.WEBGL_compressed_texture_s3tc_srgb = exts.has("WEBGL_compressed_texture_s3tc_srgb"sv);
ext->EXT_texture_compression_s3tc = exts.has("GL_EXT_texture_compression_s3tc"sv);
ext->EXT_texture_compression_s3tc_srgb = exts.has("GL_EXT_texture_compression_s3tc_srgb"sv);
ext->EXT_texture_compression_rgtc = exts.has("GL_EXT_texture_compression_rgtc"sv);
ext->EXT_texture_compression_bptc = exts.has("GL_EXT_texture_compression_bptc"sv);
ext->EXT_texture_cube_map_array = exts.has("GL_EXT_texture_cube_map_array"sv) || exts.has("GL_OES_texture_cube_map_array"sv);
ext->GOOGLE_cpp_style_line_directive = exts.has("GL_GOOGLE_cpp_style_line_directive"sv);
ext->KHR_debug = exts.has("GL_KHR_debug"sv);
ext->KHR_parallel_shader_compile = exts.has("GL_KHR_parallel_shader_compile"sv);
ext->KHR_texture_compression_astc_hdr = exts.has("GL_KHR_texture_compression_astc_hdr"sv);
ext->KHR_texture_compression_astc_ldr = exts.has("GL_KHR_texture_compression_astc_ldr"sv);
ext->OES_depth_texture = exts.has("GL_OES_depth_texture"sv);
ext->OES_depth24 = exts.has("GL_OES_depth24"sv);
ext->OES_packed_depth_stencil = exts.has("GL_OES_packed_depth_stencil"sv);
ext->OES_EGL_image_external_essl3 = exts.has("GL_OES_EGL_image_external_essl3"sv);
ext->OES_rgb8_rgba8 = exts.has("GL_OES_rgb8_rgba8"sv);
ext->OES_standard_derivatives = exts.has("GL_OES_standard_derivatives"sv);
ext->OES_texture_npot = exts.has("GL_OES_texture_npot"sv);
ext->OES_vertex_array_object = exts.has("GL_OES_vertex_array_object"sv);
ext->WEBGL_compressed_texture_etc = exts.has("WEBGL_compressed_texture_etc"sv);
ext->WEBGL_compressed_texture_s3tc = exts.has("WEBGL_compressed_texture_s3tc"sv);
ext->WEBGL_compressed_texture_s3tc_srgb = exts.has("WEBGL_compressed_texture_s3tc_srgb"sv);
// ES 3.2 implies EXT_color_buffer_float
if (state.major > 3 || (state.major == 3 && state.minor >= 2)) {
ext.EXT_color_buffer_float = true;
if (major > 3 || (major == 3 && minor >= 2)) {
ext->EXT_color_buffer_float = true;
}
// ES 3.x implies EXT_discard_framebuffer and OES_vertex_array_object
if (state.major >= 3) {
ext.EXT_discard_framebuffer = true;
ext.OES_vertex_array_object = true;
if (major >= 3) {
ext->EXT_discard_framebuffer = true;
ext->OES_vertex_array_object = true;
}
}
@@ -524,7 +611,7 @@ void OpenGLContext::initExtensionsGLES() noexcept {
#ifdef BACKEND_OPENGL_VERSION_GL
void OpenGLContext::initExtensionsGL() noexcept {
void OpenGLContext::initExtensionsGL(Extensions* ext, GLint major, GLint minor) noexcept {
GLUtils::unordered_string_set exts;
GLint n = 0;
glGetIntegerv(GL_NUM_EXTENSIONS, &n);
@@ -539,55 +626,52 @@ void OpenGLContext::initExtensionsGL() noexcept {
}
using namespace std::literals;
ext.APPLE_color_buffer_packed_float = true; // Assumes core profile.
ext.ARB_shading_language_packing = exts.has("GL_ARB_shading_language_packing"sv);
ext.EXT_color_buffer_float = true; // Assumes core profile.
ext.EXT_color_buffer_half_float = true; // Assumes core profile.
ext.EXT_clip_cull_distance = true;
ext.EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
ext.EXT_discard_framebuffer = false;
ext.EXT_disjoint_timer_query = true;
ext.EXT_multisampled_render_to_texture = false;
ext.EXT_multisampled_render_to_texture2 = false;
ext.EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv);
ext.EXT_texture_compression_bptc = exts.has("GL_EXT_texture_compression_bptc"sv);
ext.EXT_texture_compression_etc2 = exts.has("GL_ARB_ES3_compatibility"sv);
ext.EXT_texture_compression_rgtc = exts.has("GL_EXT_texture_compression_rgtc"sv);
ext.EXT_texture_compression_s3tc = exts.has("GL_EXT_texture_compression_s3tc"sv);
ext.EXT_texture_compression_s3tc_srgb = exts.has("GL_EXT_texture_compression_s3tc_srgb"sv);
ext.EXT_texture_cube_map_array = true;
ext.EXT_texture_filter_anisotropic = exts.has("GL_EXT_texture_filter_anisotropic"sv);
ext.EXT_texture_sRGB = exts.has("GL_EXT_texture_sRGB"sv);
ext.GOOGLE_cpp_style_line_directive = exts.has("GL_GOOGLE_cpp_style_line_directive"sv);
ext.KHR_parallel_shader_compile = exts.has("GL_KHR_parallel_shader_compile"sv);
ext.KHR_texture_compression_astc_hdr = exts.has("GL_KHR_texture_compression_astc_hdr"sv);
ext.KHR_texture_compression_astc_ldr = exts.has("GL_KHR_texture_compression_astc_ldr"sv);
ext.OES_depth_texture = true;
ext.OES_depth24 = true;
ext.OES_EGL_image_external_essl3 = false;
ext.OES_rgb8_rgba8 = true;
ext.OES_standard_derivatives = true;
ext.OES_texture_npot = true;
ext.OES_vertex_array_object = true;
ext.WEBGL_compressed_texture_etc = false;
ext.WEBGL_compressed_texture_s3tc = false;
ext.WEBGL_compressed_texture_s3tc_srgb = false;
auto const major = state.major;
auto const minor = state.minor;
ext->APPLE_color_buffer_packed_float = true; // Assumes core profile.
ext->ARB_shading_language_packing = exts.has("GL_ARB_shading_language_packing"sv);
ext->EXT_color_buffer_float = true; // Assumes core profile.
ext->EXT_color_buffer_half_float = true; // Assumes core profile.
ext->EXT_clip_cull_distance = true;
ext->EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
ext->EXT_discard_framebuffer = false;
ext->EXT_disjoint_timer_query = true;
ext->EXT_multisampled_render_to_texture = false;
ext->EXT_multisampled_render_to_texture2 = false;
ext->EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv);
ext->EXT_texture_compression_bptc = exts.has("GL_EXT_texture_compression_bptc"sv);
ext->EXT_texture_compression_etc2 = exts.has("GL_ARB_ES3_compatibility"sv);
ext->EXT_texture_compression_rgtc = exts.has("GL_EXT_texture_compression_rgtc"sv);
ext->EXT_texture_compression_s3tc = exts.has("GL_EXT_texture_compression_s3tc"sv);
ext->EXT_texture_compression_s3tc_srgb = exts.has("GL_EXT_texture_compression_s3tc_srgb"sv);
ext->EXT_texture_cube_map_array = true;
ext->EXT_texture_filter_anisotropic = exts.has("GL_EXT_texture_filter_anisotropic"sv);
ext->EXT_texture_sRGB = exts.has("GL_EXT_texture_sRGB"sv);
ext->GOOGLE_cpp_style_line_directive = exts.has("GL_GOOGLE_cpp_style_line_directive"sv);
ext->KHR_parallel_shader_compile = exts.has("GL_KHR_parallel_shader_compile"sv);
ext->KHR_texture_compression_astc_hdr = exts.has("GL_KHR_texture_compression_astc_hdr"sv);
ext->KHR_texture_compression_astc_ldr = exts.has("GL_KHR_texture_compression_astc_ldr"sv);
ext->OES_depth_texture = true;
ext->OES_depth24 = true;
ext->OES_EGL_image_external_essl3 = false;
ext->OES_rgb8_rgba8 = true;
ext->OES_standard_derivatives = true;
ext->OES_texture_npot = true;
ext->OES_vertex_array_object = true;
ext->WEBGL_compressed_texture_etc = false;
ext->WEBGL_compressed_texture_s3tc = false;
ext->WEBGL_compressed_texture_s3tc_srgb = false;
// OpenGL 4.2 implies ARB_shading_language_packing
if (major > 4 || (major == 4 && minor >= 2)) {
ext.ARB_shading_language_packing = true;
ext->ARB_shading_language_packing = true;
}
// OpenGL 4.3 implies EXT_discard_framebuffer
if (major > 4 || (major == 4 && minor >= 3)) {
ext.EXT_discard_framebuffer = true;
ext.KHR_debug = true;
ext->EXT_discard_framebuffer = true;
ext->KHR_debug = true;
}
// OpenGL 4.5 implies EXT_clip_control
if (major > 4 || (major == 4 && minor >= 5)) {
ext.EXT_clip_control = true;
ext->EXT_clip_control = true;
}
}
@@ -683,7 +767,7 @@ void OpenGLContext::deleteBuffers(GLsizei n, const GLuint* buffers, GLenum targe
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
assert_invariant(state.major > 2 ||
assert_invariant(mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1 ||
(target != GL_UNIFORM_BUFFER && target != GL_TRANSFORM_FEEDBACK_BUFFER));
if (target == GL_UNIFORM_BUFFER || target == GL_TRANSFORM_FEEDBACK_BUFFER) {
+41 -9
View File
@@ -92,7 +92,7 @@ public:
# ifndef BACKEND_OPENGL_LEVEL_GLES30
return true;
# else
return state.major == 2;
return mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0;
# endif
#else
return false;
@@ -151,7 +151,7 @@ public:
void deleteVertexArrays(GLsizei n, const GLuint* arrays) noexcept;
// glGet*() values
struct {
struct Gets {
GLfloat max_anisotropy;
GLint max_draw_buffers;
GLint max_renderbuffer_size;
@@ -170,7 +170,7 @@ public:
} features = {};
// supported extensions detected at runtime
struct {
struct Extensions {
bool APPLE_color_buffer_packed_float;
bool ARB_shading_language_packing;
bool EXT_clip_control;
@@ -209,7 +209,7 @@ public:
bool WEBGL_compressed_texture_s3tc_srgb;
} ext = {};
struct {
struct Bugs {
// Some drivers have issues with UBOs in the fragment shader when
// glFlush() is called between draw calls.
bool disable_glFlush;
@@ -275,6 +275,10 @@ public:
// performance more if we end-up pinned on the same one.
bool disable_thread_affinity;
// Force feature level 0. Typically used for low end ES3 devices with significant driver
// bugs or performance issues.
bool force_feature_level0;
} bugs = {};
// state getters -- as needed.
@@ -397,7 +401,7 @@ public:
} window;
} state;
struct {
struct Procs {
void (* bindVertexArray)(GLuint array);
void (* deleteVertexArrays)(GLsizei n, const GLuint* arrays);
void (* genVertexArrays)(GLsizei n, GLuint* arrays);
@@ -467,18 +471,46 @@ private:
{ bugs.disable_thread_affinity,
"disable_thread_affinity",
""},
{ bugs.force_feature_level0,
"force_feature_level0",
""},
}};
RenderPrimitive mDefaultVAO;
// this is chosen to minimize code size
#if defined(BACKEND_OPENGL_VERSION_GLES)
void initExtensionsGLES() noexcept;
static void initExtensionsGLES(Extensions* ext, GLint major, GLint minor) noexcept;
#endif
#if defined(BACKEND_OPENGL_VERSION_GL)
void initExtensionsGL() noexcept;
static void initExtensionsGL(Extensions* ext, GLint major, GLint minor) noexcept;
#endif
static void initExtensions(Extensions* ext, GLint major, GLint minor) noexcept {
#if defined(BACKEND_OPENGL_VERSION_GLES)
initExtensionsGLES(ext, major, minor);
#endif
#if defined(BACKEND_OPENGL_VERSION_GL)
initExtensionsGL(ext, major, minor);
#endif
}
static void initBugs(Bugs* bugs, Extensions const& exts,
GLint major, GLint minor,
char const* vendor,
char const* renderer,
char const* version,
char const* shader
);
static void initProcs(Procs* procs,
Extensions const& exts, GLint major, GLint minor) noexcept;
static FeatureLevel resolveFeatureLevel(GLint major, GLint minor,
Extensions const& exts,
Gets const& gets,
Bugs const& bugs) noexcept;
template <typename T, typename F>
static inline void update_state(T& state, T const& expected, F functor, bool force = false) noexcept {
if (UTILS_UNLIKELY(force || state != expected)) {
@@ -571,7 +603,7 @@ void OpenGLContext::activeTexture(GLuint unit) noexcept {
void OpenGLContext::bindSampler(GLuint unit, GLuint sampler) noexcept {
assert_invariant(unit < MAX_TEXTURE_UNIT_COUNT);
assert_invariant(state.major > 2);
assert_invariant(mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1);
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
update_state(state.textures.units[unit].sampler, sampler, [&]() {
glBindSampler(unit, sampler);
@@ -617,7 +649,7 @@ void OpenGLContext::bindVertexArray(RenderPrimitive const* p) noexcept {
void OpenGLContext::bindBufferRange(GLenum target, GLuint index, GLuint buffer,
GLintptr offset, GLsizeiptr size) noexcept {
assert_invariant(state.major > 2);
assert_invariant(mFeatureLevel >= FeatureLevel::FEATURE_LEVEL_1);
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
# ifdef BACKEND_OPENGL_LEVEL_GLES31
+16 -4
View File
@@ -1872,6 +1872,18 @@ bool OpenGLDriver::isSRGBSwapChainSupported() {
return mPlatform.isSRGBSwapChainSupported();
}
bool OpenGLDriver::isStereoSupported() {
// Stereo requires instancing and EXT_clip_cull_distance.
if (UTILS_UNLIKELY(mContext.isES2())) {
return false;
}
return mContext.ext.EXT_clip_cull_distance;
}
bool OpenGLDriver::isParallelShaderCompileSupported() {
return mShaderCompilerService.isParallelShaderCompileSupported();
}
bool OpenGLDriver::isWorkaroundNeeded(Workaround workaround) {
switch (workaround) {
case Workaround::SPLIT_EASU:
@@ -2593,7 +2605,7 @@ bool OpenGLDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapse
void OpenGLDriver::compilePrograms(CompilerPriorityQueue priority,
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
if (callback) {
getShaderCompilerService().notifyWhenAllProgramsAreReady(priority, handler, callback, user);
getShaderCompilerService().notifyWhenAllProgramsAreReady(handler, callback, user);
}
}
@@ -2905,7 +2917,7 @@ void OpenGLDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
GLuint OpenGLDriver::getSamplerSlow(SamplerParams params) const noexcept {
assert_invariant(mSamplerMap.find(params.u) == mSamplerMap.end());
assert_invariant(mSamplerMap.find(params) == mSamplerMap.end());
GLuint s;
glGenSamplers(1, &s);
@@ -2927,7 +2939,7 @@ GLuint OpenGLDriver::getSamplerSlow(SamplerParams params) const noexcept {
}
#endif
CHECK_GL_ERROR(utils::slog.e)
mSamplerMap[params.u] = s;
mSamplerMap[params] = s;
return s;
}
#endif
@@ -3258,7 +3270,7 @@ void OpenGLDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch,
}
void OpenGLDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
FrameCompletedCallback callback, void* user) {
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
DEBUG_MARKER()
}
+3 -2
View File
@@ -334,7 +334,7 @@ private:
assert_invariant(!sp.padding1);
assert_invariant(!sp.padding2);
auto& samplerMap = mSamplerMap;
auto pos = samplerMap.find(sp.u);
auto pos = samplerMap.find(sp);
if (UTILS_UNLIKELY(pos == samplerMap.end())) {
return getSamplerSlow(sp);
}
@@ -368,7 +368,8 @@ private:
// sampler buffer binding points (nullptr if not used)
std::array<GLSamplerGroup*, Program::SAMPLER_BINDING_COUNT> mSamplerBindings = {}; // 4 pointers
mutable tsl::robin_map<uint32_t, GLuint> mSamplerMap;
mutable tsl::robin_map<SamplerParams, GLuint,
SamplerParams::Hasher, SamplerParams::EqualTo> mSamplerMap;
// this must be accessed from the driver thread only
std::vector<GLTexture*> mTexturesWithStreamsAttached;
@@ -116,4 +116,7 @@ bool OpenGLPlatform::isExtraContextSupported() const noexcept {
void OpenGLPlatform::createContext(bool) {
}
void OpenGLPlatform::releaseContext() noexcept {
}
} // namespace filament::backend
@@ -129,11 +129,11 @@ void TimerQueryNative::endTimeElapsedQuery(GLTimerQuery* tq) {
// 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);
}
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;
});
}
@@ -64,17 +64,17 @@ static inline std::string to_string(float f) noexcept {
// ------------------------------------------------------------------------------------------------
struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
struct ProgramBinary {
GLenum format{};
struct ProgramData {
GLuint program{};
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders{};
std::vector<char> blob;
};
~OpenGLProgramToken();
~OpenGLProgramToken() override;
OpenGLProgramToken(ShaderCompilerService& compiler, utils::CString const& name) noexcept
: compiler(compiler), name(name) {
}
ShaderCompilerService& compiler;
utils::CString const& name;
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> attributes;
@@ -86,22 +86,21 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
} gl; // 12 bytes
// Sets the programBinary, typically from the compiler thread, and signal the main thread.
// Sets the programData, typically from the compiler thread, and signal the main thread.
// This is similar to std::promise::set_value.
void set(ProgramBinary programBinary) noexcept {
using std::swap;
void set(ProgramData const& data) noexcept {
std::unique_lock const l(lock);
swap(binary, programBinary);
programData = data;
signaled = true;
cond.notify_one();
}
// Get the programBinary, wait if necessary.
// This is similar to std::future::get
ProgramBinary const& get() const noexcept {
ProgramData const& get() const noexcept {
std::unique_lock l(lock);
cond.wait(l, [this](){ return signaled; });
return binary;
return programData;
}
// Checks if the programBinary is ready.
@@ -112,10 +111,11 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
return cond.wait_for(l, 0s, [this](){ return signaled; });
}
CallbackManager::Handle handle{};
BlobCacheKey key;
mutable utils::Mutex lock;
mutable utils::Condition cond;
ProgramBinary binary;
ProgramData programData;
bool signaled = false;
bool canceled = false; // not part of the signaling
@@ -135,11 +135,16 @@ void* ShaderCompilerService::getUserData(const program_token_t& token) noexcept
ShaderCompilerService::ShaderCompilerService(OpenGLDriver& driver)
: mDriver(driver),
mCallbackManager(driver),
KHR_parallel_shader_compile(driver.getContext().ext.KHR_parallel_shader_compile) {
}
ShaderCompilerService::~ShaderCompilerService() noexcept = default;
bool ShaderCompilerService::isParallelShaderCompileSupported() const noexcept {
return KHR_parallel_shader_compile || mShaderCompilerThreadCount;
}
void ShaderCompilerService::init() noexcept {
// If we have KHR_parallel_shader_compile, we always use it, it should be more resource
// friendly.
@@ -170,32 +175,34 @@ void ShaderCompilerService::init() noexcept {
}
mShaderCompilerThreadCount = poolSize;
mCompilerThreadPool.init(mShaderCompilerThreadCount, priority,
[platform = &mDriver.mPlatform, sharedContext = mUseSharedContext]() {
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(sharedContext);
platform.createContext(true);
},
[&platform = mDriver.mPlatform]() {
// release context and thread state
platform.releaseContext();
});
}
}
}
void ShaderCompilerService::terminate() noexcept {
// We could have some pending callbacks here, we need to execute them.
// This is equivalent to calling cancelTickOp() on all active tokens.
for (auto&& op: mRunAtNextTickOps) {
auto const& [priority, token, job] = op;
if (!token && job.callback) {
// This is a little fragile here. We know by construction that jobs that have a
// null token are the ones that dispatch the user callbacks.
mDriver.scheduleCallback(job.handler, job.user, job.callback);
}
}
mRunAtNextTickOps.clear();
// Finally stop the thread pool immediately. Pending jobs will be discarded. We guarantee by
// construction that nobody is waiting on a token (because waiting is only done on the main
// backend thread, and if we're here, we're on the backend main thread).
mCompilerThreadPool.terminate();
mRunAtNextTickOps.clear();
// We could have some pending callbacks here, we need to execute them.
// This is equivalent to calling cancelTickOp() on all active tokens.
mCallbackManager.terminate();
}
ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
@@ -203,132 +210,104 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
auto& gl = mDriver.getContext();
auto token = std::make_shared<OpenGLProgramToken>(*this, name);
if (UTILS_UNLIKELY(gl.isES2())) {
token->attributes = std::move(program.getAttributes());
}
token->gl.program = OpenGLBlobCache::retrieve(&token->key, mDriver.mPlatform, program);
if (!token->gl.program) {
CompilerPriorityQueue const priorityQueue = program.getPriorityQueue();
if (mShaderCompilerThreadCount) {
// queue a compile job
mCompilerThreadPool.queue(priorityQueue, token,
[this, &gl, program = std::move(program), token]() mutable {
if (token->gl.program) {
return token;
}
// 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);
token->handle = mCallbackManager.get();
// link the program
GLuint const glProgram = linkProgram(gl, shaders, token->attributes);
CompilerPriorityQueue const priorityQueue = program.getPriorityQueue();
if (mShaderCompilerThreadCount) {
// queue a compile job
mCompilerThreadPool.queue(priorityQueue, token,
[this, &gl, program = std::move(program), token]() mutable {
// compile the shaders
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders{};
std::array<utils::CString, Program::SHADER_TYPE_COUNT> shaderSourceCode;
compileShaders(gl,
std::move(program.getShadersSource()),
program.getSpecializationConstants(),
shaders,
shaderSourceCode);
OpenGLProgramToken::ProgramBinary binary;
binary.shaders = shaders;
// link the program
GLuint const glProgram = linkProgram(gl, shaders, token->attributes);
if (UTILS_LIKELY(mUseSharedContext)) {
// 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);
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.
token->set(std::move(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);
OpenGLProgramToken::ProgramData programData;
programData.shaders = shaders;
}
// We need to query the link status here to guarantee that the
// program is compiled and linked now (we don't want this to be
// deferred to later). We don't care about the result at this point.
GLint status;
glGetProgramiv(glProgram, GL_LINK_STATUS, &status);
programData.program = glProgram;
token->gl.program = programData.program;
// we don't need to check for success here, it'll be done on the
// main thread side.
token->set(programData);
mCallbackManager.put(token->handle);
// caching must be the last thing we do
if (token->key) {
// Attempt to cache. This calls glGetProgramBinary.
OpenGLBlobCache::insert(mDriver.mPlatform, token->key, glProgram);
}
});
} else {
// this cannot fail because we check compilation status after linking the program
// shaders[] is filled with id of shader stages present.
compileShaders(gl,
std::move(program.getShadersSource()),
program.getSpecializationConstants(),
token->gl.shaders,
token->shaderSourceCode);
runAtNextTick(priorityQueue, token, [this, token](Job const&) {
if (mShaderCompilerThreadCount) {
if (!token->gl.program) {
// TODO: see if we could completely eliminate this callback here
// and instead just rely on token->gl.program being atomically
// set by the compiler thread.
// we're using the compiler thread, check if the program is ready, no-op if not.
if (!token->isReady()) {
if (KHR_parallel_shader_compile) {
// don't attempt to link this program if all shaders are not done compiling
GLint status;
if (token->gl.program) {
glGetProgramiv(token->gl.program, GL_COMPLETION_STATUS, &status);
if (status == GL_FALSE) {
return false;
}
// program binary is ready, retrieve it without blocking
ShaderCompilerService::getProgramFromCompilerPool(
const_cast<program_token_t&>(token));
}
} else {
if (KHR_parallel_shader_compile) {
// don't attempt to link this program if all shaders are not done compiling
GLint status;
if (token->gl.program) {
glGetProgramiv(token->gl.program, GL_COMPLETION_STATUS, &status);
if (status == GL_FALSE) {
return false;
}
} else {
for (auto shader: token->gl.shaders) {
if (shader) {
glGetShaderiv(shader, GL_COMPLETION_STATUS, &status);
if (status == GL_FALSE) {
return false;
}
} else {
for (auto shader: token->gl.shaders) {
if (shader) {
glGetShaderiv(shader, GL_COMPLETION_STATUS, &status);
if (status == GL_FALSE) {
return false;
}
}
}
}
}
if (!token->gl.program) {
// link the program, this also cannot fail because status is checked later.
token->gl.program = linkProgram(mDriver.getContext(),
token->gl.shaders, token->attributes);
if (KHR_parallel_shader_compile) {
// wait until the link finishes...
return false;
}
if (!token->gl.program) {
// link the program, this also cannot fail because status is checked later.
token->gl.program = linkProgram(mDriver.getContext(),
token->gl.shaders, token->attributes);
if (KHR_parallel_shader_compile) {
// wait until the link finishes...
return false;
}
}
assert_invariant(token->gl.program);
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
// do this later, maybe depending on CPU usage?
// attempt to cache if we don't have a thread pool (otherwise it's done
@@ -343,31 +322,12 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
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 const program = initialize(token);
assert_invariant(token == nullptr);
#ifndef FILAMENT_ENABLE_MATDBG
assert_invariant(program);
#endif
return program;
}
@@ -395,74 +355,26 @@ GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t&
}
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) {
if (KHR_parallel_shader_compile || mShaderCompilerThreadCount) {
// 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;
if (callback) {
mCallbackManager.setCallback(handler, callback, user);
}
// 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 {
OpenGLProgramToken::ProgramBinary const& binary{ token->get() };
OpenGLProgramToken::ProgramData const& programData{ token->get() };
if (!token->canceled) {
token->gl.shaders = binary.shaders;
if (UTILS_LIKELY(mUseSharedContext)) {
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
token->gl.shaders = programData.shaders;
token->gl.program = programData.program;
}
}
@@ -489,8 +401,17 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
// we force the program link -- which might stall, either here or below in
// checkProgramStatus(), but we don't have a choice, we need to use the program now.
token->compiler.cancelTickOp(token);
token->gl.program = linkProgram(mDriver.getContext(),
token->gl.shaders, token->attributes);
assert_invariant(token->gl.program);
mCallbackManager.put(token->handle);
if (token->key) {
OpenGLBlobCache::insert(mDriver.mPlatform, token->key, token->gl.program);
}
} else {
// if we don't have a program yet, block until we get it.
tick();
@@ -19,6 +19,7 @@
#include "gl_headers.h"
#include "CallbackManager.h"
#include "CompilerThreadPool.h"
#include <backend/CallbackHandler.h>
@@ -65,16 +66,14 @@ public:
~ShaderCompilerService() noexcept;
bool isParallelShaderCompileSupported() const noexcept;
void init() noexcept;
void terminate() noexcept;
// creates a program (compile + link) asynchronously if supported
program_token_t createProgram(utils::CString const& name, Program&& program);
// 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.
GLuint getProgram(program_token_t& token);
@@ -91,20 +90,17 @@ public:
static void* getUserData(const program_token_t& token) noexcept;
// call the callback when all active programs are ready
void notifyWhenAllProgramsAreReady(CompilerPriorityQueue priority,
void notifyWhenAllProgramsAreReady(
CallbackHandler* handler, CallbackHandler::Callback callback, void* user);
private:
OpenGLDriver& mDriver;
CallbackManager mCallbackManager;
CompilerThreadPool mCompilerThreadPool;
const bool KHR_parallel_shader_compile;
uint32_t mShaderCompilerThreadCount = 0u;
// 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;
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));
ext.egl.ANDROID_recordable = extensions.has("EGL_ANDROID_recordable");
ext.egl.KHR_create_context = extensions.has("EGL_KHR_create_context");
ext.egl.KHR_gl_colorspace = extensions.has("EGL_KHR_gl_colorspace");
ext.egl.KHR_create_context = extensions.has("EGL_KHR_create_context");
ext.egl.KHR_no_config_context = extensions.has("EGL_KHR_no_config_context");
ext.egl.KHR_surfaceless_context = extensions.has("KHR_surfaceless_context");
if (ext.egl.KHR_create_context) {
// KHR_create_context implies KHR_surfaceless_context for ES3.x contexts
ext.egl.KHR_surfaceless_context = true;
}
eglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC) eglGetProcAddress("eglCreateSyncKHR");
eglDestroySyncKHR = (PFNEGLDESTROYSYNCKHRPROC) eglGetProcAddress("eglDestroySyncKHR");
@@ -181,13 +186,6 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon
eglConfig = mEGLConfig;
}
// create the dummy surface, just for being able to make the context current.
mEGLDummySurface = eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, pbufferAttribs);
if (UTILS_UNLIKELY(mEGLDummySurface == EGL_NO_SURFACE)) {
logEglError("eglCreatePbufferSurface");
goto error;
}
for (size_t tries = 0; tries < 3; tries++) {
mEGLContext = eglCreateContext(mEGLDisplay, eglConfig,
(EGLContext)sharedContext, contextAttribs.data());
@@ -220,6 +218,26 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon
goto error;
}
if (ext.egl.KHR_surfaceless_context) {
// Adreno 306 driver advertises KHR_create_context but doesn't support passing
// EGL_NO_SURFACE to eglMakeCurrent with a 3.0 context.
if (UTILS_UNLIKELY(!eglMakeCurrent(mEGLDisplay,
EGL_NO_SURFACE, EGL_NO_SURFACE, mEGLContext))) {
if (eglGetError() == EGL_BAD_MATCH) {
ext.egl.KHR_surfaceless_context = false;
}
}
}
if (UTILS_UNLIKELY(!ext.egl.KHR_surfaceless_context)) {
// create the dummy surface, just for being able to make the context current.
mEGLDummySurface = eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, pbufferAttribs);
if (UTILS_UNLIKELY(mEGLDummySurface == EGL_NO_SURFACE)) {
logEglError("eglCreatePbufferSurface");
goto error;
}
}
if (UTILS_UNLIKELY(!makeCurrent(mEGLDummySurface, mEGLDummySurface))) {
// eglMakeCurrent failed
logEglError("eglMakeCurrent");
@@ -255,7 +273,7 @@ error:
}
bool PlatformEGL::isExtraContextSupported() const noexcept {
return true;
return ext.egl.KHR_surfaceless_context;
}
void PlatformEGL::createContext(bool shared) {
@@ -276,6 +294,22 @@ void PlatformEGL::createContext(bool shared) {
mAdditionalContexts.push_back(context);
}
void PlatformEGL::releaseContext() noexcept {
EGLContext context = eglGetCurrentContext();
eglMakeCurrent(mEGLDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (context != EGL_NO_CONTEXT) {
eglDestroyContext(mEGLDisplay, context);
}
mAdditionalContexts.erase(
std::remove_if(mAdditionalContexts.begin(), mAdditionalContexts.end(),
[context](EGLContext c) {
return c == context;
}), mAdditionalContexts.end());
eglReleaseThread();
}
EGLBoolean PlatformEGL::makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) noexcept {
if (UTILS_UNLIKELY((drawSurface != mCurrentDrawSurface || readSurface != mCurrentReadSurface))) {
mCurrentDrawSurface = drawSurface;
@@ -286,8 +320,11 @@ EGLBoolean PlatformEGL::makeCurrent(EGLSurface drawSurface, EGLSurface readSurfa
}
void PlatformEGL::terminate() noexcept {
// it's always allowed to use EGL_NO_SURFACE, EGL_NO_CONTEXT
eglMakeCurrent(mEGLDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroySurface(mEGLDisplay, mEGLDummySurface);
if (mEGLDummySurface) {
eglDestroySurface(mEGLDisplay, mEGLDummySurface);
}
eglDestroyContext(mEGLDisplay, mEGLContext);
for (auto context : mAdditionalContexts) {
eglDestroyContext(mEGLDisplay, context);
+35 -18
View File
@@ -19,6 +19,7 @@
#include "VulkanFboCache.h"
#include "VulkanHandles.h"
#include "VulkanSamplerCache.h"
#include "VulkanTexture.h"
#include <utils/FixedCapacityVector.h>
#include <utils/Panic.h>
@@ -136,16 +137,20 @@ void VulkanBlitter::blitColor(BlitArgs args) {
VkFormatProperties info;
vkGetPhysicalDeviceFormatProperties(gpu, src.getFormat(), &info);
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT,
"Source format is not blittable")) {
"Source format is not blittable %d", src.getFormat())) {
return;
}
vkGetPhysicalDeviceFormatProperties(gpu, dst.getFormat(), &info);
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT,
"Destination format is not blittable")) {
"Destination format is not blittable %d", dst.getFormat())) {
return;
}
#endif
VkCommandBuffer const cmdbuffer = mCommands->get().cmdbuffer;
VulkanCommandBuffer& commands = mCommands->get();
VkCommandBuffer const cmdbuffer = commands.cmdbuffer;
commands.acquire(src.texture);
commands.acquire(dst.texture);
blitFast(cmdbuffer, aspect, args.filter, args.srcTarget->getExtent(), src, dst,
args.srcRectPair, args.dstRectPair);
}
@@ -160,12 +165,12 @@ void VulkanBlitter::blitDepth(BlitArgs args) {
VkFormatProperties info;
vkGetPhysicalDeviceFormatProperties(gpu, src.getFormat(), &info);
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT,
"Depth format is not blittable")) {
"Depth src format is not blittable %d", src.getFormat())) {
return;
}
vkGetPhysicalDeviceFormatProperties(gpu, dst.getFormat(), &info);
if (!ASSERT_POSTCONDITION_NON_FATAL(info.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT,
"Depth format is not blittable")) {
"Depth dst format is not blittable %d", dst.getFormat())) {
return;
}
#endif
@@ -177,7 +182,11 @@ void VulkanBlitter::blitDepth(BlitArgs args) {
args.dstRectPair);
return;
}
VkCommandBuffer const cmdbuffer = mCommands->get().cmdbuffer;
VulkanCommandBuffer& commands = mCommands->get();
VkCommandBuffer const cmdbuffer = commands.cmdbuffer;
commands.acquire(src.texture);
commands.acquire(dst.texture);
blitFast(cmdbuffer, aspect, args.filter, args.srcTarget->getExtent(), src, dst, args.srcRectPair,
args.dstRectPair);
}
@@ -247,13 +256,16 @@ void VulkanBlitter::lazyInit() noexcept {
+1.0f, +1.0f,
};
mTriangleBuffer = new VulkanBuffer(mAllocator, mCommands, mStagePool,
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, sizeof(kTriangleVertices));
VulkanCommandBuffer& commands = mCommands->get();
VkCommandBuffer const cmdbuffer = commands.cmdbuffer;
mTriangleBuffer->loadFromCpu(kTriangleVertices, 0, sizeof(kTriangleVertices));
mTriangleBuffer = new VulkanBuffer(mAllocator, mStagePool, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
sizeof(kTriangleVertices));
mParamsBuffer = new VulkanBuffer(mAllocator, mCommands, mStagePool,
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, sizeof(BlitterUniforms));
mTriangleBuffer->loadFromCpu(cmdbuffer, kTriangleVertices, 0, sizeof(kTriangleVertices));
mParamsBuffer = new VulkanBuffer(mAllocator, mStagePool, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
sizeof(BlitterUniforms));
}
// At a high level, the procedure for resolving depth looks like this:
@@ -265,11 +277,16 @@ void VulkanBlitter::blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, V
VulkanAttachment dst, const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]) {
lazyInit();
VulkanCommandBuffer* commands = &mCommands->get();
VkCommandBuffer const cmdbuffer = commands->cmdbuffer;
commands->acquire(src.texture);
commands->acquire(dst.texture);
BlitterUniforms const uniforms = {
.sampleCount = src.texture->samples,
.inverseSampleCount = 1.0f / float(src.texture->samples),
.sampleCount = src.texture->samples,
.inverseSampleCount = 1.0f / float(src.texture->samples),
};
mParamsBuffer->loadFromCpu(&uniforms, 0, sizeof(uniforms));
mParamsBuffer->loadFromCpu(cmdbuffer, &uniforms, 0, sizeof(uniforms));
VkImageAspectFlags const aspect = VK_IMAGE_ASPECT_DEPTH_BIT;
@@ -316,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.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.
const VkImageSubresourceRange srcRange = {
.aspectMask = aspect,
@@ -378,6 +393,7 @@ void VulkanBlitter::blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, V
VkSampler vksampler = mSamplerCache.getSampler({});
VkDescriptorImageInfo samplers[VulkanPipelineCache::SAMPLER_BINDING_COUNT];
VulkanTexture* textures[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {nullptr};
for (auto& sampler : samplers) {
sampler = {
.sampler = vksampler,
@@ -391,8 +407,9 @@ void VulkanBlitter::blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, V
.imageView = src.getImageView(VK_IMAGE_ASPECT_DEPTH_BIT),
.imageLayout = ImgUtil::getVkLayout(samplerLayout),
};
textures[0] = src.texture;
mPipelineCache.bindSamplers(samplers,
mPipelineCache.bindSamplers(samplers, textures,
VulkanPipelineCache::getUsageFlags(0, ShaderStageFlags::FRAGMENT));
auto previousUbo = mPipelineCache.getUniformBufferBinding(0);
@@ -409,7 +426,7 @@ void VulkanBlitter::blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, V
mPipelineCache.bindScissor(cmdbuffer, scissor);
if (!mPipelineCache.bindPipeline(cmdbuffer)) {
if (!mPipelineCache.bindPipeline(commands)) {
assert_invariant(false);
}
+9 -8
View File
@@ -23,9 +23,11 @@ using namespace bluevk;
namespace filament::backend {
VulkanBuffer::VulkanBuffer(VmaAllocator allocator, VulkanCommands* commands,
VulkanStagePool& stagePool, VkBufferUsageFlags usage, uint32_t numBytes)
: mAllocator(allocator), mCommands(commands), mStagePool(stagePool), mUsage(usage) {
VulkanBuffer::VulkanBuffer(VmaAllocator allocator, VulkanStagePool& stagePool,
VkBufferUsageFlags usage, uint32_t numBytes)
: mAllocator(allocator),
mStagePool(stagePool),
mUsage(usage) {
// for now make sure that only 1 bit is set in usage
// (because loadFromCpu() assumes that somewhat)
@@ -53,7 +55,8 @@ void VulkanBuffer::terminate() {
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);
VulkanStage const* stage = mStagePool.acquireStage(numBytes);
void* mapped;
@@ -62,10 +65,8 @@ void VulkanBuffer::loadFromCpu(const void* cpuData, uint32_t byteOffset, uint32_
vmaUnmapMemory(mAllocator, stage->memory);
vmaFlushAllocation(mAllocator, stage->memory, byteOffset, numBytes);
VkCommandBuffer const cmdbuffer = mCommands->get(true).cmdbuffer;
VkBufferCopy region{ .size = numBytes };
vkCmdCopyBuffer(cmdbuffer, stage->buffer, mGpuBuffer, 1, &region);
vkCmdCopyBuffer(cmdbuf, stage->buffer, mGpuBuffer, 1, &region);
// 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)
@@ -99,7 +100,7 @@ void VulkanBuffer::loadFromCpu(const void* cpuData, uint32_t byteOffset, uint32_
.size = VK_WHOLE_SIZE,
};
vkCmdPipelineBarrier(cmdbuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, dstStageMask, 0, 0, nullptr, 1,
vkCmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_TRANSFER_BIT, dstStageMask, 0, 0, nullptr, 1,
&barrier, 0, nullptr);
}
+8 -5
View File
@@ -25,15 +25,18 @@ namespace filament::backend {
// Encapsulates a Vulkan buffer, its attached DeviceMemory and a staging area.
class VulkanBuffer {
public:
VulkanBuffer(VmaAllocator allocator, VulkanCommands* commands, VulkanStagePool& stagePool,
VkBufferUsageFlags usage, uint32_t numBytes);
VulkanBuffer(VmaAllocator allocator, VulkanStagePool& stagePool, VkBufferUsageFlags usage,
uint32_t numBytes);
~VulkanBuffer();
void terminate();
void loadFromCpu(const void* cpuData, uint32_t byteOffset, uint32_t numBytes) const;
VkBuffer getGpuBuffer() const { return mGpuBuffer; }
void loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint32_t byteOffset,
uint32_t numBytes) const;
VkBuffer getGpuBuffer() const {
return mGpuBuffer;
}
private:
VmaAllocator mAllocator;
VulkanCommands* mCommands;
VulkanStagePool& mStagePool;
VmaAllocation mGpuMemory = VK_NULL_HANDLE;
+106 -66
View File
@@ -68,22 +68,35 @@ static VkCommandPool createPool(VkDevice device, uint32_t queueFamilyIndex) {
}
void VulkanGroupMarkers::push(std::string const& marker, Timestamp start) noexcept {
mMarkers.push(marker);
mMarkers.push_back(marker);
#if FILAMENT_VULKAN_VERBOSE
mTimestamps.push(start.time_since_epoch().count() > 0.0
mTimestamps.push_back(start.time_since_epoch().count() > 0.0
? start
: std::chrono::high_resolution_clock::now());
#endif
}
std::pair<std::string, Timestamp> VulkanGroupMarkers::pop() noexcept {
auto const marker = mMarkers.top();
mMarkers.pop();
auto const marker = mMarkers.back();
mMarkers.pop_back();
#if FILAMENT_VULKAN_VERBOSE
auto const topTimestamp = mTimestamps.top();
mTimestamps.pop();
return std::make_pair(marker, topTimestamp);
auto const timestamp = mTimestamps.back();
mTimestamps.pop_back();
return std::make_pair(marker, timestamp);
#else
return std::make_pair(marker, Timestamp{});
#endif
}
std::pair<std::string, Timestamp> VulkanGroupMarkers::pop_bottom() noexcept {
auto const marker = mMarkers.front();
mMarkers.pop_front();
#if FILAMENT_VULKAN_VERBOSE
auto const timestamp = mTimestamps.front();
mTimestamps.pop_front();
return std::make_pair(marker, timestamp);
#else
return std::make_pair(marker, Timestamp{});
#endif
@@ -91,7 +104,7 @@ std::pair<std::string, Timestamp> VulkanGroupMarkers::pop() noexcept {
std::pair<std::string, Timestamp> VulkanGroupMarkers::top() const {
assert_invariant(!empty());
auto const marker = mMarkers.top();
auto const marker = mMarkers.back();
#if FILAMENT_VULKAN_VERBOSE
auto const topTimestamp = mTimestamps.top();
return std::make_pair(marker, topTimestamp);
@@ -105,15 +118,20 @@ bool VulkanGroupMarkers::empty() const noexcept {
}
VulkanCommands::VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex,
VulkanContext* context)
VulkanContext* context, VulkanResourceAllocator* allocator)
: mDevice(device),
mQueue(queue),
mPool(createPool(mDevice, queueFamilyIndex)),
mContext(context) {
mContext(context),
mStorage(CAPACITY) {
VkSemaphoreCreateInfo sci{.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
for (auto& semaphore: mSubmissionSignals) {
vkCreateSemaphore(mDevice, &sci, nullptr, &semaphore);
}
for (size_t i = 0; i < CAPACITY; ++i) {
mStorage[i] = std::make_unique<VulkanCommandBuffer>(allocator);
}
}
VulkanCommands::~VulkanCommands() {
@@ -125,10 +143,9 @@ VulkanCommands::~VulkanCommands() {
}
}
VulkanCommandBuffer const& VulkanCommands::get(bool blockOnGC) {
if (mCurrent) {
mCurrent->blockOnGC = mCurrent->blockOnGC || blockOnGC;
return *mCurrent;
VulkanCommandBuffer& VulkanCommands::get() {
if (mCurrentCommandBufferIndex >= 0) {
return *mStorage[mCurrentCommandBufferIndex].get();
}
// If we ran out of available command buffers, stall until one finishes. This is very rare.
@@ -144,15 +161,18 @@ VulkanCommandBuffer const& VulkanCommands::get(bool blockOnGC) {
gc();
}
VulkanCommandBuffer* currentbuf = nullptr;
// Find an available slot.
for (VulkanCommandBuffer& wrapper : mStorage) {
if (wrapper.cmdbuffer == VK_NULL_HANDLE) {
mCurrent = &wrapper;
for (size_t i = 0; i < CAPACITY; ++i) {
auto wrapper = mStorage[i].get();
if (wrapper->cmdbuffer == VK_NULL_HANDLE) {
mCurrentCommandBufferIndex = static_cast<int8_t>(i);
currentbuf = wrapper;
break;
}
}
assert_invariant(mCurrent);
assert_invariant(currentbuf);
--mAvailableCount;
// Create the low-level command buffer.
@@ -162,47 +182,46 @@ VulkanCommandBuffer const& VulkanCommands::get(bool blockOnGC) {
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1
};
vkAllocateCommandBuffers(mDevice, &allocateInfo, &mCurrent->cmdbuffer);
mCurrent->blockOnGC = blockOnGC;
vkAllocateCommandBuffers(mDevice, &allocateInfo, &currentbuf->cmdbuffer);
// Note that the fence wrapper uses shared_ptr because a DriverAPI fence can also have ownership
// over it. The destruction of the low-level fence occurs either in VulkanCommands::gc(), or in
// VulkanDriver::destroyFence(), both of which are safe spots.
mCurrent->fence = std::make_shared<VulkanCmdFence>(mDevice);
currentbuf->fence = std::make_shared<VulkanCmdFence>(mDevice);
// Begin writing into the command buffer.
const VkCommandBufferBeginInfo binfo {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
};
vkBeginCommandBuffer(mCurrent->cmdbuffer, &binfo);
vkBeginCommandBuffer(currentbuf->cmdbuffer, &binfo);
// Notify the observer that a new command buffer has been activated.
if (mObserver) {
mObserver->onCommandBuffer(*mCurrent);
mObserver->onCommandBuffer(*currentbuf);
}
// We push the current markers onto a temporary stack. This must be placed after mCurrent is set
// to the new command buffer since pushGroupMarker also calls get().
// We push the current markers onto a temporary stack. This must be placed after currentbuf is
// set to the new command buffer since pushGroupMarker also calls get().
while (mCarriedOverMarkers && !mCarriedOverMarkers->empty()) {
auto [marker, time] = mCarriedOverMarkers->pop();
pushGroupMarker(marker.c_str(), time);
}
return *mCurrent;
return *currentbuf;
}
bool VulkanCommands::flush() {
// It's perfectly fine to call flush when no commands have been written.
if (mCurrent == nullptr) {
if (mCurrentCommandBufferIndex < 0) {
return false;
}
const int64_t index = mCurrent - &mStorage[0];
VkSemaphore renderingFinished = mSubmissionSignals[index];
int8_t const index = mCurrentCommandBufferIndex;
VulkanCommandBuffer const* currentbuf = mStorage[index].get();
VkSemaphore const renderingFinished = mSubmissionSignals[index];
vkEndCommandBuffer(mCurrent->cmdbuffer);
vkEndCommandBuffer(currentbuf->cmdbuffer);
// If the injected semaphore is an "image available" semaphore that has not yet been signaled,
// it is sometimes fine to start executing commands anyway, as along as we stall the GPU at the
@@ -226,7 +245,7 @@ bool VulkanCommands::flush() {
.pWaitSemaphores = signals,
.pWaitDstStageMask = waitDestStageMasks,
.commandBufferCount = 1,
.pCommandBuffers = &mCurrent->cmdbuffer,
.pCommandBuffers = &currentbuf->cmdbuffer,
.signalSemaphoreCount = 1u,
.pSignalSemaphores = &renderingFinished,
};
@@ -239,13 +258,18 @@ bool VulkanCommands::flush() {
signals[submitInfo.waitSemaphoreCount++] = mInjectedSignal;
}
if (FILAMENT_VULKAN_VERBOSE) {
slog.i << "Submitting cmdbuffer=" << mCurrent->cmdbuffer
<< " wait=(" << signals[0] << ", " << signals[1] << ") "
<< " signal=" << renderingFinished
<< io::endl;
// To address a validation warning.
if (submitInfo.waitSemaphoreCount == 0) {
submitInfo.pWaitSemaphores = VK_NULL_HANDLE;
}
#if FILAMENT_VULKAN_VERBOSE
slog.i << "Submitting cmdbuffer=" << currentbuf->cmdbuffer
<< " wait=(" << signals[0] << ", " << signals[1] << ") "
<< " signal=" << renderingFinished
<< io::endl;
#endif
// Before actually submitting, we need to pop any leftover group markers.
while (mGroupMarkers && !mGroupMarkers->empty()) {
if (!mCarriedOverMarkers) {
@@ -257,44 +281,51 @@ bool VulkanCommands::flush() {
popGroupMarker();
}
auto& cmdfence = mCurrent->fence;
auto& cmdfence = currentbuf->fence;
std::unique_lock<utils::Mutex> lock(cmdfence->mutex);
cmdfence->status.store(VK_NOT_READY);
UTILS_UNUSED_IN_RELEASE VkResult result = vkQueueSubmit(mQueue, 1, &submitInfo, cmdfence->fence);
cmdfence->condition.notify_all();
lock.unlock();
#if FILAMENT_VULKAN_VERBOSE
if (result != VK_SUCCESS) {
utils::slog.d <<"Failed command buffer submission result: " << result << utils::io::endl;
}
#endif
assert_invariant(result == VK_SUCCESS);
mSubmissionSignal = renderingFinished;
mInjectedSignal = VK_NULL_HANDLE;
mCurrent = nullptr;
mCurrentCommandBufferIndex = -1;
return true;
}
VkSemaphore VulkanCommands::acquireFinishedSignal() {
VkSemaphore semaphore = mSubmissionSignal;
mSubmissionSignal = VK_NULL_HANDLE;
if (FILAMENT_VULKAN_VERBOSE) {
slog.i << "Acquiring " << semaphore << " (e.g. for vkQueuePresentKHR)" << io::endl;
}
#if FILAMENT_VULKAN_VERBOSE
slog.i << "Acquiring " << semaphore << " (e.g. for vkQueuePresentKHR)" << io::endl;
#endif
return semaphore;
}
void VulkanCommands::injectDependency(VkSemaphore next) {
assert_invariant(mInjectedSignal == VK_NULL_HANDLE);
mInjectedSignal = next;
if (FILAMENT_VULKAN_VERBOSE) {
slog.i << "Injecting " << next << " (e.g. due to vkAcquireNextImageKHR)" << io::endl;
}
#if FILAMENT_VULKAN_VERBOSE
slog.i << "Injecting " << next << " (e.g. due to vkAcquireNextImageKHR)" << io::endl;
#endif
}
void VulkanCommands::wait() {
VkFence fences[CAPACITY];
uint32_t count = 0;
for (auto& wrapper : mStorage) {
if (wrapper.cmdbuffer != VK_NULL_HANDLE && mCurrent != &wrapper) {
fences[count++] = wrapper.fence->fence;
size_t count = 0;
for (size_t i = 0; i < CAPACITY; i++) {
auto wrapper = mStorage[i].get();
if (wrapper->cmdbuffer != VK_NULL_HANDLE
&& mCurrentCommandBufferIndex != static_cast<int8_t>(i)) {
fences[count++] = wrapper->fence->fence;
}
}
if (count > 0) {
@@ -303,26 +334,34 @@ void VulkanCommands::wait() {
}
void VulkanCommands::gc() {
for (auto& wrapper : mStorage) {
if (wrapper.cmdbuffer != VK_NULL_HANDLE) {
uint64_t const timeout = wrapper.blockOnGC ? UINT64_MAX : 0;
VkResult const result
= vkWaitForFences(mDevice, 1, &wrapper.fence->fence, VK_TRUE, timeout);
if (result == VK_SUCCESS) {
vkFreeCommandBuffers(mDevice, mPool, 1, &wrapper.cmdbuffer);
wrapper.cmdbuffer = VK_NULL_HANDLE;
wrapper.fence->status.store(VK_SUCCESS);
wrapper.fence.reset();
++mAvailableCount;
}
VkCommandBuffer buffers[CAPACITY];
size_t count = 0;
for (size_t i = 0; i < CAPACITY; i++) {
auto wrapper = mStorage[i].get();
if (wrapper->cmdbuffer == VK_NULL_HANDLE) {
continue;
}
VkResult const result = vkWaitForFences(mDevice, 1, &wrapper->fence->fence, VK_TRUE, 0);
if (result != VK_SUCCESS) {
continue;
}
buffers[count++] = wrapper->cmdbuffer;
wrapper->cmdbuffer = VK_NULL_HANDLE;
wrapper->fence->status.store(VK_SUCCESS);
wrapper->fence.reset();
wrapper->clearResources();
++mAvailableCount;
}
if (count > 0) {
vkFreeCommandBuffers(mDevice, mPool, count, buffers);
}
}
void VulkanCommands::updateFences() {
for (auto& wrapper : mStorage) {
if (wrapper.cmdbuffer != VK_NULL_HANDLE) {
VulkanCmdFence* fence = wrapper.fence.get();
for (size_t i = 0; i < CAPACITY; i++) {
auto wrapper = mStorage[i].get();
if (wrapper->cmdbuffer != VK_NULL_HANDLE) {
VulkanCmdFence* fence = wrapper->fence.get();
if (fence) {
VkResult status = vkGetFenceStatus(mDevice, fence->fence);
// This is either VK_SUCCESS, VK_NOT_READY, or VK_ERROR_DEVICE_LOST.
@@ -388,8 +427,9 @@ void VulkanCommands::popGroupMarker() {
}
} else if (mCarriedOverMarkers && !mCarriedOverMarkers->empty()) {
// It could be that pop is called between flush() and get() (new command buffer), in which
// case the marker is in "carried over" state. We'd just remove that
mCarriedOverMarkers->pop();
// case the marker is in "carried over" state, we'd just remove that. Since the
// mCarriedOverMarkers is in the opposite order, we pop the bottom instead of the top.
mCarriedOverMarkers->pop_bottom();
}
}
+30 -12
View File
@@ -22,14 +22,16 @@
#include "DriverBase.h"
#include "VulkanConstants.h"
#include "VulkanResources.h"
#include <utils/Condition.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Mutex.h>
#include <atomic>
#include <chrono>
#include <stack>
#include <list>
#include <string>
#include <utility>
@@ -43,13 +45,14 @@ public:
void push(std::string const& marker, Timestamp start = {}) noexcept;
std::pair<std::string, Timestamp> pop() noexcept;
std::pair<std::string, Timestamp> pop_bottom() noexcept;
std::pair<std::string, Timestamp> top() const;
bool empty() const noexcept;
private:
std::stack<std::string> mMarkers;
std::list<std::string> mMarkers;
#if FILAMENT_VULKAN_VERBOSE
std::stack<Timestamp> mTimestamps;
std::list<Timestamp> mTimestamps;
#endif
};
@@ -68,12 +71,28 @@ struct VulkanCmdFence {
// DriverApi fence object and should not be destroyed until both the DriverApi object is freed and
// we're done waiting on the most recent submission of the given command buffer.
struct VulkanCommandBuffer {
VulkanCommandBuffer() {}
VulkanCommandBuffer(VulkanResourceAllocator* allocator)
: mResourceManager(allocator) {}
VulkanCommandBuffer(VulkanCommandBuffer const&) = delete;
VulkanCommandBuffer& operator=(VulkanCommandBuffer const&) = delete;
VkCommandBuffer cmdbuffer = VK_NULL_HANDLE;
std::shared_ptr<VulkanCmdFence> fence;
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.
@@ -112,14 +131,11 @@ public:
class VulkanCommands {
public:
VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex,
VulkanContext* context);
VulkanContext* context, VulkanResourceAllocator* allocator);
~VulkanCommands();
// 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
// that dependent resources can be gc'd safetly after the buffer is sumbitted, completed,
// and gc'd.
VulkanCommandBuffer const& get(bool blockOnGC = false);
VulkanCommandBuffer& get();
// Submits the current command buffer if it exists, then sets "current" to null.
// If there are no outstanding commands then nothing happens and this returns false.
@@ -162,10 +178,12 @@ class VulkanCommands {
VkCommandPool const mPool;
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 mInjectedSignal = {};
VulkanCommandBuffer mStorage[CAPACITY] = {};
utils::FixedCapacityVector<std::unique_ptr<VulkanCommandBuffer>> mStorage;
VkSemaphore mSubmissionSignals[CAPACITY] = {};
size_t mAvailableCount = CAPACITY;
CommandBufferObserver* mObserver = nullptr;
+1 -2
View File
@@ -97,8 +97,7 @@ public:
}
flags >>= 1;
}
ASSERT_POSTCONDITION(false, "Unable to find a memory type that meets requirements.");
return (uint32_t) ~0ul;
return (uint32_t) VK_MAX_MEMORY_TYPES;
}
inline VkFormat getDepthFormat() const {
@@ -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
+214 -173
View File
@@ -94,7 +94,7 @@ VulkanTexture* createEmptyTexture(VkDevice device, VkPhysicalDevice physicalDevi
VulkanTexture* emptyTexture = new VulkanTexture(device, physicalDevice, context, allocator,
commands, SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, 1, 1, 1,
TextureUsage::DEFAULT | TextureUsage::COLOR_ATTACHMENT | TextureUsage::SUBPASS_INPUT,
stagePool);
stagePool, true /* heap allocated */);
uint32_t black = 0;
PixelBufferDescriptor pbd(&black, 4, PixelDataFormat::RGBA, PixelDataType::UBYTE);
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) {
utils::slog.e << "VULKAN ERROR: (" << pLayerPrefix << ") " << pMessage << utils::io::endl;
} 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.e << utils::io::endl;
@@ -122,8 +126,8 @@ VKAPI_ATTR VkBool32 VKAPI_CALL debugUtilsCallback(VkDebugUtilsMessageSeverityFla
<< utils::io::endl;
} else {
// TODO: emit best practices warnings about aggressive pipeline barriers.
if (strstr(cbdata->pMessage, "ALL_GRAPHICS_BIT") ||
strstr(cbdata->pMessage, "ALL_COMMANDS_BIT")) {
if (strstr(cbdata->pMessage, "ALL_GRAPHICS_BIT")
|| strstr(cbdata->pMessage, "ALL_COMMANDS_BIT")) {
return VK_FALSE;
}
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(),
mPlatform->getDevice())),
mContext(context),
mHandleAllocator(driverConfig.handleArenaSize),
mResourceAllocator(driverConfig.handleArenaSize),
mResourceManager(&mResourceAllocator),
mThreadSafeResourceManager(&mResourceAllocator),
mPipelineCache(&mResourceAllocator),
mBlitter(mStagePool, mPipelineCache, mFramebufferCache, mSamplerCache),
mReadPixels(mPlatform->getDevice()) {
@@ -183,7 +190,8 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
#endif
mTimestamps = std::make_unique<VulkanTimestamps>(mPlatform->getDevice());
mCommands = std::make_unique<VulkanCommands>(mPlatform->getDevice(),
mPlatform->getGraphicsQueue(), mPlatform->getGraphicsQueueFamilyIndex(), &mContext);
mPlatform->getGraphicsQueue(), mPlatform->getGraphicsQueueFamilyIndex(), &mContext,
&mResourceAllocator);
mCommands->setObserver(&mPipelineCache);
mPipelineCache.setDevice(mPlatform->getDevice(), mAllocator);
@@ -230,9 +238,8 @@ void VulkanDriver::terminate() {
mBlitter.terminate();
mReadPixels.terminate();
// Allow the stage pool and disposer to clean up.
// Allow the stage pool to clean up.
mStagePool.gc();
mDisposer.terminate();
mStagePool.terminate();
mPipelineCache.terminate();
@@ -261,10 +268,20 @@ void VulkanDriver::tick(int) {
void VulkanDriver::collectGarbage() {
// Command buffers need to be submitted and completed before other resources can be gc'd. And
// its gc() function carrys out the *wait*.
SYSTRACE_CONTEXT();
SYSTRACE_NAME_BEGIN("gc");
mCommands->gc();
SYSTRACE_NAME_BEGIN("stagepool-gc");
mStagePool.gc();
SYSTRACE_NAME_END();
SYSTRACE_NAME_BEGIN("framebuffercache-gc");
mFramebufferCache.gc();
mDisposer.gc();
SYSTRACE_NAME_END();
SYSTRACE_NAME_END(); // "gc"
}
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,
FrameCompletedCallback callback, void* user) {
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
}
void VulkanDriver::setPresentationTime(int64_t monotonic_clock_ns) {
@@ -300,86 +317,84 @@ void VulkanDriver::finish(int dummy) {
}
void VulkanDriver::createSamplerGroupR(Handle<HwSamplerGroup> sbh, uint32_t count) {
mHandleAllocator.construct<VulkanSamplerGroup>(sbh, count);
auto sg = mResourceAllocator.construct<VulkanSamplerGroup>(sbh, count);
mResourceManager.acquire(sg);
}
void VulkanDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph,
Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh,
PrimitiveType pt, uint32_t offset,
uint32_t minIndex, uint32_t maxIndex, uint32_t count) {
mHandleAllocator.construct<VulkanRenderPrimitive>(rph);
auto rp = mResourceAllocator.construct<VulkanRenderPrimitive>(rph, &mResourceAllocator);
mResourceManager.acquire(rp);
VulkanDriver::setRenderPrimitiveBuffer(rph, vbh, ibh);
VulkanDriver::setRenderPrimitiveRange(rph, pt, offset, minIndex, maxIndex, count);
}
void VulkanDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
if (rph) {
mHandleAllocator.destruct<VulkanRenderPrimitive>(rph);
if (!rph) {
return;
}
auto rp = mResourceAllocator.handle_cast<VulkanRenderPrimitive*>(rph);
mResourceManager.release(rp);
}
void VulkanDriver::createVertexBufferR(Handle<HwVertexBuffer> vbh, uint8_t bufferCount,
uint8_t attributeCount, uint32_t elementCount, AttributeArray attributes) {
auto vertexBuffer = mHandleAllocator.construct<VulkanVertexBuffer>(vbh, mContext, mStagePool,
bufferCount, attributeCount, elementCount, attributes);
mDisposer.createDisposable(vertexBuffer, [this, vbh]() {
mHandleAllocator.destruct<VulkanVertexBuffer>(vbh);
});
auto vertexBuffer = mResourceAllocator.construct<VulkanVertexBuffer>(vbh, mContext, mStagePool,
&mResourceAllocator, bufferCount, attributeCount, elementCount, attributes);
mResourceManager.acquire(vertexBuffer);
}
void VulkanDriver::destroyVertexBuffer(Handle<HwVertexBuffer> vbh) {
if (vbh) {
auto vertexBuffer = mHandleAllocator.handle_cast<VulkanVertexBuffer*>(vbh);
mDisposer.removeReference(vertexBuffer);
if (!vbh) {
return;
}
auto vertexBuffer = mResourceAllocator.handle_cast<VulkanVertexBuffer*>(vbh);
mResourceManager.release(vertexBuffer);
}
void VulkanDriver::createIndexBufferR(Handle<HwIndexBuffer> ibh, ElementType elementType,
uint32_t indexCount, BufferUsage usage) {
auto elementSize = (uint8_t) getElementTypeSize(elementType);
auto indexBuffer = mHandleAllocator.construct<VulkanIndexBuffer>(ibh, mAllocator,
mCommands.get(), mStagePool, elementSize, indexCount);
mDisposer.createDisposable(indexBuffer,
[this, ibh]() { mHandleAllocator.destruct<VulkanIndexBuffer>(ibh); });
auto indexBuffer = mResourceAllocator.construct<VulkanIndexBuffer>(ibh, mAllocator, mStagePool,
elementSize, indexCount);
mResourceManager.acquire(indexBuffer);
}
void VulkanDriver::destroyIndexBuffer(Handle<HwIndexBuffer> ibh) {
if (ibh) {
auto indexBuffer = mHandleAllocator.handle_cast<VulkanIndexBuffer*>(ibh);
mDisposer.removeReference(indexBuffer);
if (!ibh) {
return;
}
auto indexBuffer = mResourceAllocator.handle_cast<VulkanIndexBuffer*>(ibh);
mResourceManager.release(indexBuffer);
}
void VulkanDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byteCount,
BufferObjectBinding bindingType, BufferUsage usage) {
auto bufferObject = mHandleAllocator.construct<VulkanBufferObject>(boh, mAllocator,
mCommands.get(), mStagePool, byteCount, bindingType, usage);
mDisposer.createDisposable(bufferObject,
[this, boh]() { mHandleAllocator.destruct<VulkanBufferObject>(boh); });
auto bufferObject = mResourceAllocator.construct<VulkanBufferObject>(boh, mAllocator,
mStagePool, byteCount, bindingType, usage);
mResourceManager.acquire(bufferObject);
}
void VulkanDriver::destroyBufferObject(Handle<HwBufferObject> boh) {
if (boh) {
auto bufferObject = mHandleAllocator.handle_cast<VulkanBufferObject*>(boh);
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);
if (!boh) {
return;
}
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,
TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
TextureUsage usage) {
auto vktexture = mHandleAllocator.construct<VulkanTexture>(th, mPlatform->getDevice(),
auto vktexture = mResourceAllocator.construct<VulkanTexture>(th, mPlatform->getDevice(),
mPlatform->getPhysicalDevice(), mContext, mAllocator, mCommands.get(), target, levels,
format, samples, w, h, depth, usage, mStagePool);
mDisposer.createDisposable(vktexture,
[this, th]() { mHandleAllocator.destruct<VulkanTexture>(th); });
mResourceManager.acquire(vktexture);
}
void VulkanDriver::createTextureSwizzledR(Handle<HwTexture> th, SamplerType target, uint8_t levels,
@@ -388,12 +403,10 @@ void VulkanDriver::createTextureSwizzledR(Handle<HwTexture> th, SamplerType targ
TextureSwizzle r, TextureSwizzle g, TextureSwizzle b, TextureSwizzle a) {
TextureSwizzle swizzleArray[] = {r, g, b, a};
const VkComponentMapping swizzleMap = getSwizzleMap(swizzleArray);
auto vktexture = mHandleAllocator.construct<VulkanTexture>(th, mPlatform->getDevice(),
auto vktexture = mResourceAllocator.construct<VulkanTexture>(th, mPlatform->getDevice(),
mPlatform->getPhysicalDevice(), mContext, mAllocator, mCommands.get(), target, levels,
format, samples, w, h, depth, usage, mStagePool, swizzleMap);
mDisposer.createDisposable(vktexture, [this, th]() {
mHandleAllocator.destruct<VulkanTexture>(th);
});
format, samples, w, h, depth, usage, mStagePool, false /*heap allocated */, swizzleMap);
mResourceManager.acquire(vktexture);
}
void VulkanDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
@@ -404,33 +417,31 @@ void VulkanDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
}
void VulkanDriver::destroyTexture(Handle<HwTexture> th) {
if (th) {
auto texture = mHandleAllocator.handle_cast<VulkanTexture*>(th);
mPipelineCache.unbindImageView(texture->getPrimaryImageView());
mDisposer.removeReference(texture);
if (!th) {
return;
}
auto texture = mResourceAllocator.handle_cast<VulkanTexture*>(th);
mResourceManager.release(texture);
}
void VulkanDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
auto vkprogram = mHandleAllocator.construct<VulkanProgram>(ph, mPlatform->getDevice(), program);
mDisposer.createDisposable(vkprogram, [this, ph] () {
mHandleAllocator.destruct<VulkanProgram>(ph);
});
auto vkprogram = mResourceAllocator.construct<VulkanProgram>(ph, mPlatform->getDevice(), program);
mResourceManager.acquire(vkprogram);
}
void VulkanDriver::destroyProgram(Handle<HwProgram> ph) {
if (ph) {
mDisposer.removeReference(mHandleAllocator.handle_cast<VulkanProgram*>(ph));
if (!ph) {
return;
}
auto vkprogram = mResourceAllocator.handle_cast<VulkanProgram*>(ph);
mResourceManager.release(vkprogram);
}
void VulkanDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int) {
assert_invariant(mDefaultRenderTarget == nullptr);
VulkanRenderTarget* renderTarget = mHandleAllocator.construct<VulkanRenderTarget>(rth);
VulkanRenderTarget* renderTarget = mResourceAllocator.construct<VulkanRenderTarget>(rth);
mDefaultRenderTarget = renderTarget;
mDisposer.createDisposable(renderTarget, [this, rth] () {
mHandleAllocator.destruct<VulkanRenderTarget>(rth);
});
mResourceManager.acquire(renderTarget);
}
void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
@@ -444,7 +455,7 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
if (color[i].handle) {
colorTargets[i] = {
.texture = mHandleAllocator.handle_cast<VulkanTexture*>(color[i].handle),
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(color[i].handle),
.level = color[i].level,
.layer = color[i].layer,
};
@@ -458,7 +469,7 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
VulkanAttachment depthStencil[2] = {};
if (depth.handle) {
depthStencil[0] = {
.texture = mHandleAllocator.handle_cast<VulkanTexture*>(depth.handle),
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(depth.handle),
.level = depth.level,
.layer = depth.layer,
};
@@ -470,7 +481,7 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
if (stencil.handle) {
depthStencil[1] = {
.texture = mHandleAllocator.handle_cast<VulkanTexture*>(stencil.handle),
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(stencil.handle),
.level = stencil.level,
.layer = stencil.layer,
};
@@ -486,26 +497,27 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
assert_invariant(tmin == tmax);
assert_invariant(tmin.x >= width && tmin.y >= height);
auto renderTarget = mHandleAllocator.construct<VulkanRenderTarget>(rth, mPlatform->getDevice(),
auto renderTarget = mResourceAllocator.construct<VulkanRenderTarget>(rth, mPlatform->getDevice(),
mPlatform->getPhysicalDevice(), mContext, mAllocator, mCommands.get(), width, height,
samples, colorTargets, depthStencil, mStagePool);
mDisposer.createDisposable(renderTarget,
[this, rth]() { mHandleAllocator.destruct<VulkanRenderTarget>(rth); });
mResourceManager.acquire(renderTarget);
}
void VulkanDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
if (rth) {
VulkanRenderTarget* rt = mHandleAllocator.handle_cast<VulkanRenderTarget*>(rth);
if (UTILS_UNLIKELY(rt == mDefaultRenderTarget)) {
mDefaultRenderTarget = nullptr;
}
mDisposer.removeReference(rt);
if (!rth) {
return;
}
VulkanRenderTarget* rt = mResourceAllocator.handle_cast<VulkanRenderTarget*>(rth);
if (UTILS_UNLIKELY(rt == mDefaultRenderTarget)) {
mDefaultRenderTarget = nullptr;
}
mResourceManager.release(rt);
}
void VulkanDriver::createFenceR(Handle<HwFence> fh, int) {
VulkanCommandBuffer const& commandBuffer = mCommands->get();
mHandleAllocator.construct<VulkanFence>(fh, commandBuffer.fence);
mResourceAllocator.construct<VulkanFence>(fh, commandBuffer.fence);
}
void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
@@ -514,7 +526,7 @@ void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
<< utils::io::endl;
flags = flags | ~(backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE);
}
mHandleAllocator.construct<VulkanSwapChain>(sch, mPlatform, mContext, mAllocator,
mResourceAllocator.construct<VulkanSwapChain>(sch, mPlatform, mContext, mAllocator,
mCommands.get(), mStagePool, nativeWindow, flags);
}
@@ -527,7 +539,7 @@ void VulkanDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t wi
flags = flags | ~(backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE);
}
assert_invariant(width > 0 && height > 0 && "Vulkan requires non-zero swap chain dimensions.");
mHandleAllocator.construct<VulkanSwapChain>(sch, mPlatform, mContext, mAllocator,
mResourceAllocator.construct<VulkanSwapChain>(sch, mPlatform, mContext, mAllocator,
mCommands.get(), mStagePool, nullptr, flags, VkExtent2D{width, height});
}
@@ -536,106 +548,108 @@ void VulkanDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
}
Handle<HwVertexBuffer> VulkanDriver::createVertexBufferS() noexcept {
return mHandleAllocator.allocHandle<VulkanVertexBuffer>();
return mResourceAllocator.allocHandle<VulkanVertexBuffer>();
}
Handle<HwIndexBuffer> VulkanDriver::createIndexBufferS() noexcept {
return mHandleAllocator.allocHandle<VulkanIndexBuffer>();
return mResourceAllocator.allocHandle<VulkanIndexBuffer>();
}
Handle<HwBufferObject> VulkanDriver::createBufferObjectS() noexcept {
return mHandleAllocator.allocHandle<VulkanBufferObject>();
return mResourceAllocator.allocHandle<VulkanBufferObject>();
}
Handle<HwTexture> VulkanDriver::createTextureS() noexcept {
return mHandleAllocator.allocHandle<VulkanTexture>();
return mResourceAllocator.allocHandle<VulkanTexture>();
}
Handle<HwTexture> VulkanDriver::createTextureSwizzledS() noexcept {
return mHandleAllocator.allocHandle<VulkanTexture>();
return mResourceAllocator.allocHandle<VulkanTexture>();
}
Handle<HwTexture> VulkanDriver::importTextureS() noexcept {
return mHandleAllocator.allocHandle<VulkanTexture>();
return mResourceAllocator.allocHandle<VulkanTexture>();
}
Handle<HwSamplerGroup> VulkanDriver::createSamplerGroupS() noexcept {
return mHandleAllocator.allocHandle<VulkanSamplerGroup>();
return mResourceAllocator.allocHandle<VulkanSamplerGroup>();
}
Handle<HwRenderPrimitive> VulkanDriver::createRenderPrimitiveS() noexcept {
return mHandleAllocator.allocHandle<VulkanRenderPrimitive>();
return mResourceAllocator.allocHandle<VulkanRenderPrimitive>();
}
Handle<HwProgram> VulkanDriver::createProgramS() noexcept {
return mHandleAllocator.allocHandle<VulkanProgram>();
return mResourceAllocator.allocHandle<VulkanProgram>();
}
Handle<HwRenderTarget> VulkanDriver::createDefaultRenderTargetS() noexcept {
return mHandleAllocator.allocHandle<VulkanRenderTarget>();
return mResourceAllocator.allocHandle<VulkanRenderTarget>();
}
Handle<HwRenderTarget> VulkanDriver::createRenderTargetS() noexcept {
return mHandleAllocator.allocHandle<VulkanRenderTarget>();
return mResourceAllocator.allocHandle<VulkanRenderTarget>();
}
Handle<HwFence> VulkanDriver::createFenceS() noexcept {
return mHandleAllocator.initHandle<VulkanFence>();
return mResourceAllocator.initHandle<VulkanFence>();
}
Handle<HwSwapChain> VulkanDriver::createSwapChainS() noexcept {
return mHandleAllocator.allocHandle<VulkanSwapChain>();
return mResourceAllocator.allocHandle<VulkanSwapChain>();
}
Handle<HwSwapChain> VulkanDriver::createSwapChainHeadlessS() noexcept {
return mHandleAllocator.allocHandle<VulkanSwapChain>();
return mResourceAllocator.allocHandle<VulkanSwapChain>();
}
Handle<HwTimerQuery> VulkanDriver::createTimerQueryS() noexcept {
// The handle must be constructed here, as a synchronous call to getTimerQueryValue might happen
// before createTimerQueryR is executed.
Handle<HwTimerQuery> tqh
= mHandleAllocator.initHandle<VulkanTimerQuery>(mTimestamps->getNextQuery());
auto query = mHandleAllocator.handle_cast<VulkanTimerQuery*>(tqh);
mDisposer.createDisposable(query, [this, tqh] () {
mHandleAllocator.destruct<VulkanTimerQuery>(tqh);
});
= mResourceAllocator.initHandle<VulkanTimerQuery>(mTimestamps->getNextQuery());
auto query = mResourceAllocator.handle_cast<VulkanTimerQuery*>(tqh);
mThreadSafeResourceManager.acquire(query);
return tqh;
}
void VulkanDriver::destroySamplerGroup(Handle<HwSamplerGroup> sbh) {
if (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 = mHandleAllocator.handle_cast<VulkanSamplerGroup*>(sbh);
for (auto& binding : mSamplerBindings) {
if (binding == hwsb) {
binding = nullptr;
}
}
mHandleAllocator.destruct<VulkanSamplerGroup>(sbh);
if (!sbh) {
return;
}
// 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) {
if (sch) {
VulkanSwapChain& swapChain = *mHandleAllocator.handle_cast<VulkanSwapChain*>(sch);
if (mCurrentSwapChain == &swapChain) {
mCurrentSwapChain = nullptr;
}
mHandleAllocator.destruct<VulkanSwapChain>(sch);
if (!sch) {
return;
}
VulkanSwapChain* swapChain = mResourceAllocator.handle_cast<VulkanSwapChain*>(sch);
if (mCurrentSwapChain == swapChain) {
mCurrentSwapChain = nullptr;
}
mResourceManager.release(swapChain);
}
void VulkanDriver::destroyStream(Handle<HwStream> sh) {
}
void VulkanDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
if (tqh) {
mDisposer.removeReference(mHandleAllocator.handle_cast<VulkanTimerQuery*>(tqh));
if (!tqh) {
return;
}
auto vtq = mResourceAllocator.handle_cast<VulkanTimerQuery*>(tqh);
mThreadSafeResourceManager.release(vtq);
}
Handle<HwStream> VulkanDriver::createStreamNative(void* nativeStream) {
@@ -661,11 +675,11 @@ void VulkanDriver::updateStreams(CommandStream* driver) {
}
void VulkanDriver::destroyFence(Handle<HwFence> fh) {
mHandleAllocator.destruct<VulkanFence>(fh);
mResourceAllocator.destruct<VulkanFence>(fh);
}
FenceStatus VulkanDriver::getFenceStatus(Handle<HwFence> fh) {
auto& cmdfence = mHandleAllocator.handle_cast<VulkanFence*>(fh)->fence;
auto& cmdfence = mResourceAllocator.handle_cast<VulkanFence*>(fh)->fence;
if (!cmdfence) {
// 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
@@ -755,6 +769,14 @@ bool VulkanDriver::isSRGBSwapChainSupported() {
return mPlatform->isSRGBSwapChainSupported();
}
bool VulkanDriver::isStereoSupported() {
return true;
}
bool VulkanDriver::isParallelShaderCompileSupported() {
return false;
}
bool VulkanDriver::isWorkaroundNeeded(Workaround workaround) {
switch (workaround) {
case Workaround::SPLIT_EASU: {
@@ -838,34 +860,42 @@ size_t VulkanDriver::getMaxUniformBufferSize() {
void VulkanDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t index,
Handle<HwBufferObject> boh) {
auto& vb = *mHandleAllocator.handle_cast<VulkanVertexBuffer*>(vbh);
auto& bo = *mHandleAllocator.handle_cast<VulkanBufferObject*>(boh);
assert_invariant(bo.bindingType == BufferObjectBinding::VERTEX);
vb.buffers[index] = &bo.buffer;
auto vb = mResourceAllocator.handle_cast<VulkanVertexBuffer*>(vbh);
auto bo = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
assert_invariant(bo->bindingType == BufferObjectBinding::VERTEX);
vb->setBuffer(bo, index);
}
void VulkanDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&& p,
uint32_t byteOffset) {
auto ib = mHandleAllocator.handle_cast<VulkanIndexBuffer*>(ibh);
ib->buffer.loadFromCpu(p.buffer, byteOffset, p.size);
mDisposer.acquire(ib);
VulkanCommandBuffer& commands = mCommands->get();
auto ib = mResourceAllocator.handle_cast<VulkanIndexBuffer*>(ibh);
commands.acquire(ib);
ib->buffer.loadFromCpu(commands.cmdbuffer, p.buffer, byteOffset, p.size);
scheduleDestroy(std::move(p));
}
void VulkanDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescriptor&& bd,
uint32_t byteOffset) {
auto bo = mHandleAllocator.handle_cast<VulkanBufferObject*>(boh);
bo->buffer.loadFromCpu(bd.buffer, byteOffset, bd.size);
mDisposer.acquire(bo);
VulkanCommandBuffer& commands = mCommands->get();
auto bo = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
commands.acquire(bo);
bo->buffer.loadFromCpu(commands.cmdbuffer, bd.buffer, byteOffset, bd.size);
scheduleDestroy(std::move(bd));
}
void VulkanDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> boh,
BufferDescriptor&& bd, uint32_t byteOffset) {
auto bo = mHandleAllocator.handle_cast<VulkanBufferObject*>(boh);
VulkanCommandBuffer& commands = mCommands->get();
auto bo = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
commands.acquire(bo);
// TODO: implement unsynchronized version
bo->buffer.loadFromCpu(bd.buffer, byteOffset, bd.size);
mDisposer.acquire(bo);
bo->buffer.loadFromCpu(commands.cmdbuffer, bd.buffer, byteOffset, bd.size);
mResourceManager.acquire(bo);
scheduleDestroy(std::move(bd));
}
@@ -879,13 +909,13 @@ void VulkanDriver::resetBufferObject(Handle<HwBufferObject> boh) {
}
void VulkanDriver::setMinMaxLevels(Handle<HwTexture> th, uint32_t minLevel, uint32_t maxLevel) {
mHandleAllocator.handle_cast<VulkanTexture*>(th)->setPrimaryRange(minLevel, maxLevel);
mResourceAllocator.handle_cast<VulkanTexture*>(th)->setPrimaryRange(minLevel, maxLevel);
}
void VulkanDriver::update3DImage(Handle<HwTexture> th, uint32_t level, uint32_t xoffset,
uint32_t yoffset, uint32_t zoffset, uint32_t width, uint32_t height, uint32_t depth,
PixelBufferDescriptor&& data) {
mHandleAllocator.handle_cast<VulkanTexture*>(th)->updateImage(data, width, height, depth,
mResourceAllocator.handle_cast<VulkanTexture*>(th)->updateImage(data, width, height, depth,
xoffset, yoffset, zoffset, level);
scheduleDestroy(std::move(data));
}
@@ -894,7 +924,7 @@ void VulkanDriver::setupExternalImage(void* image) {
}
bool VulkanDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsedTime) {
VulkanTimerQuery* vtq = mHandleAllocator.handle_cast<VulkanTimerQuery*>(tqh);
VulkanTimerQuery* vtq = mResourceAllocator.handle_cast<VulkanTimerQuery*>(tqh);
if (!vtq->isCompleted()) {
return false;
}
@@ -938,7 +968,7 @@ bool VulkanDriver::canGenerateMipmaps() {
void VulkanDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
BufferDescriptor&& data) {
auto* sb = mHandleAllocator.handle_cast<VulkanSamplerGroup*>(sbh);
auto* sb = mResourceAllocator.handle_cast<VulkanSamplerGroup*>(sbh);
// 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.
@@ -960,7 +990,7 @@ void VulkanDriver::compilePrograms(CompilerPriorityQueue priority,
}
void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassParams& params) {
VulkanRenderTarget* const rt = mHandleAllocator.handle_cast<VulkanRenderTarget*>(rth);
VulkanRenderTarget* const rt = mResourceAllocator.handle_cast<VulkanRenderTarget*>(rth);
const VkExtent2D extent = rt->getExtent();
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
// the non-sampling case.
bool samplingDepthAttachment = false;
VkCommandBuffer const cmdbuffer = mCommands->get().cmdbuffer;
VulkanCommandBuffer& commands = mCommands->get();
VkCommandBuffer const cmdbuffer = commands.cmdbuffer;
UTILS_NOUNROLL
for (uint8_t samplerGroupIdx = 0; samplerGroupIdx < Program::SAMPLER_BINDING_COUNT;
@@ -1003,7 +1034,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
SamplerDescriptor const* boundSampler = sb->data() + i;
if (UTILS_LIKELY(boundSampler->t)) {
VulkanTexture* texture
= mHandleAllocator.handle_cast<VulkanTexture*>(boundSampler->t);
= mResourceAllocator.handle_cast<VulkanTexture*>(boundSampler->t);
if (!any(texture->usage & TextureUsage::DEPTH_ATTACHMENT)) {
continue;
}
@@ -1019,6 +1050,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
.baseArrayLayer = 0,
.layerCount = texture->depth,
};
commands.acquire(texture);
texture->transitionLayout(cmdbuffer, subresources, VulkanLayout::DEPTH_SAMPLER);
break;
}
@@ -1137,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.
// Note that we must acquire parent textures, not sidecars.
mDisposer.acquire(rt);
mDisposer.acquire((VulkanTexture const*) rt->getDepth().texture);
commands.acquire(rt);
if (depth.texture) {
commands.acquire((VulkanTexture*) depth.texture);
}
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.
@@ -1209,7 +1245,8 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
}
void VulkanDriver::endRenderPass(int) {
VkCommandBuffer cmdbuffer = mCommands->get().cmdbuffer;
VulkanCommandBuffer& commands = mCommands->get();
VkCommandBuffer cmdbuffer = commands.cmdbuffer;
vkCmdEndRenderPass(cmdbuffer);
VulkanRenderTarget* rt = mCurrentRenderPass.renderTarget;
@@ -1280,15 +1317,15 @@ void VulkanDriver::nextSubpass(int) {
void VulkanDriver::setRenderPrimitiveBuffer(Handle<HwRenderPrimitive> rph,
Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh) {
auto primitive = mHandleAllocator.handle_cast<VulkanRenderPrimitive*>(rph);
primitive->setBuffers(mHandleAllocator.handle_cast<VulkanVertexBuffer*>(vbh),
mHandleAllocator.handle_cast<VulkanIndexBuffer*>(ibh));
auto primitive = mResourceAllocator.handle_cast<VulkanRenderPrimitive*>(rph);
primitive->setBuffers(mResourceAllocator.handle_cast<VulkanVertexBuffer*>(vbh),
mResourceAllocator.handle_cast<VulkanIndexBuffer*>(ibh));
}
void VulkanDriver::setRenderPrimitiveRange(Handle<HwRenderPrimitive> rph,
PrimitiveType pt, uint32_t offset,
uint32_t minIndex, uint32_t maxIndex, uint32_t count) {
auto& primitive = *mHandleAllocator.handle_cast<VulkanRenderPrimitive*>(rph);
auto& primitive = *mResourceAllocator.handle_cast<VulkanRenderPrimitive*>(rph);
primitive.setPrimitiveType(pt);
primitive.offset = offset * primitive.indexBuffer->elementSize;
primitive.count = count;
@@ -1298,8 +1335,9 @@ void VulkanDriver::setRenderPrimitiveRange(Handle<HwRenderPrimitive> rph,
void VulkanDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain> readSch) {
ASSERT_PRECONDITION_NON_FATAL(drawSch == readSch,
"Vulkan driver does not support distinct draw/read swap chains.");
VulkanSwapChain* swapChain = mCurrentSwapChain = mHandleAllocator.handle_cast<VulkanSwapChain*>(drawSch);
"Vulkan driver does not support distinct draw/read swap chains.");
VulkanSwapChain* swapChain = mCurrentSwapChain
= mResourceAllocator.handle_cast<VulkanSwapChain*>(drawSch);
bool resized = false;
swapChain->acquire(resized);
@@ -1314,7 +1352,7 @@ void VulkanDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain>
}
void VulkanDriver::commit(Handle<HwSwapChain> sch) {
VulkanSwapChain* swapChain = mHandleAllocator.handle_cast<VulkanSwapChain*>(sch);
VulkanSwapChain* swapChain = mResourceAllocator.handle_cast<VulkanSwapChain*>(sch);
if (mCommands->flush()) {
collectGarbage();
@@ -1325,10 +1363,10 @@ void VulkanDriver::commit(Handle<HwSwapChain> sch) {
}
void VulkanDriver::bindUniformBuffer(uint32_t index, Handle<HwBufferObject> boh) {
auto* bo = mHandleAllocator.handle_cast<VulkanBufferObject*>(boh);
auto* bo = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
const VkDeviceSize offset = 0;
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,
@@ -1339,8 +1377,8 @@ void VulkanDriver::bindBufferRange(BufferObjectBinding bindingType, uint32_t ind
// TODO: implement BufferObjectBinding::SHADER_STORAGE case
auto* bo = mHandleAllocator.handle_cast<VulkanBufferObject*>(boh);
mPipelineCache.bindUniformBuffer((uint32_t)index, bo->buffer.getGpuBuffer(), offset, size);
auto* bo = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
mPipelineCache.bindUniformBufferObject((uint32_t)index, bo, offset, size);
}
void VulkanDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index) {
@@ -1348,7 +1386,7 @@ void VulkanDriver::unbindBuffer(BufferObjectBinding bindingType, uint32_t index)
}
void VulkanDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
auto* hwsb = mHandleAllocator.handle_cast<VulkanSamplerGroup*>(sbh);
auto* hwsb = mResourceAllocator.handle_cast<VulkanSamplerGroup*>(sbh);
mSamplerBindings[index] = hwsb;
}
@@ -1379,12 +1417,13 @@ void VulkanDriver::stopCapture(int) {}
void VulkanDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y, uint32_t width,
uint32_t height, PixelBufferDescriptor&& pbd) {
VulkanRenderTarget* srcTarget = mHandleAllocator.handle_cast<VulkanRenderTarget*>(src);
VulkanRenderTarget* srcTarget = mResourceAllocator.handle_cast<VulkanRenderTarget*>(src);
mCommands->flush();
mReadPixels.run(
srcTarget, x, y, width, height, mPlatform->getGraphicsQueueFamilyIndex(),
std::move(pbd),
[&context = mContext](uint32_t reqs, VkFlags flags) {
utils::slog.d <<"read pixels: reqs=" << reqs <<" flags=" << flags << utils::io::endl;
return context.selectMemoryType(reqs, flags);
},
[this](PixelBufferDescriptor&& pbd) {
@@ -1411,8 +1450,8 @@ void VulkanDriver::blit(TargetBufferFlags buffers, Handle<HwRenderTarget> dst, V
return;
}
VulkanRenderTarget* dstTarget = mHandleAllocator.handle_cast<VulkanRenderTarget*>(dst);
VulkanRenderTarget* srcTarget = mHandleAllocator.handle_cast<VulkanRenderTarget*>(src);
VulkanRenderTarget* dstTarget = mResourceAllocator.handle_cast<VulkanRenderTarget*>(dst);
VulkanRenderTarget* srcTarget = mResourceAllocator.handle_cast<VulkanRenderTarget*>(src);
VkFilter vkfilter = filter == SamplerMagFilter::NEAREST ? VK_FILTER_NEAREST : VK_FILTER_LINEAR;
@@ -1443,19 +1482,19 @@ void VulkanDriver::blit(TargetBufferFlags buffers, Handle<HwRenderTarget> dst, V
void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> rph,
const uint32_t instanceCount) {
VulkanCommandBuffer const* commands = &mCommands->get();
VulkanCommandBuffer* commands = &mCommands->get();
VkCommandBuffer cmdbuffer = commands->cmdbuffer;
const VulkanRenderPrimitive& prim = *mHandleAllocator.handle_cast<VulkanRenderPrimitive*>(rph);
const VulkanRenderPrimitive& prim = *mResourceAllocator.handle_cast<VulkanRenderPrimitive*>(rph);
Handle<HwProgram> programHandle = pipelineState.program;
RasterState rasterState = pipelineState.rasterState;
PolygonOffset depthOffset = pipelineState.polygonOffset;
const Viewport& scissorBox = pipelineState.scissor;
Viewport const& scissorBox = pipelineState.scissor;
auto* program = mHandleAllocator.handle_cast<VulkanProgram*>(programHandle);
mDisposer.acquire(program);
mDisposer.acquire(prim.indexBuffer);
mDisposer.acquire(prim.vertexBuffer);
auto* program = mResourceAllocator.handle_cast<VulkanProgram*>(programHandle);
commands->acquire(program);
commands->acquire(prim.indexBuffer);
commands->acquire(prim.vertexBuffer);
// If this is a debug build, validate the current shader.
#if !defined(NDEBUG)
@@ -1545,6 +1584,8 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
// Filament concept used to form groups of samplers.
VkDescriptorImageInfo samplerInfo[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {};
VulkanTexture* samplerTextures[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {nullptr};
VulkanPipelineCache::UsageFlags usage;
UTILS_NOUNROLL
@@ -1566,8 +1607,7 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
samplerIdx++;
if (UTILS_LIKELY(boundSampler->t)) {
VulkanTexture* texture = mHandleAllocator.handle_cast<VulkanTexture*>(boundSampler->t);
mDisposer.acquire(texture);
VulkanTexture* texture = mResourceAllocator.handle_cast<VulkanTexture*>(boundSampler->t);
// TODO: can this uninitialized check be checked in a higher layer?
// This fallback path is very flaky because the dummy texture might not have
@@ -1578,7 +1618,6 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
utils::slog.w << " in material '" << program->name.c_str() << "'";
utils::slog.w << " at binding point " << +sampler.binding << utils::io::endl;
#endif
// Calling get() won't leak here since `texture` is local.
texture = mEmptyTexture.get();
}
@@ -1592,11 +1631,12 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
.imageView = texture->getPrimaryImageView(),
.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.
// If descriptor set allocation failed, skip the draw call and bail. No need to emit an error
@@ -1629,7 +1669,7 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
// 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
// validation layer will already do so.
if (!mPipelineCache.bindPipeline(cmdbuffer)) {
if (!mPipelineCache.bindPipeline(commands)) {
return;
}
@@ -1645,6 +1685,7 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
const uint32_t firstIndex = prim.offset / prim.indexBuffer->elementSize;
const int32_t vertexOffset = 0;
const uint32_t firstInstId = 0;
vkCmdDrawIndexed(cmdbuffer, indexCount, instanceCount, firstIndex, vertexOffset, firstInstId);
}
@@ -1653,12 +1694,12 @@ void VulkanDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGr
}
void VulkanDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
VulkanTimerQuery* vtq = mHandleAllocator.handle_cast<VulkanTimerQuery*>(tqh);
VulkanTimerQuery* vtq = mResourceAllocator.handle_cast<VulkanTimerQuery*>(tqh);
mTimestamps->beginQuery(&(mCommands->get()), vtq);
}
void VulkanDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
VulkanTimerQuery* vtq = mHandleAllocator.handle_cast<VulkanTimerQuery*>(tqh);
VulkanTimerQuery* vtq = mResourceAllocator.handle_cast<VulkanTimerQuery*>(tqh);
mTimestamps->endQuery(&(mCommands->get()), vtq);
}
+8 -58
View File
@@ -20,18 +20,17 @@
#include "VulkanBlitter.h"
#include "VulkanConstants.h"
#include "VulkanContext.h"
#include "VulkanDisposer.h"
#include "VulkanFboCache.h"
#include "VulkanHandles.h"
#include "VulkanPipelineCache.h"
#include "VulkanReadPixels.h"
#include "VulkanResourceAllocator.h"
#include "VulkanSamplerCache.h"
#include "VulkanStagePool.h"
#include "VulkanUtility.h"
#include "DriverBase.h"
#include "private/backend/Driver.h"
#include "private/backend/HandleAllocator.h"
#include <utils/Allocator.h>
#include <utils/compiler.h>
@@ -41,60 +40,6 @@ namespace filament::backend {
class VulkanPlatform;
struct VulkanSamplerGroup;
class VulkanHandleAllocator {
public:
VulkanHandleAllocator(size_t arenaSize)
: mHandleAllocatorImpl("Handles", arenaSize) {}
template<typename D, typename... ARGS>
inline Handle<D> initHandle(ARGS&&... args) noexcept {
return mHandleAllocatorImpl.allocateAndConstruct<D>(std::forward<ARGS>(args)...);
}
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 {
return mHandleAllocatorImpl.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>
inline void destruct(Handle<B> handle, D const* p) noexcept {
return mHandleAllocatorImpl.deallocate(handle, p);
}
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 {
if constexpr (std::is_base_of_v<VulkanIndexBuffer, D>
|| std::is_base_of_v<VulkanBufferObject, D>) {
auto ptr = handle_cast<D*>(handle);
ptr->terminate();
}
destruct(handle, handle_cast<D const*>(handle));
}
HandleAllocatorVK mHandleAllocatorImpl;
};
class VulkanDriver final : public DriverBase {
public:
static Driver* create(VulkanPlatform* platform, VulkanContext const& context,
@@ -153,9 +98,14 @@ private:
VkDebugUtilsMessengerEXT mDebugMessenger = VK_NULL_HANDLE;
VulkanContext mContext = {};
VulkanHandleAllocator mHandleAllocator;
VulkanResourceAllocator mResourceAllocator;
VulkanResourceManager mResourceManager;
// Used for resources that are created synchronously and used and destroyed on the backend
// thread.
VulkanThreadSafeResourceManager mThreadSafeResourceManager;
VulkanPipelineCache mPipelineCache;
VulkanDisposer mDisposer;
VulkanStagePool mStagePool;
VulkanFboCache mFramebufferCache;
VulkanSamplerCache mSamplerCache;
+44 -23
View File
@@ -46,10 +46,12 @@ static void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeig
rect->extent.height = std::max(top - y, 0);
}
VulkanProgram::VulkanProgram(VkDevice device, const Program& builder) noexcept :
HwProgram(builder.getName()), mDevice(device) {
VulkanProgram::VulkanProgram(VkDevice device, const Program& builder) noexcept
: HwProgram(builder.getName()),
VulkanResource(VulkanResourceType::PROGRAM),
mDevice(device) {
auto const& blobs = builder.getShadersSource();
VkShaderModule* modules[2] = { &bundle.vertex, &bundle.fragment };
VkShaderModule* modules[2] = {&bundle.vertex, &bundle.fragment};
// TODO: handle compute shaders.
for (size_t i = 0; i < 2; i++) {
const auto& blob = blobs[i];
@@ -113,8 +115,9 @@ VulkanProgram::VulkanProgram(VkDevice device, const Program& builder) noexcept :
}
}
VulkanProgram::VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs) noexcept :
mDevice(device) {
VulkanProgram::VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs) noexcept
: VulkanResource(VulkanResourceType::PROGRAM),
mDevice(device) {
bundle.vertex = vs;
bundle.fragment = fs;
}
@@ -126,7 +129,10 @@ VulkanProgram::~VulkanProgram() {
}
// Creates a special "default" render target (i.e. associated with the swap chain)
VulkanRenderTarget::VulkanRenderTarget() : HwRenderTarget(0, 0), mOffscreen(false), mSamples(1) {}
VulkanRenderTarget::VulkanRenderTarget() :
HwRenderTarget(0, 0),
VulkanResource(VulkanResourceType::RENDER_TARGET),
mOffscreen(false), mSamples(1) {}
void VulkanRenderTarget::bindToSwapChain(VulkanSwapChain& swapChain) {
assert_invariant(!mOffscreen);
@@ -138,11 +144,14 @@ void VulkanRenderTarget::bindToSwapChain(VulkanSwapChain& swapChain) {
}
VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, VmaAllocator allocator,
VulkanCommands* commands, uint32_t width, uint32_t height, uint8_t samples,
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
uint32_t width, uint32_t height, uint8_t samples,
VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool)
: HwRenderTarget(width, height), mOffscreen(true), mSamples(samples) {
: HwRenderTarget(width, height),
VulkanResource(VulkanResourceType::RENDER_TARGET),
mOffscreen(true),
mSamples(samples) {
for (int index = 0; index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; index++) {
mColor[index] = color[index];
}
@@ -166,10 +175,11 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica
if (texture && texture->samples == 1) {
auto msTexture = texture->getSidecar();
if (UTILS_UNLIKELY(!msTexture)) {
msTexture = new VulkanTexture(device, physicalDevice, context,
allocator, commands, texture->target,
((VulkanTexture const*) texture)->levels, texture->format, samples,
texture->width, texture->height, texture->depth, texture->usage, stagePool);
// TODO: This should be allocated with the ResourceAllocator.
msTexture = new VulkanTexture(device, physicalDevice, context, allocator, commands,
texture->target, ((VulkanTexture const*) texture)->levels, texture->format,
samples, texture->width, texture->height, texture->depth, texture->usage,
stagePool, true /* heap allocated */);
texture->setSidecar(msTexture);
}
mMsaaAttachments[index] = {.texture = msTexture};
@@ -198,7 +208,7 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica
msTexture = new VulkanTexture(device, physicalDevice, context, allocator,
commands, depthTexture->target, msLevel, depthTexture->format, samples,
depthTexture->width, depthTexture->height, depthTexture->depth, depthTexture->usage,
stagePool);
stagePool, true /* heap allocated */);
depthTexture->setSidecar(msTexture);
}
@@ -257,16 +267,23 @@ uint8_t VulkanRenderTarget::getColorTargetCount(const VulkanRenderPass& pass) co
}
VulkanVertexBuffer::VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool,
uint8_t bufferCount, uint8_t attributeCount,
uint32_t elementCount, AttributeArray const& attribs) :
HwVertexBuffer(bufferCount, attributeCount, elementCount, attribs),
buffers(bufferCount, nullptr) {}
VulkanResourceAllocator* allocator, uint8_t bufferCount, uint8_t attributeCount,
uint32_t elementCount, AttributeArray const& attribs)
: HwVertexBuffer(bufferCount, attributeCount, elementCount, attribs),
VulkanResource(VulkanResourceType::VERTEX_BUFFER),
buffers(bufferCount, nullptr),
mResources(allocator) {}
VulkanBufferObject::VulkanBufferObject(VmaAllocator allocator,
VulkanCommands* commands, VulkanStagePool& stagePool, uint32_t byteCount,
BufferObjectBinding bindingType, BufferUsage usage)
void VulkanVertexBuffer::setBuffer(VulkanBufferObject* bufferObject, uint32_t index) {
buffers[index] = &bufferObject->buffer;
mResources.acquire(bufferObject);
}
VulkanBufferObject::VulkanBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool,
uint32_t byteCount, BufferObjectBinding bindingType, BufferUsage usage)
: HwBufferObject(byteCount),
buffer(allocator, commands, stagePool, getBufferObjectUsage(bindingType), byteCount),
VulkanResource(VulkanResourceType::BUFFER_OBJECT),
buffer(allocator, stagePool, getBufferObjectUsage(bindingType), byteCount),
bindingType(bindingType) {}
void VulkanRenderPrimitive::setPrimitiveType(PrimitiveType pt) {
@@ -294,10 +311,14 @@ void VulkanRenderPrimitive::setBuffers(VulkanVertexBuffer* vertexBuffer,
VulkanIndexBuffer* indexBuffer) {
this->vertexBuffer = vertexBuffer;
this->indexBuffer = indexBuffer;
mResources.acquire(vertexBuffer);
mResources.acquire(indexBuffer);
}
VulkanTimerQuery::VulkanTimerQuery(std::tuple<uint32_t, uint32_t> indices)
: 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 {
std::unique_lock<utils::Mutex> lock(mFenceMutex);
+56 -24
View File
@@ -20,8 +20,9 @@
// This needs to be at the top
#include "DriverBase.h"
#include "VulkanPipelineCache.h"
#include "VulkanBuffer.h"
#include "VulkanPipelineCache.h"
#include "VulkanResources.h"
#include "VulkanSwapChain.h"
#include "VulkanTexture.h"
#include "VulkanUtility.h"
@@ -34,7 +35,7 @@ namespace filament::backend {
class VulkanTimestamps;
struct VulkanProgram : public HwProgram {
struct VulkanProgram : public HwProgram, VulkanResource {
VulkanProgram(VkDevice device, const Program& builder) noexcept;
VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs) noexcept;
~VulkanProgram();
@@ -53,7 +54,7 @@ private:
//
// We use private inheritance to shield clients from the width / height fields in HwRenderTarget,
// which are not representative when this is the default render target.
struct VulkanRenderTarget : private HwRenderTarget {
struct VulkanRenderTarget : private HwRenderTarget, VulkanResource {
// Creates an offscreen render target.
VulkanRenderTarget(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, VmaAllocator allocator,
@@ -86,29 +87,43 @@ private:
uint8_t mSamples : 7;
};
struct VulkanVertexBuffer : public HwVertexBuffer {
struct VulkanBufferObject;
struct VulkanVertexBuffer : public HwVertexBuffer, VulkanResource {
VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool,
uint8_t bufferCount, uint8_t attributeCount, uint32_t elementCount,
AttributeArray const& attributes);
VulkanResourceAllocator* allocator, uint8_t bufferCount, uint8_t attributeCount,
uint32_t elementCount, AttributeArray const& attributes);
void setBuffer(VulkanBufferObject* bufferObject, uint32_t index);
inline void terminate() {
mResources.clear();
}
utils::FixedCapacityVector<VulkanBuffer const*> buffers;
private:
FixedSizeVulkanResourceManager mResources;
};
struct VulkanIndexBuffer : public HwIndexBuffer {
VulkanIndexBuffer(VmaAllocator allocator, VulkanCommands* commands,
VulkanStagePool& stagePool, uint8_t elementSize, uint32_t indexCount)
struct VulkanIndexBuffer : public HwIndexBuffer, VulkanResource {
VulkanIndexBuffer(VmaAllocator allocator, VulkanStagePool& stagePool, uint8_t elementSize,
uint32_t indexCount)
: HwIndexBuffer(elementSize, indexCount),
buffer(allocator, commands, stagePool, VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
elementSize * indexCount),
VulkanResource(VulkanResourceType::INDEX_BUFFER),
buffer(allocator, stagePool, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, elementSize * indexCount),
indexType(elementSize == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32) {}
void terminate() { buffer.terminate(); }
void terminate() {
buffer.terminate();
}
VulkanBuffer buffer;
const VkIndexType indexType;
};
struct VulkanBufferObject : public HwBufferObject {
VulkanBufferObject(VmaAllocator allocator, VulkanCommands* commands,
VulkanStagePool& stagePool, uint32_t byteCount, BufferObjectBinding bindingType,
BufferUsage usage);
struct VulkanBufferObject : public HwBufferObject, VulkanResource {
VulkanBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool, uint32_t byteCount,
BufferObjectBinding bindingType, BufferUsage usage);
void terminate() {
buffer.terminate();
}
@@ -116,28 +131,45 @@ struct VulkanBufferObject : public HwBufferObject {
const BufferObjectBinding bindingType;
};
struct VulkanSamplerGroup : public HwSamplerGroup {
struct VulkanSamplerGroup : public HwSamplerGroup, VulkanResource {
// NOTE: we have to use out-of-line allocation here because the size of a Handle<> is limited
std::unique_ptr<SamplerGroup> sb; // FIXME: this shouldn't depend on filament::SamplerGroup
explicit VulkanSamplerGroup(size_t size) noexcept : sb(new SamplerGroup(size)) { }
std::unique_ptr<SamplerGroup> sb;// FIXME: this shouldn't depend on filament::SamplerGroup
explicit VulkanSamplerGroup(size_t size) noexcept
: VulkanResource(VulkanResourceType::SAMPLER_GROUP),
sb(new SamplerGroup(size)) {}
};
struct VulkanRenderPrimitive : public HwRenderPrimitive {
struct VulkanRenderPrimitive : public HwRenderPrimitive, VulkanResource {
VulkanRenderPrimitive(VulkanResourceAllocator* allocator)
: VulkanResource(VulkanResourceType::RENDER_PRIMITIVE),
mResources(allocator) {}
~VulkanRenderPrimitive() {
mResources.clear();
}
void setPrimitiveType(PrimitiveType pt);
void setBuffers(VulkanVertexBuffer* vertexBuffer, VulkanIndexBuffer* indexBuffer);
VulkanVertexBuffer* vertexBuffer = nullptr;
VulkanIndexBuffer* indexBuffer = nullptr;
VkPrimitiveTopology primitiveTopology;
private:
FixedSizeVulkanResourceManager mResources;
};
struct VulkanFence : public HwFence {
VulkanFence() = default;
explicit VulkanFence(std::shared_ptr<VulkanCmdFence> fence) : fence(fence) {}
struct VulkanFence : public HwFence, VulkanResource {
VulkanFence()
: VulkanResource(VulkanResourceType::FENCE) {}
explicit VulkanFence(std::shared_ptr<VulkanCmdFence> fence)
: VulkanResource(VulkanResourceType::FENCE),
fence(fence) {}
std::shared_ptr<VulkanCmdFence> fence;
};
struct VulkanTimerQuery : public HwTimerQuery {
struct VulkanTimerQuery : public HwTimerQuery, VulkanThreadSafeResource {
explicit VulkanTimerQuery(std::tuple<uint32_t, uint32_t> indices);
~VulkanTimerQuery();
@@ -22,6 +22,7 @@
#include "VulkanConstants.h"
#include "VulkanHandles.h"
#include "VulkanTexture.h"
#include "VulkanUtility.h"
// 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;
}
VulkanPipelineCache::VulkanPipelineCache() : mCurrentRasterState(createDefaultRasterState()) {
VulkanPipelineCache::VulkanPipelineCache(VulkanResourceAllocator* allocator)
: mCurrentRasterState(createDefaultRasterState()),
mResourceAllocator(allocator),
mPipelineBoundResources(allocator) {
mDummyBufferWriteInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
mDummyBufferWriteInfo.pNext = nullptr;
mDummyBufferWriteInfo.dstArrayElement = 0;
@@ -144,6 +148,15 @@ bool VulkanPipelineCache::bindDescriptors(VkCommandBuffer cmdbuffer) noexcept {
cacheEntry->lastUsed = mCurrentTime;
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,
getOrCreatePipelineLayout()->handle, 0, VulkanPipelineCache::DESCRIPTOR_TYPE_COUNT,
@@ -152,7 +165,9 @@ bool VulkanPipelineCache::bindDescriptors(VkCommandBuffer cmdbuffer) noexcept {
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);
// Check if the required pipeline is already bound.
@@ -191,7 +206,10 @@ void VulkanPipelineCache::bindScissor(VkCommandBuffer cmdbuffer, VkRect2D scisso
VulkanPipelineCache::DescriptorCacheEntry* VulkanPipelineCache::createDescriptorSets() noexcept {
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
// 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 {
ASSERT_POSTCONDITION(bindingIndex < UBUFFER_BINDING_COUNT,
"Uniform bindings overflow: index = %d, capacity = %d.",
bindingIndex, UBUFFER_BINDING_COUNT);
"Uniform bindings overflow: index = %d, capacity = %d.", bindingIndex,
UBUFFER_BINDING_COUNT);
auto& key = mDescriptorRequirements;
key.uniformBuffers[bindingIndex] = uniformBuffer;
key.uniformBuffers[bindingIndex] = buffer;
if (size == VK_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],
UsageFlags flags) noexcept {
VulkanTexture* textures[SAMPLER_BINDING_COUNT], UsageFlags flags) noexcept {
for (uint32_t bindingIndex = 0; bindingIndex < SAMPLER_BINDING_COUNT; bindingIndex++) {
mDescriptorRequirements.samplers[bindingIndex] = samplers[bindingIndex];
if (textures[bindingIndex]) {
mPipelineBoundResources.acquire(textures[bindingIndex]);
}
}
mPipelineRequirements.layout = flags;
}
@@ -643,6 +670,7 @@ void VulkanPipelineCache::terminate() noexcept {
for (auto& iter : mPipelines) {
vkDestroyPipeline(mDevice, iter.second.handle, VKALLOC);
}
mPipelineBoundResources.clear();
mPipelines.clear();
mBoundPipeline = {};
vmaDestroyBuffer(mAllocator, mDummyBuffer, mDummyMemory);
@@ -676,6 +704,7 @@ void VulkanPipelineCache::onCommandBuffer(const VulkanCommandBuffer& cmdbuffer)
arenas[i].push_back(cacheEntry.handles[i]);
}
++mDescriptorArenasCount;
mDescriptorResources.erase(cacheEntry.id);
iter = mDescriptorSets.erase(iter);
} else {
++iter;
@@ -736,6 +765,10 @@ void VulkanPipelineCache::onCommandBuffer(const VulkanCommandBuffer& cmdbuffer)
vkDestroyDescriptorPool(mDevice, pool, VKALLOC);
}
mExtinctDescriptorPools.clear();
for (auto const& entry : mExtinctDescriptorBundles) {
mDescriptorResources.erase(entry.id);
}
mExtinctDescriptorBundles.clear();
}
}
@@ -798,6 +831,10 @@ void VulkanPipelineCache::destroyLayoutsAndDescriptors() noexcept {
mExtinctDescriptorPools.clear();
mExtinctDescriptorBundles.clear();
// Both mDescriptorSets and mExtinctDescriptorBundles have been cleared, so it's safe to call
// clear() on mDescriptorResources.
mDescriptorResources.clear();
mBoundDescriptor = {};
}
@@ -28,9 +28,11 @@
#include <utils/compiler.h>
#include <utils/Hash.h>
#include <list>
#include <tsl/robin_map.h>
#include <type_traits>
#include <vector>
#include <unordered_map>
#include "VulkanCommands.h"
@@ -41,6 +43,9 @@ VK_DEFINE_HANDLE(VmaPool)
namespace filament::backend {
struct VulkanProgram;
struct VulkanBufferObject;
struct VulkanTexture;
class VulkanResourceAllocator;
// 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
// calls. On destruction it will free any cached Vulkan objects that haven't already been freed.
VulkanPipelineCache();
VulkanPipelineCache(VulkanResourceAllocator* allocator);
~VulkanPipelineCache();
void setDevice(VkDevice device, VmaAllocator allocator);
@@ -141,7 +146,7 @@ public:
// Creates a new pipeline if necessary and binds it using vkCmdBindPipeline.
// 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.
void bindScissor(VkCommandBuffer cmdbuffer, VkRect2D scissor) noexcept;
@@ -151,9 +156,12 @@ public:
void bindRasterState(const RasterState& rasterState) noexcept;
void bindRenderPass(VkRenderPass renderPass, int subpassIndex) 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;
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 bindVertexArray(const VertexArray& varray) noexcept;
@@ -185,17 +193,22 @@ public:
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 {
return mCurrentRasterState;
return mCurrentRasterState;
}
// We need to update this outside of bindRasterState due to VulkanDriver::draw.
inline void setCurrentRasterState(RasterState const& rasterState) noexcept {
mCurrentRasterState = rasterState;
mCurrentRasterState = rasterState;
}
private:
// PIPELINE LAYOUT CACHE KEY
// -------------------------
@@ -337,7 +350,10 @@ private:
std::array<VkDescriptorSet, DESCRIPTOR_TYPE_COUNT> handles;
Timestamp lastUsed;
PipelineLayoutKey pipelineLayout;
uint32_t id;
};
uint32_t mDescriptorCacheEntryCount = 0;
struct PipelineCacheEntry {
VkPipeline handle;
@@ -372,12 +388,15 @@ private:
PipelineLayoutKeyHashFn, PipelineLayoutKeyEqual>;
using PipelineMap = tsl::robin_map<PipelineKey, PipelineCacheEntry,
PipelineHashFn, PipelineEqual>;
using DescriptorMap = tsl::robin_map<DescriptorKey, DescriptorCacheEntry,
DescHashFn, DescEqual>;
using DescriptorMap
= tsl::robin_map<DescriptorKey, DescriptorCacheEntry, DescHashFn, DescEqual>;
using DescriptorResourceMap
= std::unordered_map<uint32_t, std::unique_ptr<VulkanAcquireOnlyResourceManager>>;
PipelineLayoutMap mPipelineLayouts;
PipelineMap mPipelines;
DescriptorMap mDescriptorSets;
DescriptorResourceMap mDescriptorResources;
// These helpers all return unstable pointers that should not be stored.
DescriptorCacheEntry* createDescriptorSets() noexcept;
@@ -425,8 +444,8 @@ private:
// 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
// destroyed a few frames later.
std::vector<VkDescriptorPool> mExtinctDescriptorPools;
std::vector<DescriptorCacheEntry> mExtinctDescriptorBundles;
std::list<VkDescriptorPool> mExtinctDescriptorPools;
std::list<DescriptorCacheEntry> mExtinctDescriptorBundles;
VkDescriptorBufferInfo mDummyBufferInfo = {};
VkWriteDescriptorSet mDummyBufferWriteInfo = {};
@@ -435,6 +454,9 @@ private:
VkBuffer mDummyBuffer;
VmaAllocation mDummyMemory;
VulkanResourceAllocator* mResourceAllocator;
VulkanAcquireOnlyResourceManager mPipelineBoundResources;
};
} // namespace filament::backend
@@ -176,12 +176,28 @@ void VulkanReadPixels::run(VulkanRenderTarget const* srcTarget, uint32_t const x
VkMemoryRequirements memReqs;
VkDeviceMemory stagingMemory;
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 = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memReqs.size,
.memoryTypeIndex = selectMemoryFunc(memReqs.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
| VK_MEMORY_PROPERTY_HOST_CACHED_BIT),
.memoryTypeIndex = memoryTypeIndex,
};
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::NEAREST_MIPMAP_LINEAR:
case SamplerMinFilter::LINEAR_MIPMAP_LINEAR:
// Assuming our maximum texture size is 4k, we'll never need more than 12 miplevels.
return 12.0f;
return VK_LOD_CLAMP_NONE;
}
}
@@ -99,7 +98,7 @@ constexpr inline VkBool32 getCompareEnable(SamplerCompareMode mode) noexcept {
void VulkanSamplerCache::initialize(VkDevice device) { mDevice = device; }
VkSampler VulkanSamplerCache::getSampler(SamplerParams params) noexcept {
auto iter = mCache.find(params.u);
auto iter = mCache.find(params);
if (UTILS_LIKELY(iter != mCache.end())) {
return iter->second;
}
@@ -123,7 +122,7 @@ VkSampler VulkanSamplerCache::getSampler(SamplerParams params) noexcept {
VkSampler sampler;
VkResult error = vkCreateSampler(mDevice, &samplerInfo, VKALLOC, &sampler);
ASSERT_POSTCONDITION(!error, "Unable to create sampler.");
mCache.insert({params.u, sampler});
mCache.insert({params, sampler});
return sampler;
}
@@ -32,7 +32,7 @@ public:
void terminate() noexcept;
private:
VkDevice mDevice;
tsl::robin_map<uint32_t, VkSampler> mCache;
tsl::robin_map<SamplerParams, VkSampler, SamplerParams::Hasher, SamplerParams::EqualTo> mCache;
};
} // namespace filament::backend
@@ -28,7 +28,8 @@ namespace filament::backend {
VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const& context,
VmaAllocator allocator, VulkanCommands* commands, VulkanStagePool& stagePool,
void* nativeWindow, uint64_t flags, VkExtent2D extent)
: mPlatform(platform),
: VulkanResource(VulkanResourceType::SWAP_CHAIN),
mPlatform(platform),
mCommands(commands),
mAllocator(allocator),
mStagePool(stagePool),
@@ -67,11 +68,11 @@ void VulkanSwapChain::update() {
for (auto const color: bundle.colors) {
mColors.push_back(std::make_unique<VulkanTexture>(device, mAllocator, mCommands, color,
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,
bundle.depthFormat, 1, bundle.extent.width, bundle.extent.height,
TextureUsage::DEPTH_ATTACHMENT, mStagePool);
TextureUsage::DEPTH_ATTACHMENT, mStagePool, true /* heap allocated */);
mExtent = bundle.extent;
}
@@ -20,6 +20,7 @@
#include "DriverBase.h"
#include "VulkanContext.h"
#include "VulkanResources.h"
#include <backend/platforms/VulkanPlatform.h>
@@ -36,7 +37,7 @@ struct VulkanHeadlessSwapChain;
struct VulkanSurfaceSwapChain;
// 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,
VulkanCommands* commands, VulkanStagePool& stagePool,
void* nativeWindow, uint64_t flags, VkExtent2D extent = {0, 0});
+45 -18
View File
@@ -29,12 +29,16 @@ using namespace bluevk;
namespace filament::backend {
using ImgUtil = VulkanImageUtility;
VulkanTexture::VulkanTexture(VkDevice device, VmaAllocator allocator,
VulkanCommands* commands, VkImage image, VkFormat format, uint8_t samples,
uint32_t width, uint32_t height, TextureUsage tusage, VulkanStagePool& stagePool)
VulkanTexture::VulkanTexture(VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
VkImage image, VkFormat format, uint8_t samples, uint32_t width, uint32_t height,
TextureUsage tusage, VulkanStagePool& stagePool, bool heapAllocated)
: HwTexture(SamplerType::SAMPLER_2D, 1, samples, width, height, 1, TextureFormat::UNUSED,
tusage),
mVkFormat(format), mViewType(ImgUtil::getViewType(target)), mSwizzle({}),
tusage),
VulkanResource(
heapAllocated ? VulkanResourceType::HEAP_ALLOCATED : VulkanResourceType::TEXTURE),
mVkFormat(format),
mViewType(ImgUtil::getViewType(target)),
mSwizzle({}),
mTextureImage(image),
mPrimaryViewRange{
.aspectMask = getImageAspect(),
@@ -43,19 +47,28 @@ VulkanTexture::VulkanTexture(VkDevice device, VmaAllocator allocator,
.baseArrayLayer = 0,
.layerCount = 1,
},
mStagePool(stagePool), mDevice(device), mAllocator(allocator), mCommands(commands) {}
mStagePool(stagePool),
mDevice(device),
mAllocator(allocator),
mCommands(commands) {}
VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, VmaAllocator allocator,
VulkanCommands* commands, SamplerType target, uint8_t levels,
TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
TextureUsage tusage, VulkanStagePool& stagePool, VkComponentMapping swizzle)
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
SamplerType target, uint8_t levels, TextureFormat tformat, uint8_t samples, uint32_t w,
uint32_t h, uint32_t depth, TextureUsage tusage, VulkanStagePool& stagePool,
bool heapAllocated, VkComponentMapping swizzle)
: 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.
mVkFormat(tformat == TextureFormat::DEPTH24 ? context.getDepthFormat()
: backend::getVkFormat(tformat)),
mViewType(ImgUtil::getViewType(target)), mSwizzle(swizzle), mStagePool(stagePool),
mDevice(device), mAllocator(allocator), mCommands(commands) {
mViewType(ImgUtil::getViewType(target)),
mSwizzle(swizzle),
mStagePool(stagePool),
mDevice(device),
mAllocator(allocator),
mCommands(commands) {
// Create an appropriately-sized device-only VkImage, but do not fill it yet.
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 << ", "
<< "extent = " << w << "x" << h << "x"<< depth << ", "
<< "mipLevels = " << int(levels) << ", "
<< "TextureUsage = " << static_cast<int>(usage) << ", "
<< "TextureUsage = " << static_cast<int>(usage) << ", "
<< "usage = " << imageInfo.usage << ", "
<< "samples = " << imageInfo.samples << ", "
<< "type = " << imageInfo.imageType << ", "
@@ -167,11 +180,17 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
// Allocate memory for the VkImage and bind it.
VkMemoryRequirements 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 = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memReqs.size,
.memoryTypeIndex = context.selectMemoryType(memReqs.memoryTypeBits,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)
.memoryTypeIndex = memoryTypeIndex,
};
error = vkAllocateMemory(mDevice, &allocInfo, nullptr, &mTextureImageMemory);
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)) {
const uint32_t layers = mPrimaryViewRange.layerCount;
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));
}
}
@@ -256,7 +279,9 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
vmaUnmapMemory(mAllocator, stage->memory);
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 = {
.bufferOffset = {},
@@ -317,7 +342,9 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, u
vmaUnmapMemory(mAllocator, stage->memory);
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.
const int layer = 0;
+5 -3
View File
@@ -20,24 +20,26 @@
#include "DriverBase.h"
#include "VulkanBuffer.h"
#include "VulkanResources.h"
#include "VulkanImageUtility.h"
#include <utils/RangeMap.h>
namespace filament::backend {
struct VulkanTexture : public HwTexture {
struct VulkanTexture : public HwTexture, VulkanResource {
// Standard constructor for user-facing textures.
VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice, VulkanContext const& context,
VmaAllocator allocator, VulkanCommands* commands, SamplerType target, uint8_t levels,
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)
// The texture will never destroy the given VkImage, but it does manages its subresources.
VulkanTexture(VkDevice device, VmaAllocator allocator, VulkanCommands* commands, VkImage image,
VkFormat format, uint8_t samples, uint32_t width, uint32_t height, TextureUsage tusage,
VulkanStagePool& stagePool);
VulkanStagePool& stagePool, bool heapAllocated = false);
~VulkanTexture();
@@ -57,11 +57,17 @@ std::tuple<VkImage, VkDeviceMemory> createImageAndMemory(VulkanContext const& co
VkDeviceMemory imageMemory;
VkMemoryRequirements 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 = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memReqs.size,
.memoryTypeIndex
= context.selectMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT),
.memoryTypeIndex = memoryTypeIndex,
};
result = vkAllocateMemory(device, &allocInfo, nullptr, &imageMemory);
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to allocate image memory.");
+8
View File
@@ -513,6 +513,14 @@ public:
*/
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
*/
+2 -2
View File
@@ -133,9 +133,9 @@ struct BloomOptions {
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 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)
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
bool threshold = true; //!< whether to threshold the source
bool enabled = false; //!< enable or disable bloom
+15 -6
View File
@@ -18,10 +18,13 @@
#define TNT_FILAMENT_SWAPCHAIN_H
#include <filament/FilamentAPI.h>
#include <backend/CallbackHandler.h>
#include <backend/DriverEnums.h>
#include <backend/PresentCallable.h>
#include <utils/compiler.h>
#include <utils/Invocable.h>
namespace filament {
@@ -148,7 +151,7 @@ class Engine;
class UTILS_PUBLIC SwapChain : public FilamentAPI {
public:
using FrameScheduledCallback = backend::FrameScheduledCallback;
using FrameCompletedCallback = backend::FrameCompletedCallback;
using FrameCompletedCallback = utils::Invocable<void(SwapChain*)>;
/**
* Requests a SwapChain with an alpha channel.
@@ -241,17 +244,23 @@ public:
* contents have completed rendering on the GPU.
*
* 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.
* @param user An optional pointer to user data passed to the callback function.
* Use \c setFrameCompletedCallback() (with default arguments) to unset the callback.
*
* @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
* 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
@@ -52,6 +52,8 @@ public:
*/
TextureSampler() noexcept = default;
explicit TextureSampler(backend::SamplerParams params) noexcept : mSamplerParams(params) { }
TextureSampler(const TextureSampler& rhs) noexcept = default;
TextureSampler& operator=(const TextureSampler& rhs) noexcept = default;
+4 -1
View File
@@ -689,9 +689,12 @@ public:
* - 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) noexcept;
void setStereoscopicOptions(StereoscopicOptions const& options);
/**
* Returns the stereoscopic options associated with this View.
+4
View File
@@ -327,6 +327,10 @@ size_t Engine::getMaxAutomaticInstances() const noexcept {
return downcast(this)->getMaxAutomaticInstances();
}
bool Engine::isStereoSupported() const noexcept {
return downcast(this)->isStereoSupported();
}
#if defined(__EMSCRIPTEN__)
void Engine::resetBackendState() noexcept {
downcast(this)->resetBackendState();
+17 -3
View File
@@ -81,6 +81,21 @@ struct Froxelizer::FroxelThreadData :
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)
: mArena("froxel", PER_FROXELDATA_ARENA_SIZE),
mZLightNear(FROXEL_FIRST_SLICE_DEPTH),
@@ -144,9 +159,8 @@ void Froxelizer::setViewport(filament::Viewport const& viewport) noexcept {
}
void Froxelizer::setProjection(const mat4f& projection,
float near,
UTILS_UNUSED float far) noexcept {
if (UTILS_UNLIKELY(mat4f::fuzzyEqual(mProjection, projection))) {
float near, UTILS_UNUSED float far) noexcept {
if (UTILS_UNLIKELY(!fuzzyEqual(mProjection, projection))) {
mProjection = projection;
mNear = near;
mDirtyFlags |= PROJECTION_CHANGED;
+1 -1
View File
@@ -2132,7 +2132,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::flarePass(FrameGraph& fg,
.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("threshold",
float2{ bloomOptions.ghostThreshold, bloomOptions.haloThreshold });
+3 -2
View File
@@ -28,8 +28,9 @@ void SwapChain::setFrameScheduledCallback(FrameScheduledCallback callback, void*
return downcast(this)->setFrameScheduledCallback(callback, user);
}
void SwapChain::setFrameCompletedCallback(FrameCompletedCallback callback, void* user) {
return downcast(this)->setFrameCompletedCallback(callback, user);
void SwapChain::setFrameCompletedCallback(backend::CallbackHandler* handler,
utils::Invocable<void(SwapChain*)>&& callback) noexcept {
return downcast(this)->setFrameCompletedCallback(handler, std::move(callback));
}
bool SwapChain::isSRGBSwapChainSupported(Engine& engine) noexcept {
+1 -1
View File
@@ -283,7 +283,7 @@ bool View::isStencilBufferEnabled() const noexcept {
return downcast(this)->isStencilBufferEnabled();
}
void View::setStereoscopicOptions(const StereoscopicOptions& options) noexcept {
void View::setStereoscopicOptions(const StereoscopicOptions& options) {
return downcast(this)->setStereoscopicOptions(options);
}
+1 -1
View File
@@ -536,7 +536,7 @@ void FEngine::flushAndWait() {
// then create a fence that will trigger when we're past the finish() above
size_t tryCount = 8;
FFence* fence = FEngine::createFence(FFence::Type::SOFT);
FFence* fence = FEngine::createFence();
UTILS_NOUNROLL
do {
FenceStatus status = fence->wait(FFence::Mode::FLUSH,250000000u);
+2
View File
@@ -182,6 +182,8 @@ public:
return CONFIG_MAX_INSTANCES;
}
bool isStereoSupported() const noexcept { return getDriver().isStereoSupported(); }
PostProcessManager const& getPostProcessManager() const noexcept {
return mPostProcessManager;
}
+13 -6
View File
@@ -478,15 +478,22 @@ void FMaterial::compile(CompilerPriorityQueue priority,
backend::CallbackHandler* handler,
utils::Invocable<void(Material*)>&& callback) noexcept {
// Turn off the STE variant if stereo is not supported.
if (!mEngine.getDriverApi().isStereoSupported()) {
variantSpec &= ~UserVariantFilterMask(UserVariantFilterBit::STE);
}
UserVariantFilterMask const variantFilter =
~variantSpec & UserVariantFilterMask(UserVariantFilterBit::ALL);
auto const& variants = isVariantLit() ?
VariantUtils::getLitVariants() : VariantUtils::getUnlitVariants();
for (auto const variant : variants) {
if (!variantFilter || variant == Variant::filterUserVariant(variant, variantFilter)) {
if (hasVariant(variant)) {
prepareProgram(variant, priority);
if (UTILS_LIKELY(mEngine.getDriverApi().isParallelShaderCompileSupported())) {
auto const& variants = isVariantLit() ?
VariantUtils::getLitVariants() : VariantUtils::getUnlitVariants();
for (auto const variant: variants) {
if (!variantFilter || variant == Variant::filterUserVariant(variant, variantFilter)) {
if (hasVariant(variant)) {
prepareProgram(variant, priority);
}
}
}
}
+1 -1
View File
@@ -101,7 +101,7 @@ public:
// Must be called after prepareProgram().
[[nodiscard]] backend::Handle<backend::HwProgram> getProgram(Variant variant) const noexcept {
#if FILAMENT_ENABLE_MATDBG
assert_invariant(variant.key < VARIANT_COUNT);
assert_invariant((size_t)variant.key < VARIANT_COUNT);
std::unique_lock<utils::Mutex> lock(mActiveProgramsLock);
mActivePrograms.set(variant.key);
lock.unlock();
+18 -2
View File
@@ -38,8 +38,24 @@ void FSwapChain::setFrameScheduledCallback(FrameScheduledCallback callback, void
mEngine.getDriverApi().setFrameScheduledCallback(mSwapChain, callback, user);
}
void FSwapChain::setFrameCompletedCallback(FrameCompletedCallback callback, void* user) {
mEngine.getDriverApi().setFrameCompletedCallback(mSwapChain, callback, user);
void FSwapChain::setFrameCompletedCallback(backend::CallbackHandler* handler,
utils::Invocable<void(SwapChain*)>&& callback) noexcept {
struct Callback {
utils::Invocable<void(SwapChain*)> f;
SwapChain* s;
static void func(void* user) {
auto* const c = reinterpret_cast<Callback*>(user);
c->f(c->s);
delete c;
}
};
if (callback) {
auto* const user = new(std::nothrow) Callback{ std::move(callback), this };
mEngine.getDriverApi().setFrameCompletedCallback(
mSwapChain, handler, &Callback::func, static_cast<void*>(user));
} else {
mEngine.getDriverApi().setFrameCompletedCallback(mSwapChain, nullptr, nullptr, nullptr);
}
}
bool FSwapChain::isSRGBSwapChainSupported(FEngine& engine) noexcept {
+5 -1
View File
@@ -23,6 +23,9 @@
#include <filament/SwapChain.h>
#include <backend/CallbackHandler.h>
#include <utils/Invocable.h>
#include <utils/compiler.h>
namespace filament {
@@ -61,7 +64,8 @@ public:
void setFrameScheduledCallback(FrameScheduledCallback callback, void* user);
void setFrameCompletedCallback(FrameCompletedCallback callback, void* user);
void setFrameCompletedCallback(backend::CallbackHandler* handler,
utils::Invocable<void(SwapChain*)>&& callback) noexcept;
static bool isSRGBSwapChainSupported(FEngine& engine) noexcept;
+5 -2
View File
@@ -58,6 +58,7 @@ static constexpr float PID_CONTROLLER_Kd = 0.0f;
FView::FView(FEngine& engine)
: mFroxelizer(engine),
mFogEntity(engine.getEntityManager().create()),
mIsStereoSupported(engine.getDriverApi().isStereoSupported()),
mPerViewUniforms(engine),
mShadowMapManager(engine) {
DriverApi& driver = engine.getDriverApi();
@@ -1079,7 +1080,7 @@ void FView::setSoftShadowOptions(SoftShadowOptions options) noexcept {
void FView::setBloomOptions(BloomOptions options) noexcept {
options.dirtStrength = math::saturate(options.dirtStrength);
options.levels = math::clamp(options.levels, uint8_t(3), uint8_t(11));
options.levels = math::clamp(options.levels, uint8_t(1), uint8_t(11));
options.resolution = math::clamp(options.resolution, 1u << options.levels, 2048u);
options.anamorphism = math::clamp(options.anamorphism, 1.0f/32.0f, 32.0f);
options.highlight = std::max(10.0f, options.highlight);
@@ -1117,7 +1118,9 @@ View::PickingQuery& FView::pick(uint32_t x, uint32_t y, backend::CallbackHandler
return *pQuery;
}
void FView::setStereoscopicOptions(const StereoscopicOptions& options) noexcept {
void FView::setStereoscopicOptions(const StereoscopicOptions& options) {
ASSERT_PRECONDITION(!options.enabled || mIsStereoSupported,
"Stereo rendering is not supported.");
mStereoscopicOptions = options;
}
+2 -1
View File
@@ -193,7 +193,7 @@ public:
bool isStencilBufferEnabled() const noexcept { return mStencilBufferEnabled; }
void setStereoscopicOptions(StereoscopicOptions const& options) noexcept;
void setStereoscopicOptions(StereoscopicOptions const& options);
FCamera const* getDirectionalLightCamera() const noexcept {
return &mShadowMapManager.getShadowMap(0)->getDebugCamera();
@@ -524,6 +524,7 @@ private:
const FColorGrading* mColorGrading = nullptr;
const FColorGrading* mDefaultColorGrading = nullptr;
utils::Entity mFogEntity{};
bool mIsStereoSupported : 1;
PIDController mPidController;
DynamicResolutionOptions mDynamicResolution;
+1 -1
View File
@@ -20,7 +20,7 @@
// This substitute for the built-in "mix" function exists to work around #732,
// seen with Vulkan on the Pixel 3 + Android P.
vec4 lerp(vec4 x, vec4 y, float a) {
vec4 lerp(const vec4 x, const vec4 y, float a) {
return x * (1.0 - a) + y * a;
}
+6 -6
View File
@@ -95,7 +95,7 @@ fragment {
#define PREVENT_FLICKERING 0 // FIXME: thin lines disapear
float luma(vec3 color) {
float luma(const vec3 color) {
#if USE_YCoCg
return color.x;
#else
@@ -103,14 +103,14 @@ float luma(vec3 color) {
#endif
}
vec3 RGB_YCoCg(vec3 c) {
vec3 RGB_YCoCg(const vec3 c) {
float Y = dot(c.rgb, vec3( 1, 2, 1) * 0.25);
float Co = dot(c.rgb, vec3( 2, 0, -2) * 0.25);
float Cg = dot(c.rgb, vec3(-1, 2, -1) * 0.25);
return vec3(Y, Co, Cg);
}
vec3 YCoCg_RGB(vec3 c) {
vec3 YCoCg_RGB(const vec3 c) {
float Y = c.x;
float Co = c.y;
float Cg = c.z;
@@ -121,8 +121,8 @@ vec3 YCoCg_RGB(vec3 c) {
}
// clip the (c, h) segment to a box
vec4 clipToBox(int quality,
vec3 boxmin, vec3 boxmax, vec4 c, vec4 h) {
vec4 clipToBox(const int quality,
const vec3 boxmin, const vec3 boxmax, const vec4 c, const vec4 h) {
const float epsilon = 0.0001;
if (quality == BOX_CLIPPING_ACCURATE) {
@@ -144,7 +144,7 @@ vec4 clipToBox(int quality,
// http://vec3.ca/bicubic-filtering-in-fewer-taps/ for more details
// Optimized to 5 taps by removing the corner samples
// And modified for mediump support
vec4 sampleTextureCatmullRom(sampler2D tex, highp vec2 uv, highp vec2 texSize) {
vec4 sampleTextureCatmullRom(const sampler2D tex, const highp vec2 uv, const highp vec2 texSize) {
// We're going to sample a a 4x4 grid of texels surrounding the target UV coordinate. We'll do this by rounding
// down the sample location to get the exact center of our "starting" texel. The starting texel will be at
// location [1, 1] in the grid, where [0, 0] is the top left corner.
@@ -1,4 +1,4 @@
vec3 colorGrade(mediump sampler3D lut, vec3 x) {
vec3 colorGrade(mediump sampler3D lut, const vec3 x) {
// Alexa LogC EI 1000
const float a = 5.555556;
const float b = 0.047996;
@@ -89,7 +89,7 @@ fragment {
void dummy(){}
float starburst(vec2 uv) {
float starburst(const vec2 uv) {
// get an offset that continuously moves with the camera
vec3 forward = getViewFromWorldMatrix()[2].xyz;
float offset = forward.x + forward.y + forward.z;
@@ -104,7 +104,7 @@ float starburst(vec2 uv) {
return saturate(mask + (1.0 - smoothstep(0.0, 0.3, d)));
}
vec3 bloom(highp vec2 uv, vec3 color) {
vec3 bloom(highp vec2 uv, const vec3 color) {
vec3 result = vec3(0.0);
if (materialParams.bloom.x > 0.0) {
@@ -128,7 +128,7 @@ vec3 bloom(highp vec2 uv, vec3 color) {
return result;
}
vec4 resolveFragment(highp vec2 uv) {
vec4 resolveFragment(const highp vec2 uv) {
#if POST_PROCESS_OPAQUE
return vec4(textureLod(materialParams_colorBuffer, uv, 0.0).rgb, 1.0);
#else
+29 -29
View File
@@ -108,16 +108,16 @@ float ringCountFast() {
return materialParams.ringCounts[2];
}
float sampleCount(float ringCount) {
float sampleCount(const float ringCount) {
float s = (ringCount * 2.0 - 1.0);
return s * s;
}
float computeNeededRings(float kernelSizeInPixel) {
float computeNeededRings(const float kernelSizeInPixel) {
return ceil(kernelSizeInPixel + 0.5);
}
float getMipLevel(float ringCount, float kernelSizeInPixels) {
float getMipLevel(const float ringCount, const float kernelSizeInPixels) {
#if KERNEL_USE_MIPMAP
// note: the 0.5 is to convert from highres to our downslampled texture
float ringDistanceInTexels = kernelSizeInPixels * rcp(ringCount - 0.5);
@@ -140,7 +140,7 @@ float getMipLevel(float ringCount, float kernelSizeInPixels) {
#endif
}
float sampleWeight(float coc, float mip) {
float sampleWeight(const float coc, const float mip) {
// The contribution of sample is inversely proportional to *its* area
// (the larger area, the fainter it is).
// In theory this factor should be 1 / pi * radius^2, however 1/pi is a constant, and
@@ -159,7 +159,7 @@ float sampleWeight(float coc, float mip) {
return (MAX_COC_RADIUS * MAX_COC_RADIUS) / (max(coc * coc, pixelRadiusSquared));
}
float intersection(float border, float absCoc, float mip) {
float intersection(const float border, const float absCoc, const float mip) {
// there is very little visible difference, so use the cheaper version on mobile
#if FILAMENT_QUALITY < FILAMENT_QUALITY_HIGH
return saturate((absCoc - border) + 0.5);
@@ -168,7 +168,7 @@ float intersection(float border, float absCoc, float mip) {
#endif
}
highp vec2 diaphragm(highp vec2 center, vec2 offset) {
highp vec2 diaphragm(const highp vec2 center, const vec2 offset) {
#if DOF_DIAPHRAGM == DOF_DIAPHRAGM_CIRCLE
return center + offset;
#elif DOF_DIAPHRAGM == DOF_DIAPHRAGM_STRAIGHT_BLADES
@@ -217,7 +217,7 @@ void initBucket(out Bucket ring) {
ring.coc = 0.0;
}
void initRing(float i, float ringCount, float kernelSize, vec2 noise,
void initRing(const float i, const float ringCount, const float kernelSize, const vec2 noise,
out float offset, out float count, out mat2 r, out vec2 p) {
float radius = (kernelSize / (ringCount - 0.5)) * i;
@@ -240,7 +240,7 @@ void initRing(float i, float ringCount, float kernelSize, vec2 noise,
offset = length(p + noise);
}
void mergeRings(inout Bucket curr, inout Bucket prev, float count) {
void mergeRings(inout Bucket curr, inout Bucket prev, const float count) {
if (curr.cw >= MEDIUMP_FLT_MIN) {
// "Life of a Bokeh", SIGGRAPH 2018 -- slide 32
// How much the current ring is occluding the previous ring -- we estimate this based
@@ -265,8 +265,8 @@ void mergeRings(inout Bucket curr, inout Bucket prev, float count) {
}
}
void accumulate(inout Bucket curr, inout Bucket prev, Sample tap,
float radius, float border, float mip, bool first) {
void accumulate(inout Bucket curr, inout Bucket prev, const Sample tap,
const float radius, const float border, const float mip, const bool first) {
float inLayer = tap.inLayer;
float coc = abs(tap.coc);
float w = inLayer * intersection(radius, coc, mip) * sampleWeight(coc, mip);
@@ -299,8 +299,8 @@ void accumulate(inout Bucket curr, inout Bucket prev, Sample tap,
}
}
void accumulateBackground(inout Bucket curr, inout Bucket prev, highp vec2 pos,
float radius, float border, float mip, bool first) {
void accumulateBackground(inout Bucket curr, inout Bucket prev, const highp vec2 pos,
const float radius, const float border, const float mip, const bool first) {
Sample tap;
tap.s = textureLod(materialParams_color, pos, mip);
tap.coc = textureLod(materialParams_coc, pos, mip).r;
@@ -309,15 +309,15 @@ void accumulateBackground(inout Bucket curr, inout Bucket prev, highp vec2 pos,
}
void accumulateBackgroundMirror(inout Bucket curr, inout Bucket prev,
highp vec2 center, vec2 offset,
float radius, float border, float mip, bool first) {
const highp vec2 center, const vec2 offset,
const float radius, const float border, const float mip, const bool first) {
accumulateBackground(curr, prev, diaphragm(center, offset), radius, border, mip, first);
accumulateBackground(curr, prev, diaphragm(center, -offset), radius, border, mip, first);
}
void accumulateBackgroundCenter(inout Bucket prev,
highp vec2 pos, float ringCount,
float kernelSize, float noiseRadius, float mip) {
const highp vec2 pos, const float ringCount,
const float kernelSize, const float noiseRadius, const float mip) {
Bucket curr;
initBucket(curr);
float border = (kernelSize / (ringCount - 0.5)) * 0.5;
@@ -327,9 +327,9 @@ void accumulateBackgroundCenter(inout Bucket prev,
mergeRings(curr, prev, 1.0);
}
void accumulateRing(inout Bucket prev, float index, float ringCount,
float kernelSize, vec2 noise, highp vec2 uvCenter,
highp vec2 cocToTexelScale, float mip, bool first) {
void accumulateRing(inout Bucket prev, const float index, const float ringCount,
const float kernelSize, const vec2 noise, const highp vec2 uvCenter,
const highp vec2 cocToTexelScale, const float mip, const bool first) {
// we accumulate the larger rings first
float i = (ringCount - 1.0) - index;
@@ -378,12 +378,12 @@ float computeLayer(float coc, vec2 tiles) {
return saturate((coc - tiles.r - frontLayerCocSize) * invTransitionSize);
}
float foregroundFadding(float coc) {
float foregroundFadding(const float coc) {
return saturate(abs(coc) - (MAX_IN_FOCUS_COC - 1.0));
}
void accumulateForeground(inout Layer layer[2], vec2 tiles,
highp vec2 pos, float border, float mip) {
void accumulateForeground(inout Layer layer[2], const vec2 tiles,
const highp vec2 pos, const float border, const float mip) {
float coc = textureLod(materialParams_coc, pos, mip).r;
vec4 s = textureLod(materialParams_color, pos, mip);
@@ -399,13 +399,13 @@ void accumulateForeground(inout Layer layer[2], vec2 tiles,
layer[1].weight += inBackWeight;
}
void accumulateForegroundCenter(inout Layer layer[2], vec2 tiles,
highp vec2 pos, float border, float mip) {
void accumulateForegroundCenter(inout Layer layer[2], const vec2 tiles,
const highp vec2 pos, const float border, const float mip) {
accumulateForeground(layer, tiles, pos, border, mip);
}
void accumulateForegroundMirror(inout Layer layer[2], vec2 tiles,
highp vec2 center, vec2 offset, float border, float mip) {
void accumulateForegroundMirror(inout Layer layer[2], const vec2 tiles,
const highp vec2 center, const vec2 offset, const float border, const float mip) {
// The code below is equivalent to:
// accumulateForeground(layer, tiles, diaphragm(center, offset), border, mip);
// accumulateForeground(layer, tiles, diaphragm(center, -offset), border, mip);
@@ -440,7 +440,7 @@ void accumulateForegroundMirror(inout Layer layer[2], vec2 tiles,
*/
void fastTile(inout vec4 color, inout float alpha,
highp vec2 uv, vec2 tiles, NoiseState noiseState) {
const highp vec2 uv, const vec2 tiles, const NoiseState noiseState) {
highp vec2 cocToTexelScale = materialParams.cocToTexelScale;
float cocToPixelScale = materialParams.cocToPixelScale;
@@ -481,7 +481,7 @@ void fastTile(inout vec4 color, inout float alpha,
}
void foregroundTile(inout vec4 foreground, inout float fgOpacity,
highp vec2 uv, vec2 tiles, NoiseState noiseState) {
const highp vec2 uv, const vec2 tiles, const NoiseState noiseState) {
highp vec2 cocToTexelScale = materialParams.cocToTexelScale;
float cocToPixelScale = materialParams.cocToPixelScale;
@@ -536,7 +536,7 @@ void foregroundTile(inout vec4 foreground, inout float fgOpacity,
}
void backgroundTile(inout vec4 background, inout float bgOpacity,
highp vec2 uv, vec2 tiles, NoiseState noiseState) {
const highp vec2 uv, const vec2 tiles, const NoiseState noiseState) {
highp vec2 cocToTexelScale = materialParams.cocToTexelScale;
float cocToPixelScale = materialParams.cocToPixelScale;
+2 -2
View File
@@ -30,11 +30,11 @@ void dummy(){}
// Size of a tile in full-resolution pixels -- must match tileSize in PostProcessManager.cpp
#define TILE_SIZE 16.0
vec2 tap(vec2 uv, vec2 offset) {
vec2 tap(const vec2 uv, const vec2 offset) {
return textureLod(materialParams_tiles, uv + offset, 0.0).rg;
}
vec2 dilate(inout vec2 center, vec2 tap) {
vec2 dilate(inout vec2 center, const vec2 tap) {
// Tiles that can affect us need to transfer their CoC to us (for the foreground it's
// min(ourCoc, theirCoc) because CoC are negative (the min is the larger radius).
center.r = min(center.r, tap.r);
+2 -2
View File
@@ -32,12 +32,12 @@ fragment {
void dummy(){}
float max4(vec4 f) {
float max4(const vec4 f) {
vec2 t = max(f.xy, f.zw);
return max(t.x, t.y);
}
float min4(vec4 f) {
float min4(const vec4 f) {
vec2 t = min(f.xy, f.zw);
return min(t.x, t.y);
}
@@ -32,12 +32,12 @@ fragment {
void dummy(){}
float max4(vec4 f) {
float max4(const vec4 f) {
vec2 t = max(f.xy, f.zw);
return max(t.x, t.y);
}
float min4(vec4 f) {
float min4(const vec4 f) {
vec2 t = min(f.xy, f.zw);
return min(t.x, t.y);
}
+18 -18
View File
@@ -17,68 +17,68 @@
// Currently our dilate pass is set-up for 32 max.
#define MAX_COC_RADIUS 32.0
float min2(vec2 v) {
float min2(const vec2 v) {
return min(v.x, v.y);
}
float max2(vec2 v) {
float max2(const vec2 v) {
return max(v.x, v.y);
}
float max4(vec4 v) {
float max4(const vec4 v) {
return max2(max(v.xy, v.zw));
}
float min4(vec4 v) {
float min4(const vec4 v) {
return min2(min(v.xy, v.zw));
}
float rcp(float x) {
float rcp(const float x) {
return 1.0 / x;
}
float rcpOrZero(float x) {
float rcpOrZero(const float x) {
return x > MEDIUMP_FLT_MIN ? (1.0 / x) : 0.0;
}
highp float rcpOrZeroHighp(highp float x) {
highp float rcpOrZeroHighp(const highp float x) {
return x > MEDIUMP_FLT_MIN ? (1.0 / x) : 0.0;
}
float cocToAlpha(float coc) {
float cocToAlpha(const float coc) {
// CoC is positive for background field.
// CoC is negative for the foreground field.
return saturate(abs(coc) - MAX_IN_FOCUS_COC);
}
// returns circle-of-confusion diameter in pixels
float getCOC(float depth, vec2 cocParams) {
float getCOC(const float depth, const vec2 cocParams) {
return depth * cocParams.x + cocParams.y;
}
vec4 getCOC(vec4 depth, vec2 cocParams) {
vec4 getCOC(const vec4 depth, const vec2 cocParams) {
return depth * cocParams.x + cocParams.y;
}
float isForeground(float coc) {
float isForeground(const float coc) {
return coc < 0.0 ? 1.0 : 0.0;
}
float isBackground(float coc) {
float isBackground(const float coc) {
return coc > 0.0 ? 1.0 : 0.0;
}
bool isForegroundTile(vec2 tiles) {
bool isForegroundTile(const vec2 tiles) {
// A foreground tile is one where the smallest CoC is negative
return tiles.r < 0.0;
}
bool isBackgroundTile(vec2 tiles) {
bool isBackgroundTile(const vec2 tiles) {
// A background tile is one where the largest CoC is positive
return tiles.g > 0.0;
}
bool isFastTile(vec2 tiles) {
bool isFastTile(const vec2 tiles) {
// We use the distance between the min and max CoC and if the relative error is less than
// 5% we assume the tile contains a constant CoC.
// We could cannot use the absolute value of the min/mac CoC -- which would categorize more
@@ -87,19 +87,19 @@ bool isFastTile(vec2 tiles) {
return (tiles.g - tiles.r) <= abs(tiles.g) * 0.05;
}
bool isTrivialTile(vec2 tiles) {
bool isTrivialTile(const vec2 tiles) {
float maxCocRadius = max(abs(tiles.r), abs(tiles.g));
return maxCocRadius < MAX_IN_FOCUS_COC;
}
float downsampleCoC(vec4 c) {
float downsampleCoC(const vec4 c) {
// We need to compute a suitable CoC to represent the 4 pixels that are downsampled.
// We pick the min because this always selects the most foreground sample if there is one,
// because the foreground can leak on the background, but not the reverse.
return min4(c);
}
vec4 downsampleCocWeights(vec4 c, float outCoc, float scale) {
vec4 downsampleCocWeights(const vec4 c, const float outCoc, const float scale) {
// The bilateral weight is normally computed as saturate(1 - |outCoc - c|) which selects
// the sample with the outCoc weight (and does a little bit of cross-fade if other samples
// are close). However, this can also cause some aliasing with dithered objects, so by

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