Compare commits
43
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69fc1f302c | ||
|
|
e7c96cd124 | ||
|
|
730bc99025 | ||
|
|
7441e878bb | ||
|
|
ef4c4a76fc | ||
|
|
be4f287b07 | ||
|
|
d3a35de386 | ||
|
|
9057c47a56 | ||
|
|
4a5081388d | ||
|
|
a60fe41681 | ||
|
|
a8ace2891d | ||
|
|
669ffc85c0 | ||
|
|
d957dd8082 | ||
|
|
8eda62c957 | ||
|
|
90f079ac2c | ||
|
|
3728f06603 | ||
|
|
39cfce641c | ||
|
|
3fbf3f7111 | ||
|
|
ef9bbece2a | ||
|
|
eb8fa9ecdf | ||
|
|
e1bfe7ca81 | ||
|
|
ca4c7ac739 | ||
|
|
9676bc94e8 | ||
|
|
f7a5111106 | ||
|
|
56ac7ab902 | ||
|
|
44b4de904e | ||
|
|
a2fe9f745c | ||
|
|
659b8b6e03 | ||
|
|
c73f4e64d5 | ||
|
|
fe743523bf | ||
|
|
2c1044e812 | ||
|
|
db9183a105 | ||
|
|
e4a0bb8fa0 | ||
|
|
d15c53e530 | ||
|
|
cc8a3ed96b | ||
|
|
213eb6af9e | ||
|
|
e04c6d406f | ||
|
|
cab799f531 | ||
|
|
e95edef9d7 | ||
|
|
9cfef925bb | ||
|
|
3900fc6c37 | ||
|
|
b8ec1658f3 | ||
|
|
371bd1f01f |
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.53.0'
|
||||
implementation 'com.google.android.filament:filament-android:1.53.2'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
||||
iOS projects can use CocoaPods to install the latest release:
|
||||
|
||||
```shell
|
||||
pod 'Filament', '~> 1.53.0'
|
||||
pod 'Filament', '~> 1.53.2'
|
||||
```
|
||||
|
||||
### Snapshots
|
||||
@@ -176,6 +176,7 @@ steps:
|
||||
- [x] KHR_materials_unlit
|
||||
- [x] KHR_materials_variants
|
||||
- [x] KHR_materials_volume
|
||||
- [x] KHR_materials_specular
|
||||
- [x] KHR_mesh_quantization
|
||||
- [x] KHR_texture_basisu
|
||||
- [x] KHR_texture_transform
|
||||
|
||||
@@ -7,6 +7,16 @@ 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.53.3
|
||||
|
||||
- Add drag and drop support for IBL files for desktop gltf_viewer.
|
||||
|
||||
## v1.53.2
|
||||
|
||||
|
||||
## v1.53.1
|
||||
|
||||
|
||||
## v1.53.0
|
||||
|
||||
- engine: fix skinning normals with large transforms (b/342459864) [⚠️ **New Material Version**]
|
||||
|
||||
@@ -571,3 +571,9 @@ Java_com_google_android_filament_Engine_nBuilderBuild(JNIEnv*, jclass, jlong nat
|
||||
Engine::Builder* builder = (Engine::Builder*) nativeBuilder;
|
||||
return (jlong) builder->build();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_Engine_getSteadyClockTimeNano(JNIEnv *env, jclass clazz) {
|
||||
return (jlong)Engine::getSteadyClockTimeNano();
|
||||
}
|
||||
|
||||
@@ -25,12 +25,17 @@ using namespace filament;
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_Material_nBuilderBuild(JNIEnv *env, jclass,
|
||||
jlong nativeEngine, jobject buffer_, jint size) {
|
||||
jlong nativeEngine, jobject buffer_, jint size, jint shBandCount) {
|
||||
Engine* engine = (Engine*) nativeEngine;
|
||||
AutoBuffer buffer(env, buffer_, size);
|
||||
Material* material = Material::Builder()
|
||||
auto builder = Material::Builder();
|
||||
if (shBandCount) {
|
||||
builder.sphericalHarmonicsBandCount(shBandCount);
|
||||
}
|
||||
Material* material = builder
|
||||
.package(buffer.getData(), buffer.getSize())
|
||||
.build(*engine);
|
||||
|
||||
return (jlong) material;
|
||||
}
|
||||
|
||||
|
||||
@@ -253,16 +253,10 @@ Java_com_google_android_filament_RenderableManager_nBuilderMorphingStandard(JNIE
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_RenderableManager_nBuilderSetMorphTargetBufferAt(JNIEnv*, jclass,
|
||||
jlong nativeBuilder, int level, int primitiveIndex, jlong nativeMorphTargetBuffer,
|
||||
int offset, int count) {
|
||||
Java_com_google_android_filament_RenderableManager_nBuilderSetMorphTargetBufferOffsetAt(JNIEnv*, jclass,
|
||||
jlong nativeBuilder, int level, int primitiveIndex, int offset) {
|
||||
RenderableManager::Builder *builder = (RenderableManager::Builder *) nativeBuilder;
|
||||
if (nativeMorphTargetBuffer) {
|
||||
MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeMorphTargetBuffer;
|
||||
builder->morphing(level, primitiveIndex, morphTargetBuffer, offset, count);
|
||||
} else {
|
||||
builder->morphing(level, primitiveIndex, offset, count);
|
||||
}
|
||||
builder->morphing(level, primitiveIndex, offset);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
@@ -334,18 +328,12 @@ Java_com_google_android_filament_RenderableManager_nSetMorphWeights(JNIEnv* env,
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_RenderableManager_nSetMorphTargetBufferAt(JNIEnv*,
|
||||
Java_com_google_android_filament_RenderableManager_nSetMorphTargetBufferOffsetAt(JNIEnv*,
|
||||
jclass, jlong nativeRenderableManager, jint i, int level, jint primitiveIndex,
|
||||
jlong nativeMorphTargetBuffer, jint offset, jint count) {
|
||||
jlong, jint offset) {
|
||||
RenderableManager *rm = (RenderableManager *) nativeRenderableManager;
|
||||
if (nativeMorphTargetBuffer) {
|
||||
MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeMorphTargetBuffer;
|
||||
rm->setMorphTargetBufferAt((RenderableManager::Instance) i, (uint8_t) level,
|
||||
(size_t) primitiveIndex, morphTargetBuffer, (size_t) offset, (size_t) count);
|
||||
} else {
|
||||
rm->setMorphTargetBufferAt((RenderableManager::Instance) i, (uint8_t) level,
|
||||
(size_t) primitiveIndex, (size_t) offset, (size_t) count);
|
||||
}
|
||||
rm->setMorphTargetBufferOffsetAt((RenderableManager::Instance) i, (uint8_t) level,
|
||||
(size_t) primitiveIndex, (size_t) offset);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
|
||||
@@ -28,6 +28,14 @@
|
||||
using namespace filament;
|
||||
using namespace backend;
|
||||
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_Renderer_nSkipFrame(JNIEnv *, jclass, jlong nativeRenderer,
|
||||
jlong vsyncSteadyClockTimeNano) {
|
||||
Renderer *renderer = (Renderer *) nativeRenderer;
|
||||
renderer->skipFrame(uint64_t(vsyncSteadyClockTimeNano));
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Renderer_nBeginFrame(JNIEnv *, jclass, jlong nativeRenderer,
|
||||
jlong nativeSwapChain, jlong frameTimeNanos) {
|
||||
@@ -187,3 +195,10 @@ Java_com_google_android_filament_Renderer_nSetPresentationTime(JNIEnv *, jclass
|
||||
Renderer *renderer = (Renderer *) nativeRenderer;
|
||||
renderer->setPresentationTime(monotonicClockNanos);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_Renderer_nSetVsyncTime(JNIEnv *, jclass,
|
||||
jlong nativeRenderer, jlong steadyClockTimeNano) {
|
||||
Renderer *renderer = (Renderer *) nativeRenderer;
|
||||
renderer->setVsyncTime(steadyClockTimeNano);
|
||||
}
|
||||
|
||||
@@ -531,3 +531,12 @@ Java_com_google_android_filament_View_nGetFogEntity(JNIEnv *env, jclass clazz,
|
||||
View *view = (View *) nativeView;
|
||||
return (jint)view->getFogEntity().getId();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_View_nClearFrameHistory(JNIEnv *env, jclass clazz,
|
||||
jlong nativeView, jlong nativeEngine) {
|
||||
View *view = (View *) nativeView;
|
||||
Engine *engine = (Engine *) nativeEngine;
|
||||
view->clearFrameHistory(*engine);
|
||||
}
|
||||
|
||||
@@ -425,9 +425,12 @@ public class Engine {
|
||||
public long resourceAllocatorCacheSizeMB = 64;
|
||||
|
||||
/*
|
||||
* This value determines for how many frames are texture entries kept in the cache.
|
||||
* This value determines how many frames texture entries are kept for in the cache. This
|
||||
* is a soft limit, meaning some texture older than this are allowed to stay in the cache.
|
||||
* Typically only one texture is evicted per frame.
|
||||
* The default is 1.
|
||||
*/
|
||||
public long resourceAllocatorCacheMaxAge = 2;
|
||||
public long resourceAllocatorCacheMaxAge = 1;
|
||||
|
||||
/*
|
||||
* Disable backend handles use-after-free checks.
|
||||
@@ -1292,7 +1295,6 @@ public class Engine {
|
||||
/**
|
||||
* Switch the command queue to unprotected mode. Protected mode can be activated via
|
||||
* Renderer::beginFrame() using a protected SwapChain.
|
||||
|
||||
* @see Renderer
|
||||
* @see SwapChain
|
||||
*/
|
||||
@@ -1300,6 +1302,13 @@ public class Engine {
|
||||
nUnprotected(getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current time. This is a convenience function that simply returns the
|
||||
* time in nanosecond since epoch of std::chrono::steady_clock.
|
||||
* @return current time in nanosecond since epoch of std::chrono::steady_clock.
|
||||
* @see Renderer#beginFrame
|
||||
*/
|
||||
public static native long getSteadyClockTimeNano();
|
||||
|
||||
@UsedByReflection("TextureHelper.java")
|
||||
public long getNativeObject() {
|
||||
|
||||
@@ -346,6 +346,7 @@ public class Material {
|
||||
public static class Builder {
|
||||
private Buffer mBuffer;
|
||||
private int mSize;
|
||||
private int mShBandCount = 0;
|
||||
|
||||
/**
|
||||
* Specifies the material data. The material data is a binary blob produced by
|
||||
@@ -361,6 +362,22 @@ public class Material {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the quality of the indirect lights computations. This is only taken into account
|
||||
* if this material is lit and in the surface domain. This setting will affect the
|
||||
* IndirectLight computation if one is specified on the Scene and Spherical Harmonics
|
||||
* are used for the irradiance.
|
||||
*
|
||||
* @param shBandCount Number of spherical harmonic bands. Must be 1, 2 or 3 (default).
|
||||
* @return Reference to this Builder for chaining calls.
|
||||
* @see IndirectLight
|
||||
*/
|
||||
@NonNull
|
||||
public Builder sphericalHarmonicsBandCount(@IntRange(from = 0) int shBandCount) {
|
||||
mShBandCount = shBandCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns the Material object.
|
||||
*
|
||||
@@ -372,7 +389,8 @@ public class Material {
|
||||
*/
|
||||
@NonNull
|
||||
public Material build(@NonNull Engine engine) {
|
||||
long nativeMaterial = nBuilderBuild(engine.getNativeObject(), mBuffer, mSize);
|
||||
long nativeMaterial = nBuilderBuild(engine.getNativeObject(),
|
||||
mBuffer, mSize, mShBandCount);
|
||||
if (nativeMaterial == 0) throw new IllegalStateException("Couldn't create Material");
|
||||
return new Material(nativeMaterial);
|
||||
}
|
||||
@@ -1023,7 +1041,7 @@ public class Material {
|
||||
mNativeObject = 0;
|
||||
}
|
||||
|
||||
private static native long nBuilderBuild(long nativeEngine, @NonNull Buffer buffer, int size);
|
||||
private static native long nBuilderBuild(long nativeEngine, @NonNull Buffer buffer, int size, int shBandCount);
|
||||
private static native long nCreateInstance(long nativeMaterial);
|
||||
private static native long nCreateInstanceWithName(long nativeMaterial, @NonNull String name);
|
||||
private static native long nGetDefaultInstance(long nativeMaterial);
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ public class MorphTargetBuffer {
|
||||
*
|
||||
* @exception IllegalStateException if the MorphTargetBuffer could not be created
|
||||
*
|
||||
* @see #setMorphTargetBufferAt
|
||||
* @see #setMorphTargetBufferOffsetAt
|
||||
*/
|
||||
@NonNull
|
||||
public MorphTargetBuffer build(@NonNull Engine engine) {
|
||||
|
||||
+7
-57
@@ -569,40 +569,13 @@ public class RenderableManager {
|
||||
*
|
||||
* @param level the level of detail (lod), only 0 can be specified
|
||||
* @param primitiveIndex zero-based index of the primitive, must be less than the count passed to Builder constructor
|
||||
* @param morphTargetBuffer specifies the morph target buffer
|
||||
* @param offset specifies where in the morph target buffer to start reading (expressed as a number of vertices)
|
||||
* @param count number of vertices in the morph target buffer to read, must equal the geometry's count (for triangles, this should be a multiple of 3)
|
||||
*/
|
||||
@NonNull
|
||||
public Builder morphing(@IntRange(from = 0) int level,
|
||||
@IntRange(from = 0) int primitiveIndex,
|
||||
@IntRange(from = 0) int offset,
|
||||
@IntRange(from = 0) int count) {
|
||||
nBuilderSetMorphTargetBufferAt(mNativeBuilder, level, primitiveIndex, 0, offset, count);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
@Deprecated
|
||||
@NonNull
|
||||
public Builder morphing(@IntRange(from = 0) int level,
|
||||
@IntRange(from = 0) int primitiveIndex,
|
||||
@NonNull MorphTargetBuffer morphTargetBuffer,
|
||||
@IntRange(from = 0) int offset,
|
||||
@IntRange(from = 0) int count) {
|
||||
nBuilderSetMorphTargetBufferAt(mNativeBuilder, level, primitiveIndex,
|
||||
morphTargetBuffer.getNativeObject(), offset, count);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
@Deprecated
|
||||
@NonNull
|
||||
public Builder morphing(@IntRange(from = 0) int level,
|
||||
@IntRange(from = 0) int primitiveIndex,
|
||||
@NonNull MorphTargetBuffer morphTargetBuffer) {
|
||||
nBuilderSetMorphTargetBufferAt(mNativeBuilder, level, primitiveIndex,
|
||||
morphTargetBuffer.getNativeObject(), 0, morphTargetBuffer.getVertexCount());
|
||||
@IntRange(from = 0) int offset) {
|
||||
nBuilderSetMorphTargetBufferOffsetAt(mNativeBuilder, level, primitiveIndex, offset);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -705,34 +678,11 @@ public class RenderableManager {
|
||||
*
|
||||
* @see Builder#morphing
|
||||
*/
|
||||
public void setMorphTargetBufferAt(@EntityInstance int i,
|
||||
public void setMorphTargetBufferOffsetAt(@EntityInstance int i,
|
||||
@IntRange(from = 0) int level,
|
||||
@IntRange(from = 0) int primitiveIndex,
|
||||
@IntRange(from = 0) int offset,
|
||||
@IntRange(from = 0) int count) {
|
||||
nSetMorphTargetBufferAt(mNativeObject, i, level, primitiveIndex, 0, offset, count);
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
@Deprecated
|
||||
public void setMorphTargetBufferAt(@EntityInstance int i,
|
||||
@IntRange(from = 0) int level,
|
||||
@IntRange(from = 0) int primitiveIndex,
|
||||
@NonNull MorphTargetBuffer morphTargetBuffer,
|
||||
@IntRange(from = 0) int offset,
|
||||
@IntRange(from = 0) int count) {
|
||||
nSetMorphTargetBufferAt(mNativeObject, i, level, primitiveIndex,
|
||||
morphTargetBuffer.getNativeObject(), offset, count);
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
@Deprecated
|
||||
public void setMorphTargetBufferAt(@EntityInstance int i,
|
||||
@IntRange(from = 0) int level,
|
||||
@IntRange(from = 0) int primitiveIndex,
|
||||
@NonNull MorphTargetBuffer morphTargetBuffer) {
|
||||
nSetMorphTargetBufferAt(mNativeObject, i, level, primitiveIndex,
|
||||
morphTargetBuffer.getNativeObject(), 0, morphTargetBuffer.getVertexCount());
|
||||
@IntRange(from = 0) int offset) {
|
||||
nSetMorphTargetBufferOffsetAt(mNativeObject, i, level, primitiveIndex, 0, offset);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1033,7 +983,7 @@ public class RenderableManager {
|
||||
private static native void nBuilderSkinningBuffer(long nativeBuilder, long nativeSkinningBuffer, int boneCount, int offset);
|
||||
private static native void nBuilderMorphing(long nativeBuilder, int targetCount);
|
||||
private static native void nBuilderMorphingStandard(long nativeBuilder, long nativeMorphTargetBuffer);
|
||||
private static native void nBuilderSetMorphTargetBufferAt(long nativeBuilder, int level, int primitiveIndex, long nativeMorphTargetBuffer, int offset, int count);
|
||||
private static native void nBuilderSetMorphTargetBufferOffsetAt(long nativeBuilder, int level, int primitiveIndex, int offset);
|
||||
private static native void nBuilderEnableSkinningBuffers(long nativeBuilder, boolean enabled);
|
||||
private static native void nBuilderFog(long nativeBuilder, boolean enabled);
|
||||
private static native void nBuilderLightChannel(long nativeRenderableManager, int channel, boolean enable);
|
||||
@@ -1043,7 +993,7 @@ public class RenderableManager {
|
||||
private static native int nSetBonesAsMatrices(long nativeObject, int i, Buffer matrices, int remaining, int boneCount, int offset);
|
||||
private static native int nSetBonesAsQuaternions(long nativeObject, int i, Buffer quaternions, int remaining, int boneCount, int offset);
|
||||
private static native void nSetMorphWeights(long nativeObject, int instance, float[] weights, int offset);
|
||||
private static native void nSetMorphTargetBufferAt(long nativeObject, int i, int level, int primitiveIndex, long nativeMorphTargetBuffer, int offset, int count);
|
||||
private static native void nSetMorphTargetBufferOffsetAt(long nativeObject, int i, int level, int primitiveIndex, long nativeMorphTargetBuffer, int offset);
|
||||
private static native int nGetMorphTargetCount(long nativeObject, int i);
|
||||
private static native void nSetAxisAlignedBoundingBox(long nativeRenderableManager, int i, float cx, float cy, float cz, float ex, float ey, float ez);
|
||||
private static native void nSetLayerMask(long nativeRenderableManager, int i, int select, int value);
|
||||
|
||||
@@ -284,6 +284,33 @@ public class Renderer {
|
||||
nSetPresentationTime(getNativeObject(), monotonicClockNanos);
|
||||
}
|
||||
|
||||
/**
|
||||
* The use of this method is optional. It sets the VSYNC time expressed as the duration in
|
||||
* nanosecond since epoch of std::chrono::steady_clock.
|
||||
* If called, passing 0 to frameTimeNanos in Renderer.BeginFrame will use this
|
||||
* time instead.
|
||||
* @param steadyClockTimeNano duration in nanosecond since epoch of std::chrono::steady_clock
|
||||
* @see Engine#getSteadyClockTimeNano
|
||||
* @see Renderer#beginFrame
|
||||
*/
|
||||
public void setVsyncTime(long steadyClockTimeNano) {
|
||||
nSetVsyncTime(getNativeObject(), steadyClockTimeNano);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call skipFrame when momentarily skipping frames, for instance if the content of the
|
||||
* scene doesn't change.
|
||||
*
|
||||
* @param vsyncSteadyClockTimeNano The time in nanoseconds when the frame started being rendered,
|
||||
* in the {@link System#nanoTime()} timebase. Divide this value by 1000000 to
|
||||
* convert it to the {@link android.os.SystemClock#uptimeMillis()}
|
||||
* time base. This typically comes from
|
||||
* {@link android.view.Choreographer.FrameCallback}.
|
||||
*/
|
||||
public void skipFrame(long vsyncSteadyClockTimeNano) {
|
||||
nSkipFrame(getNativeObject(), vsyncSteadyClockTimeNano);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a frame for this <code>Renderer</code>.
|
||||
* <p><code>beginFrame</code> manages frame pacing, and returns whether or not a frame should be
|
||||
@@ -702,6 +729,8 @@ public class Renderer {
|
||||
}
|
||||
|
||||
private static native void nSetPresentationTime(long nativeObject, long monotonicClockNanos);
|
||||
private static native void nSetVsyncTime(long nativeObject, long steadyClockTimeNano);
|
||||
private static native void nSkipFrame(long nativeObject, long vsyncSteadyClockTimeNano);
|
||||
private static native boolean nBeginFrame(long nativeRenderer, long nativeSwapChain, long frameTimeNanos);
|
||||
private static native void nEndFrame(long nativeRenderer);
|
||||
private static native void nRender(long nativeRenderer, long nativeView);
|
||||
|
||||
@@ -1233,6 +1233,18 @@ public class View {
|
||||
return nGetFogEntity(getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* When certain temporal features are used (e.g.: TAA or Screen-space reflections), the view
|
||||
* keeps a history of previous frame renders associated with the Renderer the view was last
|
||||
* used with. When switching Renderer, it may be necessary to clear that history by calling
|
||||
* this method. Similarly, if the whole content of the screen change, like when a cut-scene
|
||||
* starts, clearing the history might be needed to avoid artifacts due to the previous frame
|
||||
* being very different.
|
||||
*/
|
||||
public void clearFrameHistory(Engine engine) {
|
||||
nClearFrameHistory(getNativeObject(), engine.getNativeObject());
|
||||
}
|
||||
|
||||
public long getNativeObject() {
|
||||
if (mNativeObject == 0) {
|
||||
throw new IllegalStateException("Calling method on destroyed View");
|
||||
@@ -1294,7 +1306,7 @@ public class View {
|
||||
private static native void nSetMaterialGlobal(long nativeView, int index, float x, float y, float z, float w);
|
||||
private static native void nGetMaterialGlobal(long nativeView, int index, float[] out);
|
||||
private static native int nGetFogEntity(long nativeView);
|
||||
|
||||
private static native void nClearFrameHistory(long nativeView, long nativeEngine);
|
||||
|
||||
/**
|
||||
* List of available ambient occlusion techniques.
|
||||
|
||||
@@ -125,6 +125,11 @@ extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Manipulator_nBu
|
||||
builder->groundPlane(a, b, c, d);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Manipulator_nBuilderPanning(JNIEnv*, jclass, jlong nativeBuilder, jboolean enabled) {
|
||||
Builder* builder = (Builder*) nativeBuilder;
|
||||
builder->panning(enabled);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT long Java_com_google_android_filament_utils_Manipulator_nBuilderBuild(JNIEnv*, jclass, jlong nativeBuilder, jint mode) {
|
||||
Builder* builder = (Builder*) nativeBuilder;
|
||||
return (jlong) builder->build((Mode) mode);
|
||||
|
||||
+12
@@ -274,6 +274,17 @@ public class Manipulator {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether panning is enabled in the manipulator.
|
||||
*
|
||||
* @return this <code>Builder</code> object for chaining calls
|
||||
*/
|
||||
@NonNull
|
||||
public Builder panning(Boolean enabled) {
|
||||
nBuilderPanning(mNativeBuilder, enabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns the <code>Manipulator</code> object.
|
||||
*
|
||||
@@ -483,6 +494,7 @@ public class Manipulator {
|
||||
private static native void nBuilderFlightPanSpeed(long nativeBuilder, float x, float y);
|
||||
private static native void nBuilderFlightMoveDamping(long nativeBuilder, float damping);
|
||||
private static native void nBuilderGroundPlane(long nativeBuilder, float a, float b, float c, float d);
|
||||
private static native void nBuilderPanning(long nativeBuilder, Boolean enabled);
|
||||
private static native long nBuilderBuild(long nativeBuilder, int mode);
|
||||
|
||||
private static native void nDestroyManipulator(long nativeManip);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.53.0
|
||||
VERSION_NAME=1.53.2
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -75,6 +75,8 @@ public:
|
||||
*/
|
||||
size_t textureUseAfterFreePoolSize = 0;
|
||||
|
||||
size_t metalUploadBufferSizeBytes = 512 * 1024;
|
||||
|
||||
/**
|
||||
* Set to `true` to forcibly disable parallel shader compilation in the backend.
|
||||
* Currently only honored by the GL and Metal backends.
|
||||
|
||||
@@ -90,6 +90,20 @@ public:
|
||||
VkExtent2D extent = {0, 0};
|
||||
};
|
||||
|
||||
struct ImageSyncData {
|
||||
static constexpr uint32_t INVALID_IMAGE_INDEX = UINT32_MAX;
|
||||
|
||||
// The index of the next image as returned by vkAcquireNextImage or equivalent.
|
||||
uint32_t imageIndex = INVALID_IMAGE_INDEX;
|
||||
|
||||
// Semaphore to be signaled once the image is available.
|
||||
VkSemaphore imageReadySemaphore = VK_NULL_HANDLE;
|
||||
|
||||
// A function called right before vkQueueSubmit. After this call, the image must be
|
||||
// available. This pointer can be null if imageReadySemaphore is not VK_NULL_HANDLE.
|
||||
std::function<void(SwapChainPtr handle)> explicitImageReadyWait = nullptr;
|
||||
};
|
||||
|
||||
VulkanPlatform();
|
||||
|
||||
~VulkanPlatform() override;
|
||||
@@ -127,6 +141,12 @@ public:
|
||||
* before recreating the swapchain. Default is true.
|
||||
*/
|
||||
bool flushAndWaitOnWindowResize = true;
|
||||
|
||||
/**
|
||||
* Whether the swapchain image should be transitioned to a layout suitable for
|
||||
* presentation. Default is true.
|
||||
*/
|
||||
bool transitionSwapChainImageLayoutForPresent = true;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -155,13 +175,10 @@ public:
|
||||
* corresponding VkImage will be used as the output color attachment. The client should signal
|
||||
* the `clientSignal` semaphore when the image is ready to be used by the backend.
|
||||
* @param handle The handle returned by createSwapChain()
|
||||
* @param clientSignal The semaphore that the client will signal to indicate that the backend
|
||||
* may render into the image.
|
||||
* @param index Pointer to memory that will be filled with the index that corresponding
|
||||
* to an image in the `SwapChainBundle.colors` array.
|
||||
* @param outImageSyncData The synchronization data used for image readiness
|
||||
* @return Result of acquire
|
||||
*/
|
||||
virtual VkResult acquire(SwapChainPtr handle, VkSemaphore clientSignal, uint32_t* index);
|
||||
virtual VkResult acquire(SwapChainPtr handle, ImageSyncData* outImageSyncData);
|
||||
|
||||
/**
|
||||
* Present the image corresponding to `index` to the display. The client should wait on
|
||||
|
||||
@@ -181,6 +181,18 @@ public:
|
||||
return static_cast<Dp>(p);
|
||||
}
|
||||
|
||||
template<typename B>
|
||||
bool is_valid(Handle<B>& handle) {
|
||||
if (handle && isPoolHandle(handle.getId())) {
|
||||
auto [p, tag] = handleToPointer(handle.getId());
|
||||
uint8_t const age = (tag & HANDLE_AGE_MASK) >> HANDLE_AGE_SHIFT;
|
||||
auto const pNode = static_cast<typename Allocator::Node*>(p);
|
||||
uint8_t const expectedAge = pNode[-1].age;
|
||||
return expectedAge == age;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename Dp, typename B>
|
||||
inline typename std::enable_if_t<
|
||||
std::is_pointer_v<Dp> &&
|
||||
|
||||
@@ -32,10 +32,11 @@
|
||||
# define HAS_MMAP 0
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
using namespace utils;
|
||||
|
||||
@@ -81,6 +82,9 @@ void* CircularBuffer::alloc(size_t size) noexcept {
|
||||
// map the circular buffer once...
|
||||
vaddr = mmap(reserve_vaddr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
|
||||
if (vaddr != MAP_FAILED) {
|
||||
// populate the address space with pages (because this is a circular buffer,
|
||||
// all the pages will be allocated eventually, might as well do it now)
|
||||
memset(vaddr, 0, size);
|
||||
// and map the circular buffer again, behind the previous copy...
|
||||
vaddr_shadow = mmap((char*)vaddr + size, size,
|
||||
PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
|
||||
@@ -101,7 +105,7 @@ void* CircularBuffer::alloc(size_t size) noexcept {
|
||||
if (UTILS_UNLIKELY(mAshmemFd < 0)) {
|
||||
// ashmem failed
|
||||
if (vaddr_guard != MAP_FAILED) {
|
||||
munmap(vaddr_guard, size);
|
||||
munmap(vaddr_guard, BLOCK_SIZE);
|
||||
}
|
||||
|
||||
if (vaddr_shadow != MAP_FAILED) {
|
||||
@@ -119,12 +123,11 @@ void* CircularBuffer::alloc(size_t size) noexcept {
|
||||
data = mmap(nullptr, size * 2 + BLOCK_SIZE,
|
||||
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
|
||||
FILAMENT_CHECK_POSTCONDITION(data) <<
|
||||
FILAMENT_CHECK_POSTCONDITION(data != MAP_FAILED) <<
|
||||
"couldn't allocate " << (size * 2 / 1024) <<
|
||||
" KiB of virtual address space for the command buffer";
|
||||
|
||||
slog.d << "WARNING: Using soft CircularBuffer (" << (size * 2 / 1024) << " KiB)"
|
||||
<< io::endl;
|
||||
slog.w << "Using 'soft' CircularBuffer (" << (size * 2 / 1024) << " KiB)" << io::endl;
|
||||
|
||||
// guard page at the end
|
||||
void* guard = (void*)(uintptr_t(data) + size * 2);
|
||||
|
||||
@@ -204,6 +204,15 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
enum class UploadStrategy {
|
||||
POOL,
|
||||
BUMP_ALLOCATOR,
|
||||
};
|
||||
|
||||
void uploadWithPoolBuffer(void* src, size_t size, size_t byteOffset) const;
|
||||
void uploadWithBumpAllocator(void* src, size_t size, size_t byteOffset) const;
|
||||
|
||||
UploadStrategy mUploadStrategy;
|
||||
TrackedMetalBuffer mBuffer;
|
||||
size_t mBufferSize = 0;
|
||||
void* mCpuBuffer = nullptr;
|
||||
|
||||
@@ -27,7 +27,16 @@ MetalPlatform* TrackedMetalBuffer::platform = nullptr;
|
||||
MetalPlatform* ScopedAllocationTimer::platform = nullptr;
|
||||
|
||||
MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType, BufferUsage usage,
|
||||
size_t size, bool forceGpuBuffer) : mBufferSize(size), mContext(context) {
|
||||
size_t size, bool forceGpuBuffer)
|
||||
: mBufferSize(size), mContext(context) {
|
||||
const MetalBumpAllocator& allocator = *mContext.bumpAllocator;
|
||||
// VERTEX is also used for index buffers
|
||||
if (allocator.getCapacity() > 0 && bindingType == BufferObjectBinding::VERTEX) {
|
||||
mUploadStrategy = UploadStrategy::BUMP_ALLOCATOR;
|
||||
} else {
|
||||
mUploadStrategy = UploadStrategy::POOL;
|
||||
}
|
||||
|
||||
// If the buffer is less than 4K in size and is updated frequently, we don't use an explicit
|
||||
// buffer. Instead, we use immediate command encoder methods like setVertexBytes:length:atIndex:.
|
||||
// This won't work for SSBOs, since they are read/write.
|
||||
@@ -61,34 +70,23 @@ void MetalBuffer::copyIntoBuffer(void* src, size_t size, size_t byteOffset) {
|
||||
FILAMENT_CHECK_PRECONDITION(size + byteOffset <= mBufferSize)
|
||||
<< "Attempting to copy " << size << " bytes into a buffer of size " << mBufferSize
|
||||
<< " at offset " << byteOffset;
|
||||
// The copy blit requires that byteOffset be a multiple of 4.
|
||||
FILAMENT_CHECK_PRECONDITION(!(byteOffset & 0x3)) << "byteOffset must be a multiple of 4";
|
||||
|
||||
// Either copy into the Metal buffer or into our cpu buffer.
|
||||
// If we have a cpu buffer, we can directly copy into it.
|
||||
if (mCpuBuffer) {
|
||||
memcpy(static_cast<uint8_t*>(mCpuBuffer) + byteOffset, src, size);
|
||||
return;
|
||||
}
|
||||
|
||||
// Acquire a staging buffer to hold the contents of this update.
|
||||
MetalBufferPool* bufferPool = mContext.bufferPool;
|
||||
const MetalBufferPoolEntry* const staging = bufferPool->acquireBuffer(size);
|
||||
memcpy(staging->buffer.get().contents, src, size);
|
||||
|
||||
// The blit below requires that byteOffset be a multiple of 4.
|
||||
FILAMENT_CHECK_PRECONDITION(!(byteOffset & 0x3u)) << "byteOffset must be a multiple of 4";
|
||||
|
||||
// Encode a blit from the staging buffer into the private GPU buffer.
|
||||
id<MTLCommandBuffer> cmdBuffer = getPendingCommandBuffer(&mContext);
|
||||
id<MTLBlitCommandEncoder> blitEncoder = [cmdBuffer blitCommandEncoder];
|
||||
blitEncoder.label = @"Buffer upload blit";
|
||||
[blitEncoder copyFromBuffer:staging->buffer.get()
|
||||
sourceOffset:0
|
||||
toBuffer:mBuffer.get()
|
||||
destinationOffset:byteOffset
|
||||
size:size];
|
||||
[blitEncoder endEncoding];
|
||||
[cmdBuffer addCompletedHandler:^(id<MTLCommandBuffer> cb) {
|
||||
bufferPool->releaseBuffer(staging);
|
||||
}];
|
||||
switch (mUploadStrategy) {
|
||||
case UploadStrategy::BUMP_ALLOCATOR:
|
||||
uploadWithBumpAllocator(src, size, byteOffset);
|
||||
break;
|
||||
case UploadStrategy::POOL:
|
||||
uploadWithPoolBuffer(src, size, byteOffset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MetalBuffer::copyIntoBufferUnsynchronized(void* src, size_t size, size_t byteOffset) {
|
||||
@@ -199,5 +197,42 @@ void MetalBuffer::bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLCommandEncod
|
||||
}
|
||||
}
|
||||
|
||||
void MetalBuffer::uploadWithPoolBuffer(void* src, size_t size, size_t byteOffset) const {
|
||||
MetalBufferPool* bufferPool = mContext.bufferPool;
|
||||
const MetalBufferPoolEntry* const staging = bufferPool->acquireBuffer(size);
|
||||
memcpy(staging->buffer.get().contents, src, size);
|
||||
|
||||
// Encode a blit from the staging buffer into the private GPU buffer.
|
||||
id<MTLCommandBuffer> cmdBuffer = getPendingCommandBuffer(&mContext);
|
||||
id<MTLBlitCommandEncoder> blitEncoder = [cmdBuffer blitCommandEncoder];
|
||||
blitEncoder.label = @"Buffer upload blit - pool buffer";
|
||||
[blitEncoder copyFromBuffer:staging->buffer.get()
|
||||
sourceOffset:0
|
||||
toBuffer:mBuffer.get()
|
||||
destinationOffset:byteOffset
|
||||
size:size];
|
||||
[blitEncoder endEncoding];
|
||||
[cmdBuffer addCompletedHandler:^(id<MTLCommandBuffer> cb) {
|
||||
bufferPool->releaseBuffer(staging);
|
||||
}];
|
||||
}
|
||||
|
||||
void MetalBuffer::uploadWithBumpAllocator(void* src, size_t size, size_t byteOffset) const {
|
||||
MetalBumpAllocator& allocator = *mContext.bumpAllocator;
|
||||
auto [buffer, offset] = allocator.allocateStagingArea(size);
|
||||
memcpy(static_cast<char*>(buffer.contents) + offset, src, size);
|
||||
|
||||
// Encode a blit from the staging buffer into the private GPU buffer.
|
||||
id<MTLCommandBuffer> cmdBuffer = getPendingCommandBuffer(&mContext);
|
||||
id<MTLBlitCommandEncoder> blitEncoder = [cmdBuffer blitCommandEncoder];
|
||||
blitEncoder.label = @"Buffer upload blit - bump allocator";
|
||||
[blitEncoder copyFromBuffer:buffer
|
||||
sourceOffset:offset
|
||||
toBuffer:mBuffer.get()
|
||||
destinationOffset:byteOffset
|
||||
size:size];
|
||||
[blitEncoder endEncoding];
|
||||
}
|
||||
|
||||
} // namespace backend
|
||||
} // namespace filament
|
||||
|
||||
@@ -38,6 +38,28 @@ struct MetalBufferPoolEntry {
|
||||
mutable uint32_t referenceCount;
|
||||
};
|
||||
|
||||
class MetalBumpAllocator {
|
||||
public:
|
||||
MetalBumpAllocator(id<MTLDevice> device, size_t capacity);
|
||||
|
||||
/**
|
||||
* Allocates a staging area of the given size. Returns a pair of the buffer and the offset
|
||||
* within the buffer. The buffer is guaranteed to be at least the given size, but may be larger.
|
||||
* Clients must not write to the buffer beyond the returned offset + size.
|
||||
* Clients are responsible for holding a reference to the returned buffer.
|
||||
* Allocations are guaranteed to be aligned to 4 bytes.
|
||||
*/
|
||||
std::pair<id<MTLBuffer>, size_t> allocateStagingArea(size_t size);
|
||||
|
||||
size_t getCapacity() const noexcept { return mCapacity; }
|
||||
|
||||
private:
|
||||
id<MTLDevice> mDevice;
|
||||
TrackedMetalBuffer mCurrentUploadBuffer = nil;
|
||||
size_t mHead = 0;
|
||||
size_t mCapacity;
|
||||
};
|
||||
|
||||
// Manages a pool of Metal buffers, periodically releasing ones that have been unused for awhile.
|
||||
class MetalBufferPool {
|
||||
public:
|
||||
|
||||
@@ -116,5 +116,39 @@ void MetalBufferPool::reset() noexcept {
|
||||
mFreeStages.clear();
|
||||
}
|
||||
|
||||
MetalBumpAllocator::MetalBumpAllocator(id<MTLDevice> device, size_t capacity)
|
||||
: mDevice(device), mCapacity(capacity) {
|
||||
if (mCapacity > 0) {
|
||||
mCurrentUploadBuffer = { [device newBufferWithLength:capacity options:MTLStorageModeShared],
|
||||
TrackedMetalBuffer::Type::STAGING };
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<id<MTLBuffer>, size_t> MetalBumpAllocator::allocateStagingArea(size_t size) {
|
||||
if (size == 0) {
|
||||
return { nil, 0 };
|
||||
}
|
||||
if (size > mCapacity) {
|
||||
return { [mDevice newBufferWithLength:size options:MTLStorageModeShared], 0 };
|
||||
}
|
||||
assert_invariant(mCurrentUploadBuffer);
|
||||
|
||||
// Align the head to a 4-byte boundary.
|
||||
mHead = (mHead + 3) & ~3;
|
||||
|
||||
if (UTILS_LIKELY(mHead + size <= mCapacity)) {
|
||||
const size_t oldHead = mHead;
|
||||
mHead += size;
|
||||
return { mCurrentUploadBuffer.get(), oldHead };
|
||||
}
|
||||
|
||||
// We're finished with the current allocation.
|
||||
mCurrentUploadBuffer = { [mDevice newBufferWithLength:mCapacity options:MTLStorageModeShared],
|
||||
TrackedMetalBuffer::Type::STAGING };
|
||||
mHead = size;
|
||||
|
||||
return { mCurrentUploadBuffer.get(), 0 };
|
||||
}
|
||||
|
||||
} // namespace backend
|
||||
} // namespace filament
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace backend {
|
||||
class MetalDriver;
|
||||
class MetalBlitter;
|
||||
class MetalBufferPool;
|
||||
class MetalBumpAllocator;
|
||||
class MetalRenderTarget;
|
||||
class MetalSamplerGroup;
|
||||
class MetalSwapChain;
|
||||
@@ -141,6 +142,7 @@ struct MetalContext {
|
||||
utils::FixedCircularBuffer<Handle<HwTexture>> texturesToDestroy;
|
||||
|
||||
MetalBufferPool* bufferPool;
|
||||
MetalBumpAllocator* bumpAllocator;
|
||||
|
||||
MetalSwapChain* currentDrawSwapChain = nil;
|
||||
MetalSwapChain* currentReadSwapChain = nil;
|
||||
|
||||
@@ -171,6 +171,8 @@ MetalDriver::MetalDriver(MetalPlatform* platform, const Platform::DriverConfig&
|
||||
mContext->samplerStateCache.setDevice(mContext->device);
|
||||
mContext->argumentEncoderCache.setDevice(mContext->device);
|
||||
mContext->bufferPool = new MetalBufferPool(*mContext);
|
||||
mContext->bumpAllocator =
|
||||
new MetalBumpAllocator(mContext->device, driverConfig.metalUploadBufferSizeBytes);
|
||||
mContext->blitter = new MetalBlitter(*mContext);
|
||||
|
||||
if (@available(iOS 12, *)) {
|
||||
@@ -209,6 +211,7 @@ MetalDriver::~MetalDriver() noexcept {
|
||||
mContext->emptyTexture = nil;
|
||||
CFRelease(mContext->textureCache);
|
||||
delete mContext->bufferPool;
|
||||
delete mContext->bumpAllocator;
|
||||
delete mContext->blitter;
|
||||
delete mContext->timerQueryImpl;
|
||||
delete mContext->shaderCompiler;
|
||||
|
||||
@@ -109,7 +109,7 @@ private:
|
||||
NSUInteger headlessWidth = 0;
|
||||
NSUInteger headlessHeight = 0;
|
||||
CAMetalLayer* layer = nullptr;
|
||||
std::mutex layerDrawableMutex;
|
||||
std::shared_ptr<std::mutex> layerDrawableMutex;
|
||||
MetalExternalImage externalImage;
|
||||
SwapChainType type;
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ MetalSwapChain::MetalSwapChain(MetalContext& context, CAMetalLayer* nativeWindow
|
||||
: context(context),
|
||||
depthStencilFormat(decideDepthStencilFormat(flags)),
|
||||
layer(nativeWindow),
|
||||
layerDrawableMutex(std::make_shared<std::mutex>()),
|
||||
externalImage(context),
|
||||
type(SwapChainType::CAMETALLAYER) {
|
||||
|
||||
@@ -179,7 +180,7 @@ id<MTLTexture> MetalSwapChain::acquireDrawable() {
|
||||
// calling -nextDrawable, or when releasing the last known reference
|
||||
// to any CAMetalDrawable returned from a previous -nextDrawable.
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(layerDrawableMutex);
|
||||
std::lock_guard<std::mutex> lock(*layerDrawableMutex);
|
||||
drawable = [layer nextDrawable];
|
||||
}
|
||||
|
||||
@@ -188,8 +189,10 @@ id<MTLTexture> MetalSwapChain::acquireDrawable() {
|
||||
}
|
||||
|
||||
void MetalSwapChain::releaseDrawable() {
|
||||
std::lock_guard<std::mutex> lock(layerDrawableMutex);
|
||||
drawable = nil;
|
||||
if (drawable) {
|
||||
std::lock_guard<std::mutex> lock(*layerDrawableMutex);
|
||||
drawable = nil;
|
||||
}
|
||||
}
|
||||
|
||||
id<MTLTexture> MetalSwapChain::acquireDepthTexture() {
|
||||
@@ -265,7 +268,7 @@ public:
|
||||
PresentDrawableData& operator=(const PresentDrawableData&) = delete;
|
||||
|
||||
static PresentDrawableData* create(id<CAMetalDrawable> drawable,
|
||||
std::mutex* drawableMutex, MetalDriver* driver) {
|
||||
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver) {
|
||||
assert_invariant(drawableMutex);
|
||||
assert_invariant(driver);
|
||||
return new PresentDrawableData(drawable, drawableMutex, driver);
|
||||
@@ -287,22 +290,22 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
PresentDrawableData(id<CAMetalDrawable> drawable, std::mutex* drawableMutex,
|
||||
PresentDrawableData(id<CAMetalDrawable> drawable, std::shared_ptr<std::mutex> drawableMutex,
|
||||
MetalDriver* driver)
|
||||
: mDrawable(drawable), mDrawableMutex(drawableMutex), mDriver(driver) {}
|
||||
|
||||
static void cleanupAndDestroy(PresentDrawableData *that) {
|
||||
{
|
||||
if (that->mDrawable) {
|
||||
std::lock_guard<std::mutex> lock(*(that->mDrawableMutex));
|
||||
that->mDrawable = nil;
|
||||
}
|
||||
that->mDrawableMutex = nullptr;
|
||||
that->mDrawableMutex.reset();
|
||||
that->mDriver = nullptr;
|
||||
delete that;
|
||||
}
|
||||
|
||||
id<CAMetalDrawable> mDrawable;
|
||||
std::mutex* mDrawableMutex = nullptr;
|
||||
std::shared_ptr<std::mutex> mDrawableMutex;
|
||||
MetalDriver* mDriver = nullptr;
|
||||
};
|
||||
|
||||
@@ -320,7 +323,7 @@ void MetalSwapChain::scheduleFrameScheduledCallback() {
|
||||
|
||||
struct Callback {
|
||||
Callback(std::shared_ptr<FrameScheduledCallback> callback, id<CAMetalDrawable> drawable,
|
||||
std::mutex* drawableMutex, MetalDriver* driver)
|
||||
std::shared_ptr<std::mutex> drawableMutex, MetalDriver* driver)
|
||||
: f(callback), data(PresentDrawableData::create(drawable, drawableMutex, driver)) {}
|
||||
std::shared_ptr<FrameScheduledCallback> f;
|
||||
// PresentDrawableData* is destroyed by maybePresentAndDestroyAsync() later.
|
||||
@@ -337,7 +340,7 @@ void MetalSwapChain::scheduleFrameScheduledCallback() {
|
||||
// This callback pointer will be captured by the block. Even if the scheduled handler is never
|
||||
// called, the unique_ptr will still ensure we don't leak memory.
|
||||
__block auto callback = std::make_unique<Callback>(
|
||||
frameScheduled.callback, drawable, &layerDrawableMutex, context.driver);
|
||||
frameScheduled.callback, drawable, layerDrawableMutex, context.driver);
|
||||
|
||||
backend::CallbackHandler* handler = frameScheduled.handler;
|
||||
MetalDriver* driver = context.driver;
|
||||
@@ -553,8 +556,6 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
|
||||
descriptor.usage = getMetalTextureUsage(usage);
|
||||
descriptor.storageMode = MTLStorageModePrivate;
|
||||
texture = [context.device newTextureWithDescriptor:descriptor];
|
||||
FILAMENT_CHECK_POSTCONDITION(texture != nil)
|
||||
<< "Could not create Metal texture. Out of memory?";
|
||||
break;
|
||||
case SamplerType::SAMPLER_CUBEMAP:
|
||||
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
|
||||
@@ -569,8 +570,6 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
|
||||
descriptor.usage = getMetalTextureUsage(usage);
|
||||
descriptor.storageMode = MTLStorageModePrivate;
|
||||
texture = [context.device newTextureWithDescriptor:descriptor];
|
||||
FILAMENT_CHECK_POSTCONDITION(texture != nil)
|
||||
<< "Could not create Metal texture. Out of memory?";
|
||||
break;
|
||||
case SamplerType::SAMPLER_3D:
|
||||
descriptor = [MTLTextureDescriptor new];
|
||||
@@ -583,8 +582,6 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
|
||||
descriptor.usage = getMetalTextureUsage(usage);
|
||||
descriptor.storageMode = MTLStorageModePrivate;
|
||||
texture = [context.device newTextureWithDescriptor:descriptor];
|
||||
FILAMENT_CHECK_POSTCONDITION(texture != nil)
|
||||
<< "Could not create Metal texture. Out of memory?";
|
||||
break;
|
||||
case SamplerType::SAMPLER_EXTERNAL:
|
||||
// If we're using external textures (CVPixelBufferRefs), we don't need to make any
|
||||
@@ -593,6 +590,12 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
|
||||
break;
|
||||
}
|
||||
|
||||
FILAMENT_CHECK_POSTCONDITION(target == SamplerType::SAMPLER_EXTERNAL || texture != nil)
|
||||
<< "Could not create Metal texture (SamplerType = " << int(target)
|
||||
<< ", levels = " << int(levels) << ", MTLPixelFormat = " << int(devicePixelFormat)
|
||||
<< ", width = " << width << ", height = " << height << ", depth = " << depth
|
||||
<< "). Out of memory?";
|
||||
|
||||
// If swizzling is set, set up a swizzled texture view that we'll use when sampling this texture.
|
||||
const bool isDefaultSwizzle =
|
||||
r == TextureSwizzle::CHANNEL_0 &&
|
||||
|
||||
@@ -304,6 +304,13 @@ void OpenGLDriver::bindSampler(GLuint unit, GLuint sampler) noexcept {
|
||||
void OpenGLDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
|
||||
backend::PushConstantVariant value) {
|
||||
assert_invariant(stage == ShaderStage::VERTEX || stage == ShaderStage::FRAGMENT);
|
||||
|
||||
#if FILAMENT_ENABLE_MATDBG
|
||||
if (UTILS_UNLIKELY(!mValidProgram)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
utils::Slice<std::pair<GLint, ConstantType>> constants;
|
||||
if (stage == ShaderStage::VERTEX) {
|
||||
constants = mCurrentPushConstants->vertexConstants;
|
||||
@@ -340,15 +347,11 @@ void OpenGLDriver::bindTexture(GLuint unit, GLTexture const* t) noexcept {
|
||||
}
|
||||
|
||||
bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
|
||||
if (UTILS_UNLIKELY(!p->isValid())) {
|
||||
// If the program is not valid, we can't call use().
|
||||
return false;
|
||||
}
|
||||
|
||||
// set-up textures and samplers in the proper TMUs (as specified in setSamplers)
|
||||
p->use(this, mContext);
|
||||
bool const success = p->use(this, mContext);
|
||||
assert_invariant(success == p->isValid());
|
||||
|
||||
if (UTILS_UNLIKELY(mContext.isES2())) {
|
||||
if (UTILS_UNLIKELY(mContext.isES2() && success)) {
|
||||
for (uint32_t i = 0; i < Program::UNIFORM_BINDING_COUNT; i++) {
|
||||
auto [id, buffer, age] = mContext.getEs2UniformBinding(i);
|
||||
if (buffer) {
|
||||
@@ -359,7 +362,8 @@ bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
|
||||
// when mPlatform.isSRGBSwapChainSupported() is false (no need to check though).
|
||||
p->setRec709ColorSpace(mRec709OutputColorspace);
|
||||
}
|
||||
return true;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
@@ -2332,9 +2336,9 @@ void OpenGLDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
|
||||
auto const* const pSamplers = (SamplerDescriptor const*)data.buffer;
|
||||
for (size_t i = 0, c = sb->textureUnitEntries.size(); i < c; i++) {
|
||||
GLuint samplerId = 0u;
|
||||
const GLTexture* t = nullptr;
|
||||
if (UTILS_LIKELY(pSamplers[i].t)) {
|
||||
t = handle_cast<const GLTexture*>(pSamplers[i].t);
|
||||
Handle<HwTexture> th = pSamplers[i].t;
|
||||
if (UTILS_LIKELY(th)) {
|
||||
GLTexture const* const t = handle_cast<const GLTexture*>(th);
|
||||
assert_invariant(t);
|
||||
|
||||
SamplerParams params = pSamplers[i].s;
|
||||
@@ -2390,7 +2394,7 @@ void OpenGLDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
|
||||
// which is not an error.
|
||||
}
|
||||
|
||||
sb->textureUnitEntries[i] = { t, samplerId };
|
||||
sb->textureUnitEntries[i] = { th, samplerId };
|
||||
}
|
||||
scheduleDestroy(std::move(data));
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ public:
|
||||
struct GLSamplerGroup : public HwSamplerGroup {
|
||||
using HwSamplerGroup::HwSamplerGroup;
|
||||
struct Entry {
|
||||
GLTexture const* texture = nullptr;
|
||||
Handle<HwTexture> th;
|
||||
GLuint sampler = 0u;
|
||||
};
|
||||
utils::FixedCapacityVector<Entry> textureUnitEntries;
|
||||
@@ -256,6 +256,11 @@ private:
|
||||
return mHandleAllocator.handle_cast<Dp, B>(handle);
|
||||
}
|
||||
|
||||
template<typename B>
|
||||
bool is_valid(Handle<B>& handle) {
|
||||
return mHandleAllocator.is_valid(handle);
|
||||
}
|
||||
|
||||
template<typename Dp, typename B>
|
||||
inline typename std::enable_if_t<
|
||||
std::is_pointer_v<Dp> &&
|
||||
|
||||
@@ -20,21 +20,24 @@
|
||||
#include "OpenGLDriver.h"
|
||||
#include "ShaderCompilerService.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Program.h>
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <private/backend/BackendUtils.h>
|
||||
|
||||
#include <utils/debug.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Systrace.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <new>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -241,8 +244,9 @@ void OpenGLProgram::updateSamplers(OpenGLDriver* const gld) const noexcept {
|
||||
assert_invariant(sb);
|
||||
if (!sb) continue; // should never happen, this would be a user error.
|
||||
for (uint8_t j = 0, m = sb->textureUnitEntries.size(); j < m; ++j, ++tmu) { // "<=" on purpose here
|
||||
const GLTexture* const t = sb->textureUnitEntries[j].texture;
|
||||
if (t) { // program may not use all samplers of sampler group
|
||||
Handle<HwTexture> th = sb->textureUnitEntries[j].th;
|
||||
if (th) { // program may not use all samplers of sampler group
|
||||
GLTexture const* const t = gld->handle_cast<GLTexture const*>(th);
|
||||
gld->bindTexture(tmu, t);
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
if (UTILS_LIKELY(!es2)) {
|
||||
|
||||
@@ -53,11 +53,21 @@ public:
|
||||
|
||||
bool isValid() const noexcept { return mToken || gl.program != 0; }
|
||||
|
||||
void use(OpenGLDriver* const gld, OpenGLContext& context) noexcept {
|
||||
if (UTILS_UNLIKELY(!gl.program)) {
|
||||
bool use(OpenGLDriver* const gld, OpenGLContext& context) noexcept {
|
||||
// both non-null is impossible by construction
|
||||
assert_invariant(!mToken || !gl.program);
|
||||
|
||||
if (UTILS_UNLIKELY(mToken && !gl.program)) {
|
||||
// first time a program is used
|
||||
initialize(*gld);
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(!gl.program)) {
|
||||
// compilation failed (token should be null)
|
||||
assert_invariant(!mToken);
|
||||
return false;
|
||||
}
|
||||
|
||||
context.useProgram(gl.program);
|
||||
if (UTILS_UNLIKELY(mUsedBindingsCount)) {
|
||||
// We rely on GL state tracking to avoid unnecessary glBindTexture / glBindSampler
|
||||
@@ -74,6 +84,7 @@ public:
|
||||
|
||||
updateSamplers(gld);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// For ES2 only
|
||||
|
||||
@@ -359,7 +359,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||
GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t& token) {
|
||||
GLuint const program = initialize(token);
|
||||
assert_invariant(token == nullptr);
|
||||
#ifndef FILAMENT_ENABLE_MATDBG
|
||||
#if !FILAMENT_ENABLE_MATDBG
|
||||
assert_invariant(program);
|
||||
#endif
|
||||
return program;
|
||||
@@ -572,16 +572,23 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context,
|
||||
|
||||
// split shader source, so we can insert the specialization constants and the packing
|
||||
// functions
|
||||
auto const [prolog, body] = splitShaderSource({ shader_src, shader_len });
|
||||
auto [version, prolog, body] = splitShaderSource({ shader_src, shader_len });
|
||||
|
||||
const std::array<const char*, 4> sources = {
|
||||
// enable ESSL 3.10 if available
|
||||
if (context.isAtLeastGLES<3, 1>()) {
|
||||
version = "#version 310 es\n";
|
||||
}
|
||||
|
||||
const std::array<const char*, 5> sources = {
|
||||
version.data(),
|
||||
prolog.data(),
|
||||
specializationConstantString.c_str(),
|
||||
packingFunctions.data(),
|
||||
body.data()
|
||||
};
|
||||
|
||||
const std::array<GLint, 4> lengths = {
|
||||
const std::array<GLint, 5> lengths = {
|
||||
(GLint)version.length(),
|
||||
(GLint)prolog.length(),
|
||||
(GLint)specializationConstantString.length(),
|
||||
(GLint)packingFunctions.length(),
|
||||
@@ -661,6 +668,7 @@ void ShaderCompilerService::process_OVR_multiview2(OpenGLContext& context,
|
||||
|
||||
// Tragically, OpenGL 4.1 doesn't support unpackHalf2x16 (appeared in 4.2) and
|
||||
// macOS doesn't support GL_ARB_shading_language_packing
|
||||
// Also GLES3.0 didn't have the full set of packing/unpacking functions
|
||||
std::string_view ShaderCompilerService::process_ARB_shading_language_packing(OpenGLContext& context) noexcept {
|
||||
using namespace std::literals;
|
||||
#ifdef BACKEND_OPENGL_VERSION_GL
|
||||
@@ -700,31 +708,102 @@ highp uint packHalf2x16(vec2 v) {
|
||||
highp uint y = fp32tou16(v.y);
|
||||
return (y << 16u) | x;
|
||||
}
|
||||
highp uint packUnorm4x8(mediump vec4 v) {
|
||||
v = round(clamp(v, 0.0, 1.0) * 255.0);
|
||||
highp uint a = uint(v.x);
|
||||
highp uint b = uint(v.y) << 8;
|
||||
highp uint c = uint(v.z) << 16;
|
||||
highp uint d = uint(v.w) << 24;
|
||||
return (a|b|c|d);
|
||||
}
|
||||
highp uint packSnorm4x8(mediump vec4 v) {
|
||||
v = round(clamp(v, -1.0, 1.0) * 127.0);
|
||||
highp uint a = uint((int(v.x) & 0xff));
|
||||
highp uint b = uint((int(v.y) & 0xff)) << 8;
|
||||
highp uint c = uint((int(v.z) & 0xff)) << 16;
|
||||
highp uint d = uint((int(v.w) & 0xff)) << 24;
|
||||
return (a|b|c|d);
|
||||
}
|
||||
mediump vec4 unpackUnorm4x8(highp uint v) {
|
||||
return vec4(float((v & 0x000000ffu) ),
|
||||
float((v & 0x0000ff00u) >> 8),
|
||||
float((v & 0x00ff0000u) >> 16),
|
||||
float((v & 0xff000000u) >> 24)) / 255.0;
|
||||
}
|
||||
mediump vec4 unpackSnorm4x8(highp uint v) {
|
||||
int a = int(((v ) & 0xffu) << 24u) >> 24 ;
|
||||
int b = int(((v >> 8u) & 0xffu) << 24u) >> 24 ;
|
||||
int c = int(((v >> 16u) & 0xffu) << 24u) >> 24 ;
|
||||
int d = int(((v >> 24u) & 0xffu) << 24u) >> 24 ;
|
||||
return clamp(vec4(float(a), float(b), float(c), float(d)) / 127.0, -1.0, 1.0);
|
||||
}
|
||||
)"sv;
|
||||
}
|
||||
#endif // BACKEND_OPENGL_VERSION_GL
|
||||
|
||||
#ifdef BACKEND_OPENGL_VERSION_GLES
|
||||
if (!context.isES2() && !context.isAtLeastGLES<3, 1>()) {
|
||||
return R"(
|
||||
|
||||
highp uint packUnorm4x8(mediump vec4 v) {
|
||||
v = round(clamp(v, 0.0, 1.0) * 255.0);
|
||||
highp uint a = uint(v.x);
|
||||
highp uint b = uint(v.y) << 8;
|
||||
highp uint c = uint(v.z) << 16;
|
||||
highp uint d = uint(v.w) << 24;
|
||||
return (a|b|c|d);
|
||||
}
|
||||
highp uint packSnorm4x8(mediump vec4 v) {
|
||||
v = round(clamp(v, -1.0, 1.0) * 127.0);
|
||||
highp uint a = uint((int(v.x) & 0xff));
|
||||
highp uint b = uint((int(v.y) & 0xff)) << 8;
|
||||
highp uint c = uint((int(v.z) & 0xff)) << 16;
|
||||
highp uint d = uint((int(v.w) & 0xff)) << 24;
|
||||
return (a|b|c|d);
|
||||
}
|
||||
mediump vec4 unpackUnorm4x8(highp uint v) {
|
||||
return vec4(float((v & 0x000000ffu) ),
|
||||
float((v & 0x0000ff00u) >> 8),
|
||||
float((v & 0x00ff0000u) >> 16),
|
||||
float((v & 0xff000000u) >> 24)) / 255.0;
|
||||
}
|
||||
mediump vec4 unpackSnorm4x8(highp uint v) {
|
||||
int a = int(((v ) & 0xffu) << 24u) >> 24 ;
|
||||
int b = int(((v >> 8u) & 0xffu) << 24u) >> 24 ;
|
||||
int c = int(((v >> 16u) & 0xffu) << 24u) >> 24 ;
|
||||
int d = int(((v >> 24u) & 0xffu) << 24u) >> 24 ;
|
||||
return clamp(vec4(float(a), float(b), float(c), float(d)) / 127.0, -1.0, 1.0);
|
||||
}
|
||||
)"sv;
|
||||
}
|
||||
#endif // BACKEND_OPENGL_VERSION_GLES
|
||||
return ""sv;
|
||||
}
|
||||
|
||||
// split shader source code in two, the first section goes from the start to the line after the
|
||||
// last #extension, and the 2nd part goes from there to the end.
|
||||
std::array<std::string_view, 2> ShaderCompilerService::splitShaderSource(std::string_view source) noexcept {
|
||||
auto start = source.find("#version");
|
||||
assert_invariant(start != std::string_view::npos);
|
||||
// split shader source code in three:
|
||||
// - the version line
|
||||
// - extensions
|
||||
// - everything else
|
||||
std::array<std::string_view, 3> ShaderCompilerService::splitShaderSource(std::string_view source) noexcept {
|
||||
auto version_start = source.find("#version");
|
||||
assert_invariant(version_start != std::string_view::npos);
|
||||
|
||||
auto pos = source.rfind("\n#extension");
|
||||
if (pos == std::string_view::npos) {
|
||||
pos = start;
|
||||
auto version_eol = source.find('\n', version_start) + 1;
|
||||
assert_invariant(version_eol != std::string_view::npos);
|
||||
|
||||
auto prolog_start = version_eol;
|
||||
auto prolog_eol = source.rfind("\n#extension"); // last #extension line
|
||||
if (prolog_eol == std::string_view::npos) {
|
||||
prolog_eol = prolog_start;
|
||||
} else {
|
||||
++pos;
|
||||
prolog_eol = source.find('\n', prolog_eol + 1) + 1;
|
||||
}
|
||||
auto body_start = prolog_eol;
|
||||
|
||||
auto eol = source.find('\n', pos) + 1;
|
||||
assert_invariant(eol != std::string_view::npos);
|
||||
|
||||
std::string_view const version = source.substr(start, eol - start);
|
||||
std::string_view const body = source.substr(version.length(), source.length() - version.length());
|
||||
return { version, body };
|
||||
std::string_view const version = source.substr(version_start, version_eol - version_start);
|
||||
std::string_view const prolog = source.substr(prolog_start, prolog_eol - prolog_start);
|
||||
std::string_view const body = source.substr(body_start, source.length() - body_start);
|
||||
return { version, prolog, body };
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -146,7 +146,7 @@ private:
|
||||
|
||||
static std::string_view process_ARB_shading_language_packing(OpenGLContext& context) noexcept;
|
||||
|
||||
static std::array<std::string_view, 2> splitShaderSource(std::string_view source) noexcept;
|
||||
static std::array<std::string_view, 3> splitShaderSource(std::string_view source) noexcept;
|
||||
|
||||
static GLuint linkProgram(OpenGLContext& context,
|
||||
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders,
|
||||
|
||||
@@ -37,7 +37,7 @@ inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
|
||||
VulkanAttachment src, VulkanAttachment dst,
|
||||
const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]) {
|
||||
if constexpr (FVK_ENABLED(FVK_DEBUG_BLITTER)) {
|
||||
utils::slog.d << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level
|
||||
FVK_LOGD << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level
|
||||
<< " layout=" << src.getLayout()
|
||||
<< " to=" << dst.texture->getVkImage() << ",level=" << (int) dst.level
|
||||
<< " layout=" << dst.getLayout() << utils::io::endl;
|
||||
@@ -76,7 +76,7 @@ inline void blitFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
|
||||
inline void resolveFast(const VkCommandBuffer cmdbuffer, VkImageAspectFlags aspect,
|
||||
VulkanAttachment src, VulkanAttachment dst) {
|
||||
if constexpr (FVK_ENABLED(FVK_DEBUG_BLITTER)) {
|
||||
utils::slog.d << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level
|
||||
FVK_LOGD << "Fast blit from=" << src.texture->getVkImage() << ",level=" << (int) src.level
|
||||
<< " layout=" << src.getLayout()
|
||||
<< " to=" << dst.texture->getVkImage() << ",level=" << (int) dst.level
|
||||
<< " layout=" << dst.getLayout() << utils::io::endl;
|
||||
|
||||
@@ -28,6 +28,7 @@ VulkanBuffer::VulkanBuffer(VmaAllocator allocator, VulkanStagePool& stagePool,
|
||||
: mAllocator(allocator),
|
||||
mStagePool(stagePool),
|
||||
mUsage(usage),
|
||||
mUpdatedOffset(0),
|
||||
mUpdatedBytes(0) {
|
||||
// for now make sure that only 1 bit is set in usage
|
||||
// (because loadFromCpu() assumes that somewhat)
|
||||
@@ -80,6 +81,7 @@ void VulkanBuffer::loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.buffer = mGpuBuffer,
|
||||
.offset = mUpdatedOffset,
|
||||
.size = mUpdatedBytes,
|
||||
};
|
||||
vkCmdPipelineBarrier(cmdbuf, srcStage, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1,
|
||||
@@ -93,6 +95,7 @@ void VulkanBuffer::loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint
|
||||
};
|
||||
vkCmdCopyBuffer(cmdbuf, stage->buffer, mGpuBuffer, 1, ®ion);
|
||||
|
||||
mUpdatedOffset = byteOffset;
|
||||
mUpdatedBytes = numBytes;
|
||||
|
||||
// Firstly, ensure that the copy finishes before the next draw call.
|
||||
|
||||
@@ -42,6 +42,7 @@ private:
|
||||
VmaAllocation mGpuMemory = VK_NULL_HANDLE;
|
||||
VkBuffer mGpuBuffer = VK_NULL_HANDLE;
|
||||
VkBufferUsageFlags mUsage = {};
|
||||
uint32_t mUpdatedOffset = 0;
|
||||
uint32_t mUpdatedBytes = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -178,7 +178,7 @@ VulkanCommandBuffer& VulkanCommands::get() {
|
||||
// presenting the swap chain or waiting on a fence.
|
||||
while (mAvailableBufferCount == 0) {
|
||||
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
|
||||
slog.i << "VulkanCommands has stalled. "
|
||||
FVK_LOGI << "VulkanCommands has stalled. "
|
||||
<< "If this occurs frequently, consider increasing VK_MAX_COMMAND_BUFFERS."
|
||||
<< io::endl;
|
||||
#endif
|
||||
@@ -289,7 +289,7 @@ bool VulkanCommands::flush() {
|
||||
};
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
|
||||
slog.i << "Submitting cmdbuffer=" << cmdbuffer
|
||||
FVK_LOGI << "Submitting cmdbuffer=" << cmdbuffer
|
||||
<< " wait=(" << signals[0] << ", " << signals[1] << ") "
|
||||
<< " signal=" << renderingFinished
|
||||
<< " fence=" << currentbuf->fence->fence
|
||||
@@ -305,7 +305,7 @@ bool VulkanCommands::flush() {
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
|
||||
if (result != VK_SUCCESS) {
|
||||
utils::slog.d << "Failed command buffer submission result: " << result << utils::io::endl;
|
||||
FVK_LOGD << "Failed command buffer submission result: " << result << utils::io::endl;
|
||||
}
|
||||
#endif
|
||||
assert_invariant(result == VK_SUCCESS);
|
||||
@@ -320,7 +320,7 @@ VkSemaphore VulkanCommands::acquireFinishedSignal() {
|
||||
VkSemaphore semaphore = mSubmissionSignal;
|
||||
mSubmissionSignal = VK_NULL_HANDLE;
|
||||
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
|
||||
slog.i << "Acquiring " << semaphore << " (e.g. for vkQueuePresentKHR)" << io::endl;
|
||||
FVK_LOGI << "Acquiring " << semaphore << " (e.g. for vkQueuePresentKHR)" << io::endl;
|
||||
#endif
|
||||
return semaphore;
|
||||
}
|
||||
@@ -329,7 +329,7 @@ void VulkanCommands::injectDependency(VkSemaphore next) {
|
||||
assert_invariant(mInjectedSignal == VK_NULL_HANDLE);
|
||||
mInjectedSignal = next;
|
||||
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
|
||||
slog.i << "Injecting " << next << " (e.g. due to vkAcquireNextImageKHR)" << io::endl;
|
||||
FVK_LOGI << "Injecting " << next << " (e.g. due to vkAcquireNextImageKHR)" << io::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -398,7 +398,7 @@ void VulkanCommands::pushGroupMarker(char const* str, VulkanGroupMarkers::Timest
|
||||
// If the timestamp is not 0, then we are carrying over a marker across buffer submits.
|
||||
// If it is 0, then this is a normal marker push and we should just print debug line as usual.
|
||||
if (timestamp.time_since_epoch().count() == 0.0) {
|
||||
utils::slog.d << "----> " << str << utils::io::endl;
|
||||
FVK_LOGD << "----> " << str << utils::io::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -436,7 +436,7 @@ void VulkanCommands::popGroupMarker() {
|
||||
auto const [marker, startTime] = mGroupMarkers->pop();
|
||||
auto const endTime = std::chrono::high_resolution_clock::now();
|
||||
std::chrono::duration<double> diff = endTime - startTime;
|
||||
utils::slog.d << "<---- " << marker << " elapsed: " << (diff.count() * 1000) << " ms"
|
||||
FVK_LOGD << "<---- " << marker << " elapsed: " << (diff.count() * 1000) << " ms"
|
||||
<< utils::io::endl;
|
||||
#else
|
||||
mGroupMarkers->pop();
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKANCONSTANTS_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANCONSTANTS_H
|
||||
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// In debug builds, we enable validation layers and set up a debug callback.
|
||||
@@ -70,6 +72,11 @@
|
||||
// the currently active resources.
|
||||
#define FVK_DEBUG_RESOURCE_LEAK 0x00010000
|
||||
|
||||
// Set this to enable logging "only" to one output stream. This is useful in the case where we want
|
||||
// to debug with print statements and want ordered logging (e.g slog.i and slog.e will not appear in
|
||||
// order of calls).
|
||||
#define FVK_DEBUG_FORCE_LOG_TO_I 0x00020000
|
||||
|
||||
// Useful default combinations
|
||||
#define FVK_DEBUG_EVERYTHING 0xFFFFFFFF
|
||||
#define FVK_DEBUG_PERFORMANCE \
|
||||
@@ -133,6 +140,18 @@ static_assert(FVK_ENABLED(FVK_DEBUG_VALIDATION));
|
||||
#define FVK_HANDLE_ARENA_SIZE_IN_MB 8
|
||||
#endif
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_FORCE_LOG_TO_I)
|
||||
#define FVK_LOGI (utils::slog.i)
|
||||
#define FVK_LOGD FVK_LOGI
|
||||
#define FVK_LOGE FVK_LOGI
|
||||
#define FVK_LOGW FVK_LOGI
|
||||
#else
|
||||
#define FVK_LOGE (utils::slog.e)
|
||||
#define FVK_LOGW (utils::slog.w)
|
||||
#define FVK_LOGD (utils::slog.d)
|
||||
#define FVK_LOGI (utils::slog.i)
|
||||
#endif
|
||||
|
||||
// All vkCreate* functions take an optional allocator. For now we select the default allocator by
|
||||
// passing in a null pointer, and we highlight the argument by using the VKALLOC constant.
|
||||
constexpr struct VkAllocationCallbacks* VKALLOC = nullptr;
|
||||
|
||||
@@ -100,7 +100,7 @@ std::tuple<uint32_t, uint32_t> VulkanTimestamps::getNextQuery() {
|
||||
return std::make_tuple(timerIndex * 2, timerIndex * 2 + 1);
|
||||
}
|
||||
}
|
||||
utils::slog.e << "More than " << maxTimers << " timers are not supported." << utils::io::endl;
|
||||
FVK_LOGE << "More than " << maxTimers << " timers are not supported." << utils::io::endl;
|
||||
return std::make_tuple(0, 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -131,6 +131,10 @@ public:
|
||||
return mDebugUtilsSupported;
|
||||
}
|
||||
|
||||
inline bool isMultiviewEnabled() const noexcept {
|
||||
return mMultiviewEnabled;
|
||||
}
|
||||
|
||||
inline bool isClipDistanceSupported() const noexcept {
|
||||
return mPhysicalDeviceFeatures.shaderClipDistance == VK_TRUE;
|
||||
}
|
||||
@@ -141,6 +145,7 @@ private:
|
||||
VkPhysicalDeviceFeatures mPhysicalDeviceFeatures = {};
|
||||
bool mDebugMarkersSupported = false;
|
||||
bool mDebugUtilsSupported = false;
|
||||
bool mMultiviewEnabled = false;
|
||||
|
||||
VkFormatList mDepthStencilFormats;
|
||||
VkFormatList mBlittableDepthStencilFormats;
|
||||
|
||||
@@ -115,15 +115,15 @@ VKAPI_ATTR VkBool32 VKAPI_CALL debugReportCallback(VkDebugReportFlagsEXT flags,
|
||||
VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location,
|
||||
int32_t messageCode, const char* pLayerPrefix, const char* pMessage, void* pUserData) {
|
||||
if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) {
|
||||
utils::slog.e << "VULKAN ERROR: (" << pLayerPrefix << ") " << pMessage << utils::io::endl;
|
||||
FVK_LOGE << "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;
|
||||
FVK_LOGW << "VULKAN WARNING: (" << pLayerPrefix << ") " << pMessage << utils::io::endl;
|
||||
}
|
||||
utils::slog.e << utils::io::endl;
|
||||
FVK_LOGE << utils::io::endl;
|
||||
return VK_FALSE;
|
||||
}
|
||||
#endif // FVK_EANBLED(FVK_DEBUG_VALIDATION)
|
||||
@@ -133,18 +133,18 @@ VKAPI_ATTR VkBool32 VKAPI_CALL debugUtilsCallback(VkDebugUtilsMessageSeverityFla
|
||||
VkDebugUtilsMessageTypeFlagsEXT types, const VkDebugUtilsMessengerCallbackDataEXT* cbdata,
|
||||
void* pUserData) {
|
||||
if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
|
||||
utils::slog.e << "VULKAN ERROR: (" << cbdata->pMessageIdName << ") " << cbdata->pMessage
|
||||
<< utils::io::endl;
|
||||
FVK_LOGE << "VULKAN ERROR: (" << cbdata->pMessageIdName << ") " << cbdata->pMessage
|
||||
<< 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")) {
|
||||
return VK_FALSE;
|
||||
}
|
||||
utils::slog.w << "VULKAN WARNING: (" << cbdata->pMessageIdName << ") " << cbdata->pMessage
|
||||
<< utils::io::endl;
|
||||
FVK_LOGW << "VULKAN WARNING: (" << cbdata->pMessageIdName << ") " << cbdata->pMessage
|
||||
<< utils::io::endl;
|
||||
}
|
||||
utils::slog.e << utils::io::endl;
|
||||
FVK_LOGE << utils::io::endl;
|
||||
return VK_FALSE;
|
||||
}
|
||||
#endif // FVK_EANBLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
@@ -292,7 +292,7 @@ Driver* VulkanDriver::create(VulkanPlatform* platform, VulkanContext const& cont
|
||||
// VulkanRenderTarget : 312 few
|
||||
// -- less than or equal to 312 bytes
|
||||
|
||||
utils::slog.d
|
||||
FVK_LOGD
|
||||
<< "\nVulkanSwapChain: " << sizeof(VulkanSwapChain)
|
||||
<< "\nVulkanBufferObject: " << sizeof(VulkanBufferObject)
|
||||
<< "\nVulkanVertexBuffer: " << sizeof(VulkanVertexBuffer)
|
||||
@@ -660,8 +660,8 @@ void VulkanDriver::createFenceR(Handle<HwFence> fh, int) {
|
||||
|
||||
void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
|
||||
if ((flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0 && !isSRGBSwapChainSupported()) {
|
||||
utils::slog.w << "sRGB swapchain requested, but Platform does not support it"
|
||||
<< utils::io::endl;
|
||||
FVK_LOGW << "sRGB swapchain requested, but Platform does not support it"
|
||||
<< utils::io::endl;
|
||||
flags = flags | ~(backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE);
|
||||
}
|
||||
auto swapChain = mResourceAllocator.construct<VulkanSwapChain>(sch, mPlatform, mContext,
|
||||
@@ -672,7 +672,7 @@ void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
|
||||
void VulkanDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t width,
|
||||
uint32_t height, uint64_t flags) {
|
||||
if ((flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0 && !isSRGBSwapChainSupported()) {
|
||||
utils::slog.w << "sRGB swapchain requested, but Platform does not support it"
|
||||
FVK_LOGW << "sRGB swapchain requested, but Platform does not support it"
|
||||
<< utils::io::endl;
|
||||
flags = flags | ~(backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE);
|
||||
}
|
||||
@@ -914,7 +914,7 @@ bool VulkanDriver::isStereoSupported() {
|
||||
case backend::StereoscopicType::INSTANCED:
|
||||
return mContext.isClipDistanceSupported();
|
||||
case backend::StereoscopicType::MULTIVIEW:
|
||||
// TODO: implement multiview feature in Vulkan.
|
||||
return mContext.isMultiviewEnabled();
|
||||
case backend::StereoscopicType::NONE:
|
||||
return false;
|
||||
}
|
||||
@@ -986,15 +986,15 @@ FeatureLevel VulkanDriver::getFeatureLevel() {
|
||||
|
||||
// If the max sampler counts do not meet FL2 standards, then this is an FL1 device.
|
||||
const auto& fl2 = FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_2];
|
||||
if (fl2.MAX_VERTEX_SAMPLER_COUNT < limits.maxPerStageDescriptorSamplers ||
|
||||
fl2.MAX_FRAGMENT_SAMPLER_COUNT < limits.maxPerStageDescriptorSamplers) {
|
||||
if (limits.maxPerStageDescriptorSamplers < fl2.MAX_VERTEX_SAMPLER_COUNT ||
|
||||
limits.maxPerStageDescriptorSamplers < fl2.MAX_FRAGMENT_SAMPLER_COUNT) {
|
||||
return FeatureLevel::FEATURE_LEVEL_1;
|
||||
}
|
||||
|
||||
// If the max sampler counts do not meet FL3 standards, then this is an FL2 device.
|
||||
const auto& fl3 = FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_3];
|
||||
if (fl3.MAX_VERTEX_SAMPLER_COUNT < limits.maxPerStageDescriptorSamplers ||
|
||||
fl3.MAX_FRAGMENT_SAMPLER_COUNT < limits.maxPerStageDescriptorSamplers) {
|
||||
if (limits.maxPerStageDescriptorSamplers < fl3.MAX_VERTEX_SAMPLER_COUNT||
|
||||
limits.maxPerStageDescriptorSamplers < fl3.MAX_FRAGMENT_SAMPLER_COUNT) {
|
||||
return FeatureLevel::FEATURE_LEVEL_2;
|
||||
}
|
||||
|
||||
@@ -1862,9 +1862,9 @@ void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
// matching characteristics. (e.g. if the missing texture is a 3D texture)
|
||||
if (UTILS_UNLIKELY(texture->getPrimaryImageLayout() == VulkanLayout::UNDEFINED)) {
|
||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE) && FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
utils::slog.w << "Uninitialized texture bound to '" << bindingToName[binding] << "'";
|
||||
utils::slog.w << " in material '" << program->name.c_str() << "'";
|
||||
utils::slog.w << " at binding point " << +binding << utils::io::endl;
|
||||
FVK_LOGW << "Uninitialized texture bound to '" << bindingToName[binding] << "'";
|
||||
FVK_LOGW << " in material '" << program->name.c_str() << "'";
|
||||
FVK_LOGW << " at binding point " << +binding << utils::io::endl;
|
||||
#endif
|
||||
texture = mEmptyTexture;
|
||||
}
|
||||
@@ -2014,7 +2014,7 @@ void VulkanDriver::debugCommandBegin(CommandStream* cmds, bool synchronous, cons
|
||||
assert_invariant(inRenderPass);
|
||||
inRenderPass = false;
|
||||
} else if (inRenderPass && OUTSIDE_COMMANDS.find(command) != OUTSIDE_COMMANDS.end()) {
|
||||
utils::slog.e << command.data() << " issued inside a render pass." << utils::io::endl;
|
||||
FVK_LOGE << command.data() << " issued inside a render pass." << utils::io::endl;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ VkFramebuffer VulkanFboCache::getFramebuffer(FboKey config) noexcept {
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_FBO_CACHE)
|
||||
utils::slog.d << "Creating framebuffer " << config.width << "x" << config.height << " "
|
||||
FVK_LOGD << "Creating framebuffer " << config.width << "x" << config.height << " "
|
||||
<< "for render pass " << config.renderPass << ", "
|
||||
<< "samples = " << int(config.samples) << ", "
|
||||
<< "depth = " << (config.depth ? 1 : 0) << ", "
|
||||
@@ -310,7 +310,7 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
|
||||
mRenderPassCache[config] = {renderPass, mCurrentTime};
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_FBO_CACHE)
|
||||
utils::slog.d << "Created render pass " << renderPass << " with "
|
||||
FVK_LOGD << "Created render pass " << renderPass << " with "
|
||||
<< "samples = " << int(config.samples) << ", "
|
||||
<< "depth = " << (hasDepth ? 1 : 0) << ", "
|
||||
<< "colorAttachmentCount[0] = " << subpasses[0].colorAttachmentCount
|
||||
|
||||
@@ -292,8 +292,8 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
|
||||
utils::slog.d << "Created VulkanProgram " << builder << ", shaders = (" << modules[0]
|
||||
<< ", " << modules[1] << ")" << utils::io::endl;
|
||||
FVK_LOGD << "Created VulkanProgram " << builder << ", shaders = (" << modules[0]
|
||||
<< ", " << modules[1] << ")" << utils::io::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -230,14 +230,15 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
||||
PipelineCacheEntry cacheEntry = {};
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
|
||||
utils::slog.d << "vkCreateGraphicsPipelines with shaders = ("
|
||||
<< shaderStages[0].module << ", " << shaderStages[1].module << ")" << utils::io::endl;
|
||||
FVK_LOGD << "vkCreateGraphicsPipelines with shaders = ("
|
||||
<< shaderStages[0].module << ", " << shaderStages[1].module << ")"
|
||||
<< utils::io::endl;
|
||||
#endif
|
||||
VkResult error = vkCreateGraphicsPipelines(mDevice, VK_NULL_HANDLE, 1, &pipelineCreateInfo,
|
||||
VKALLOC, &cacheEntry.handle);
|
||||
assert_invariant(error == VK_SUCCESS);
|
||||
if (error != VK_SUCCESS) {
|
||||
utils::slog.e << "vkCreateGraphicsPipelines error " << error << utils::io::endl;
|
||||
FVK_LOGE << "vkCreateGraphicsPipelines error " << error << utils::io::endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -252,7 +253,7 @@ void VulkanPipelineCache::bindProgram(VulkanProgram* program) noexcept {
|
||||
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
|
||||
if (mPipelineRequirements.shaders[0] == VK_NULL_HANDLE ||
|
||||
mPipelineRequirements.shaders[1] == VK_NULL_HANDLE) {
|
||||
utils::slog.e << "Binding missing shader: " << program->name.c_str() << utils::io::endl;
|
||||
FVK_LOGE << "Binding missing shader: " << program->name.c_str() << utils::io::endl;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -167,9 +167,9 @@ void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint
|
||||
vkCreateImage(device, &imageInfo, VKALLOC, &stagingImage);
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_READ_PIXELS)
|
||||
utils::slog.d << "readPixels created image=" << stagingImage
|
||||
<< " to copy from image=" << srcTexture->getVkImage()
|
||||
<< " src-layout=" << srcTexture->getLayout(0, 0) << utils::io::endl;
|
||||
FVK_LOGD << "readPixels created image=" << stagingImage
|
||||
<< " to copy from image=" << srcTexture->getVkImage()
|
||||
<< " src-layout=" << srcTexture->getLayout(0, 0) << utils::io::endl;
|
||||
#endif
|
||||
|
||||
VkMemoryRequirements memReqs;
|
||||
@@ -185,7 +185,7 @@ void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint
|
||||
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
|
||||
FVK_LOGW
|
||||
<< "readPixels is slow because VK_MEMORY_PROPERTY_HOST_CACHED_BIT is not available"
|
||||
<< utils::io::endl;
|
||||
}
|
||||
@@ -303,7 +303,7 @@ void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint
|
||||
VkResult status = vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
|
||||
// Fence hasn't been reached. Try waiting again.
|
||||
if (status != VK_SUCCESS) {
|
||||
utils::slog.e << "Failed to wait for readPixels fence" << utils::io::endl;
|
||||
FVK_LOGE << "Failed to wait for readPixels fence" << utils::io::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -321,7 +321,7 @@ void VulkanReadPixels::run(VulkanRenderTarget* srcTarget, uint32_t const x, uint
|
||||
getComponentCount(srcFormat), srcPixels,
|
||||
static_cast<int>(subResourceLayout.rowPitch), static_cast<int>(width),
|
||||
static_cast<int>(height), swizzle)) {
|
||||
utils::slog.e << "Unsupported PixelDataFormat or PixelDataType" << utils::io::endl;
|
||||
FVK_LOGE << "Unsupported PixelDataFormat or PixelDataType" << utils::io::endl;
|
||||
}
|
||||
|
||||
vkUnmapMemory(device, stagingMemory);
|
||||
|
||||
@@ -111,11 +111,11 @@ private:
|
||||
#if DEBUG_RESOURCE_LEAKS
|
||||
public:
|
||||
void print() {
|
||||
utils::slog.d << "Resource Allocator state (debug only)" << utils::io::endl;
|
||||
FVK_LOGD << "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;
|
||||
FVK_LOGD << "[" << i << "]=" << mDebugOnlyResourceCount[i] << utils::io::endl;
|
||||
}
|
||||
utils::slog.d << "+++++++++++++++++++++++++++++++++++++" << utils::io::endl;
|
||||
FVK_LOGD << "+++++++++++++++++++++++++++++++++++++" << utils::io::endl;
|
||||
}
|
||||
private:
|
||||
utils::FixedCapacityVector<size_t> mDebugOnlyResourceCount;
|
||||
|
||||
@@ -61,7 +61,7 @@ VulkanStage const* VulkanStagePool::acquireStage(uint32_t numBytes) {
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_ALLOCATION)
|
||||
if (result != VK_SUCCESS) {
|
||||
utils::slog.e << "Allocation error: " << result << utils::io::endl;
|
||||
FVK_LOGE << "Allocation error: " << result << utils::io::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -35,29 +35,13 @@ VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const&
|
||||
mStagePool(stagePool),
|
||||
mHeadless(extent.width != 0 && extent.height != 0 && !nativeWindow),
|
||||
mFlushAndWaitOnResize(platform->getCustomization().flushAndWaitOnWindowResize),
|
||||
mCurrentImageReadyIndex(0),
|
||||
mTransitionSwapChainImageLayoutForPresent(
|
||||
platform->getCustomization().transitionSwapChainImageLayoutForPresent),
|
||||
mAcquired(false),
|
||||
mIsFirstRenderPass(true) {
|
||||
swapChain = mPlatform->createSwapChain(nativeWindow, flags, extent);
|
||||
FILAMENT_CHECK_POSTCONDITION(swapChain) << "Unable to create swapchain";
|
||||
|
||||
VkSemaphoreCreateInfo const createInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
|
||||
};
|
||||
|
||||
// No need to wait on this semaphore before drawing when in Headless mode.
|
||||
if (mHeadless) {
|
||||
// Set all sempahores to VK_NULL_HANDLE
|
||||
memset(mImageReady, 0, sizeof(mImageReady[0]) * IMAGE_READY_SEMAPHORE_COUNT);
|
||||
} else {
|
||||
for (uint32_t i = 0; i < IMAGE_READY_SEMAPHORE_COUNT; ++i) {
|
||||
VkResult result =
|
||||
vkCreateSemaphore(mPlatform->getDevice(), &createInfo, nullptr, mImageReady + i);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "Failed to create semaphore";
|
||||
}
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -68,11 +52,6 @@ VulkanSwapChain::~VulkanSwapChain() {
|
||||
mCommands->wait();
|
||||
|
||||
mPlatform->destroy(swapChain);
|
||||
for (uint32_t i = 0; i < IMAGE_READY_SEMAPHORE_COUNT; ++i) {
|
||||
if (mImageReady[i] != VK_NULL_HANDLE) {
|
||||
vkDestroySemaphore(mPlatform->getDevice(), mImageReady[i], VKALLOC);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanSwapChain::update() {
|
||||
@@ -95,7 +74,7 @@ void VulkanSwapChain::update() {
|
||||
}
|
||||
|
||||
void VulkanSwapChain::present() {
|
||||
if (!mHeadless) {
|
||||
if (!mHeadless && mTransitionSwapChainImageLayoutForPresent) {
|
||||
VkCommandBuffer const cmdbuf = mCommands->get().buffer();
|
||||
VkImageSubresourceRange const subresources{
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
@@ -106,10 +85,15 @@ void VulkanSwapChain::present() {
|
||||
};
|
||||
mColors[mCurrentSwapIndex]->transitionLayout(cmdbuf, subresources, VulkanLayout::PRESENT);
|
||||
}
|
||||
mCommands->flush();
|
||||
|
||||
// We only present if it is not headless. No-op for headless (but note that we still need the
|
||||
// flush() in the above line).
|
||||
mCommands->flush();
|
||||
|
||||
// call the image ready wait function
|
||||
if (mExplicitImageReadyWait != nullptr) {
|
||||
mExplicitImageReadyWait(swapChain);
|
||||
}
|
||||
|
||||
// We only present if it is not headless. No-op for headless.
|
||||
if (!mHeadless) {
|
||||
VkSemaphore const finishedDrawing = mCommands->acquireFinishedSignal();
|
||||
VkResult const result = mPlatform->present(swapChain, mCurrentSwapIndex, finishedDrawing);
|
||||
@@ -139,13 +123,14 @@ void VulkanSwapChain::acquire(bool& resized) {
|
||||
update();
|
||||
}
|
||||
|
||||
mCurrentImageReadyIndex = (mCurrentImageReadyIndex + 1) % IMAGE_READY_SEMAPHORE_COUNT;
|
||||
const VkSemaphore imageReady = mImageReady[mCurrentImageReadyIndex];
|
||||
VkResult const result = mPlatform->acquire(swapChain, imageReady, &mCurrentSwapIndex);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR)
|
||||
<< "Cannot acquire in swapchain.";
|
||||
if (imageReady != VK_NULL_HANDLE) {
|
||||
mCommands->injectDependency(imageReady);
|
||||
VulkanPlatform::ImageSyncData imageSyncData;
|
||||
VkResult const result = mPlatform->acquire(swapChain, &imageSyncData);
|
||||
mCurrentSwapIndex = imageSyncData.imageIndex;
|
||||
mExplicitImageReadyWait = imageSyncData.explicitImageReadyWait;
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR)
|
||||
<< "Cannot acquire in swapchain.";
|
||||
if (imageSyncData.imageReadySemaphore != VK_NULL_HANDLE) {
|
||||
mCommands->injectDependency(imageSyncData.imageReadySemaphore);
|
||||
}
|
||||
mAcquired = true;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,10 @@ struct VulkanSwapChain : public HwSwapChain, VulkanResource {
|
||||
void acquire(bool& reized);
|
||||
|
||||
inline VulkanTexture* getCurrentColor() const noexcept {
|
||||
return mColors[mCurrentSwapIndex].get();
|
||||
uint32_t const imageIndex = mCurrentSwapIndex;
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
imageIndex != VulkanPlatform::ImageSyncData::INVALID_IMAGE_INDEX);
|
||||
return mColors[imageIndex].get();
|
||||
}
|
||||
|
||||
inline VulkanTexture* getDepth() const noexcept {
|
||||
@@ -80,15 +83,15 @@ private:
|
||||
VulkanStagePool& mStagePool;
|
||||
bool const mHeadless;
|
||||
bool const mFlushAndWaitOnResize;
|
||||
bool const mTransitionSwapChainImageLayoutForPresent;
|
||||
|
||||
// We create VulkanTextures based on VkImages. VulkanTexture has facilities for doing layout
|
||||
// transitions, which are useful here.
|
||||
utils::FixedCapacityVector<std::unique_ptr<VulkanTexture>> mColors;
|
||||
std::unique_ptr<VulkanTexture> mDepth;
|
||||
VkExtent2D mExtent;
|
||||
VkSemaphore mImageReady[IMAGE_READY_SEMAPHORE_COUNT];
|
||||
uint32_t mCurrentImageReadyIndex;
|
||||
uint32_t mCurrentSwapIndex;
|
||||
std::function<void(Platform::SwapChain* handle)> mExplicitImageReadyWait = nullptr;
|
||||
bool mAcquired;
|
||||
bool mIsFirstRenderPass;
|
||||
};
|
||||
|
||||
@@ -113,7 +113,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
VkFormatProperties props;
|
||||
vkGetPhysicalDeviceFormatProperties(physicalDevice, mVkFormat, &props);
|
||||
if (!(props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)) {
|
||||
utils::slog.w << "Texture usage is SAMPLEABLE but format " << mVkFormat << " is not "
|
||||
FVK_LOGW << "Texture usage is SAMPLEABLE but format " << mVkFormat << " is not "
|
||||
"sampleable with optimal tiling." << utils::io::endl;
|
||||
}
|
||||
#endif
|
||||
@@ -163,7 +163,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
|
||||
VkResult error = vkCreateImage(mDevice, &imageInfo, VKALLOC, &mTextureImage);
|
||||
if (error || FVK_ENABLED(FVK_DEBUG_TEXTURE)) {
|
||||
utils::slog.d << "vkCreateImage: "
|
||||
FVK_LOGD << "vkCreateImage: "
|
||||
<< "image = " << mTextureImage << ", "
|
||||
<< "result = " << error << ", "
|
||||
<< "handle = " << utils::io::hex << mTextureImage << utils::io::dec << ", "
|
||||
@@ -450,7 +450,7 @@ void VulkanTexture::transitionLayout(VkCommandBuffer cmdbuf, const VkImageSubres
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_LAYOUT_TRANSITION)
|
||||
utils::slog.d << "transition texture=" << mTextureImage
|
||||
FVK_LOGD << "transition texture=" << mTextureImage
|
||||
<< " (" << range.baseArrayLayer
|
||||
<< "," << range.baseMipLevel << ")"
|
||||
<< " count=(" << range.layerCount
|
||||
@@ -539,7 +539,7 @@ void VulkanTexture::print() const {
|
||||
layer < (mPrimaryViewRange.baseArrayLayer + mPrimaryViewRange.layerCount) &&
|
||||
level >= mPrimaryViewRange.baseMipLevel &&
|
||||
level < (mPrimaryViewRange.baseMipLevel + mPrimaryViewRange.levelCount);
|
||||
utils::slog.d << "[" << mTextureImage << "]: (" << layer << "," << level
|
||||
FVK_LOGD << "[" << mTextureImage << "]: (" << layer << "," << level
|
||||
<< ")=" << getLayout(layer, level)
|
||||
<< " primary=" << primary
|
||||
<< utils::io::endl;
|
||||
@@ -548,7 +548,7 @@ void VulkanTexture::print() const {
|
||||
|
||||
for (auto view: mCachedImageViews) {
|
||||
auto& range = view.first.range;
|
||||
utils::slog.d << "[" << mTextureImage << ", imageView=" << view.second << "]=>"
|
||||
FVK_LOGD << "[" << mTextureImage << ", imageView=" << view.second << "]=>"
|
||||
<< " (" << range.baseArrayLayer << "," << range.baseMipLevel << ")"
|
||||
<< " count=(" << range.layerCount << "," << range.levelCount << ")"
|
||||
<< " aspect=" << range.aspectMask << " viewType=" << view.first.type
|
||||
|
||||
@@ -33,11 +33,11 @@ VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
|
||||
|
||||
// build the push constant layout key
|
||||
uint32_t pushConstantRangeCount = program->getPushConstantRangeCount();
|
||||
auto const& pushCostantRanges = program->getPushConstantRanges();
|
||||
auto const& pushConstantRanges = program->getPushConstantRanges();
|
||||
if (pushConstantRangeCount > 0) {
|
||||
assert_invariant(pushConstantRangeCount <= Program::SHADER_TYPE_COUNT);
|
||||
for (uint8_t i = 0; i < pushConstantRangeCount; ++i) {
|
||||
auto const& range = pushCostantRanges[i];
|
||||
auto const& range = pushConstantRanges[i];
|
||||
auto& pushConstant = key.pushConstant[i];
|
||||
if (range.stageFlags & VK_SHADER_STAGE_VERTEX_BIT) {
|
||||
pushConstant.stage = static_cast<uint8_t>(ShaderStage::VERTEX);
|
||||
@@ -52,9 +52,8 @@ VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
|
||||
}
|
||||
}
|
||||
|
||||
auto iter = mPipelineLayouts.find(key);
|
||||
if (iter != mPipelineLayouts.end()) {
|
||||
PipelineLayoutCacheEntry& entry = mPipelineLayouts[key];
|
||||
if (PipelineLayoutMap::iterator iter = mPipelineLayouts.find(key); iter != mPipelineLayouts.end()) {
|
||||
PipelineLayoutCacheEntry& entry = iter.value();
|
||||
entry.lastUsed = mTimestamp++;
|
||||
return entry.handle;
|
||||
}
|
||||
@@ -65,7 +64,7 @@ VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
|
||||
.setLayoutCount = (uint32_t) descSetLayoutCount,
|
||||
.pSetLayouts = key.descSetLayouts.data(),
|
||||
.pushConstantRangeCount = pushConstantRangeCount,
|
||||
.pPushConstantRanges = pushCostantRanges,
|
||||
.pPushConstantRanges = pushConstantRanges,
|
||||
};
|
||||
|
||||
VkPipelineLayout layout;
|
||||
|
||||
@@ -95,7 +95,7 @@ void printDeviceInfo(VkInstance instance, VkPhysicalDevice device) {
|
||||
.pNext = &driverProperties,
|
||||
};
|
||||
vkGetPhysicalDeviceProperties2(device, &physicalDeviceProperties2);
|
||||
utils::slog.i << "Vulkan device driver: " << driverProperties.driverName << " "
|
||||
FVK_LOGI << "Vulkan device driver: " << driverProperties.driverName << " "
|
||||
<< driverProperties.driverInfo << utils::io::endl;
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ void printDeviceInfo(VkInstance instance, VkPhysicalDevice device) {
|
||||
const FixedCapacityVector<VkPhysicalDevice> physicalDevices
|
||||
= filament::backend::enumerate(vkEnumeratePhysicalDevices, instance);
|
||||
|
||||
utils::slog.i << "Selected physical device '" << deviceProperties.deviceName << "' from "
|
||||
FVK_LOGI << "Selected physical device '" << deviceProperties.deviceName << "' from "
|
||||
<< physicalDevices.size() << " physical devices. "
|
||||
<< "(vendor " << utils::io::hex << vendorID << ", "
|
||||
<< "device " << deviceID << ", "
|
||||
@@ -142,15 +142,15 @@ void printDepthFormats(VkPhysicalDevice device) {
|
||||
// Note that Vulkan is more constrained than OpenGL ES 3.1 in this area.
|
||||
const VkFormatFeatureFlags required = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT
|
||||
| VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;
|
||||
utils::slog.i << "Sampleable depth formats: ";
|
||||
FVK_LOGI << "Sampleable depth formats: ";
|
||||
for (VkFormat format : ALL_VK_FORMATS) {
|
||||
VkFormatProperties props;
|
||||
vkGetPhysicalDeviceFormatProperties(device, format, &props);
|
||||
if ((props.optimalTilingFeatures & required) == required) {
|
||||
utils::slog.i << format << " ";
|
||||
FVK_LOGI << format << " ";
|
||||
}
|
||||
}
|
||||
utils::slog.i << utils::io::endl;
|
||||
FVK_LOGI << utils::io::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -198,6 +198,7 @@ ExtensionSet getDeviceExtensions(VkPhysicalDevice device) {
|
||||
VK_KHR_MAINTENANCE1_EXTENSION_NAME,
|
||||
VK_KHR_MAINTENANCE2_EXTENSION_NAME,
|
||||
VK_KHR_MAINTENANCE3_EXTENSION_NAME,
|
||||
VK_KHR_MULTIVIEW_EXTENSION_NAME,
|
||||
};
|
||||
ExtensionSet exts;
|
||||
// Identify supported physical device extensions
|
||||
@@ -241,11 +242,11 @@ VkInstance createInstance(ExtensionSet const& requiredExts) {
|
||||
instanceCreateInfo.ppEnabledLayerNames = enabledLayers.data();
|
||||
} else {
|
||||
#if defined(__ANDROID__)
|
||||
utils::slog.d << "Validation layers are not available; did you set jniLibs in your "
|
||||
<< "gradle file?" << utils::io::endl;
|
||||
FVK_LOGD << "Validation layers are not available; did you set jniLibs in your "
|
||||
<< "gradle file?" << utils::io::endl;
|
||||
#else
|
||||
utils::slog.d << "Validation layer not available; did you install the Vulkan SDK?\n"
|
||||
<< "Please ensure that VK_LAYER_PATH is set correctly." << utils::io::endl;
|
||||
FVK_LOGD << "Validation layer not available; did you install the Vulkan SDK?\n"
|
||||
<< "Please ensure that VK_LAYER_PATH is set correctly." << utils::io::endl;
|
||||
#endif // __ANDROID__
|
||||
|
||||
}
|
||||
@@ -334,6 +335,7 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
|
||||
deviceCreateInfo.enabledExtensionCount = (uint32_t) requestExtensions.size();
|
||||
deviceCreateInfo.ppEnabledExtensionNames = requestExtensions.data();
|
||||
|
||||
void* pNext = nullptr;
|
||||
VkPhysicalDevicePortabilitySubsetFeaturesKHR portability = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PORTABILITY_SUBSET_FEATURES_KHR,
|
||||
.pNext = nullptr,
|
||||
@@ -341,9 +343,24 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
|
||||
.mutableComparisonSamplers = VK_TRUE,
|
||||
};
|
||||
if (setContains(deviceExtensions, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME)) {
|
||||
deviceCreateInfo.pNext = &portability;
|
||||
portability.pNext = pNext;
|
||||
pNext = &portability;
|
||||
}
|
||||
|
||||
VkPhysicalDeviceMultiviewFeaturesKHR multiview = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES_KHR,
|
||||
.pNext = nullptr,
|
||||
.multiview = VK_TRUE,
|
||||
.multiviewGeometryShader = VK_FALSE,
|
||||
.multiviewTessellationShader = VK_FALSE
|
||||
};
|
||||
if (setContains(deviceExtensions, VK_KHR_MULTIVIEW_EXTENSION_NAME)) {
|
||||
multiview.pNext = pNext;
|
||||
pNext = &multiview;
|
||||
}
|
||||
|
||||
deviceCreateInfo.pNext = pNext;
|
||||
|
||||
VkResult result = vkCreateDevice(physicalDevice, &deviceCreateInfo, VKALLOC, &device);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "vkCreateDevice error=" << result << ".";
|
||||
|
||||
@@ -418,8 +435,8 @@ inline int deviceTypeOrder(VkPhysicalDeviceType deviceType) {
|
||||
case VK_PHYSICAL_DEVICE_TYPE_OTHER:
|
||||
return 1;
|
||||
default:
|
||||
utils::slog.w << "deviceTypeOrder: Unexpected deviceType: " << deviceType
|
||||
<< utils::io::endl;
|
||||
FVK_LOGW << "deviceTypeOrder: Unexpected deviceType: " << deviceType
|
||||
<< utils::io::endl;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -701,6 +718,10 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
= pruneExtensions(mImpl->mPhysicalDevice, instExts, deviceExts);
|
||||
instExts = prunedInstExts;
|
||||
deviceExts = prunedDeviceExts;
|
||||
|
||||
if (driverConfig.stereoscopicType != StereoscopicType::MULTIVIEW) {
|
||||
deviceExts.erase(VK_KHR_MULTIVIEW_EXTENSION_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
mImpl->mDevice
|
||||
@@ -716,6 +737,7 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
// Store the extension support in the context
|
||||
context.mDebugUtilsSupported = setContains(instExts, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
context.mDebugMarkersSupported = setContains(deviceExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
|
||||
context.mMultiviewEnabled = setContains(deviceExts, VK_KHR_MULTIVIEW_EXTENSION_NAME);
|
||||
|
||||
#ifdef NDEBUG
|
||||
// If we are in release build, we should not have turned on debug extensions
|
||||
@@ -750,8 +772,8 @@ VulkanPlatform::SwapChainBundle VulkanPlatform::getSwapChainBundle(SwapChainPtr
|
||||
SWAPCHAIN_RET_FUNC(getSwapChainBundle, handle, )
|
||||
}
|
||||
|
||||
VkResult VulkanPlatform::acquire(SwapChainPtr handle, VkSemaphore clientSignal, uint32_t* index) {
|
||||
SWAPCHAIN_RET_FUNC(acquire, handle, clientSignal, index)
|
||||
VkResult VulkanPlatform::acquire(SwapChainPtr handle, ImageSyncData* outImageSyncData) {
|
||||
SWAPCHAIN_RET_FUNC(acquire, handle, outImageSyncData)
|
||||
}
|
||||
|
||||
VkResult VulkanPlatform::present(SwapChainPtr handle, uint32_t index,
|
||||
|
||||
@@ -133,6 +133,7 @@ VulkanPlatformSurfaceSwapChain::VulkanPlatformSurfaceSwapChain(VulkanContext con
|
||||
VulkanPlatformSurfaceSwapChain::~VulkanPlatformSurfaceSwapChain() {
|
||||
vkDestroySwapchainKHR(mDevice, mSwapchain, VKALLOC);
|
||||
vkDestroySurfaceKHR(mInstance, mSurface, VKALLOC);
|
||||
destroy();
|
||||
}
|
||||
|
||||
VkResult VulkanPlatformSurfaceSwapChain::create() {
|
||||
@@ -153,7 +154,7 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
|
||||
// the number of images, though there may be limits related to the total amount of memory used
|
||||
// by presentable images."
|
||||
if (maxImageCount != 0 && desiredImageCount > maxImageCount) {
|
||||
utils::slog.e << "Swap chain does not support " << desiredImageCount << " images."
|
||||
FVK_LOGE << "Swap chain does not support " << desiredImageCount << " images."
|
||||
<< utils::io::endl;
|
||||
desiredImageCount = caps.minImageCount;
|
||||
}
|
||||
@@ -246,7 +247,7 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
|
||||
selectDepthFormat(mContext.getAttachmentDepthStencilFormats(), mHasStencil);
|
||||
mSwapChainBundle.depth = createImage(mSwapChainBundle.extent, mSwapChainBundle.depthFormat);
|
||||
|
||||
slog.i << "vkCreateSwapchain"
|
||||
FVK_LOGI << "vkCreateSwapchain"
|
||||
<< ": " << mSwapChainBundle.extent.width << "x" << mSwapChainBundle.extent.height << ", "
|
||||
<< surfaceFormat.format << ", " << surfaceFormat.colorSpace << ", "
|
||||
<< "swapchain-size=" << mSwapChainBundle.colors.size() << ", "
|
||||
@@ -254,19 +255,29 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
|
||||
<< "depth=" << mSwapChainBundle.depthFormat
|
||||
<< io::endl;
|
||||
|
||||
VkSemaphoreCreateInfo const semaphoreCreateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
|
||||
};
|
||||
|
||||
for (uint32_t i = 0; i < IMAGE_READY_SEMAPHORE_COUNT; ++i) {
|
||||
VkResult result = vkCreateSemaphore(mDevice, &semaphoreCreateInfo, nullptr,
|
||||
mImageReady + i);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Failed to create semaphore";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
VkResult VulkanPlatformSurfaceSwapChain::acquire(VkSemaphore clientSignal, uint32_t* index) {
|
||||
// This immediately retrieves the index of the next available presentable image, and
|
||||
// asynchronously requests the GPU to trigger the "imageAvailable" semaphore.
|
||||
VkResult result = vkAcquireNextImageKHR(mDevice, mSwapchain, UINT64_MAX, clientSignal,
|
||||
VK_NULL_HANDLE, index);
|
||||
VkResult VulkanPlatformSurfaceSwapChain::acquire(VulkanPlatform::ImageSyncData* outImageSyncData) {
|
||||
mCurrentImageReadyIndex = (mCurrentImageReadyIndex + 1) % IMAGE_READY_SEMAPHORE_COUNT;
|
||||
outImageSyncData->imageReadySemaphore = mImageReady[mCurrentImageReadyIndex];
|
||||
VkResult result = vkAcquireNextImageKHR(mDevice, mSwapchain, UINT64_MAX,
|
||||
outImageSyncData->imageReadySemaphore, VK_NULL_HANDLE, &outImageSyncData->imageIndex);
|
||||
|
||||
// Users should be notified of a suboptimal surface, but it should not cause a cascade of
|
||||
// log messages or a loop of re-creations.
|
||||
if (result == VK_SUBOPTIMAL_KHR && !mSuboptimal) {
|
||||
slog.w << "Vulkan Driver: Suboptimal swap chain." << io::endl;
|
||||
FVK_LOGW << "Vulkan Driver: Suboptimal swap chain." << io::endl;
|
||||
mSuboptimal = true;
|
||||
}
|
||||
return result;
|
||||
@@ -288,7 +299,7 @@ VkResult VulkanPlatformSurfaceSwapChain::present(uint32_t index, VkSemaphore fin
|
||||
// On Android Q and above, a suboptimal surface is always reported after screen rotation:
|
||||
// https://android-developers.googleblog.com/2020/02/handling-device-orientation-efficiently.html
|
||||
if (result == VK_SUBOPTIMAL_KHR && !mSuboptimal) {
|
||||
utils::slog.w << "Vulkan Driver: Suboptimal swap chain." << utils::io::endl;
|
||||
FVK_LOGW << "Vulkan Driver: Suboptimal swap chain." << utils::io::endl;
|
||||
mSuboptimal = true;
|
||||
}
|
||||
return result;
|
||||
@@ -312,6 +323,17 @@ VkResult VulkanPlatformSurfaceSwapChain::recreate() {
|
||||
return create();
|
||||
}
|
||||
|
||||
void VulkanPlatformSurfaceSwapChain::destroy() {
|
||||
VulkanPlatformSwapChainImpl::destroy();
|
||||
|
||||
for (uint32_t i = 0; i < IMAGE_READY_SEMAPHORE_COUNT; ++i) {
|
||||
if (mImageReady[i] != VK_NULL_HANDLE) {
|
||||
vkDestroySemaphore(mDevice, mImageReady[i], VKALLOC);
|
||||
mImageReady[i] = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VulkanPlatformHeadlessSwapChain::VulkanPlatformHeadlessSwapChain(VulkanContext const& context,
|
||||
VkDevice device, VkQueue queue, VkExtent2D extent, uint64_t flags)
|
||||
: VulkanPlatformSwapChainImpl(context, device, queue),
|
||||
@@ -343,8 +365,8 @@ VkResult VulkanPlatformHeadlessSwapChain::present(uint32_t index, VkSemaphore fi
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
VkResult VulkanPlatformHeadlessSwapChain::acquire(VkSemaphore clientSignal, uint32_t* index) {
|
||||
*index = mCurrentIndex;
|
||||
VkResult VulkanPlatformHeadlessSwapChain::acquire(VulkanPlatform::ImageSyncData* outImageSyncData) {
|
||||
outImageSyncData->imageIndex = mCurrentIndex;
|
||||
mCurrentIndex = (mCurrentIndex + 1) % HEADLESS_SWAPCHAIN_SIZE;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ struct VulkanPlatformSwapChainImpl : public Platform::SwapChain {
|
||||
~VulkanPlatformSwapChainImpl();
|
||||
|
||||
// Non-virtual override-able method
|
||||
VkResult acquire(VkSemaphore clientSignal, uint32_t* index) {
|
||||
VkResult acquire(VulkanPlatform::ImageSyncData* outImageSyncData) {
|
||||
PANIC_PRECONDITION("Should not be called");
|
||||
return VK_ERROR_UNKNOWN;
|
||||
}
|
||||
@@ -86,7 +86,7 @@ struct VulkanPlatformSurfaceSwapChain : public VulkanPlatformSwapChainImpl {
|
||||
~VulkanPlatformSurfaceSwapChain();
|
||||
|
||||
// Non-virtual override
|
||||
VkResult acquire(VkSemaphore clientSignal, uint32_t* index);
|
||||
VkResult acquire(VulkanPlatform::ImageSyncData* outImageSyncData);
|
||||
|
||||
// Non-virtual override
|
||||
VkResult present(uint32_t index, VkSemaphore finished);
|
||||
@@ -97,7 +97,13 @@ struct VulkanPlatformSurfaceSwapChain : public VulkanPlatformSwapChainImpl {
|
||||
// Non-virtual override-able method
|
||||
bool hasResized();
|
||||
|
||||
protected:
|
||||
// Non-virtual override-able method
|
||||
void destroy();
|
||||
|
||||
private:
|
||||
static constexpr int IMAGE_READY_SEMAPHORE_COUNT = FVK_MAX_COMMAND_BUFFERS;
|
||||
|
||||
VkResult create();
|
||||
|
||||
VkInstance mInstance;
|
||||
@@ -106,6 +112,8 @@ private:
|
||||
VkSurfaceKHR mSurface;
|
||||
VkSwapchainKHR mSwapchain = VK_NULL_HANDLE;
|
||||
VkExtent2D const mFallbackExtent;
|
||||
VkSemaphore mImageReady[IMAGE_READY_SEMAPHORE_COUNT];
|
||||
uint32_t mCurrentImageReadyIndex;
|
||||
|
||||
bool mUsesRGB = false;
|
||||
bool mHasStencil = false;
|
||||
@@ -121,7 +129,7 @@ struct VulkanPlatformHeadlessSwapChain : public VulkanPlatformSwapChainImpl {
|
||||
~VulkanPlatformHeadlessSwapChain();
|
||||
|
||||
// Non-virtual override
|
||||
VkResult acquire(VkSemaphore clientSignal, uint32_t* index);
|
||||
VkResult acquire(VulkanPlatform::ImageSyncData* outImageSyncData);
|
||||
|
||||
// Non-virtual override
|
||||
VkResult present(uint32_t index, VkSemaphore finished);
|
||||
|
||||
@@ -64,14 +64,15 @@ First determine the Filament material and variant that causes the problem.
|
||||
What we want is the "raw" GLSL version of the shader, before any optimizations / cross-compilation
|
||||
happens.
|
||||
|
||||
The easiest way I've found to do this is by adding a `std::cout` statement inside `MaterialBuilder`,
|
||||
right after `sg.createFragmentProgram` or `sg.createVertexProgram` is called. For example,
|
||||
We can use the `--save-raw-variants` debug flag in matc to export each GLSL
|
||||
shader to a file. For example:
|
||||
|
||||
```
|
||||
if (v.variant.key == 8u) { std::cout << shader << std::endl; }
|
||||
matc --save-raw-variants --optimize-size --variant-filter fog,ssr,vsm,stereo \
|
||||
-a all -p all -o mymaterial.filamat mymaterial.mat
|
||||
```
|
||||
|
||||
Then, I invoke `matc` and redirect stdout to a .frag or .vert file.
|
||||
Files will be named like `mymaterial_0x05.frag` or `mymaterial_0x05.vert`.
|
||||
|
||||
Note that gltfio material "templates" first go through a build step. After building gltfio, the
|
||||
gltfio Filament materials are output to:
|
||||
|
||||
@@ -298,6 +298,24 @@ public:
|
||||
*/
|
||||
size_t textureUseAfterFreePoolSize = 0;
|
||||
|
||||
/**
|
||||
* When uploading vertex or index data, the Filament Metal backend copies data
|
||||
* into a shared staging area before transferring it to the GPU. This setting controls
|
||||
* the total size of the buffer used to perform these allocations.
|
||||
*
|
||||
* Higher values can improve performance when performing many uploads across a small
|
||||
* number of frames.
|
||||
*
|
||||
* This buffer remains alive throughout the lifetime of the Engine, so this size adds to the
|
||||
* memory footprint of the app and should be set as conservative as possible.
|
||||
*
|
||||
* A value of 0 disables the shared staging buffer entirely; uploads will acquire an
|
||||
* individual buffer from a pool of shared buffers.
|
||||
*
|
||||
* Only respected by the Metal backend.
|
||||
*/
|
||||
size_t metalUploadBufferSizeBytes = 512 * 1024;
|
||||
|
||||
/**
|
||||
* Set to `true` to forcibly disable parallel shader compilation in the backend.
|
||||
* Currently only honored by the GL and Metal backends.
|
||||
@@ -332,9 +350,12 @@ public:
|
||||
uint32_t resourceAllocatorCacheSizeMB = 64;
|
||||
|
||||
/*
|
||||
* This value determines for how many frames are texture entries kept in the cache.
|
||||
* This value determines how many frames texture entries are kept for in the cache. This
|
||||
* is a soft limit, meaning some texture older than this are allowed to stay in the cache.
|
||||
* Typically only one texture is evicted per frame.
|
||||
* The default is 1.
|
||||
*/
|
||||
uint32_t resourceAllocatorCacheMaxAge = 2;
|
||||
uint32_t resourceAllocatorCacheMaxAge = 1;
|
||||
|
||||
/*
|
||||
* Disable backend handles use-after-free checks.
|
||||
@@ -1021,6 +1042,21 @@ public:
|
||||
void resetBackendState() noexcept;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Get the current time. This is a convenience function that simply returns the
|
||||
* time in nanosecond since epoch of std::chrono::steady_clock.
|
||||
* A possible implementation is:
|
||||
*
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
* return std::chrono::steady_clock::now().time_since_epoch().count();
|
||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* @return current time in nanosecond since epoch of std::chrono::steady_clock.
|
||||
* @see Renderer::beginFrame()
|
||||
*/
|
||||
static uint64_t getSteadyClockTimeNano() noexcept;
|
||||
|
||||
|
||||
DebugRegistry& getDebugRegistry() noexcept;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -158,6 +158,8 @@ public:
|
||||
*
|
||||
* @return This Builder, for chaining calls.
|
||||
*
|
||||
* @see Material::Builder::sphericalHarmonicsBandCount()
|
||||
*
|
||||
* @note
|
||||
* Because the coefficients are pre-scaled, `sh[0]` is the environment's
|
||||
* average irradiance.
|
||||
|
||||
@@ -140,6 +140,18 @@ public:
|
||||
return constant(name, strlen(name), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the quality of the indirect lights computations. This is only taken into account
|
||||
* if this material is lit and in the surface domain. This setting will affect the
|
||||
* IndirectLight computation if one is specified on the Scene and Spherical Harmonics
|
||||
* are used for the irradiance.
|
||||
*
|
||||
* @param shBandCount Number of spherical harmonic bands. Must be 1, 2 or 3 (default).
|
||||
* @return Reference to this Builder for chaining calls.
|
||||
* @see IndirectLight
|
||||
*/
|
||||
Builder& sphericalHarmonicsBandCount(size_t shBandCount) noexcept;
|
||||
|
||||
/**
|
||||
* Creates the Material object and returns a pointer to it.
|
||||
*
|
||||
|
||||
@@ -140,6 +140,7 @@ public:
|
||||
* @param values Array of values to set to the named parameter array.
|
||||
* @param count Size of the array to set.
|
||||
* @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled.
|
||||
* @see Material::hasParameter
|
||||
*/
|
||||
template<typename T, typename = is_supported_parameter_t<T>>
|
||||
void setParameter(const char* UTILS_NONNULL name, size_t nameLength,
|
||||
@@ -234,6 +235,32 @@ public:
|
||||
setParameter(name, strlen(name), type, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a parameter by name.
|
||||
*
|
||||
* Note: Only supports non-texture parameters such as numeric and math types.
|
||||
*
|
||||
* @param name Name of the parameter as defined by Material. Cannot be nullptr.
|
||||
* @param nameLength Length in `char` of the name parameter.
|
||||
* @throws utils::PreConditionPanic if name doesn't exist or no-op if exceptions are disabled.
|
||||
*
|
||||
* @see Material::hasParameter
|
||||
*/
|
||||
template<typename T>
|
||||
T getParameter(const char* UTILS_NONNULL name, size_t nameLength) const;
|
||||
|
||||
/** inline helper to provide the name as a null-terminated C string */
|
||||
template<typename T, typename = is_supported_parameter_t<T>>
|
||||
inline T getParameter(StringLiteral name) const {
|
||||
return getParameter<T>(name.data, name.size);
|
||||
}
|
||||
|
||||
/** inline helper to provide the name as a null-terminated C string */
|
||||
template<typename T, typename = is_supported_parameter_t<T>>
|
||||
inline T getParameter(const char* UTILS_NONNULL name) const {
|
||||
return getParameter<T>(name, strlen(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set-up a custom scissor rectangle; by default it is disabled.
|
||||
*
|
||||
|
||||
@@ -489,43 +489,20 @@ public:
|
||||
* For standard morphing, A MorphTargetBuffer must be provided.
|
||||
* Standard morphing supports up to \c CONFIG_MAX_MORPH_TARGET_COUNT morph targets.
|
||||
*
|
||||
* For legacy morphing, the attached VertexBuffer must provide data in the
|
||||
* appropriate VertexAttribute slots (\c MORPH_POSITION_0 etc). Legacy morphing only
|
||||
* supports up to 4 morph targets and will be deprecated in the future. Legacy morphing must
|
||||
* be enabled on the material definition: either via the legacyMorphing material attribute
|
||||
* or by calling filamat::MaterialBuilder::useLegacyMorphing().
|
||||
*
|
||||
* See also RenderableManager::setMorphWeights(), which can be called on a per-frame basis
|
||||
* to advance the animation.
|
||||
*/
|
||||
Builder& morphing(MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) noexcept;
|
||||
|
||||
/**
|
||||
* @deprecated Use morphing(uint8_t level, size_t primitiveIndex, size_t offset, size_t count) instead
|
||||
*/
|
||||
Builder& morphing(uint8_t level, size_t primitiveIndex,
|
||||
MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer,
|
||||
size_t offset, size_t count) noexcept;
|
||||
|
||||
/**
|
||||
* @deprecated Use morphing(uint8_t level, size_t primitiveIndex, size_t offset, size_t count) instead
|
||||
*/
|
||||
inline Builder& morphing(uint8_t level, size_t primitiveIndex,
|
||||
MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) noexcept {
|
||||
return morphing(level, primitiveIndex, morphTargetBuffer, 0,
|
||||
morphTargetBuffer->getVertexCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the the range of the MorphTargetBuffer to use with this primitive.
|
||||
*
|
||||
* @param level the level of detail (lod), only 0 can be specified
|
||||
* @param primitiveIndex zero-based index of the primitive, must be less than the count passed to Builder constructor
|
||||
* @param offset specifies where in the morph target buffer to start reading (expressed as a number of vertices)
|
||||
* @param count number of vertices in the morph target buffer to read, must equal the geometry's count (for triangles, this should be a multiple of 3)
|
||||
*/
|
||||
Builder& morphing(uint8_t level, size_t primitiveIndex,
|
||||
size_t offset, size_t count) noexcept;
|
||||
RenderableManager::Builder& morphing(uint8_t level,
|
||||
size_t primitiveIndex, size_t offset) noexcept;
|
||||
|
||||
|
||||
/**
|
||||
@@ -786,25 +763,13 @@ public:
|
||||
/**
|
||||
* Associates a MorphTargetBuffer to the given primitive.
|
||||
*/
|
||||
void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex,
|
||||
size_t offset, size_t count);
|
||||
|
||||
/** @deprecated */
|
||||
void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex,
|
||||
MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer, size_t offset, size_t count);
|
||||
|
||||
/** @deprecated */
|
||||
inline void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex,
|
||||
MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) {
|
||||
setMorphTargetBufferAt(instance, level, primitiveIndex, morphTargetBuffer, 0,
|
||||
morphTargetBuffer->getVertexCount());
|
||||
}
|
||||
void setMorphTargetBufferOffsetAt(Instance instance, uint8_t level, size_t primitiveIndex,
|
||||
size_t offset);
|
||||
|
||||
/**
|
||||
* Get a MorphTargetBuffer to the given primitive or null if it doesn't exist.
|
||||
* Get a MorphTargetBuffer to the given renderable or null if it doesn't exist.
|
||||
*/
|
||||
MorphTargetBuffer* UTILS_NULLABLE getMorphTargetBufferAt(Instance instance,
|
||||
uint8_t level, size_t primitiveIndex) const noexcept;
|
||||
MorphTargetBuffer* UTILS_NULLABLE getMorphTargetBuffer(Instance instance) const noexcept;
|
||||
|
||||
/**
|
||||
* Gets the number of morphing in the given entity.
|
||||
|
||||
@@ -22,9 +22,11 @@
|
||||
#include <filament/FilamentAPI.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <math/vec4.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament {
|
||||
@@ -81,6 +83,37 @@ public:
|
||||
UTILS_DEPRECATED uint64_t vsyncOffsetNanos = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Timing information about a frame
|
||||
* @see getFrameInfoHistory()
|
||||
*/
|
||||
struct FrameInfo {
|
||||
using time_point_ns = int64_t;
|
||||
using duration_ns = int64_t;
|
||||
uint32_t frameId; //!< monotonically increasing frame identifier
|
||||
duration_ns frameTime; //!< frame duration on the GPU in nanosecond [ns]
|
||||
duration_ns denoisedFrameTime; //!< denoised frame duration on the GPU in [ns]
|
||||
time_point_ns beginFrame; //!< Renderer::beginFrame() time since epoch [ns]
|
||||
time_point_ns endFrame; //!< Renderer::endFrame() time since epoch [ns]
|
||||
time_point_ns backendBeginFrame; //!< Backend thread time of frame start since epoch [ns]
|
||||
time_point_ns backendEndFrame; //!< Backend thread time of frame end since epoch [ns]
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve an historic of frame timing information. The maximum frame history size is
|
||||
* given by getMaxFrameHistorySize().
|
||||
* @param historySize requested history size. The returned vector could be smaller.
|
||||
* @return A vector of FrameInfo.
|
||||
*/
|
||||
utils::FixedCapacityVector<Renderer::FrameInfo> getFrameInfoHistory(
|
||||
size_t historySize = 1) const noexcept;
|
||||
|
||||
/**
|
||||
* @return the maximum supported frame history size.
|
||||
* @see getFrameInfoHistory()
|
||||
*/
|
||||
size_t getMaxFrameHistorySize() const noexcept;
|
||||
|
||||
/**
|
||||
* Use FrameRateOptions to set the desired frame rate and control how quickly the system
|
||||
* reacts to GPU load changes.
|
||||
@@ -227,6 +260,25 @@ public:
|
||||
static constexpr CopyFrameFlag CLEAR = 0x4;
|
||||
|
||||
|
||||
/**
|
||||
* The use of this method is optional. It sets the VSYNC time expressed as the duration in
|
||||
* nanosecond since epoch of std::chrono::steady_clock.
|
||||
* If called, passing 0 to vsyncSteadyClockTimeNano in Renderer::BeginFrame will use this
|
||||
* time instead.
|
||||
* @param steadyClockTimeNano duration in nanosecond since epoch of std::chrono::steady_clock
|
||||
* @see Engine::getSteadyClockTimeNano()
|
||||
* @see Renderer::BeginFrame()
|
||||
*/
|
||||
void setVsyncTime(uint64_t steadyClockTimeNano) noexcept;
|
||||
|
||||
/**
|
||||
* Call skipFrame when momentarily skipping frames, for instance if the content of the
|
||||
* scene doesn't change.
|
||||
*
|
||||
* @param vsyncSteadyClockTimeNano
|
||||
*/
|
||||
void skipFrame(uint64_t vsyncSteadyClockTimeNano = 0u);
|
||||
|
||||
/**
|
||||
* Set-up a frame for this Renderer.
|
||||
*
|
||||
|
||||
@@ -40,6 +40,7 @@ class CallbackHandler;
|
||||
|
||||
class Camera;
|
||||
class ColorGrading;
|
||||
class Engine;
|
||||
class MaterialInstance;
|
||||
class RenderTarget;
|
||||
class Scene;
|
||||
@@ -878,6 +879,17 @@ public:
|
||||
*/
|
||||
utils::Entity getFogEntity() const noexcept;
|
||||
|
||||
|
||||
/**
|
||||
* When certain temporal features are used (e.g.: TAA or Screen-space reflections), the view
|
||||
* keeps a history of previous frame renders associated with the Renderer the view was last
|
||||
* used with. When switching Renderer, it may be necessary to clear that history by calling
|
||||
* this method. Similarly, if the whole content of the screen change, like when a cut-scene
|
||||
* starts, clearing the history might be needed to avoid artifacts due to the previous frame
|
||||
* being very different.
|
||||
*/
|
||||
void clearFrameHistory(Engine& engine) noexcept;
|
||||
|
||||
/**
|
||||
* List of available ambient occlusion techniques
|
||||
* @deprecated use AmbientOcclusionOptions::enabled instead
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -361,7 +363,7 @@ const Engine::Config& Engine::getConfig() const noexcept {
|
||||
return downcast(this)->getConfig();
|
||||
}
|
||||
|
||||
bool Engine::isStereoSupported(StereoscopicType stereoscopicType) const noexcept {
|
||||
bool Engine::isStereoSupported(StereoscopicType) const noexcept {
|
||||
return downcast(this)->isStereoSupported();
|
||||
}
|
||||
|
||||
@@ -369,6 +371,10 @@ size_t Engine::getMaxStereoscopicEyes() noexcept {
|
||||
return FEngine::getMaxStereoscopicEyes();
|
||||
}
|
||||
|
||||
uint64_t Engine::getSteadyClockTimeNano() noexcept {
|
||||
return std::chrono::steady_clock::now().time_since_epoch().count();
|
||||
}
|
||||
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
void Engine::resetBackendState() noexcept {
|
||||
downcast(this)->resetBackendState();
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
#include <fg/FrameGraphTexture.h>
|
||||
|
||||
#include <math/mat4.h>
|
||||
#include <math/vec2.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
|
||||
+132
-45
@@ -16,30 +16,33 @@
|
||||
|
||||
#include "FrameInfo.h"
|
||||
|
||||
#include <filament/Renderer.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Systrace.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <math/scalar.h>
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <ratio>
|
||||
|
||||
#include <cmath>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
using namespace utils;
|
||||
using namespace backend;
|
||||
|
||||
// this is to avoid a call to memmove
|
||||
template<class InputIterator, class OutputIterator>
|
||||
static inline
|
||||
void move_backward(InputIterator first, InputIterator last, OutputIterator result) {
|
||||
while (first != last) {
|
||||
*--result = *--last;
|
||||
}
|
||||
}
|
||||
|
||||
FrameInfoManager::FrameInfoManager(DriverApi& driver) noexcept {
|
||||
for (auto& query : mQueries) {
|
||||
query = driver.createTimerQuery();
|
||||
query.handle = driver.createTimerQuery();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,14 +50,36 @@ FrameInfoManager::~FrameInfoManager() noexcept = default;
|
||||
|
||||
void FrameInfoManager::terminate(DriverApi& driver) noexcept {
|
||||
for (auto& query : mQueries) {
|
||||
driver.destroyTimerQuery(query);
|
||||
driver.destroyTimerQuery(query.handle);
|
||||
}
|
||||
}
|
||||
|
||||
void FrameInfoManager::beginFrame(DriverApi& driver,Config const& config, uint32_t) noexcept {
|
||||
driver.beginTimerQuery(mQueries[mIndex]);
|
||||
void FrameInfoManager::beginFrame(DriverApi& driver, Config const& config, uint32_t frameId) noexcept {
|
||||
auto& history = mFrameTimeHistory;
|
||||
// don't exceed the capacity, drop the oldest entry
|
||||
if (UTILS_LIKELY(history.size() == history.capacity())) {
|
||||
history.pop_back();
|
||||
}
|
||||
|
||||
// create a new entry
|
||||
auto& front = history.emplace_front(frameId);
|
||||
|
||||
// store the current time
|
||||
front.beginFrame = std::chrono::steady_clock::now();
|
||||
|
||||
// references are not invalidated by CircularQueue<>, so we can associate a reference to
|
||||
// the slot we created to the timer query used to find the frame time.
|
||||
mQueries[mIndex].pInfo = std::addressof(front);
|
||||
// issue the timer query
|
||||
driver.beginTimerQuery(mQueries[mIndex].handle);
|
||||
// issue the custom backend command to get the backend time
|
||||
driver.queueCommand([&front](){
|
||||
front.backendBeginFrame = std::chrono::steady_clock::now();
|
||||
});
|
||||
|
||||
// now is a good time to check the oldest active query
|
||||
uint64_t elapsed = 0;
|
||||
TimerQueryResult const result = driver.getTimerQueryValue(mQueries[mLast], &elapsed);
|
||||
TimerQueryResult const result = driver.getTimerQueryValue(mQueries[mLast].handle, &elapsed);
|
||||
switch (result) {
|
||||
case TimerQueryResult::NOT_READY:
|
||||
// nothing to do
|
||||
@@ -63,49 +88,111 @@ void FrameInfoManager::beginFrame(DriverApi& driver,Config const& config, uint32
|
||||
mLast = (mLast + 1) % POOL_COUNT;
|
||||
break;
|
||||
case TimerQueryResult::AVAILABLE:
|
||||
mLast = (mLast + 1) % POOL_COUNT;
|
||||
// conversion to our duration happens here
|
||||
mFrameTime = std::chrono::duration<uint64_t, std::nano>(elapsed);
|
||||
pFront = mQueries[mLast].pInfo;
|
||||
pFront->frameTime = std::chrono::duration<uint64_t, std::nano>(elapsed);
|
||||
mLast = (mLast + 1) % POOL_COUNT;
|
||||
denoiseFrameTime(config);
|
||||
break;
|
||||
}
|
||||
update(config, mFrameTime);
|
||||
|
||||
|
||||
// keep this just for debugging
|
||||
if constexpr (false) {
|
||||
using namespace utils;
|
||||
auto h = getFrameInfoHistory(1);
|
||||
if (!h.empty()) {
|
||||
slog.d << frameId << ": "
|
||||
<< h[0].frameId << " (" << frameId - h[0].frameId << ")"
|
||||
<< ", Dm=" << h[0].endFrame - h[0].beginFrame
|
||||
<< ", L =" << h[0].backendBeginFrame - h[0].beginFrame
|
||||
<< ", Db=" << h[0].backendEndFrame - h[0].backendBeginFrame
|
||||
<< ", T =" << h[0].frameTime
|
||||
<< io::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FrameInfoManager::endFrame(DriverApi& driver) noexcept {
|
||||
driver.endTimerQuery(mQueries[mIndex]);
|
||||
auto& front = mFrameTimeHistory.front();
|
||||
// close the timer query
|
||||
driver.endTimerQuery(mQueries[mIndex].handle);
|
||||
// queue custom backend command to query the current time
|
||||
driver.queueCommand([&front](){
|
||||
// backend frame end-time
|
||||
front.backendEndFrame = std::chrono::steady_clock::now();
|
||||
// signal that the data is available
|
||||
front.ready.store(true, std::memory_order_release);
|
||||
});
|
||||
// and finally acquire the time on the main thread
|
||||
front.endFrame = std::chrono::steady_clock::now();
|
||||
mIndex = (mIndex + 1) % POOL_COUNT;
|
||||
}
|
||||
|
||||
void FrameInfoManager::update(Config const& config,
|
||||
FrameInfoManager::duration lastFrameTime) noexcept {
|
||||
// keep an history of frame times
|
||||
void FrameInfoManager::denoiseFrameTime(Config const& config) noexcept {
|
||||
auto& history = mFrameTimeHistory;
|
||||
assert_invariant(!history.empty());
|
||||
|
||||
// this is like doing { pop_back(); push_front(); }
|
||||
filament::move_backward(history.begin(), history.end() - 1, history.end());
|
||||
history[0].frameTime = lastFrameTime;
|
||||
|
||||
mFrameTimeHistorySize = std::min(++mFrameTimeHistorySize, uint32_t(MAX_FRAMETIME_HISTORY));
|
||||
if (UTILS_UNLIKELY(mFrameTimeHistorySize < 3)) {
|
||||
// not enough history to do anything useful
|
||||
history[0].valid = false;
|
||||
return;
|
||||
// find the first slot that has a valid frame duration
|
||||
size_t first = history.size();
|
||||
for (size_t i = 0, c = history.size(); i < c; ++i) {
|
||||
if (history[i].frameTime != duration(0)) {
|
||||
assert_invariant(std::addressof(history[i]) == pFront);
|
||||
first = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert_invariant(first != history.size());
|
||||
|
||||
// apply a median filter to get a good representation of the frame time of the last
|
||||
// N frames.
|
||||
std::array<duration, MAX_FRAMETIME_HISTORY> median; // NOLINT -- it's initialized below
|
||||
size_t const size = std::min(mFrameTimeHistorySize,
|
||||
std::min(config.historySize, (uint32_t)median.size()));
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
median[i] = history[i].frameTime;
|
||||
}
|
||||
std::sort(median.begin(), median.begin() + size);
|
||||
duration const denoisedFrameTime = median[size / 2];
|
||||
// we need at least 3 valid frame time to calculate the median
|
||||
if (history.size() >= first + 3) {
|
||||
// apply a median filter to get a good representation of the frame time of the last
|
||||
// N frames.
|
||||
std::array<duration, MAX_FRAMETIME_HISTORY> median; // NOLINT -- it's initialized below
|
||||
size_t const size = std::min({
|
||||
history.size() - first,
|
||||
median.size(),
|
||||
size_t(config.historySize) });
|
||||
|
||||
history[0].denoisedFrameTime = denoisedFrameTime;
|
||||
history[0].valid = true;
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
median[i] = history[first + i].frameTime;
|
||||
}
|
||||
std::sort(median.begin(), median.begin() + size);
|
||||
duration const denoisedFrameTime = median[size / 2];
|
||||
|
||||
history[first].denoisedFrameTime = denoisedFrameTime;
|
||||
history[first].valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
utils::FixedCapacityVector<Renderer::FrameInfo> FrameInfoManager::getFrameInfoHistory(
|
||||
size_t historySize) const noexcept {
|
||||
auto result = utils::FixedCapacityVector<Renderer::FrameInfo>::with_capacity(MAX_FRAMETIME_HISTORY);
|
||||
auto const& history = mFrameTimeHistory;
|
||||
size_t i = 0;
|
||||
size_t const c = history.size();
|
||||
for (; i < c; ++i) {
|
||||
auto const& entry = history[i];
|
||||
if (entry.ready.load(std::memory_order_acquire) && entry.valid) {
|
||||
// once we found an entry ready,
|
||||
// we know by construction that all following ones are too
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (; i < c && historySize; ++i, --historySize) {
|
||||
auto const& entry = history[i];
|
||||
using namespace std::chrono;
|
||||
result.push_back({
|
||||
entry.frameId,
|
||||
duration_cast<nanoseconds>(entry.frameTime).count(),
|
||||
duration_cast<nanoseconds>(entry.denoisedFrameTime).count(),
|
||||
duration_cast<nanoseconds>(entry.beginFrame.time_since_epoch()).count(),
|
||||
duration_cast<nanoseconds>(entry.endFrame.time_since_epoch()).count(),
|
||||
duration_cast<nanoseconds>(entry.backendBeginFrame.time_since_epoch()).count(),
|
||||
duration_cast<nanoseconds>(entry.backendEndFrame.time_since_epoch()).count()
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
+124
-17
@@ -17,30 +17,136 @@
|
||||
#ifndef TNT_FILAMENT_FRAMEINFO_H
|
||||
#define TNT_FILAMENT_FRAMEINFO_H
|
||||
|
||||
#include "backend/Handle.h"
|
||||
#include <filament/Renderer.h>
|
||||
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <private/backend/DriverApi.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <ratio>
|
||||
#include <type_traits>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
namespace filament {
|
||||
class FEngine;
|
||||
|
||||
namespace details {
|
||||
struct FrameInfo {
|
||||
using duration = std::chrono::duration<float, std::milli>;
|
||||
duration frameTime{}; // frame period
|
||||
duration denoisedFrameTime{}; // frame period (median filter)
|
||||
bool valid = false;
|
||||
bool valid = false; // true if the data of the structure is valid
|
||||
};
|
||||
} // namespace details
|
||||
|
||||
struct FrameInfoImpl : public details::FrameInfo {
|
||||
using clock = std::chrono::steady_clock;
|
||||
using time_point = clock::time_point;
|
||||
uint32_t const frameId;
|
||||
time_point beginFrame; // main thread beginFrame time
|
||||
time_point endFrame; // main thread endFrame time
|
||||
time_point backendBeginFrame; // backend thread beginFrame time (makeCurrent time)
|
||||
time_point backendEndFrame; // backend thread endFrame time (present time)
|
||||
std::atomic_bool ready{}; // true once backend thread has populated its data
|
||||
explicit FrameInfoImpl(uint32_t frameId) noexcept
|
||||
: frameId(frameId) {
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, size_t CAPACITY>
|
||||
class CircularQueue {
|
||||
public:
|
||||
using value_type = T;
|
||||
using reference = value_type&;
|
||||
using const_reference = value_type const&;
|
||||
|
||||
size_t capacity() const {
|
||||
return CAPACITY;
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
bool empty() const noexcept {
|
||||
return !size();
|
||||
}
|
||||
|
||||
void pop_back() noexcept {
|
||||
assert_invariant(!empty());
|
||||
--mSize;
|
||||
std::destroy_at(&mStorage[(mFront - mSize) % CAPACITY]);
|
||||
}
|
||||
|
||||
void push_front(T const& v) noexcept {
|
||||
assert_invariant(size() < CAPACITY);
|
||||
mFront = advance(mFront);
|
||||
new(&mStorage[mFront]) T(v);
|
||||
++mSize;
|
||||
}
|
||||
|
||||
void push_front(T&& v) noexcept {
|
||||
assert_invariant(size() < CAPACITY);
|
||||
mFront = advance(mFront);
|
||||
new(&mStorage[mFront]) T(std::move(v));
|
||||
++mSize;
|
||||
}
|
||||
|
||||
template<typename ...Args>
|
||||
T& emplace_front(Args&&... args) noexcept {
|
||||
assert_invariant(size() < CAPACITY);
|
||||
mFront = advance(mFront);
|
||||
new(&mStorage[mFront]) T(std::forward<Args>(args)...);
|
||||
++mSize;
|
||||
return front();
|
||||
}
|
||||
|
||||
T& operator[](size_t pos) noexcept {
|
||||
assert_invariant(pos < size());
|
||||
size_t const index = (mFront + CAPACITY - pos) % CAPACITY;
|
||||
return *std::launder(reinterpret_cast<T*>(&mStorage[index]));
|
||||
}
|
||||
|
||||
T const& operator[](size_t pos) const noexcept {
|
||||
return const_cast<CircularQueue&>(*this)[pos];
|
||||
}
|
||||
|
||||
T const& front() const noexcept {
|
||||
assert_invariant(!empty());
|
||||
return operator[](0);
|
||||
}
|
||||
|
||||
T& front() noexcept {
|
||||
assert_invariant(!empty());
|
||||
return operator[](0);
|
||||
}
|
||||
|
||||
private:
|
||||
using Storage = std::aligned_storage_t<sizeof(T), alignof(T)>;
|
||||
Storage mStorage[CAPACITY];
|
||||
uint32_t mFront = 0; // always index 0
|
||||
uint32_t mSize = 0;
|
||||
[[nodiscard]] inline uint32_t advance(uint32_t v) noexcept {
|
||||
return (v + 1) % CAPACITY;
|
||||
}
|
||||
};
|
||||
|
||||
class FrameInfoManager {
|
||||
static constexpr size_t POOL_COUNT = 4;
|
||||
static constexpr size_t MAX_FRAMETIME_HISTORY = 31u;
|
||||
static constexpr size_t MAX_FRAMETIME_HISTORY = 16u;
|
||||
|
||||
public:
|
||||
using duration = FrameInfo::duration;
|
||||
using duration = FrameInfoImpl::duration;
|
||||
using clock = FrameInfoImpl::clock;
|
||||
|
||||
struct Config {
|
||||
uint32_t historySize;
|
||||
@@ -57,23 +163,24 @@ public:
|
||||
// call this immediately before "swap buffers"
|
||||
void endFrame(backend::DriverApi& driver) noexcept;
|
||||
|
||||
FrameInfo const& getLastFrameInfo() const noexcept {
|
||||
return mFrameTimeHistory[0];
|
||||
details::FrameInfo const& getLastFrameInfo() const noexcept {
|
||||
// if pFront is not set yet, return front() but in this case front().valid will be false
|
||||
return pFront ? *pFront : mFrameTimeHistory.front();
|
||||
}
|
||||
|
||||
duration getLastFrameTime() const noexcept {
|
||||
return getLastFrameInfo().frameTime;
|
||||
}
|
||||
utils::FixedCapacityVector<Renderer::FrameInfo> getFrameInfoHistory(size_t historySize) const noexcept;
|
||||
|
||||
private:
|
||||
void update(Config const& config, duration lastFrameTime) noexcept;
|
||||
backend::Handle<backend::HwTimerQuery> mQueries[POOL_COUNT];
|
||||
duration mFrameTime{};
|
||||
uint32_t mIndex = 0;
|
||||
uint32_t mLast = 0;
|
||||
|
||||
std::array<FrameInfo, MAX_FRAMETIME_HISTORY> mFrameTimeHistory;
|
||||
uint32_t mFrameTimeHistorySize = 0;
|
||||
void denoiseFrameTime(Config const& config) noexcept;
|
||||
struct Query {
|
||||
backend::Handle<backend::HwTimerQuery> handle{};
|
||||
FrameInfoImpl* pInfo = nullptr;
|
||||
};
|
||||
std::array<Query, POOL_COUNT> mQueries;
|
||||
uint32_t mIndex = 0; // index of current query
|
||||
uint32_t mLast = 0; // index of oldest query still active
|
||||
FrameInfoImpl* pFront = nullptr; // the most recent slot with a valid frame time
|
||||
CircularQueue<FrameInfoImpl, MAX_FRAMETIME_HISTORY> mFrameTimeHistory;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <filament/Viewport.h>
|
||||
|
||||
#include <utils/BinaryTreeArray.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Systrace.h>
|
||||
#include <utils/debug.h>
|
||||
|
||||
|
||||
@@ -183,6 +183,35 @@ template UTILS_PUBLIC void MaterialInstance::setParameter<mat4f> (const char*
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
template<typename T>
|
||||
T FMaterialInstance::getParameterImpl(std::string_view name) const {
|
||||
ssize_t offset = mMaterial->getUniformInterfaceBlock().getFieldOffset(name, 0);
|
||||
assert_invariant(offset>=0);
|
||||
return downcast(this)->getUniformBuffer().getUniform<T>(offset);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T MaterialInstance::getParameter(const char* name, size_t nameLength) const {
|
||||
return downcast(this)->getParameterImpl<T>({ name, nameLength });
|
||||
}
|
||||
|
||||
// explicit template instantiation of our supported types
|
||||
template UTILS_PUBLIC float MaterialInstance::getParameter<float> (const char* name, size_t nameLength) const;
|
||||
template UTILS_PUBLIC int32_t MaterialInstance::getParameter<int32_t> (const char* name, size_t nameLength) const;
|
||||
template UTILS_PUBLIC uint32_t MaterialInstance::getParameter<uint32_t>(const char* name, size_t nameLength) const;
|
||||
template UTILS_PUBLIC int2 MaterialInstance::getParameter<int2> (const char* name, size_t nameLength) const;
|
||||
template UTILS_PUBLIC int3 MaterialInstance::getParameter<int3> (const char* name, size_t nameLength) const;
|
||||
template UTILS_PUBLIC int4 MaterialInstance::getParameter<int4> (const char* name, size_t nameLength) const;
|
||||
template UTILS_PUBLIC uint2 MaterialInstance::getParameter<uint2> (const char* name, size_t nameLength) const;
|
||||
template UTILS_PUBLIC uint3 MaterialInstance::getParameter<uint3> (const char* name, size_t nameLength) const;
|
||||
template UTILS_PUBLIC uint4 MaterialInstance::getParameter<uint4> (const char* name, size_t nameLength) const;
|
||||
template UTILS_PUBLIC float2 MaterialInstance::getParameter<float2> (const char* name, size_t nameLength) const;
|
||||
template UTILS_PUBLIC float3 MaterialInstance::getParameter<float3> (const char* name, size_t nameLength) const;
|
||||
template UTILS_PUBLIC float4 MaterialInstance::getParameter<float4> (const char* name, size_t nameLength) const;
|
||||
template UTILS_PUBLIC mat3f MaterialInstance::getParameter<mat3f> (const char* name, size_t nameLength) const;
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
Material const* MaterialInstance::getMaterial() const noexcept {
|
||||
return downcast(this)->getMaterial();
|
||||
}
|
||||
|
||||
@@ -1965,8 +1965,8 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg,
|
||||
// - visible bloom size changes with dynamic resolution in non-homogenous mode
|
||||
// This allows us to use the 9 sample downsampling filter (instead of 13)
|
||||
// for at least 4 levels.
|
||||
uint32_t width = std::max(1u, uint32_t(std::floor(bloomWidth)));
|
||||
uint32_t height = std::max(1u, uint32_t(std::floor(bloomHeight)));
|
||||
uint32_t width = std::max(16u, uint32_t(std::floor(bloomWidth)));
|
||||
uint32_t height = std::max(16u, uint32_t(std::floor(bloomHeight)));
|
||||
width &= ~((1 << 4) - 1); // at least 4 levels
|
||||
height &= ~((1 << 4) - 1);
|
||||
bloomWidth = float(width);
|
||||
@@ -1978,6 +1978,8 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg,
|
||||
// we don't need to do the fireflies reduction if we have TAA (it already does it)
|
||||
bool fireflies = threshold && !taaOptions.enabled;
|
||||
|
||||
assert_invariant(bloomWidth && bloomHeight);
|
||||
|
||||
while (2 * bloomWidth < float(desc.width) || 2 * bloomHeight < float(desc.height)) {
|
||||
if (inoutBloomOptions.quality == QualityLevel::LOW ||
|
||||
inoutBloomOptions.quality == QualityLevel::MEDIUM) {
|
||||
|
||||
+28
-11
@@ -38,6 +38,7 @@
|
||||
#include <backend/PipelineState.h>
|
||||
|
||||
#include "private/backend/CircularBuffer.h"
|
||||
#include "private/backend/CommandStream.h"
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
@@ -153,7 +154,7 @@ RenderPass::RenderPass(FEngine& engine, RenderPassBuilder const& builder) noexce
|
||||
sortCommands(builder.mArena);
|
||||
|
||||
if (engine.isAutomaticInstancingEnabled()) {
|
||||
uint32_t stereoscopicEyeCount = 1;
|
||||
int32_t stereoscopicEyeCount = 1;
|
||||
if (builder.mFlags & IS_INSTANCED_STEREOSCOPIC) {
|
||||
stereoscopicEyeCount *= engine.getConfig().stereoscopicEyeCount;
|
||||
}
|
||||
@@ -162,8 +163,7 @@ RenderPass::RenderPass(FEngine& engine, RenderPassBuilder const& builder) noexce
|
||||
}
|
||||
|
||||
// this destructor is actually heavy because it inlines ~vector<>
|
||||
RenderPass::~RenderPass() noexcept {
|
||||
}
|
||||
RenderPass::~RenderPass() noexcept = default;
|
||||
|
||||
void RenderPass::resize(Arena& arena, size_t count) noexcept {
|
||||
if (mCommandBegin) {
|
||||
@@ -221,7 +221,7 @@ void RenderPass::appendCommands(FEngine& engine,
|
||||
work(vr.first, vr.size());
|
||||
} else {
|
||||
auto* jobCommandsParallel = jobs::parallel_for(js, nullptr, vr.first, (uint32_t)vr.size(),
|
||||
std::cref(work), jobs::CountSplitter<JOBS_PARALLEL_FOR_COMMANDS_COUNT, 5>());
|
||||
std::cref(work), jobs::CountSplitter<JOBS_PARALLEL_FOR_COMMANDS_COUNT>());
|
||||
js.runAndWait(jobCommandsParallel);
|
||||
}
|
||||
|
||||
@@ -671,7 +671,6 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
|
||||
*/
|
||||
for (size_t pi = 0, c = primitives.size(); pi < c; ++pi) {
|
||||
auto const& primitive = primitives[pi];
|
||||
auto const& morphTargets = morphing.targets[pi];
|
||||
FMaterialInstance const* const mi = primitive.getMaterialInstance();
|
||||
FMaterial const* const ma = mi->getMaterial();
|
||||
|
||||
@@ -684,8 +683,9 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
|
||||
cmd.info.indexOffset = primitive.getIndexOffset();
|
||||
cmd.info.indexCount = primitive.getIndexCount();
|
||||
cmd.info.type = primitive.getPrimitiveType();
|
||||
cmd.info.morphTargetBuffer = morphTargets.buffer->getHwHandle();
|
||||
cmd.info.morphingOffset = morphTargets.offset;
|
||||
cmd.info.morphTargetBuffer = morphing.morphTargetBuffer ?
|
||||
morphing.morphTargetBuffer->getHwHandle() : SamplerGroupHandle{};
|
||||
cmd.info.morphingOffset = primitive.getMorphingBufferOffset();
|
||||
|
||||
if constexpr (isColorPass) {
|
||||
RenderPass::setupColorCommand(cmd, renderableVariant, mi, inverseFrontFaces);
|
||||
@@ -885,14 +885,31 @@ void RenderPass::Executor::execute(FEngine& engine,
|
||||
|
||||
// Maximum space occupied in the CircularBuffer by a single `Command`. This must be
|
||||
// reevaluated when the inner loop below adds DriverApi commands or when we change the
|
||||
// CommandStream protocol. Currently, the maximum is 240 bytes, and we use 256 to be on
|
||||
// the safer side.
|
||||
size_t const maxCommandSizeInBytes = 256;
|
||||
// CommandStream protocol. Currently, the maximum is 320 bytes.
|
||||
// The batch size is calculated by adding the size of all commands that can possibly be
|
||||
// emitted per draw call:
|
||||
constexpr size_t const maxCommandSizeInBytes =
|
||||
sizeof(CustomCommand) +
|
||||
sizeof(COMMAND_TYPE(scissor)) +
|
||||
sizeof(COMMAND_TYPE(bindUniformBuffer)) +
|
||||
sizeof(COMMAND_TYPE(bindSamplers)) +
|
||||
sizeof(COMMAND_TYPE(bindBufferRange)) +
|
||||
sizeof(COMMAND_TYPE(bindBufferRange)) +
|
||||
sizeof(COMMAND_TYPE(bindSamplers)) +
|
||||
sizeof(COMMAND_TYPE(bindSamplers)) +
|
||||
sizeof(COMMAND_TYPE(bindUniformBuffer)) +
|
||||
sizeof(COMMAND_TYPE(bindSamplers)) +
|
||||
sizeof(COMMAND_TYPE(bindSamplers)) +
|
||||
sizeof(COMMAND_TYPE(bindPipeline)) +
|
||||
sizeof(COMMAND_TYPE(setPushConstant)) +
|
||||
sizeof(COMMAND_TYPE(bindRenderPrimitive)) +
|
||||
sizeof(COMMAND_TYPE(draw2));
|
||||
|
||||
|
||||
// Number of Commands that can be issued and guaranteed to fit in the current
|
||||
// CircularBuffer allocation. In practice, we'll have tons of headroom especially if
|
||||
// skinning and morphing aren't used. With a 2 MiB buffer (the default) a batch is
|
||||
// 8192 commands (i.e. draw calls).
|
||||
// 6553 commands (i.e. draw calls).
|
||||
size_t const batchCommandCount = capacity / maxCommandSizeInBytes;
|
||||
while(first != last) {
|
||||
Command const* const batchLast = std::min(first + batchCommandCount, last);
|
||||
|
||||
@@ -417,8 +417,7 @@ private:
|
||||
void instanceify(FEngine& engine, Arena& arena, int32_t eyeCount) noexcept;
|
||||
|
||||
// We choose the command count per job to minimize JobSystem overhead.
|
||||
// On a Pixel 4, 2048 commands is about half a millisecond of processing.
|
||||
static constexpr size_t JOBS_PARALLEL_FOR_COMMANDS_COUNT = 2048;
|
||||
static constexpr size_t JOBS_PARALLEL_FOR_COMMANDS_COUNT = 128;
|
||||
static constexpr size_t JOBS_PARALLEL_FOR_COMMANDS_SIZE =
|
||||
sizeof(Command) * JOBS_PARALLEL_FOR_COMMANDS_COUNT;
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ public:
|
||||
backend::VertexBufferInfoHandle getVertexBufferInfoHandle() const { return mVertexBufferInfoHandle; }
|
||||
uint32_t getIndexOffset() const noexcept { return mIndexOffset; }
|
||||
uint32_t getIndexCount() const noexcept { return mIndexCount; }
|
||||
uint32_t getMorphingBufferOffset() const noexcept { return mMorphingBufferOffset; }
|
||||
|
||||
backend::PrimitiveType getPrimitiveType() const noexcept { return mPrimitiveType; }
|
||||
AttributeBitset getEnabledAttributes() const noexcept { return mEnabledAttributes; }
|
||||
@@ -72,6 +73,10 @@ public:
|
||||
mGlobalBlendOrderEnabled = enabled;
|
||||
}
|
||||
|
||||
void setMorphingBufferOffset(uint32_t offset) noexcept {
|
||||
mMorphingBufferOffset = offset;
|
||||
}
|
||||
|
||||
private:
|
||||
// These first fields are dereferences from PrimitiveInfo, keep them together
|
||||
struct {
|
||||
@@ -80,6 +85,7 @@ private:
|
||||
backend::Handle<backend::HwVertexBufferInfo> mVertexBufferInfoHandle = {};
|
||||
uint32_t mIndexOffset = 0;
|
||||
uint32_t mIndexCount = 0;
|
||||
uint32_t mMorphingBufferOffset = 0;
|
||||
};
|
||||
|
||||
AttributeBitset mEnabledAttributes = {};
|
||||
|
||||
@@ -158,20 +158,14 @@ void RenderableManager::setMorphWeights(Instance instance, float const* weights,
|
||||
downcast(this)->setMorphWeights(instance, weights, count, offset);
|
||||
}
|
||||
|
||||
void RenderableManager::setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex,
|
||||
MorphTargetBuffer* morphTargetBuffer, size_t offset, size_t count) {
|
||||
downcast(this)->setMorphTargetBufferAt(instance, level, primitiveIndex,
|
||||
downcast(morphTargetBuffer), offset, count);
|
||||
void RenderableManager::setMorphTargetBufferOffsetAt(Instance instance, uint8_t level,
|
||||
size_t primitiveIndex,
|
||||
size_t offset) {
|
||||
downcast(this)->setMorphTargetBufferOffsetAt(instance, level, primitiveIndex, offset);
|
||||
}
|
||||
|
||||
void RenderableManager::setMorphTargetBufferAt(
|
||||
Instance instance, uint8_t level, size_t primitiveIndex, size_t offset, size_t count) {
|
||||
downcast(this)->setMorphTargetBufferAt(instance, level, primitiveIndex, offset, count);
|
||||
}
|
||||
|
||||
MorphTargetBuffer* RenderableManager::getMorphTargetBufferAt(Instance instance, uint8_t level,
|
||||
size_t primitiveIndex) const noexcept {
|
||||
return downcast(this)->getMorphTargetBufferAt(instance, level, primitiveIndex);
|
||||
MorphTargetBuffer* RenderableManager::getMorphTargetBuffer(Instance instance) const noexcept {
|
||||
return downcast(this)->getMorphTargetBuffer(instance);
|
||||
}
|
||||
|
||||
size_t RenderableManager::getMorphTargetCount(Instance instance) const noexcept {
|
||||
|
||||
@@ -14,11 +14,20 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <filament/Renderer.h>
|
||||
|
||||
#include "details/Renderer.h"
|
||||
|
||||
#include "details/Engine.h"
|
||||
#include "details/View.h"
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
using namespace math;
|
||||
@@ -36,6 +45,10 @@ void Renderer::setPresentationTime(int64_t monotonic_clock_ns) {
|
||||
downcast(this)->setPresentationTime(monotonic_clock_ns);
|
||||
}
|
||||
|
||||
void Renderer::skipFrame(uint64_t vsyncSteadyClockTimeNano) {
|
||||
downcast(this)->skipFrame(vsyncSteadyClockTimeNano);
|
||||
}
|
||||
|
||||
bool Renderer::beginFrame(SwapChain* swapChain, uint64_t vsyncSteadyClockTimeNano) {
|
||||
return downcast(this)->beginFrame(downcast(swapChain), vsyncSteadyClockTimeNano);
|
||||
}
|
||||
@@ -89,4 +102,16 @@ void Renderer::renderStandaloneView(View const* view) {
|
||||
downcast(this)->renderStandaloneView(downcast(view));
|
||||
}
|
||||
|
||||
void Renderer::setVsyncTime(uint64_t steadyClockTimeNano) noexcept {
|
||||
downcast(this)->setVsyncTime(steadyClockTimeNano);
|
||||
}
|
||||
|
||||
utils::FixedCapacityVector<Renderer::FrameInfo> Renderer::getFrameInfoHistory(size_t historySize) const noexcept {
|
||||
return downcast(this)->getFrameInfoHistory(historySize);
|
||||
}
|
||||
|
||||
size_t Renderer::getMaxFrameHistorySize() const noexcept {
|
||||
return downcast(this)->getMaxFrameHistorySize();
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -172,7 +172,7 @@ FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
|
||||
|
||||
data.color = builder.write(data.color, FrameGraphTexture::Usage::COLOR_ATTACHMENT);
|
||||
data.depth = builder.write(data.depth, FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
|
||||
if (engine.getConfig().stereoscopicType == StereoscopicType::MULTIVIEW) {
|
||||
if (view.hasStereo() && engine.getConfig().stereoscopicType == StereoscopicType::MULTIVIEW) {
|
||||
layerCount = engine.getConfig().stereoscopicEyeCount;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,15 +27,18 @@
|
||||
|
||||
#include "private/backend/DriverApi.h"
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/bitset.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include <stddef.h>
|
||||
@@ -89,8 +92,15 @@ void ResourceAllocator::AssociativeContainer<K, V, H>::emplace(ARGS&& ... args)
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
ResourceAllocatorInterface::~ResourceAllocatorInterface() = default;
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
ResourceAllocatorDisposerInterface::~ResourceAllocatorDisposerInterface() = default;
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
size_t ResourceAllocator::TextureKey::getSize() const noexcept {
|
||||
size_t const pixelCount = width * height * depth;
|
||||
size_t size = pixelCount * FTexture::getFormatSize(format);
|
||||
@@ -110,16 +120,22 @@ size_t ResourceAllocator::TextureKey::getSize() const noexcept {
|
||||
|
||||
ResourceAllocator::ResourceAllocator(Engine::Config const& config, DriverApi& driverApi) noexcept
|
||||
: mCacheMaxAge(config.resourceAllocatorCacheMaxAge),
|
||||
mBackend(driverApi) {
|
||||
mBackend(driverApi),
|
||||
mDisposer(std::make_shared<ResourceAllocatorDisposer>(driverApi)) {
|
||||
}
|
||||
|
||||
ResourceAllocator::ResourceAllocator(std::shared_ptr<ResourceAllocatorDisposer> disposer,
|
||||
Engine::Config const& config, DriverApi& driverApi) noexcept
|
||||
: mCacheMaxAge(config.resourceAllocatorCacheMaxAge),
|
||||
mBackend(driverApi),
|
||||
mDisposer(std::move(disposer)) {
|
||||
}
|
||||
|
||||
ResourceAllocator::~ResourceAllocator() noexcept {
|
||||
assert_invariant(!mTextureCache.size());
|
||||
assert_invariant(!mInUseTextures.size());
|
||||
assert_invariant(mTextureCache.empty());
|
||||
}
|
||||
|
||||
void ResourceAllocator::terminate() noexcept {
|
||||
assert_invariant(!mInUseTextures.size());
|
||||
auto& textureCache = mTextureCache;
|
||||
for (auto it = textureCache.begin(); it != textureCache.end();) {
|
||||
mBackend.destroyTexture(it->second.handle);
|
||||
@@ -174,7 +190,7 @@ backend::TextureHandle ResourceAllocator::createTexture(const char* name,
|
||||
swizzle[0], swizzle[1], swizzle[2], swizzle[3]);
|
||||
}
|
||||
}
|
||||
mInUseTextures.emplace(handle, key);
|
||||
mDisposer->checkout(handle, key);
|
||||
} else {
|
||||
if (swizzle == defaultSwizzle) {
|
||||
handle = mBackend.createTexture(
|
||||
@@ -190,58 +206,99 @@ backend::TextureHandle ResourceAllocator::createTexture(const char* name,
|
||||
|
||||
void ResourceAllocator::destroyTexture(TextureHandle h) noexcept {
|
||||
if constexpr (mEnabled) {
|
||||
// find the texture in the in-use list (it must be there!)
|
||||
auto it = mInUseTextures.find(h);
|
||||
assert_invariant(it != mInUseTextures.end());
|
||||
|
||||
// move it to the cache
|
||||
const TextureKey key = it->second;
|
||||
uint32_t const size = key.getSize();
|
||||
|
||||
mTextureCache.emplace(key, TextureCachePayload{ h, mAge, size });
|
||||
mCacheSize += size;
|
||||
|
||||
// remove it from the in-use list
|
||||
mInUseTextures.erase(it);
|
||||
auto const key = mDisposer->checkin(h);
|
||||
if (UTILS_LIKELY(key.has_value())) {
|
||||
uint32_t const size = key.value().getSize();
|
||||
mTextureCache.emplace(key.value(), TextureCachePayload{ h, mAge, size });
|
||||
mCacheSize += size;
|
||||
mCacheSizeHiWaterMark = std::max(mCacheSizeHiWaterMark, mCacheSize);
|
||||
}
|
||||
} else {
|
||||
mBackend.destroyTexture(h);
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceAllocator::gc() noexcept {
|
||||
// this is called regularly -- usually once per frame of each Renderer
|
||||
ResourceAllocatorDisposerInterface& ResourceAllocator::getDisposer() noexcept {
|
||||
return *mDisposer;
|
||||
}
|
||||
|
||||
// increase our age
|
||||
const size_t age = mAge++;
|
||||
void ResourceAllocator::gc(bool skippedFrame) noexcept {
|
||||
// this is called regularly -- usually once per frame
|
||||
|
||||
// increase our age at each (non-skipped) frame
|
||||
const size_t age = mAge;
|
||||
if (!skippedFrame) {
|
||||
mAge++;
|
||||
}
|
||||
|
||||
// Purging strategy:
|
||||
// - remove entries that are older than a certain age
|
||||
// - remove only one entry per gc(),
|
||||
// - remove all entries older than MAX_AGE_SKIPPED_FRAME when skipping a frame
|
||||
// - remove entries older than mCacheMaxAgeSoft
|
||||
// - remove only MAX_EVICTION_COUNT entry per gc(),
|
||||
// - look for the number of unique resource ages present in the cache (this basically gives
|
||||
// us how many buckets of resources we have corresponding to previous frames.
|
||||
// - remove all resources that have an age older than the MAX_UNIQUE_AGE_COUNT'th bucket
|
||||
|
||||
auto& textureCache = mTextureCache;
|
||||
|
||||
// when skipping a frame, the maximum age to keep in the cache
|
||||
constexpr size_t MAX_AGE_SKIPPED_FRAME = 1;
|
||||
|
||||
// maximum entry count to evict per GC, under the mCacheMaxAgeSoft limit
|
||||
constexpr size_t MAX_EVICTION_COUNT = 1;
|
||||
|
||||
// maximum number of unique ages in the cache
|
||||
constexpr size_t MAX_UNIQUE_AGE_COUNT = 3;
|
||||
|
||||
utils::bitset32 ages;
|
||||
uint32_t evictedCount = 0;
|
||||
for (auto it = textureCache.begin(); it != textureCache.end();) {
|
||||
const size_t ageDiff = age - it->second.age;
|
||||
if (ageDiff >= mCacheMaxAge) {
|
||||
size_t const ageDiff = age - it->second.age;
|
||||
if ((ageDiff >= MAX_AGE_SKIPPED_FRAME && skippedFrame) ||
|
||||
(ageDiff >= mCacheMaxAge && evictedCount < MAX_EVICTION_COUNT)) {
|
||||
evictedCount++;
|
||||
purge(it);
|
||||
// only purge a single entry per gc
|
||||
break;
|
||||
} else {
|
||||
// build the set of ages present in the cache after eviction
|
||||
ages.set(std::min(size_t(31), ageDiff));
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
// if we have MAX_UNIQUE_AGE_COUNT ages or more, we evict all the resources that
|
||||
// are older than the MAX_UNIQUE_AGE_COUNT'th age.
|
||||
if (!skippedFrame && ages.count() >= MAX_UNIQUE_AGE_COUNT) {
|
||||
uint32_t bits = ages.getValue();
|
||||
// remove from the set the ages we keep
|
||||
for (size_t i = 0; i < MAX_UNIQUE_AGE_COUNT - 1; i++) {
|
||||
bits &= ~(1 << utils::ctz(bits));
|
||||
}
|
||||
size_t const maxAge = utils::ctz(bits);
|
||||
for (auto it = textureCache.begin(); it != textureCache.end();) {
|
||||
const size_t ageDiff = age - it->second.age;
|
||||
if (ageDiff >= maxAge) {
|
||||
purge(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
void ResourceAllocator::dump(bool brief) const noexcept {
|
||||
slog.d << "# entries=" << mTextureCache.size() << ", sz=" << mCacheSize / float(1u << 20u)
|
||||
<< " MiB" << io::endl;
|
||||
constexpr float MiB = 1.0f / float(1u << 20u);
|
||||
slog.d << "# entries=" << mTextureCache.size()
|
||||
<< ", sz=" << (float)mCacheSize * MiB << " MiB"
|
||||
<< ", max=" << (float)mCacheSizeHiWaterMark * MiB << " MiB"
|
||||
<< io::endl;
|
||||
if (!brief) {
|
||||
for (auto const& it : mTextureCache) {
|
||||
auto w = it.first.width;
|
||||
auto h = it.first.height;
|
||||
auto f = FTexture::getFormatSize(it.first.format);
|
||||
slog.d << it.first.name << ": w=" << w << ", h=" << h << ", f=" << f << ", sz="
|
||||
<< it.second.size / float(1u << 20u) << io::endl;
|
||||
<< (float)it.second.size * MiB << io::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -254,4 +311,46 @@ void ResourceAllocator::purge(
|
||||
mTextureCache.erase(pos);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
ResourceAllocatorDisposer::ResourceAllocatorDisposer(DriverApi& driverApi) noexcept
|
||||
: mBackend(driverApi) {
|
||||
}
|
||||
|
||||
ResourceAllocatorDisposer::~ResourceAllocatorDisposer() noexcept {
|
||||
assert_invariant(mInUseTextures.empty());
|
||||
}
|
||||
|
||||
void ResourceAllocatorDisposer::terminate() noexcept {
|
||||
assert_invariant(mInUseTextures.empty());
|
||||
}
|
||||
|
||||
void ResourceAllocatorDisposer::destroy(backend::TextureHandle handle) noexcept {
|
||||
if (handle) {
|
||||
auto r = checkin(handle);
|
||||
if (r.has_value()) {
|
||||
mBackend.destroyTexture(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceAllocatorDisposer::checkout(backend::TextureHandle handle,
|
||||
ResourceAllocator::TextureKey key) {
|
||||
mInUseTextures.emplace(handle, key);
|
||||
}
|
||||
|
||||
std::optional<ResourceAllocator::TextureKey> ResourceAllocatorDisposer::checkin(
|
||||
backend::TextureHandle handle) {
|
||||
// find the texture in the in-use list (it must be there!)
|
||||
auto it = mInUseTextures.find(handle);
|
||||
assert_invariant(it != mInUseTextures.end());
|
||||
if (it == mInUseTextures.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
TextureKey const key = it->second;
|
||||
// remove it from the in-use list
|
||||
mInUseTextures.erase(it);
|
||||
return key;
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -28,16 +28,29 @@
|
||||
#include <utils/Hash.h>
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cstddef>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
class ResourceAllocatorDisposer;
|
||||
|
||||
// The only reason we use an interface here is for unit-tests, so we can mock this allocator.
|
||||
// This is not too time-critical, so that's okay.
|
||||
|
||||
class ResourceAllocatorDisposerInterface {
|
||||
public:
|
||||
virtual void destroy(backend::TextureHandle handle) noexcept = 0;
|
||||
protected:
|
||||
virtual ~ResourceAllocatorDisposerInterface();
|
||||
};
|
||||
|
||||
class ResourceAllocatorInterface {
|
||||
public:
|
||||
virtual backend::RenderTargetHandle createRenderTarget(const char* name,
|
||||
@@ -60,15 +73,20 @@ public:
|
||||
|
||||
virtual void destroyTexture(backend::TextureHandle h) noexcept = 0;
|
||||
|
||||
virtual ResourceAllocatorDisposerInterface& getDisposer() noexcept = 0;
|
||||
|
||||
protected:
|
||||
virtual ~ResourceAllocatorInterface();
|
||||
};
|
||||
|
||||
|
||||
class ResourceAllocator final : public ResourceAllocatorInterface {
|
||||
public:
|
||||
explicit ResourceAllocator(std::shared_ptr<ResourceAllocatorDisposer> disposer,
|
||||
Engine::Config const& config, backend::DriverApi& driverApi) noexcept;
|
||||
|
||||
explicit ResourceAllocator(
|
||||
Engine::Config const& config, backend::DriverApi& driverApi) noexcept;
|
||||
|
||||
~ResourceAllocator() noexcept override;
|
||||
|
||||
void terminate() noexcept;
|
||||
@@ -93,7 +111,9 @@ public:
|
||||
|
||||
void destroyTexture(backend::TextureHandle h) noexcept override;
|
||||
|
||||
void gc() noexcept;
|
||||
ResourceAllocatorDisposerInterface& getDisposer() noexcept override;
|
||||
|
||||
void gc(bool skippedFrame = false) noexcept;
|
||||
|
||||
private:
|
||||
size_t const mCacheMaxAge;
|
||||
@@ -181,6 +201,7 @@ private:
|
||||
using value_type = typename Container::value_type::second_type;
|
||||
|
||||
size_t size() const { return mContainer.size(); }
|
||||
bool empty() const { return size() == 0; }
|
||||
iterator begin() { return mContainer.begin(); }
|
||||
const_iterator begin() const { return mContainer.begin(); }
|
||||
iterator end() { return mContainer.end(); }
|
||||
@@ -193,16 +214,36 @@ private:
|
||||
};
|
||||
|
||||
using CacheContainer = AssociativeContainer<TextureKey, TextureCachePayload>;
|
||||
using InUseContainer = AssociativeContainer<backend::TextureHandle, TextureKey>;
|
||||
|
||||
void purge(ResourceAllocator::CacheContainer::iterator const& pos);
|
||||
|
||||
backend::DriverApi& mBackend;
|
||||
std::shared_ptr<ResourceAllocatorDisposer> mDisposer;
|
||||
CacheContainer mTextureCache;
|
||||
InUseContainer mInUseTextures;
|
||||
size_t mAge = 0;
|
||||
uint32_t mCacheSize = 0;
|
||||
uint32_t mCacheSizeHiWaterMark = 0;
|
||||
static constexpr bool mEnabled = true;
|
||||
|
||||
friend class ResourceAllocatorDisposer;
|
||||
};
|
||||
|
||||
class ResourceAllocatorDisposer final : public ResourceAllocatorDisposerInterface {
|
||||
using TextureKey = ResourceAllocator::TextureKey;
|
||||
public:
|
||||
explicit ResourceAllocatorDisposer(backend::DriverApi& driverApi) noexcept;
|
||||
~ResourceAllocatorDisposer() noexcept override;
|
||||
void terminate() noexcept;
|
||||
void destroy(backend::TextureHandle handle) noexcept override;
|
||||
|
||||
private:
|
||||
friend class ResourceAllocator;
|
||||
void checkout(backend::TextureHandle handle, TextureKey key);
|
||||
std::optional<TextureKey> checkin(backend::TextureHandle handle);
|
||||
|
||||
using InUseContainer = ResourceAllocator::AssociativeContainer<backend::TextureHandle, TextureKey>;
|
||||
backend::DriverApi& mBackend;
|
||||
InUseContainer mInUseTextures;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
|
||||
#include "details/View.h"
|
||||
#include "filament/View.h"
|
||||
|
||||
|
||||
namespace filament {
|
||||
|
||||
@@ -312,4 +314,8 @@ utils::Entity View::getFogEntity() const noexcept {
|
||||
return downcast(this)->getFogEntity();
|
||||
}
|
||||
|
||||
void View::clearFrameHistory(Engine& engine) noexcept {
|
||||
downcast(this)->clearFrameHistory(downcast(engine));
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -20,12 +20,13 @@
|
||||
|
||||
#include "details/Engine.h"
|
||||
|
||||
#include <math/fast.h>
|
||||
#include <math/scalar.h>
|
||||
|
||||
#include <utils/debug.h>
|
||||
#include <filament/LightManager.h>
|
||||
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include <math/fast.h>
|
||||
#include <math/scalar.h>
|
||||
|
||||
using namespace filament::math;
|
||||
using namespace utils;
|
||||
|
||||
@@ -281,19 +281,13 @@ RenderableManager::Builder& RenderableManager::Builder::morphing(
|
||||
return *this;
|
||||
}
|
||||
|
||||
RenderableManager::Builder& RenderableManager::Builder::morphing(
|
||||
uint8_t level, size_t primitiveIndex, size_t offset, size_t count) noexcept {
|
||||
return morphing(level, primitiveIndex, mImpl->mMorphTargetBuffer, offset, count);
|
||||
}
|
||||
|
||||
RenderableManager::Builder& RenderableManager::Builder::morphing(uint8_t, size_t primitiveIndex,
|
||||
MorphTargetBuffer* morphTargetBuffer, size_t offset, size_t count) noexcept {
|
||||
std::vector<Entry>& entries = mImpl->mEntries;
|
||||
RenderableManager::Builder& RenderableManager::Builder::morphing(uint8_t level,
|
||||
size_t primitiveIndex, size_t offset) noexcept {
|
||||
// the last parameter "count" is unused, because it must be equal to the primitive's vertex count
|
||||
std::vector<BuilderDetails::Entry>& entries = mImpl->mEntries;
|
||||
if (primitiveIndex < entries.size()) {
|
||||
auto& morphing = entries[primitiveIndex].morphing;
|
||||
morphing.buffer = morphTargetBuffer;
|
||||
morphing.offset = offset;
|
||||
morphing.count = count;
|
||||
morphing.offset = uint32_t(offset);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -449,10 +443,10 @@ RenderableManager::Builder::Result RenderableManager::Builder::build(Engine& eng
|
||||
<< ") instances when supplying transforms via an InstanceBuffer.";
|
||||
|
||||
if (mImpl->mGeometryType == GeometryType::STATIC) {
|
||||
FILAMENT_CHECK_PRECONDITION(mImpl->mSkinningBoneCount > 0)
|
||||
FILAMENT_CHECK_PRECONDITION(mImpl->mSkinningBoneCount == 0)
|
||||
<< "Skinning can't be used with STATIC geometry";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(mImpl->mMorphTargetCount > 0)
|
||||
FILAMENT_CHECK_PRECONDITION(mImpl->mMorphTargetCount == 0)
|
||||
<< "Morphing can't be used with STATIC geometry";
|
||||
}
|
||||
|
||||
@@ -668,13 +662,6 @@ void FRenderableManager::create(
|
||||
if (morphTargetBuffer == nullptr) {
|
||||
morphTargetBuffer = mEngine.getDummyMorphTargetBuffer();
|
||||
}
|
||||
MorphTargets* const morphTargets = new MorphTargets[entryCount];
|
||||
std::generate_n(morphTargets, entryCount,
|
||||
[morphTargetBuffer]() -> MorphTargets {
|
||||
return { morphTargetBuffer, 0, 0 };
|
||||
});
|
||||
|
||||
mManager[ci].morphTargets = { morphTargets, size_type(entryCount) };
|
||||
|
||||
// Always create skinning and morphing resources if one of them is enabled because
|
||||
// the shader always handles both. See Variant::SKINNING_OR_MORPHING.
|
||||
@@ -700,13 +687,13 @@ void FRenderableManager::create(
|
||||
backend::BufferUsage::DYNAMIC),
|
||||
.count = targetCount };
|
||||
|
||||
for (size_t i = 0; i < entryCount; ++i) {
|
||||
const auto& morphing = builder->mEntries[i].morphing;
|
||||
if (!morphing.buffer) {
|
||||
continue;
|
||||
Slice<FRenderPrimitive>& primitives = mManager[ci].primitives;
|
||||
mManager[ci].morphTargetBuffer = morphTargetBuffer;
|
||||
if (builder->mMorphTargetBuffer) {
|
||||
for (size_t i = 0; i < entryCount; ++i) {
|
||||
const auto& morphing = builder->mEntries[i].morphing;
|
||||
primitives[i].setMorphingBufferOffset(morphing.offset);
|
||||
}
|
||||
morphTargets[i] = { downcast(morphing.buffer), (uint32_t)morphing.offset,
|
||||
(uint32_t)morphing.count };
|
||||
}
|
||||
|
||||
// When targetCount equal 0, boneCount>0 in this case, do an initialization for the
|
||||
@@ -762,7 +749,6 @@ void FRenderableManager::destroyComponent(Instance ci) noexcept {
|
||||
|
||||
// See create(RenderableManager::Builder&, Entity)
|
||||
destroyComponentPrimitives(mHwRenderPrimitiveFactory, driver, manager[ci].primitives);
|
||||
destroyComponentMorphTargets(engine, manager[ci].morphTargets);
|
||||
|
||||
// destroy the bones structures if any
|
||||
Bones const& bones = manager[ci].bones;
|
||||
@@ -795,11 +781,6 @@ void FRenderableManager::destroyComponentPrimitives(
|
||||
delete[] primitives.data();
|
||||
}
|
||||
|
||||
void FRenderableManager::destroyComponentMorphTargets(FEngine&,
|
||||
utils::Slice<MorphTargets>& morphTargets) noexcept {
|
||||
delete[] morphTargets.data();
|
||||
}
|
||||
|
||||
void FRenderableManager::setMaterialInstanceAt(Instance instance, uint8_t level,
|
||||
size_t primitiveIndex, FMaterialInstance const* mi) {
|
||||
if (instance) {
|
||||
@@ -968,42 +949,21 @@ void FRenderableManager::setMorphWeights(Instance instance, float const* weights
|
||||
}
|
||||
}
|
||||
|
||||
void FRenderableManager::setMorphTargetBufferAt(Instance instance, uint8_t level,
|
||||
size_t primitiveIndex, FMorphTargetBuffer* morphTargetBuffer, size_t offset, size_t count) {
|
||||
void FRenderableManager::setMorphTargetBufferOffsetAt(Instance instance, uint8_t level,
|
||||
size_t primitiveIndex,
|
||||
size_t offset) {
|
||||
if (instance) {
|
||||
assert_invariant(morphTargetBuffer);
|
||||
|
||||
MorphWeights const& morphWeights = mManager[instance].morphWeights;
|
||||
FILAMENT_CHECK_PRECONDITION(morphWeights.count == count)
|
||||
<< "Only " << morphWeights.count << " morph targets can be set (count=" << count
|
||||
<< ")";
|
||||
|
||||
Slice<MorphTargets>& morphTargets = getMorphTargets(instance, level);
|
||||
if (primitiveIndex < morphTargets.size()) {
|
||||
morphTargets[primitiveIndex] = { morphTargetBuffer, (uint32_t)offset,
|
||||
(uint32_t)count };
|
||||
assert_invariant(mManager[instance].morphTargetBuffer);
|
||||
Slice<FRenderPrimitive>& primitives = mManager[instance].primitives;
|
||||
if (primitiveIndex < primitives.size()) {
|
||||
primitives[primitiveIndex].setMorphingBufferOffset(offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FRenderableManager::setMorphTargetBufferAt(Instance instance, uint8_t level,
|
||||
size_t primitiveIndex, size_t offset, size_t count) {
|
||||
MorphTargetBuffer* FRenderableManager::getMorphTargetBuffer(Instance instance) const noexcept {
|
||||
if (instance) {
|
||||
Slice<MorphTargets>& morphTargets = getMorphTargets(instance, level);
|
||||
if (primitiveIndex < morphTargets.size()) {
|
||||
setMorphTargetBufferAt(instance, level,
|
||||
primitiveIndex, morphTargets[primitiveIndex].buffer, offset, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MorphTargetBuffer* FRenderableManager::getMorphTargetBufferAt(Instance instance, uint8_t level,
|
||||
size_t primitiveIndex) const noexcept {
|
||||
if (instance) {
|
||||
const Slice<MorphTargets>& morphTargets = getMorphTargets(instance, level);
|
||||
if (primitiveIndex < morphTargets.size()) {
|
||||
return morphTargets[primitiveIndex].buffer;
|
||||
}
|
||||
return mManager[instance].morphTargetBuffer;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -80,12 +80,6 @@ public:
|
||||
|
||||
static_assert(sizeof(Visibility) == sizeof(uint16_t), "Visibility should be 16 bits");
|
||||
|
||||
struct MorphTargets {
|
||||
FMorphTargetBuffer* buffer = nullptr;
|
||||
uint32_t offset = 0;
|
||||
uint32_t count = 0;
|
||||
};
|
||||
|
||||
explicit FRenderableManager(FEngine& engine) noexcept;
|
||||
~FRenderableManager();
|
||||
|
||||
@@ -155,11 +149,9 @@ public:
|
||||
|
||||
inline void setMorphing(Instance instance, bool enable);
|
||||
void setMorphWeights(Instance instance, float const* weights, size_t count, size_t offset);
|
||||
void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex,
|
||||
FMorphTargetBuffer* morphTargetBuffer, size_t offset, size_t count);
|
||||
void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex,
|
||||
size_t offset, size_t count);
|
||||
MorphTargetBuffer* getMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex) const noexcept;
|
||||
void setMorphTargetBufferOffsetAt(Instance instance, uint8_t level, size_t primitiveIndex,
|
||||
size_t offset);
|
||||
MorphTargetBuffer* getMorphTargetBuffer(Instance instance) const noexcept;
|
||||
size_t getMorphTargetCount(Instance instance) const noexcept;
|
||||
|
||||
void setLightChannel(Instance instance, unsigned int channel, bool enable) noexcept;
|
||||
@@ -189,7 +181,7 @@ public:
|
||||
struct MorphingBindingInfo {
|
||||
backend::Handle<backend::HwBufferObject> handle;
|
||||
uint32_t count;
|
||||
MorphTargets const* targets; // Pointer to Slice<MorphTargets> at a renderable.
|
||||
FMorphTargetBuffer const* morphTargetBuffer;
|
||||
};
|
||||
inline MorphingBindingInfo getMorphingBufferInfo(Instance instance) const noexcept;
|
||||
|
||||
@@ -218,16 +210,12 @@ public:
|
||||
AttributeBitset getEnabledAttributesAt(Instance instance, uint8_t level, size_t primitiveIndex) const noexcept;
|
||||
inline utils::Slice<FRenderPrimitive> const& getRenderPrimitives(Instance instance, uint8_t level) const noexcept;
|
||||
inline utils::Slice<FRenderPrimitive>& getRenderPrimitives(Instance instance, uint8_t level) noexcept;
|
||||
inline utils::Slice<MorphTargets> const& getMorphTargets(Instance instance, uint8_t level) const noexcept;
|
||||
inline utils::Slice<MorphTargets>& getMorphTargets(Instance instance, uint8_t level) noexcept;
|
||||
|
||||
private:
|
||||
void destroyComponent(Instance ci) noexcept;
|
||||
static void destroyComponentPrimitives(
|
||||
HwRenderPrimitiveFactory& factory, backend::DriverApi& driver,
|
||||
utils::Slice<FRenderPrimitive>& primitives) noexcept;
|
||||
static void destroyComponentMorphTargets(FEngine& engine,
|
||||
utils::Slice<MorphTargets>& morphTargets) noexcept;
|
||||
|
||||
struct Bones {
|
||||
backend::Handle<backend::HwBufferObject> handle;
|
||||
@@ -254,7 +242,7 @@ private:
|
||||
VISIBILITY, // user data
|
||||
PRIMITIVES, // user data
|
||||
BONES, // filament data, UBO storing a pointer to the bones information
|
||||
MORPH_TARGETS
|
||||
MORPHTARGET_BUFFER // morphtarget buffer for the component
|
||||
};
|
||||
|
||||
using Base = utils::SingleInstanceComponentManager<
|
||||
@@ -266,7 +254,7 @@ private:
|
||||
Visibility, // VISIBILITY
|
||||
utils::Slice<FRenderPrimitive>, // PRIMITIVES
|
||||
Bones, // BONES
|
||||
utils::Slice<MorphTargets> // MORPH_TARGETS
|
||||
FMorphTargetBuffer* // MORPHTARGET_BUFFER
|
||||
>;
|
||||
|
||||
struct Sim : public Base {
|
||||
@@ -289,7 +277,7 @@ private:
|
||||
Field<VISIBILITY> visibility;
|
||||
Field<PRIMITIVES> primitives;
|
||||
Field<BONES> bones;
|
||||
Field<MORPH_TARGETS> morphTargets;
|
||||
Field<MORPHTARGET_BUFFER> morphTargetBuffer;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -461,8 +449,8 @@ inline uint32_t FRenderableManager::getBoneCount(Instance instance) const noexce
|
||||
FRenderableManager::MorphingBindingInfo
|
||||
FRenderableManager::getMorphingBufferInfo(Instance instance) const noexcept {
|
||||
MorphWeights const& morphWeights = mManager[instance].morphWeights;
|
||||
utils::Slice<MorphTargets> const& morphTargets = getMorphTargets(instance, 0);
|
||||
return { morphWeights.handle, morphWeights.count, morphTargets.data() };
|
||||
FMorphTargetBuffer const* const buffer = mManager[instance].morphTargetBuffer;
|
||||
return { morphWeights.handle, morphWeights.count, buffer };
|
||||
}
|
||||
|
||||
FRenderableManager::InstancesInfo
|
||||
@@ -480,16 +468,6 @@ utils::Slice<FRenderPrimitive>& FRenderableManager::getRenderPrimitives(
|
||||
return mManager[instance].primitives;
|
||||
}
|
||||
|
||||
utils::Slice<FRenderableManager::MorphTargets> const& FRenderableManager::getMorphTargets(
|
||||
Instance instance, UTILS_UNUSED uint8_t level) const noexcept {
|
||||
return mManager[instance].morphTargets;
|
||||
}
|
||||
|
||||
utils::Slice<FRenderableManager::MorphTargets>& FRenderableManager::getMorphTargets(
|
||||
Instance instance, UTILS_UNUSED uint8_t level) noexcept {
|
||||
return mManager[instance].morphTargets;
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
#endif // TNT_FILAMENT_COMPONENTS_RENDERABLEMANAGER_H
|
||||
|
||||
@@ -102,6 +102,7 @@ Engine* FEngine::create(Engine::Builder const& builder) {
|
||||
DriverConfig const driverConfig{
|
||||
.handleArenaSize = instance->getRequestedDriverHandleArenaSize(),
|
||||
.textureUseAfterFreePoolSize = instance->getConfig().textureUseAfterFreePoolSize,
|
||||
.metalUploadBufferSizeBytes = instance->getConfig().metalUploadBufferSizeBytes,
|
||||
.disableParallelShaderCompile = instance->getConfig().disableParallelShaderCompile,
|
||||
.disableHandleUseAfterFreeCheck = instance->getConfig().disableHandleUseAfterFreeCheck,
|
||||
.forceGLES2Context = instance->getConfig().forceGLES2Context,
|
||||
@@ -162,8 +163,7 @@ FEngine* FEngine::getEngine(void* token) {
|
||||
FILAMENT_CHECK_PRECONDITION(ThreadUtils::isThisThread(instance->mMainThreadId))
|
||||
<< "Engine::createAsync() and Engine::getEngine() must be called on the same thread.";
|
||||
|
||||
// we use mResourceAllocator as a proxy for "am I already initialized"
|
||||
if (!instance->mResourceAllocator) {
|
||||
if (!instance->mInitialized) {
|
||||
if (UTILS_UNLIKELY(!instance->mDriver)) {
|
||||
// something went horribly wrong during driver initialization
|
||||
instance->mDriverThread.join();
|
||||
@@ -261,7 +261,7 @@ void FEngine::init() {
|
||||
slog.i << "FEngine feature level: " << int(mActiveFeatureLevel) << io::endl;
|
||||
|
||||
|
||||
mResourceAllocator = new ResourceAllocator(mConfig, driverApi);
|
||||
mResourceAllocatorDisposer = std::make_shared<ResourceAllocatorDisposer>(driverApi);
|
||||
|
||||
mFullScreenTriangleVb = downcast(VertexBuffer::Builder()
|
||||
.vertexCount(3)
|
||||
@@ -424,11 +424,13 @@ void FEngine::init() {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
mInitialized = true;
|
||||
}
|
||||
|
||||
FEngine::~FEngine() noexcept {
|
||||
SYSTRACE_CALL();
|
||||
delete mResourceAllocator;
|
||||
assert_invariant(!mResourceAllocatorDisposer);
|
||||
delete mDriver;
|
||||
if (mOwnPlatform) {
|
||||
PlatformFactory::destroy(&mPlatform);
|
||||
@@ -439,7 +441,7 @@ void FEngine::shutdown() {
|
||||
SYSTRACE_CALL();
|
||||
|
||||
// by construction this should never be nullptr
|
||||
assert_invariant(mResourceAllocator);
|
||||
assert_invariant(mResourceAllocatorDisposer);
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(ThreadUtils::isThisThread(mMainThreadId))
|
||||
<< "Engine::shutdown() called from the wrong thread!";
|
||||
@@ -459,7 +461,8 @@ void FEngine::shutdown() {
|
||||
*/
|
||||
|
||||
mPostProcessManager.terminate(driver); // free-up post-process manager resources
|
||||
mResourceAllocator->terminate();
|
||||
mResourceAllocatorDisposer->terminate();
|
||||
mResourceAllocatorDisposer.reset();
|
||||
mDFG.terminate(*this); // free-up the DFG
|
||||
mRenderableManager.terminate(); // free-up all renderables
|
||||
mLightManager.terminate(); // free-up all lights
|
||||
@@ -678,6 +681,7 @@ int FEngine::loop() {
|
||||
DriverConfig const driverConfig {
|
||||
.handleArenaSize = getRequestedDriverHandleArenaSize(),
|
||||
.textureUseAfterFreePoolSize = mConfig.textureUseAfterFreePoolSize,
|
||||
.metalUploadBufferSizeBytes = mConfig.metalUploadBufferSizeBytes,
|
||||
.disableParallelShaderCompile = mConfig.disableParallelShaderCompile,
|
||||
.disableHandleUseAfterFreeCheck = mConfig.disableHandleUseAfterFreeCheck,
|
||||
.forceGLES2Context = mConfig.forceGLES2Context,
|
||||
|
||||
@@ -86,6 +86,7 @@ namespace filament {
|
||||
|
||||
class Renderer;
|
||||
class MaterialParser;
|
||||
class ResourceAllocatorDisposer;
|
||||
|
||||
namespace backend {
|
||||
class Driver;
|
||||
@@ -254,9 +255,13 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
ResourceAllocator& getResourceAllocator() noexcept {
|
||||
assert_invariant(mResourceAllocator);
|
||||
return *mResourceAllocator;
|
||||
ResourceAllocatorDisposer& getResourceAllocatorDisposer() noexcept {
|
||||
assert_invariant(mResourceAllocatorDisposer);
|
||||
return *mResourceAllocatorDisposer;
|
||||
}
|
||||
|
||||
std::shared_ptr<ResourceAllocatorDisposer> const& getSharedResourceAllocatorDisposer() noexcept {
|
||||
return mResourceAllocatorDisposer;
|
||||
}
|
||||
|
||||
void* streamAlloc(size_t size, size_t alignment) noexcept;
|
||||
@@ -490,7 +495,7 @@ private:
|
||||
FTransformManager mTransformManager;
|
||||
FLightManager mLightManager;
|
||||
FCameraManager mCameraManager;
|
||||
ResourceAllocator* mResourceAllocator = nullptr;
|
||||
std::shared_ptr<ResourceAllocatorDisposer> mResourceAllocatorDisposer;
|
||||
HwVertexBufferInfoFactory mHwVertexBufferInfoFactory;
|
||||
|
||||
ResourceList<FBufferObject> mBufferObjects{ "BufferObject" };
|
||||
@@ -562,6 +567,8 @@ private:
|
||||
|
||||
std::thread::id mMainThreadId{};
|
||||
|
||||
bool mInitialized = false;
|
||||
|
||||
// Creation parameters
|
||||
Config mConfig;
|
||||
|
||||
|
||||
@@ -119,6 +119,7 @@ struct Material::BuilderDetails {
|
||||
const void* mPayload = nullptr;
|
||||
size_t mSize = 0;
|
||||
bool mDefaultMaterial = false;
|
||||
int32_t mShBandsCount = 3;
|
||||
std::unordered_map<
|
||||
utils::CString,
|
||||
std::variant<int32_t, float, bool>,
|
||||
@@ -143,6 +144,11 @@ Material::Builder& Material::Builder::package(const void* payload, size_t size)
|
||||
return *this;
|
||||
}
|
||||
|
||||
Material::Builder& Material::Builder::sphericalHarmonicsBandCount(size_t shBandCount) noexcept {
|
||||
mImpl->mShBandsCount = math::clamp(shBandCount, size_t(1), size_t(3));
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T, typename>
|
||||
Material::Builder& Material::Builder::constant(const char* name, size_t nameLength, T value) {
|
||||
FILAMENT_CHECK_PRECONDITION(name != nullptr) << "name cannot be null";
|
||||
@@ -897,6 +903,8 @@ void FMaterial::processSpecializationConstants(FEngine& engine, Material::Builde
|
||||
mSpecializationConstants.push_back({
|
||||
+ReservedSpecializationConstants::CONFIG_STEREO_EYE_COUNT,
|
||||
(int)engine.getConfig().stereoscopicEyeCount });
|
||||
mSpecializationConstants.push_back({
|
||||
+ReservedSpecializationConstants::CONFIG_SH_BANDS_COUNT, builder->mShBandsCount });
|
||||
if (UTILS_UNLIKELY(parser->getShaderLanguage() == ShaderLanguage::ESSL1)) {
|
||||
// The actual value of this spec-constant is set in the OpenGLDriver backend.
|
||||
mSpecializationConstants.push_back({
|
||||
|
||||
@@ -231,6 +231,9 @@ private:
|
||||
void setParameterImpl(std::string_view name,
|
||||
FTexture const* texture, TextureSampler const& sampler);
|
||||
|
||||
template<typename T>
|
||||
T getParameterImpl(std::string_view name) const;
|
||||
|
||||
// initialize the default instance
|
||||
FMaterialInstance(FEngine& engine, FMaterial const* material) noexcept;
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
|
||||
#include <utils/Allocator.h>
|
||||
|
||||
#include <vector>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
@@ -59,16 +60,21 @@ public:
|
||||
inline size_t getVertexCount() const noexcept { return mVertexCount; }
|
||||
inline size_t getCount() const noexcept { return mCount; }
|
||||
|
||||
private:
|
||||
friend class FView;
|
||||
friend class RenderPass;
|
||||
backend::TextureHandle getPositionsHandle() const noexcept {
|
||||
return mPbHandle;
|
||||
}
|
||||
|
||||
backend::TextureHandle getTangentsHandle() const noexcept {
|
||||
return mTbHandle;
|
||||
}
|
||||
|
||||
inline backend::Handle<backend::HwSamplerGroup> getHwHandle() const noexcept { return mSbHandle; }
|
||||
|
||||
private:
|
||||
void updateDataAt(backend::DriverApi& driver, backend::Handle <backend::HwTexture> handle,
|
||||
backend::PixelDataFormat format, backend::PixelDataType type, const char* out,
|
||||
size_t elementSize, size_t targetIndex, size_t count, size_t offset);
|
||||
|
||||
inline backend::Handle<backend::HwSamplerGroup> getHwHandle() const noexcept { return mSbHandle; }
|
||||
|
||||
backend::Handle<backend::HwSamplerGroup> mSbHandle;
|
||||
backend::Handle<backend::HwTexture> mPbHandle;
|
||||
backend::Handle<backend::HwTexture> mTbHandle;
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include <stddef.h>
|
||||
@@ -88,7 +89,11 @@ FRenderer::FRenderer(FEngine& engine) :
|
||||
mHdrQualityMedium(TextureFormat::R11F_G11F_B10F),
|
||||
mHdrQualityHigh(TextureFormat::RGB16F),
|
||||
mIsRGB8Supported(false),
|
||||
mUserEpoch(engine.getEngineEpoch())
|
||||
mUserEpoch(engine.getEngineEpoch()),
|
||||
mResourceAllocator(std::make_unique<ResourceAllocator>(
|
||||
engine.getSharedResourceAllocatorDisposer(),
|
||||
engine.getConfig(),
|
||||
engine.getDriverApi()))
|
||||
{
|
||||
FDebugRegistry& debugRegistry = engine.getDebugRegistry();
|
||||
debugRegistry.registerProperty("d.renderer.doFrameCapture",
|
||||
@@ -176,6 +181,7 @@ void FRenderer::terminate(FEngine& engine) {
|
||||
}
|
||||
mFrameInfoManager.terminate(driver);
|
||||
mFrameSkipper.terminate(driver);
|
||||
mResourceAllocator->terminate();
|
||||
}
|
||||
|
||||
void FRenderer::resetUserTime() {
|
||||
@@ -232,6 +238,43 @@ void FRenderer::setPresentationTime(int64_t monotonic_clock_ns) {
|
||||
driver.setPresentationTime(monotonic_clock_ns);
|
||||
}
|
||||
|
||||
void FRenderer::setVsyncTime(uint64_t steadyClockTimeNano) noexcept {
|
||||
mVsyncSteadyClockTimeNano = steadyClockTimeNano;
|
||||
}
|
||||
|
||||
void FRenderer::skipFrame(uint64_t vsyncSteadyClockTimeNano) {
|
||||
SYSTRACE_CALL();
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(!mSwapChain) <<
|
||||
"skipFrame() can't be called between beginFrame() and endFrame()";
|
||||
|
||||
if (!vsyncSteadyClockTimeNano) {
|
||||
vsyncSteadyClockTimeNano = mVsyncSteadyClockTimeNano;
|
||||
mVsyncSteadyClockTimeNano = 0;
|
||||
}
|
||||
|
||||
FEngine& engine = mEngine;
|
||||
FEngine::DriverApi& driver = engine.getDriverApi();
|
||||
|
||||
// Gives the backend a chance to execute periodic tasks. This must be called before
|
||||
// the frame skipper.
|
||||
driver.tick();
|
||||
|
||||
// do this before engine.flush()
|
||||
mResourceAllocator->gc(true);
|
||||
|
||||
// Run the component managers' GC in parallel
|
||||
// WARNING: while doing this we can't access any component manager
|
||||
auto& js = engine.getJobSystem();
|
||||
|
||||
auto *job = js.runAndRetain(jobs::createJob(js, nullptr, &FEngine::gc, &engine)); // gc all managers
|
||||
|
||||
engine.flush(); // flush command stream
|
||||
|
||||
// make sure we're done with the gcs
|
||||
js.waitAndRelease(job);
|
||||
}
|
||||
|
||||
bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeNano) {
|
||||
assert_invariant(swapChain);
|
||||
|
||||
@@ -252,6 +295,11 @@ bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeN
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!vsyncSteadyClockTimeNano) {
|
||||
vsyncSteadyClockTimeNano = mVsyncSteadyClockTimeNano;
|
||||
mVsyncSteadyClockTimeNano = 0;
|
||||
}
|
||||
|
||||
// get the timestamp as soon as possible
|
||||
using namespace std::chrono;
|
||||
const steady_clock::time_point now{ steady_clock::now() };
|
||||
@@ -371,7 +419,7 @@ void FRenderer::endFrame() {
|
||||
}
|
||||
|
||||
// do this before engine.flush()
|
||||
engine.getResourceAllocator().gc();
|
||||
mResourceAllocator->gc();
|
||||
|
||||
// Run the component managers' GC in parallel
|
||||
// WARNING: while doing this we can't access any component manager
|
||||
@@ -459,6 +507,9 @@ void FRenderer::renderStandaloneView(FView const* view) {
|
||||
FILAMENT_CHECK_PRECONDITION(view->getRenderTarget())
|
||||
<< "View \"" << view->getName() << "\" must have a RenderTarget associated";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(!mSwapChain)
|
||||
<< "renderStandaloneView() must be called outside of beginFrame() / endFrame()";
|
||||
|
||||
if (UTILS_LIKELY(view->getScene())) {
|
||||
mPreviousRenderTargets.clear();
|
||||
mFrameId++;
|
||||
@@ -738,7 +789,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
|
||||
* Frame graph
|
||||
*/
|
||||
|
||||
FrameGraph fg(engine.getResourceAllocator(),
|
||||
FrameGraph fg(*mResourceAllocator,
|
||||
isProtectedContent ? FrameGraph::Mode::PROTECTED : FrameGraph::Mode::UNPROTECTED);
|
||||
auto& blackboard = fg.getBlackboard();
|
||||
|
||||
|
||||
@@ -29,9 +29,6 @@
|
||||
|
||||
#include "backend/DriverApiForward.h"
|
||||
|
||||
#include <fg/FrameGraphId.h>
|
||||
#include <fg/FrameGraphTexture.h>
|
||||
|
||||
#include <filament/Renderer.h>
|
||||
#include <filament/Viewport.h>
|
||||
|
||||
@@ -40,11 +37,25 @@
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Allocator.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <math/vec4.h>
|
||||
|
||||
#include <tsl/robin_set.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
class ResourceAllocator;
|
||||
|
||||
namespace backend {
|
||||
class Driver;
|
||||
} // namespace backend
|
||||
@@ -78,6 +89,11 @@ public:
|
||||
|
||||
void setPresentationTime(int64_t monotonic_clock_ns);
|
||||
|
||||
void setVsyncTime(uint64_t steadyClockTimeNano) noexcept;
|
||||
|
||||
// skip a frame
|
||||
void skipFrame(uint64_t vsyncSteadyClockTimeNano);
|
||||
|
||||
// start a frame
|
||||
bool beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeNano);
|
||||
|
||||
@@ -131,6 +147,14 @@ public:
|
||||
return mClearOptions;
|
||||
}
|
||||
|
||||
utils::FixedCapacityVector<Renderer::FrameInfo> getFrameInfoHistory(size_t historySize) const noexcept {
|
||||
return mFrameInfoManager.getFrameInfoHistory(historySize);
|
||||
}
|
||||
|
||||
size_t getMaxFrameHistorySize() const noexcept {
|
||||
return MAX_FRAMETIME_HISTORY;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Renderer;
|
||||
using Command = RenderPass::Command;
|
||||
@@ -187,6 +211,8 @@ private:
|
||||
backend::TargetBufferFlags mClearFlags{};
|
||||
tsl::robin_set<FRenderTarget*> mPreviousRenderTargets;
|
||||
std::function<void()> mBeginFrameInternal;
|
||||
uint64_t mVsyncSteadyClockTimeNano = 0;
|
||||
std::unique_ptr<ResourceAllocator> mResourceAllocator{};
|
||||
};
|
||||
|
||||
FILAMENT_DOWNCAST(Renderer)
|
||||
|
||||
@@ -248,7 +248,7 @@ void FScene::prepare(utils::JobSystem& js,
|
||||
|
||||
auto* renderableJob = jobs::parallel_for(js, rootJob,
|
||||
renderableInstances.data(), renderableInstances.size(),
|
||||
std::cref(renderableWork), jobs::CountSplitter<128, 5>());
|
||||
std::cref(renderableWork), jobs::CountSplitter<64>());
|
||||
|
||||
auto* lightJob = jobs::parallel_for(js, rootJob,
|
||||
lightInstances.data(), lightInstances.size(),
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "details/View.h"
|
||||
|
||||
#include "Culler.h"
|
||||
#include "FrameHistory.h"
|
||||
#include "Froxelizer.h"
|
||||
#include "RenderPrimitive.h"
|
||||
#include "ResourceAllocator.h"
|
||||
@@ -118,7 +119,7 @@ void FView::terminate(FEngine& engine) {
|
||||
DriverApi& driver = engine.getDriverApi();
|
||||
driver.destroyBufferObject(mLightUbh);
|
||||
driver.destroyBufferObject(mRenderableUbh);
|
||||
drainFrameHistory(engine);
|
||||
clearFrameHistory(engine);
|
||||
|
||||
ShadowMapManager::terminate(engine, mShadowMapManager);
|
||||
mPerViewUniforms.terminate(driver);
|
||||
@@ -162,7 +163,7 @@ void FView::setDynamicLightingOptions(float zLightNear, float zLightFar) noexcep
|
||||
}
|
||||
|
||||
float2 FView::updateScale(FEngine& engine,
|
||||
FrameInfo const& info,
|
||||
filament::details::FrameInfo const& info,
|
||||
Renderer::FrameRateOptions const& frameRateOptions,
|
||||
Renderer::DisplayInfo const& displayInfo) noexcept {
|
||||
|
||||
@@ -1007,20 +1008,29 @@ FrameGraphId<FrameGraphTexture> FView::renderShadowMaps(FEngine& engine, FrameGr
|
||||
|
||||
void FView::commitFrameHistory(FEngine& engine) noexcept {
|
||||
// Here we need to destroy resources in mFrameHistory.back()
|
||||
auto& disposer = engine.getResourceAllocatorDisposer();
|
||||
auto& frameHistory = mFrameHistory;
|
||||
|
||||
FrameHistoryEntry& last = frameHistory.back();
|
||||
last.taa.color.destroy(engine.getResourceAllocator());
|
||||
last.ssr.color.destroy(engine.getResourceAllocator());
|
||||
disposer.destroy(last.taa.color.handle);
|
||||
disposer.destroy(last.ssr.color.handle);
|
||||
last.taa.color.handle.clear();
|
||||
last.ssr.color.handle.clear();
|
||||
|
||||
// and then push the new history entry to the history stack
|
||||
frameHistory.commit();
|
||||
}
|
||||
|
||||
void FView::drainFrameHistory(FEngine& engine) noexcept {
|
||||
void FView::clearFrameHistory(FEngine& engine) noexcept {
|
||||
// make sure we free all resources in the history
|
||||
for (size_t i = 0; i < mFrameHistory.size(); ++i) {
|
||||
commitFrameHistory(engine);
|
||||
auto& disposer = engine.getResourceAllocatorDisposer();
|
||||
auto& frameHistory = mFrameHistory;
|
||||
for (size_t i = 0; i < frameHistory.size(); ++i) {
|
||||
FrameHistoryEntry& last = frameHistory[i];
|
||||
disposer.destroy(last.taa.color.handle);
|
||||
disposer.destroy(last.ssr.color.handle);
|
||||
last.taa.color.handle.clear();
|
||||
last.ssr.color.handle.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -283,7 +283,7 @@ public:
|
||||
}
|
||||
|
||||
math::float2 updateScale(FEngine& engine,
|
||||
FrameInfo const& info,
|
||||
details::FrameInfo const& info,
|
||||
Renderer::FrameRateOptions const& frameRateOptions,
|
||||
Renderer::DisplayInfo const& displayInfo) noexcept;
|
||||
|
||||
@@ -422,6 +422,10 @@ public:
|
||||
// (e.g.: after the FrameGraph execution).
|
||||
void commitFrameHistory(FEngine& engine) noexcept;
|
||||
|
||||
// Clean-up the whole history, free all resources. This is typically called when the View is
|
||||
// being terminated. Or we're changing Renderer.
|
||||
void clearFrameHistory(FEngine& engine) noexcept;
|
||||
|
||||
// create the picking query
|
||||
View::PickingQuery& pick(uint32_t x, uint32_t y, backend::CallbackHandler* handler,
|
||||
View::PickingQueryResultCallback callback) noexcept;
|
||||
@@ -485,10 +489,6 @@ private:
|
||||
Culler::result_type* visibleMask,
|
||||
size_t count);
|
||||
|
||||
// Clean-up the whole history, free all resources. This is typically called when the View is
|
||||
// being terminated.
|
||||
void drainFrameHistory(FEngine& engine) noexcept;
|
||||
|
||||
// we don't inline this one, because the function is quite large and there is not much to
|
||||
// gain from inlining.
|
||||
static FScene::RenderableSoa::iterator partition(
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "FrameGraphPass.h"
|
||||
#include "FrameGraphRenderPass.h"
|
||||
#include "FrameGraphTexture.h"
|
||||
#include "ResourceAllocator.h"
|
||||
|
||||
#include "details/Engine.h"
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ if (ANDROID)
|
||||
target_link_libraries(test_compiler PRIVATE utils)
|
||||
target_link_libraries(test_compiler PRIVATE EGL)
|
||||
target_link_libraries(test_compiler PRIVATE GLESv3)
|
||||
target_link_libraries(test_compiler PRIVATE bluevk)
|
||||
endif()
|
||||
|
||||
add_executable(test_material_parser
|
||||
|
||||
+67
-166
@@ -1,181 +1,82 @@
|
||||
/*
|
||||
* 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 <iostream>
|
||||
#include <utils/Hash.h>
|
||||
#include <tsl/robin_map.h>
|
||||
#include <bluevk/BlueVK.h>
|
||||
|
||||
#include <utils/Log.h>
|
||||
namespace {
|
||||
|
||||
#include <GLES3/gl3.h>
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace utils;
|
||||
using namespace std::literals;
|
||||
|
||||
class CompilerTest : public testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
EGLBoolean success;
|
||||
EGLint major, minor;
|
||||
dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
ASSERT_NE(dpy, EGL_NO_DISPLAY);
|
||||
|
||||
EGLBoolean const initialized = eglInitialize(dpy, &major, &minor);
|
||||
ASSERT_TRUE(initialized);
|
||||
|
||||
EGLint const contextAttribs[] = {
|
||||
EGL_CONTEXT_CLIENT_VERSION, 3,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
EGLint configsCount;
|
||||
EGLint configAttribs[] = {
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR, // 0
|
||||
EGL_RED_SIZE, 8, // 2
|
||||
EGL_GREEN_SIZE, 8, // 4
|
||||
EGL_BLUE_SIZE, 8, // 6
|
||||
EGL_NONE // 14
|
||||
};
|
||||
success = eglChooseConfig(dpy, configAttribs, &config, 1, &configsCount);
|
||||
ASSERT_TRUE(success);
|
||||
|
||||
context = eglCreateContext(dpy, config, EGL_NO_CONTEXT, contextAttribs);
|
||||
ASSERT_NE(context, EGL_NO_CONTEXT);
|
||||
|
||||
success = eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, context);
|
||||
ASSERT_TRUE(success);
|
||||
|
||||
ASSERT_EQ(eglGetError(), EGL_SUCCESS);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
eglDestroyContext(dpy, context);
|
||||
eglTerminate(dpy);
|
||||
}
|
||||
|
||||
private:
|
||||
EGLDisplay dpy;
|
||||
EGLContext context;
|
||||
EGLConfig config;
|
||||
struct T {
|
||||
};
|
||||
|
||||
TEST_F(CompilerTest, Simple) {
|
||||
auto shader = R"(
|
||||
#version 300 es
|
||||
void main()
|
||||
{
|
||||
})"sv;
|
||||
constexpr uint8_t const UNIQUE_DESCRIPTOR_SET_COUNT = 3;
|
||||
constexpr uint8_t const SHADER_TYPE_COUNT = 3;
|
||||
using Timestamp = uint64_t;
|
||||
using VulkanResourceAllocator = T;
|
||||
|
||||
const char* const src = shader.data();
|
||||
GLint const len = (GLint)shader.size();
|
||||
struct PushConstantKey {
|
||||
uint8_t stage;// We have one set of push constant per shader stage (fragment, vertex, etc).
|
||||
uint8_t size;
|
||||
};
|
||||
|
||||
GLuint const id = glCreateShader(GL_VERTEX_SHADER);
|
||||
EXPECT_EQ(glGetError(), GL_NO_ERROR);
|
||||
struct PipelineLayoutKey {
|
||||
using DescriptorSetLayoutArray = std::array<VkDescriptorSetLayout, UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||
DescriptorSetLayoutArray descSetLayouts = {}; // 8 * 3
|
||||
PushConstantKey pushConstant[SHADER_TYPE_COUNT] = {};// 2 * 3
|
||||
uint16_t padding = 0;
|
||||
};
|
||||
static_assert(sizeof(PipelineLayoutKey) == 32);
|
||||
|
||||
glShaderSource(id, 1, &src, &len);
|
||||
EXPECT_EQ(glGetError(), GL_NO_ERROR);
|
||||
using PipelineLayoutKeyHashFn = utils::hash::MurmurHashFn<PipelineLayoutKey>;
|
||||
struct PipelineLayoutKeyEqual {
|
||||
bool operator()(PipelineLayoutKey const& k1, PipelineLayoutKey const& k2) const {
|
||||
return 0 == memcmp((const void*) &k1, (const void*) &k2, sizeof(PipelineLayoutKey));
|
||||
}
|
||||
};
|
||||
|
||||
glCompileShader(id);
|
||||
EXPECT_EQ(glGetError(), GL_NO_ERROR);
|
||||
struct PipelineLayoutCacheEntry {
|
||||
VkPipelineLayout handle;
|
||||
Timestamp lastUsed;
|
||||
};
|
||||
|
||||
GLint result = 0;
|
||||
glGetShaderiv(id, GL_COMPILE_STATUS, &result);
|
||||
EXPECT_EQ(result, GL_TRUE);
|
||||
using PipelineLayoutMap = tsl::robin_map<PipelineLayoutKey, PipelineLayoutCacheEntry,
|
||||
PipelineLayoutKeyHashFn, PipelineLayoutKeyEqual>;
|
||||
|
||||
glDeleteShader(id);
|
||||
EXPECT_EQ(glGetError(), GL_NO_ERROR);
|
||||
class TestClass {
|
||||
public:
|
||||
VkDevice mDevice;
|
||||
VulkanResourceAllocator* mAllocator;
|
||||
Timestamp mTimestamp;
|
||||
PipelineLayoutMap layout;
|
||||
};
|
||||
|
||||
void insert(PipelineLayoutMap& layout, uint64_t val) {
|
||||
PipelineLayoutKey key{};
|
||||
key.descSetLayouts[0] = (VkDescriptorSetLayout) val;
|
||||
layout[key] = {(VkPipelineLayout) val, val};
|
||||
}
|
||||
|
||||
TEST_F(CompilerTest, CrashPVRUniFlexCompileToHw) {
|
||||
|
||||
// Some PowerVR driver crash with this shader
|
||||
|
||||
auto shader = R"(
|
||||
#version 300 es
|
||||
|
||||
layout(location = 0) in vec4 mesh_position;
|
||||
|
||||
layout(std140) uniform FrameUniforms {
|
||||
vec2 i;
|
||||
} frameUniforms;
|
||||
|
||||
void main() {
|
||||
gl_Position = mesh_position;
|
||||
gl_Position.z = dot(gl_Position.zw, frameUniforms.i);
|
||||
})"sv;
|
||||
|
||||
const char* const src = shader.data();
|
||||
GLint const len = (GLint)shader.size();
|
||||
|
||||
GLuint const id = glCreateShader(GL_VERTEX_SHADER);
|
||||
EXPECT_EQ(glGetError(), GL_NO_ERROR);
|
||||
|
||||
glShaderSource(id, 1, &src, &len);
|
||||
EXPECT_EQ(glGetError(), GL_NO_ERROR);
|
||||
|
||||
glCompileShader(id);
|
||||
EXPECT_EQ(glGetError(), GL_NO_ERROR);
|
||||
|
||||
GLint result = 0;
|
||||
glGetShaderiv(id, GL_COMPILE_STATUS, &result);
|
||||
EXPECT_EQ(result, GL_TRUE);
|
||||
|
||||
glDeleteShader(id);
|
||||
EXPECT_EQ(glGetError(), GL_NO_ERROR);
|
||||
}
|
||||
|
||||
TEST_F(CompilerTest, ConstParameters) {
|
||||
|
||||
// Some PowerVR driver fail to compile this shader
|
||||
|
||||
auto shader = R"(
|
||||
#version 300 es
|
||||
|
||||
highp mat3 m;
|
||||
void buggy(const mediump vec3 n) {
|
||||
m*n;
|
||||
}
|
||||
|
||||
void main() {
|
||||
})"sv;
|
||||
|
||||
const char* const src = shader.data();
|
||||
GLint const len = (GLint)shader.size();
|
||||
|
||||
GLuint const id = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
EXPECT_EQ(glGetError(), GL_NO_ERROR);
|
||||
|
||||
glShaderSource(id, 1, &src, &len);
|
||||
EXPECT_EQ(glGetError(), GL_NO_ERROR);
|
||||
|
||||
glCompileShader(id);
|
||||
EXPECT_EQ(glGetError(), GL_NO_ERROR);
|
||||
|
||||
GLint result = 0;
|
||||
glGetShaderiv(id, GL_COMPILE_STATUS, &result);
|
||||
EXPECT_EQ(result, GL_TRUE);
|
||||
|
||||
EXPECT_EQ(glGetError(), GL_NO_ERROR);
|
||||
|
||||
glDeleteShader(id);
|
||||
EXPECT_EQ(glGetError(), GL_NO_ERROR);
|
||||
}
|
||||
}// namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
constexpr uint32_t START = 1000;
|
||||
TestClass c;
|
||||
auto& layout = c.layout;
|
||||
|
||||
uint64_t count = 0;
|
||||
for (; count < 20; count++) {
|
||||
insert(layout, count + START);
|
||||
}
|
||||
|
||||
for (uint64_t t = 0; t < count; ++t) {
|
||||
PipelineLayoutKey key = {};
|
||||
key.descSetLayouts[0] = (VkDescriptorSetLayout)(START + t);
|
||||
|
||||
if (layout.find(key) != layout.end()) {
|
||||
std::cout << "found " << t << std::endl;
|
||||
} else {
|
||||
std::cout << "not found" << t << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@ using namespace backend;
|
||||
|
||||
class MockResourceAllocator : public ResourceAllocatorInterface {
|
||||
uint32_t handle = 0;
|
||||
struct MockDisposer : public ResourceAllocatorDisposerInterface {
|
||||
void destroy(backend::TextureHandle) noexcept override {}
|
||||
} disposer;
|
||||
|
||||
public:
|
||||
backend::RenderTargetHandle createRenderTarget(const char* name,
|
||||
backend::TargetBufferFlags targetBufferFlags,
|
||||
@@ -60,6 +64,10 @@ public:
|
||||
|
||||
void destroyTexture(backend::TextureHandle h) noexcept override {
|
||||
}
|
||||
|
||||
ResourceAllocatorDisposerInterface& getDisposer() noexcept override {
|
||||
return disposer;
|
||||
}
|
||||
};
|
||||
|
||||
class FrameGraphTest : public testing::Test {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Filament"
|
||||
spec.version = "1.53.0"
|
||||
spec.version = "1.53.2"
|
||||
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
|
||||
spec.homepage = "https://google.github.io/filament"
|
||||
spec.authors = "Google LLC."
|
||||
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
|
||||
spec.platform = :ios, "11.0"
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.53.0/filament-v1.53.0-ios.tgz" }
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.53.2/filament-v1.53.2-ios.tgz" }
|
||||
|
||||
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
|
||||
spec.pod_target_xcconfig = {
|
||||
|
||||
@@ -109,6 +109,7 @@ public:
|
||||
vec4 groundPlane;
|
||||
RayCallback raycastCallback;
|
||||
void* raycastUserdata;
|
||||
bool panning = true;
|
||||
};
|
||||
|
||||
struct Builder {
|
||||
@@ -143,6 +144,8 @@ public:
|
||||
Builder& groundPlane(FLOAT a, FLOAT b, FLOAT c, FLOAT d); //! Plane equation used as a raycast fallback
|
||||
Builder& raycastCallback(RayCallback cb, void* userdata); //! Raycast function for accurate grab-and-pan
|
||||
|
||||
Builder& panning(bool enabled); //! Sets whether panning is enabled
|
||||
|
||||
/**
|
||||
* Creates a new camera manipulator, either ORBIT, MAP, or FREE_FLIGHT.
|
||||
*
|
||||
|
||||
@@ -144,6 +144,14 @@ Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::raycastCallback(RayCal
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
template <typename FLOAT> typename
|
||||
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::panning(bool enabled) {
|
||||
details.panning = enabled;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename FLOAT>
|
||||
Manipulator<FLOAT>* Manipulator<FLOAT>::Builder::build(Mode mode) {
|
||||
switch (mode) {
|
||||
|
||||
@@ -69,7 +69,7 @@ public:
|
||||
}
|
||||
|
||||
void grabBegin(int x, int y, bool strafe) override {
|
||||
mGrabState = strafe ? PANNING : ORBITING;
|
||||
mGrabState = strafe && Base::mProps.panning ? PANNING : ORBITING;
|
||||
mGrabPivot = mPivot;
|
||||
mGrabEye = Base::mEye;
|
||||
mGrabTarget = Base::mTarget;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user