Compare commits
75
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3fc8e3796 | ||
|
|
5e7106b521 | ||
|
|
30387af61c | ||
|
|
1c2ffc9ed4 | ||
|
|
574518ea74 | ||
|
|
d5a274fa25 | ||
|
|
3351db1178 | ||
|
|
b70aa43727 | ||
|
|
1f9a11802d | ||
|
|
89835a7a67 | ||
|
|
28ef805e5d | ||
|
|
6c0bd360b3 | ||
|
|
063affb612 | ||
|
|
9c857f64ae | ||
|
|
5966b5dd8f | ||
|
|
1795c40591 | ||
|
|
26f4239d8c | ||
|
|
ad29b9c70a | ||
|
|
1c817026f2 | ||
|
|
44a954b559 | ||
|
|
cdd0147a17 | ||
|
|
9de29a475e | ||
|
|
7f97363d9c | ||
|
|
d88b4c1bc3 | ||
|
|
3badf9db18 | ||
|
|
929f793cf3 | ||
|
|
06bffaa650 | ||
|
|
2e2f111435 | ||
|
|
06f9626429 | ||
|
|
fa0e20a699 | ||
|
|
59593830e5 | ||
|
|
112f9d742c | ||
|
|
8ba20eb03c | ||
|
|
1c0370d5d7 | ||
|
|
56f714633d | ||
|
|
f0bc338c80 | ||
|
|
9750ddb9db | ||
|
|
ec2ee9db7a | ||
|
|
1ce1e335a0 | ||
|
|
5fe356e446 | ||
|
|
14ddadd2f6 | ||
|
|
a2f5b635d1 | ||
|
|
324dcd3c86 | ||
|
|
4125802644 | ||
|
|
2d21fcbe55 | ||
|
|
3f4dac6248 | ||
|
|
40ae416715 | ||
|
|
b54a2046cb | ||
|
|
8add6ae1ac | ||
|
|
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 |
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.53.1'
|
||||
implementation 'com.google.android.filament:filament-android:1.54.0'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
||||
iOS projects can use CocoaPods to install the latest release:
|
||||
|
||||
```shell
|
||||
pod 'Filament', '~> 1.53.1'
|
||||
pod 'Filament', '~> 1.54.0'
|
||||
```
|
||||
|
||||
### Snapshots
|
||||
|
||||
@@ -7,6 +7,26 @@ 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.54.1
|
||||
|
||||
|
||||
## v1.54.0
|
||||
|
||||
- materials: add a new `stereoscopicType` material parameter. [⚠️ **New Material Version**]
|
||||
- Fix a crash when compiling shaders on IMG devices
|
||||
|
||||
## v1.53.5
|
||||
|
||||
- engine: Fix bug causing certain sampler parameters to not be applied correctly in GLES 2.0 and on
|
||||
certain GLES 3.0 drivers.
|
||||
|
||||
## v1.53.4
|
||||
|
||||
|
||||
## v1.53.3
|
||||
|
||||
- Add drag and drop support for IBL files for desktop gltf_viewer.
|
||||
|
||||
## v1.53.2
|
||||
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
+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);
|
||||
|
||||
@@ -297,6 +297,20 @@ public class Renderer {
|
||||
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
|
||||
@@ -716,6 +730,7 @@ 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.1
|
||||
VERSION_NAME=1.54.0
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
+11
-2
@@ -1308,7 +1308,12 @@ Description
|
||||
declare a variable called `eyeDirection` you can access it in the fragment shader using
|
||||
`variable_eyeDirection`. In the vertex shader, the interpolant name is simply a member of
|
||||
the `MaterialVertexInputs` structure (`material.eyeDirection` in your example). Each
|
||||
interpolant is of type `float4` (`vec4`) in the shaders.
|
||||
interpolant is of type `float4` (`vec4`) in the shaders. By default the precision of the
|
||||
interpolant is `highp` in *both* the vertex and fragment shaders.
|
||||
An alternate syntax can be used to specify both the name and precision of the interpolant.
|
||||
In this case the specified precision is used as-is in both fragment and vertex stages, in
|
||||
particular if `default` is specified the default precision is used is the fragment shader
|
||||
(`mediump`) and in the vertex shader (`highp`).
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
|
||||
material {
|
||||
@@ -1320,7 +1325,11 @@ material {
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
eyeDirection
|
||||
eyeDirection,
|
||||
{
|
||||
name : eyeColor,
|
||||
precision : medium
|
||||
}
|
||||
],
|
||||
vertexDomain : device,
|
||||
depthWrite : false,
|
||||
|
||||
@@ -1058,7 +1058,7 @@ struct RasterState {
|
||||
bool inverseFrontFaces : 1; // 31
|
||||
|
||||
//! padding, must be 0
|
||||
uint8_t padding : 1; // 32
|
||||
bool depthClamp : 1; // 32
|
||||
};
|
||||
uint32_t u = 0;
|
||||
};
|
||||
@@ -1244,7 +1244,7 @@ enum class Workaround : uint16_t {
|
||||
ADRENO_UNIFORM_ARRAY_CRASH,
|
||||
// Workaround a Metal pipeline compilation error with the message:
|
||||
// "Could not statically determine the target of a texture". See light_indirect.fs
|
||||
A8X_STATIC_TEXTURE_TARGET_ERROR,
|
||||
METAL_STATIC_TEXTURE_TARGET_ERROR,
|
||||
// Adreno drivers sometimes aren't able to blit into a layer of a texture array.
|
||||
DISABLE_BLIT_INTO_TEXTURE_ARRAY,
|
||||
// Multiple workarounds needed for PowerVR GPUs
|
||||
|
||||
@@ -305,6 +305,7 @@ DECL_DRIVER_API_SYNCHRONOUS_0(bool, isParallelShaderCompileSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isDepthStencilResolveSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isDepthStencilBlitSupported, backend::TextureFormat, format)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isProtectedTexturesSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isDepthClampSupported)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(uint8_t, getMaxDrawBuffers)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getMaxUniformBufferSize)
|
||||
DECL_DRIVER_API_SYNCHRONOUS_0(math::float2, getClipSpaceParams)
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
#define HandleAllocatorGL HandleAllocator<32, 64, 136> // ~4520 / pool / MiB
|
||||
#define HandleAllocatorVK HandleAllocator<64, 160, 312> // ~1820 / pool / MiB
|
||||
#define HandleAllocatorMTL HandleAllocator<32, 48, 552> // ~1660 / pool / MiB
|
||||
#define HandleAllocatorMTL HandleAllocator<32, 64, 552> // ~1660 / pool / MiB
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -101,9 +101,8 @@ void CommandBufferQueue::flush() noexcept {
|
||||
size_t const used = std::distance(
|
||||
static_cast<char const*>(begin), static_cast<char const*>(end));
|
||||
|
||||
|
||||
std::unique_lock<utils::Mutex> lock(mLock);
|
||||
mCommandBuffersToExecute.push_back({ begin, end });
|
||||
mCondition.notify_one();
|
||||
|
||||
// circular buffer is too small, we corrupted the stream
|
||||
FILAMENT_CHECK_POSTCONDITION(used <= mFreeSpace) <<
|
||||
@@ -112,10 +111,12 @@ void CommandBufferQueue::flush() noexcept {
|
||||
"Space used at this time: " << used <<
|
||||
" bytes, overflow: " << used - mFreeSpace << " bytes";
|
||||
|
||||
// wait until there is enough space in the buffer
|
||||
mFreeSpace -= used;
|
||||
if (UTILS_UNLIKELY(mFreeSpace < requiredSize)) {
|
||||
mCommandBuffersToExecute.push_back({ begin, end });
|
||||
mCondition.notify_one();
|
||||
|
||||
// wait until there is enough space in the buffer
|
||||
if (UTILS_UNLIKELY(mFreeSpace < requiredSize)) {
|
||||
|
||||
#ifndef NDEBUG
|
||||
size_t const totalUsed = circularBuffer.size() - mFreeSpace;
|
||||
@@ -153,8 +154,10 @@ std::vector<CommandBufferQueue::Range> CommandBufferQueue::waitForCommands() con
|
||||
}
|
||||
|
||||
void CommandBufferQueue::releaseBuffer(CommandBufferQueue::Range const& buffer) {
|
||||
size_t const used = std::distance(
|
||||
static_cast<char const*>(buffer.begin), static_cast<char const*>(buffer.end));
|
||||
std::lock_guard<utils::Mutex> const lock(mLock);
|
||||
mFreeSpace += uintptr_t(buffer.end) - uintptr_t(buffer.begin);
|
||||
mFreeSpace += used;
|
||||
mCondition.notify_one();
|
||||
}
|
||||
|
||||
|
||||
@@ -145,6 +145,7 @@ std::pair<id<MTLBuffer>, size_t> MetalBumpAllocator::allocateStagingArea(size_t
|
||||
// We're finished with the current allocation.
|
||||
mCurrentUploadBuffer = { [mDevice newBufferWithLength:mCapacity options:MTLStorageModeShared],
|
||||
TrackedMetalBuffer::Type::STAGING };
|
||||
mHead = size;
|
||||
|
||||
return { mCurrentUploadBuffer.get(), 0 };
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ struct MetalContext {
|
||||
} highestSupportedGpuFamily;
|
||||
|
||||
struct {
|
||||
bool a8xStaticTextureTargetError;
|
||||
bool staticTextureTargetError;
|
||||
} bugs;
|
||||
|
||||
// sampleCountLookup[requestedSamples] gives a <= sample count supported by the device.
|
||||
@@ -112,6 +112,7 @@ struct MetalContext {
|
||||
std::array<BufferState, MAX_SSBO_COUNT> ssboState;
|
||||
CullModeStateTracker cullModeState;
|
||||
WindingStateTracker windingState;
|
||||
DepthClampStateTracker depthClampState;
|
||||
Handle<HwRenderPrimitive> currentRenderPrimitive;
|
||||
|
||||
// State caches.
|
||||
|
||||
@@ -53,14 +53,14 @@ Driver* MetalDriverFactory::create(MetalPlatform* const platform, const Platform
|
||||
// MetalRenderPrimitive : 24 many
|
||||
// MetalVertexBuffer : 32 moderate
|
||||
// -- less than or equal 32 bytes
|
||||
// MetalIndexBuffer : 40 moderate
|
||||
// MetalFence : 48 few
|
||||
// MetalBufferObject : 48 many
|
||||
// -- less than or equal 48 bytes
|
||||
// MetalIndexBuffer : 56 moderate
|
||||
// MetalBufferObject : 64 many
|
||||
// -- less than or equal 64 bytes
|
||||
// MetalSamplerGroup : 112 few
|
||||
// MetalProgram : 152 moderate
|
||||
// MetalTexture : 152 moderate
|
||||
// MetalSwapChain : 184 few
|
||||
// MetalSwapChain : 208 few
|
||||
// MetalRenderTarget : 272 few
|
||||
// MetalVertexBufferInfo : 552 moderate
|
||||
// -- less than or equal to 552 bytes
|
||||
@@ -162,8 +162,10 @@ MetalDriver::MetalDriver(MetalPlatform* platform, const Platform::DriverConfig&
|
||||
sc[s] = [mContext->device supportsTextureSampleCount:s] ? s : sc[s - 1];
|
||||
}
|
||||
|
||||
mContext->bugs.a8xStaticTextureTargetError =
|
||||
[mContext->device.name containsString:@"Apple A8X GPU"];
|
||||
mContext->bugs.staticTextureTargetError =
|
||||
[mContext->device.name containsString:@"Apple A8X GPU"] ||
|
||||
[mContext->device.name containsString:@"Apple A8 GPU"] ||
|
||||
[mContext->device.name containsString:@"Apple A7 GPU"];
|
||||
|
||||
mContext->commandQueue = mPlatform.createCommandQueue(mContext->device);
|
||||
mContext->pipelineStateCache.setDevice(mContext->device);
|
||||
@@ -832,6 +834,10 @@ bool MetalDriver::isProtectedTexturesSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MetalDriver::isDepthClampSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MetalDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
switch (workaround) {
|
||||
case Workaround::SPLIT_EASU:
|
||||
@@ -840,8 +846,8 @@ bool MetalDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
return true;
|
||||
case Workaround::ADRENO_UNIFORM_ARRAY_CRASH:
|
||||
return false;
|
||||
case Workaround::A8X_STATIC_TEXTURE_TARGET_ERROR:
|
||||
return mContext->bugs.a8xStaticTextureTargetError;
|
||||
case Workaround::METAL_STATIC_TEXTURE_TARGET_ERROR:
|
||||
return mContext->bugs.staticTextureTargetError;
|
||||
case Workaround::DISABLE_BLIT_INTO_TEXTURE_ARRAY:
|
||||
return false;
|
||||
default:
|
||||
@@ -1749,6 +1755,13 @@ void MetalDriver::bindPipeline(PipelineState const& ps) {
|
||||
[mContext->currentRenderPassEncoder setFrontFacingWinding:winding];
|
||||
}
|
||||
|
||||
// depth clip mode
|
||||
MTLDepthClipMode depthClipMode = rs.depthClamp ? MTLDepthClipModeClamp : MTLDepthClipModeClip;
|
||||
mContext->depthClampState.updateState(depthClipMode);
|
||||
if (mContext->depthClampState.stateChanged()) {
|
||||
[mContext->currentRenderPassEncoder setDepthClipMode:depthClipMode];
|
||||
}
|
||||
|
||||
// Set the depth-stencil state, if a state change is needed.
|
||||
DepthStencilState depthState;
|
||||
if (depthAttachment) {
|
||||
|
||||
@@ -556,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:
|
||||
@@ -572,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];
|
||||
@@ -586,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
|
||||
@@ -596,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 &&
|
||||
|
||||
@@ -382,6 +382,7 @@ using SamplerStateCache = StateCache<SamplerState, id<MTLSamplerState>, SamplerS
|
||||
|
||||
using CullModeStateTracker = StateTracker<MTLCullMode>;
|
||||
using WindingStateTracker = StateTracker<MTLWinding>;
|
||||
using DepthClampStateTracker = StateTracker<MTLDepthClipMode>;
|
||||
|
||||
// Argument encoder
|
||||
|
||||
|
||||
@@ -202,6 +202,10 @@ bool NoopDriver::isProtectedTexturesSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NoopDriver::isDepthClampSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NoopDriver::isWorkaroundNeeded(Workaround) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -676,6 +676,7 @@ void OpenGLContext::initExtensionsGLES(Extensions* ext, GLint major, GLint minor
|
||||
#ifndef __EMSCRIPTEN__
|
||||
ext->EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
|
||||
#endif
|
||||
ext->EXT_depth_clamp = exts.has("GL_EXT_depth_clamp"sv);
|
||||
ext->EXT_discard_framebuffer = exts.has("GL_EXT_discard_framebuffer"sv);
|
||||
#ifndef __EMSCRIPTEN__
|
||||
ext->EXT_disjoint_timer_query = exts.has("GL_EXT_disjoint_timer_query"sv);
|
||||
@@ -746,6 +747,7 @@ void OpenGLContext::initExtensionsGL(Extensions* ext, GLint major, GLint minor)
|
||||
ext->EXT_color_buffer_half_float = true; // Assumes core profile.
|
||||
ext->EXT_clip_cull_distance = true;
|
||||
ext->EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
|
||||
ext->EXT_depth_clamp = true;
|
||||
ext->EXT_discard_framebuffer = false;
|
||||
ext->EXT_disjoint_timer_query = true;
|
||||
ext->EXT_multisampled_render_to_texture = false;
|
||||
|
||||
@@ -220,8 +220,9 @@ public:
|
||||
bool EXT_color_buffer_float;
|
||||
bool EXT_color_buffer_half_float;
|
||||
bool EXT_debug_marker;
|
||||
bool EXT_disjoint_timer_query;
|
||||
bool EXT_depth_clamp;
|
||||
bool EXT_discard_framebuffer;
|
||||
bool EXT_disjoint_timer_query;
|
||||
bool EXT_multisampled_render_to_texture2;
|
||||
bool EXT_multisampled_render_to_texture;
|
||||
bool EXT_protected_textures;
|
||||
@@ -239,10 +240,10 @@ public:
|
||||
bool KHR_parallel_shader_compile;
|
||||
bool KHR_texture_compression_astc_hdr;
|
||||
bool KHR_texture_compression_astc_ldr;
|
||||
bool OES_depth_texture;
|
||||
bool OES_depth24;
|
||||
bool OES_packed_depth_stencil;
|
||||
bool OES_EGL_image_external_essl3;
|
||||
bool OES_depth24;
|
||||
bool OES_depth_texture;
|
||||
bool OES_packed_depth_stencil;
|
||||
bool OES_rgb8_rgba8;
|
||||
bool OES_standard_derivatives;
|
||||
bool OES_texture_npot;
|
||||
@@ -627,6 +628,7 @@ constexpr size_t OpenGLContext::getIndexForCap(GLenum cap) noexcept { //NOLINT
|
||||
#ifdef BACKEND_OPENGL_VERSION_GL
|
||||
case GL_PROGRAM_POINT_SIZE: index = 10; break;
|
||||
#endif
|
||||
case GL_DEPTH_CLAMP: index = 11; break;
|
||||
default: break;
|
||||
}
|
||||
assert_invariant(index < state.enables.caps.size());
|
||||
|
||||
@@ -83,12 +83,20 @@
|
||||
#define HAS_MAPBUFFERS 1
|
||||
#endif
|
||||
|
||||
#define DEBUG_MARKER_NONE 0x00 // no debug marker
|
||||
#define DEBUG_MARKER_OPENGL 0x01 // markers in the gl command queue (req. driver support)
|
||||
#define DEBUG_MARKER_BACKEND 0x02 // markers on the backend side (systrace)
|
||||
#define DEBUG_MARKER_ALL 0x03 // all markers
|
||||
#define DEBUG_GROUP_MARKER_NONE 0x00 // no debug marker
|
||||
#define DEBUG_GROUP_MARKER_OPENGL 0x01 // markers in the gl command queue (req. driver support)
|
||||
#define DEBUG_GROUP_MARKER_BACKEND 0x02 // markers on the backend side (systrace)
|
||||
#define DEBUG_GROUP_MARKER_ALL 0x03 // all markers
|
||||
|
||||
// set to the desired debug marker level
|
||||
#define DEBUG_MARKER_NONE 0x00 // no debug marker
|
||||
#define DEBUG_MARKER_OPENGL 0x01 // markers in the gl command queue (req. driver support)
|
||||
#define DEBUG_MARKER_BACKEND 0x02 // markers on the backend side (systrace)
|
||||
#define DEBUG_MARKER_ALL 0x03 // all markers
|
||||
|
||||
// set to the desired debug marker level (for user markers [default: All])
|
||||
#define DEBUG_GROUP_MARKER_LEVEL DEBUG_GROUP_MARKER_ALL
|
||||
|
||||
// set to the desired debug level (for internal debugging [Default: None])
|
||||
#define DEBUG_MARKER_LEVEL DEBUG_MARKER_NONE
|
||||
|
||||
#if DEBUG_MARKER_LEVEL > DEBUG_MARKER_NONE
|
||||
@@ -196,11 +204,37 @@ Driver* OpenGLDriver::create(OpenGLPlatform* const platform,
|
||||
|
||||
OpenGLDriver::DebugMarker::DebugMarker(OpenGLDriver& driver, const char* string) noexcept
|
||||
: driver(driver) {
|
||||
driver.pushGroupMarker(string, strlen(string));
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_EXT_debug_marker
|
||||
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_OPENGL
|
||||
if (UTILS_LIKELY(driver.getContext().ext.EXT_debug_marker)) {
|
||||
glPushGroupMarkerEXT(GLsizei(strlen(string)), string);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_BACKEND
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_NAME_BEGIN(string);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
OpenGLDriver::DebugMarker::~DebugMarker() noexcept {
|
||||
driver.popGroupMarker();
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_EXT_debug_marker
|
||||
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_OPENGL
|
||||
if (UTILS_LIKELY(driver.getContext().ext.EXT_debug_marker)) {
|
||||
glPopGroupMarkerEXT();
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_BACKEND
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_NAME_END();
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
@@ -417,6 +451,14 @@ void OpenGLDriver::setRasterState(RasterState rs) noexcept {
|
||||
} else {
|
||||
gl.disable(GL_SAMPLE_ALPHA_TO_COVERAGE);
|
||||
}
|
||||
|
||||
if (gl.ext.EXT_depth_clamp) {
|
||||
if (rs.depthClamp) {
|
||||
gl.enable(GL_DEPTH_CLAMP);
|
||||
} else {
|
||||
gl.disable(GL_DEPTH_CLAMP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDriver::setStencilState(StencilState ss) noexcept {
|
||||
@@ -2085,6 +2127,10 @@ bool OpenGLDriver::isProtectedTexturesSupported() {
|
||||
return getContext().ext.EXT_protected_textures;
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isDepthClampSupported() {
|
||||
return getContext().ext.EXT_depth_clamp;
|
||||
}
|
||||
|
||||
bool OpenGLDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
switch (workaround) {
|
||||
case Workaround::SPLIT_EASU:
|
||||
@@ -2341,6 +2387,15 @@ void OpenGLDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
|
||||
GLTexture const* const t = handle_cast<const GLTexture*>(th);
|
||||
assert_invariant(t);
|
||||
|
||||
if (UTILS_UNLIKELY(es2)
|
||||
#if defined(GL_EXT_texture_filter_anisotropic)
|
||||
|| UTILS_UNLIKELY(anisotropyWorkaround)
|
||||
#endif
|
||||
) {
|
||||
// We must set texture parameters on the texture itself.
|
||||
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
|
||||
}
|
||||
|
||||
SamplerParams params = pSamplers[i].s;
|
||||
if (UTILS_UNLIKELY(t->target == SamplerType::SAMPLER_EXTERNAL)) {
|
||||
// From OES_EGL_image_external spec:
|
||||
@@ -3151,14 +3206,14 @@ void OpenGLDriver::insertEventMarker(char const* string, uint32_t len) {
|
||||
void OpenGLDriver::pushGroupMarker(char const* string, uint32_t len) {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_EXT_debug_marker
|
||||
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_OPENGL
|
||||
#if DEBUG_GROUP_MARKER_LEVEL & DEBUG_GROUP_MARKER_OPENGL
|
||||
if (UTILS_LIKELY(mContext.ext.EXT_debug_marker)) {
|
||||
glPushGroupMarkerEXT(GLsizei(len ? len : strlen(string)), string);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_BACKEND
|
||||
#if DEBUG_GROUP_MARKER_LEVEL & DEBUG_GROUP_MARKER_BACKEND
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_NAME_BEGIN(string);
|
||||
#endif
|
||||
@@ -3168,14 +3223,14 @@ void OpenGLDriver::pushGroupMarker(char const* string, uint32_t len) {
|
||||
void OpenGLDriver::popGroupMarker(int) {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifdef GL_EXT_debug_marker
|
||||
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_OPENGL
|
||||
#if DEBUG_GROUP_MARKER_LEVEL & DEBUG_GROUP_MARKER_OPENGL
|
||||
if (UTILS_LIKELY(mContext.ext.EXT_debug_marker)) {
|
||||
glPopGroupMarkerEXT();
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_BACKEND
|
||||
#if DEBUG_GROUP_MARKER_LEVEL & DEBUG_GROUP_MARKER_BACKEND
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_NAME_END();
|
||||
#endif
|
||||
@@ -3881,6 +3936,7 @@ void OpenGLDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
}
|
||||
|
||||
void OpenGLDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
|
||||
DEBUG_MARKER()
|
||||
GLRenderPrimitive const* const rp = mBoundRenderPrimitive;
|
||||
if (UTILS_UNLIKELY(!rp || !mValidProgram)) {
|
||||
return;
|
||||
@@ -3902,6 +3958,7 @@ void OpenGLDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins
|
||||
}
|
||||
|
||||
void OpenGLDriver::draw2GLES2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
|
||||
DEBUG_MARKER()
|
||||
GLRenderPrimitive const* const rp = mBoundRenderPrimitive;
|
||||
if (UTILS_UNLIKELY(!rp || !mValidProgram)) {
|
||||
return;
|
||||
@@ -3922,6 +3979,7 @@ void OpenGLDriver::draw2GLES2(uint32_t indexOffset, uint32_t indexCount, uint32_
|
||||
}
|
||||
|
||||
void OpenGLDriver::scissor(Viewport scissor) {
|
||||
DEBUG_MARKER()
|
||||
setScissor(scissor);
|
||||
}
|
||||
|
||||
@@ -3941,6 +3999,7 @@ void OpenGLDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph,
|
||||
}
|
||||
|
||||
void OpenGLDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
|
||||
DEBUG_MARKER()
|
||||
getShaderCompilerService().tick();
|
||||
|
||||
OpenGLProgram* const p = handle_cast<OpenGLProgram*>(program);
|
||||
|
||||
@@ -81,7 +81,7 @@ public:
|
||||
const Platform::DriverConfig& driverConfig) noexcept;
|
||||
|
||||
class DebugMarker {
|
||||
OpenGLDriver& driver;
|
||||
UTILS_UNUSED OpenGLDriver& driver;
|
||||
public:
|
||||
DebugMarker(OpenGLDriver& driver, const char* string) noexcept;
|
||||
~DebugMarker() noexcept;
|
||||
|
||||
@@ -143,21 +143,25 @@ void TimerQueryNativeFactory::endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQ
|
||||
|
||||
driver.runEveryNowAndThen([&context = mContext, weak]() -> bool {
|
||||
auto state = weak.lock();
|
||||
if (state) {
|
||||
GLuint available = 0;
|
||||
context.procs.getQueryObjectuiv(state->gl.query, GL_QUERY_RESULT_AVAILABLE, &available);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
if (!available) {
|
||||
// we need to try this one again later
|
||||
return false;
|
||||
}
|
||||
GLuint64 elapsedTime = 0;
|
||||
// we won't end-up here if we're on ES and don't have GL_EXT_disjoint_timer_query
|
||||
context.procs.getQueryObjectui64v(state->gl.query, GL_QUERY_RESULT, &elapsedTime);
|
||||
state->elapsed.store((int64_t)elapsedTime, std::memory_order_relaxed);
|
||||
} else {
|
||||
state->elapsed.store(int64_t(TimerQueryResult::ERROR), std::memory_order_relaxed);
|
||||
if (!state) {
|
||||
// The timer query state has been destroyed on the way, very likely due to the IBL
|
||||
// prefilter context destruction. We still return true to get this element removed from
|
||||
// the query list.
|
||||
return true;
|
||||
}
|
||||
|
||||
GLuint available = 0;
|
||||
context.procs.getQueryObjectuiv(state->gl.query, GL_QUERY_RESULT_AVAILABLE, &available);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
if (!available) {
|
||||
// we need to try this one again later
|
||||
return false;
|
||||
}
|
||||
GLuint64 elapsedTime = 0;
|
||||
// we won't end-up here if we're on ES and don't have GL_EXT_disjoint_timer_query
|
||||
context.procs.getQueryObjectui64v(state->gl.query, GL_QUERY_RESULT, &elapsedTime);
|
||||
state->elapsed.store((int64_t)elapsedTime, std::memory_order_relaxed);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -26,16 +26,28 @@
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/CString.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/JobSystem.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/ostream.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/Systrace.h>
|
||||
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
using namespace utils;
|
||||
@@ -476,6 +488,12 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
|
||||
// check status of program linking and shader compilation, logs error and free all resources
|
||||
// in case of error.
|
||||
bool const success = checkProgramStatus(token);
|
||||
|
||||
// Unless we have matdbg, we panic if a program is invalid. Otherwise, we'd get a UB.
|
||||
// The compilation error has been logged to log.e by this point.
|
||||
FILAMENT_CHECK_POSTCONDITION(FILAMENT_ENABLE_MATDBG || success)
|
||||
<< "OpenGL program " << token->name.c_str_safe() << " failed to link or compile";
|
||||
|
||||
if (UTILS_LIKELY(success)) {
|
||||
program = token->gl.program;
|
||||
// no need to keep the shaders around
|
||||
@@ -579,24 +597,31 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context,
|
||||
version = "#version 310 es\n";
|
||||
}
|
||||
|
||||
const std::array<const char*, 5> sources = {
|
||||
version.data(),
|
||||
prolog.data(),
|
||||
specializationConstantString.c_str(),
|
||||
packingFunctions.data(),
|
||||
body.data()
|
||||
std::array<std::string_view, 5> sources = {
|
||||
version,
|
||||
prolog,
|
||||
specializationConstantString,
|
||||
packingFunctions,
|
||||
{ body.data(), body.size() - 1 } // null-terminated
|
||||
};
|
||||
|
||||
const std::array<GLint, 5> lengths = {
|
||||
(GLint)version.length(),
|
||||
(GLint)prolog.length(),
|
||||
(GLint)specializationConstantString.length(),
|
||||
(GLint)packingFunctions.length(),
|
||||
(GLint)body.length() - 1 // null terminated
|
||||
};
|
||||
// Some of the sources may be zero-length. Remove them as to avoid passing lengths of
|
||||
// zero to glShaderSource(). glShaderSource should work with lengths of zero, but some
|
||||
// drivers instead interpret zero as a sentinel for a null-terminated string.
|
||||
auto partitionPoint = std::stable_partition(
|
||||
sources.begin(), sources.end(), [](std::string_view s) { return !s.empty(); });
|
||||
size_t count = std::distance(sources.begin(), partitionPoint);
|
||||
|
||||
std::array<const char*, 5> shaderStrings;
|
||||
std::array<GLint, 5> lengths;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
shaderStrings[i] = sources[i].data();
|
||||
lengths[i] = sources[i].size();
|
||||
}
|
||||
|
||||
GLuint const shaderId = glCreateShader(glShaderType);
|
||||
glShaderSource(shaderId, sources.size(), sources.data(), lengths.data());
|
||||
glShaderSource(shaderId, count, shaderStrings.data(), lengths.data());
|
||||
|
||||
glCompileShader(shaderId);
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -785,25 +810,24 @@ mediump vec4 unpackSnorm4x8(highp uint v) {
|
||||
// - extensions
|
||||
// - everything else
|
||||
std::array<std::string_view, 3> ShaderCompilerService::splitShaderSource(std::string_view source) noexcept {
|
||||
auto start = source.find("#version");
|
||||
assert_invariant(start != std::string_view::npos);
|
||||
auto version_start = source.find("#version");
|
||||
assert_invariant(version_start != std::string_view::npos);
|
||||
|
||||
auto version_eol = source.find('\n', start) + 1;
|
||||
auto version_eol = source.find('\n', version_start) + 1;
|
||||
assert_invariant(version_eol != std::string_view::npos);
|
||||
|
||||
auto pos = source.rfind("\n#extension");
|
||||
if (pos == std::string_view::npos) {
|
||||
pos = version_eol;
|
||||
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, version_eol - start);
|
||||
std::string_view const prolog = source.substr(version_eol, eol - version_eol);
|
||||
std::string_view const body = source.substr(eol, source.length() - eol);
|
||||
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 };
|
||||
}
|
||||
|
||||
|
||||
@@ -201,6 +201,12 @@ using namespace glext;
|
||||
# define GL_CLIP_DISTANCE1 0x3001
|
||||
#endif
|
||||
|
||||
#if defined(GL_EXT_depth_clamp)
|
||||
# define GL_DEPTH_CLAMP GL_DEPTH_CLAMP_EXT
|
||||
#else
|
||||
# define GL_DEPTH_CLAMP 0x864F
|
||||
#endif
|
||||
|
||||
#if defined(GL_KHR_debug)
|
||||
# define GL_DEBUG_OUTPUT GL_DEBUG_OUTPUT_KHR
|
||||
# define GL_DEBUG_OUTPUT_SYNCHRONOUS GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -59,7 +59,11 @@ VkExtent2D VulkanAttachment::getExtent2D() const {
|
||||
|
||||
VkImageView VulkanAttachment::getImageView() {
|
||||
assert_invariant(texture);
|
||||
return texture->getAttachmentView(getSubresourceRange());
|
||||
VkImageSubresourceRange range = getSubresourceRange();
|
||||
if (range.layerCount > 1) {
|
||||
return texture->getMultiviewAttachmentView(range);
|
||||
}
|
||||
return texture->getAttachmentView(range);
|
||||
}
|
||||
|
||||
bool VulkanAttachment::isDepth() const {
|
||||
@@ -73,7 +77,7 @@ VkImageSubresourceRange VulkanAttachment::getSubresourceRange() const {
|
||||
.baseMipLevel = uint32_t(level),
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = uint32_t(layer),
|
||||
.layerCount = 1,
|
||||
.layerCount = layerCount,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ struct VulkanCommandBuffer;
|
||||
struct VulkanAttachment {
|
||||
VulkanTexture* texture = nullptr;
|
||||
uint8_t level = 0;
|
||||
uint8_t baseViewIndex = 0;
|
||||
uint8_t layerCount = 1;
|
||||
uint16_t layer = 0;
|
||||
|
||||
bool isDepth() const;
|
||||
@@ -123,6 +125,10 @@ public:
|
||||
return mPhysicalDeviceFeatures.imageCubeArray == VK_TRUE;
|
||||
}
|
||||
|
||||
inline bool isDepthClampSupported() const noexcept {
|
||||
return mPhysicalDeviceFeatures.depthClamp == VK_TRUE;
|
||||
}
|
||||
|
||||
inline bool isDebugMarkersSupported() const noexcept {
|
||||
return mDebugMarkersSupported;
|
||||
}
|
||||
|
||||
@@ -595,6 +595,8 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
||||
colorTargets[i] = {
|
||||
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(color[i].handle),
|
||||
.level = color[i].level,
|
||||
.baseViewIndex = color[i].baseViewIndex,
|
||||
.layerCount = layerCount,
|
||||
.layer = color[i].layer,
|
||||
};
|
||||
UTILS_UNUSED_IN_RELEASE VkExtent2D extent = colorTargets[i].getExtent2D();
|
||||
@@ -609,6 +611,8 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
||||
depthStencil[0] = {
|
||||
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(depth.handle),
|
||||
.level = depth.level,
|
||||
.baseViewIndex = depth.baseViewIndex,
|
||||
.layerCount = layerCount,
|
||||
.layer = depth.layer,
|
||||
};
|
||||
UTILS_UNUSED_IN_RELEASE VkExtent2D extent = depthStencil[0].getExtent2D();
|
||||
@@ -621,6 +625,8 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
||||
depthStencil[1] = {
|
||||
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(stencil.handle),
|
||||
.level = stencil.level,
|
||||
.baseViewIndex = stencil.baseViewIndex,
|
||||
.layerCount = layerCount,
|
||||
.layer = stencil.layer,
|
||||
};
|
||||
UTILS_UNUSED_IN_RELEASE VkExtent2D extent = depthStencil[1].getExtent2D();
|
||||
@@ -637,7 +643,7 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
||||
|
||||
auto renderTarget = mResourceAllocator.construct<VulkanRenderTarget>(rth, mPlatform->getDevice(),
|
||||
mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, width, height,
|
||||
samples, colorTargets, depthStencil, mStagePool);
|
||||
samples, colorTargets, depthStencil, mStagePool, layerCount);
|
||||
mResourceManager.acquire(renderTarget);
|
||||
}
|
||||
|
||||
@@ -938,6 +944,10 @@ bool VulkanDriver::isProtectedTexturesSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VulkanDriver::isDepthClampSupported() {
|
||||
return mContext.isDepthClampSupported();
|
||||
}
|
||||
|
||||
bool VulkanDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
switch (workaround) {
|
||||
case Workaround::SPLIT_EASU: {
|
||||
@@ -986,15 +996,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;
|
||||
}
|
||||
|
||||
@@ -1267,6 +1277,8 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t const renderTargetLayerCount = rt->getLayerCount();
|
||||
|
||||
// Create the VkRenderPass or fetch it from cache.
|
||||
VulkanFboCache::RenderPassKey rpkey = {
|
||||
.initialColorLayoutMask = 0,
|
||||
@@ -1277,10 +1289,12 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
.discardEnd = discardEndVal,
|
||||
.samples = rt->getSamples(),
|
||||
.subpassMask = uint8_t(params.subpassMask),
|
||||
.viewCount = renderTargetLayerCount,
|
||||
};
|
||||
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
|
||||
const VulkanAttachment& info = rt->getColor(i);
|
||||
if (info.texture) {
|
||||
assert_invariant(info.layerCount == renderTargetLayerCount);
|
||||
rpkey.initialColorLayoutMask |= 1 << i;
|
||||
rpkey.colorFormat[i] = info.getFormat();
|
||||
if (rpkey.samples > 1 && info.texture->samples == 1) {
|
||||
@@ -1806,6 +1820,8 @@ void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
.dstAlphaBlendFactor = getBlendFactor(rasterState.blendFunctionDstAlpha),
|
||||
.colorWriteMask = (VkColorComponentFlags) (rasterState.colorWrite ? 0xf : 0x0),
|
||||
.rasterizationSamples = rt->getSamples(),
|
||||
.depthClamp = rasterState.depthClamp,
|
||||
.reserved = 0,
|
||||
.colorTargetCount = rt->getColorTargetCount(mCurrentRenderPass),
|
||||
.colorBlendOp = rasterState.blendEquationRGB,
|
||||
.alphaBlendOp = rasterState.blendEquationAlpha,
|
||||
|
||||
@@ -43,6 +43,7 @@ bool VulkanFboCache::RenderPassEq::operator()(const RenderPassKey& k1,
|
||||
if (k1.samples != k2.samples) return false;
|
||||
if (k1.needsResolveMask != k2.needsResolveMask) return false;
|
||||
if (k1.subpassMask != k2.subpassMask) return false;
|
||||
if (k1.viewCount != k2.viewCount) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -186,6 +187,25 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
|
||||
.pDependencies = dependencies
|
||||
};
|
||||
|
||||
VkRenderPassMultiviewCreateInfo multiviewCreateInfo = {};
|
||||
uint32_t const subpassViewMask = (1 << config.viewCount) - 1;
|
||||
// Prepare a view mask array for the maximum number of subpasses. All subpasses have all views
|
||||
// activated.
|
||||
uint32_t const viewMasks[2] = {subpassViewMask, subpassViewMask};
|
||||
if (config.viewCount > 1) {
|
||||
// Fill the multiview create info.
|
||||
multiviewCreateInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO;
|
||||
multiviewCreateInfo.pNext = nullptr;
|
||||
multiviewCreateInfo.subpassCount = hasSubpasses ? 2u : 1u;
|
||||
multiviewCreateInfo.pViewMasks = viewMasks;
|
||||
multiviewCreateInfo.dependencyCount = 0;
|
||||
multiviewCreateInfo.pViewOffsets = nullptr;
|
||||
multiviewCreateInfo.correlationMaskCount = 1;
|
||||
multiviewCreateInfo.pCorrelationMasks = &subpassViewMask;
|
||||
|
||||
renderPassInfo.pNext = &multiviewCreateInfo;
|
||||
}
|
||||
|
||||
int attachmentIndex = 0;
|
||||
|
||||
// Populate the Color Attachments.
|
||||
|
||||
@@ -64,7 +64,7 @@ public:
|
||||
uint8_t samples; // 1 byte
|
||||
uint8_t needsResolveMask; // 1 byte
|
||||
uint8_t subpassMask; // 1 byte
|
||||
uint8_t padding2; // 1 byte
|
||||
uint8_t viewCount; // 1 byte
|
||||
};
|
||||
struct RenderPassVal {
|
||||
VkRenderPass handle;
|
||||
|
||||
@@ -323,11 +323,12 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica
|
||||
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
|
||||
uint32_t width, uint32_t height, uint8_t samples,
|
||||
VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
|
||||
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool)
|
||||
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool, uint8_t layerCount)
|
||||
: HwRenderTarget(width, height),
|
||||
VulkanResource(VulkanResourceType::RENDER_TARGET),
|
||||
mOffscreen(true),
|
||||
mSamples(samples) {
|
||||
mSamples(samples),
|
||||
mLayerCount(layerCount) {
|
||||
for (int index = 0; index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; index++) {
|
||||
mColor[index] = color[index];
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ struct VulkanRenderTarget : private HwRenderTarget, VulkanResource {
|
||||
VulkanContext const& context, VmaAllocator allocator,
|
||||
VulkanCommands* commands, uint32_t width, uint32_t height,
|
||||
uint8_t samples, VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
|
||||
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool);
|
||||
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool, uint8_t layerCount);
|
||||
|
||||
// Creates a special "default" render target (i.e. associated with the swap chain)
|
||||
explicit VulkanRenderTarget();
|
||||
@@ -319,6 +319,7 @@ struct VulkanRenderTarget : private HwRenderTarget, VulkanResource {
|
||||
VulkanAttachment& getMsaaDepth();
|
||||
uint8_t getColorTargetCount(const VulkanRenderPass& pass) const;
|
||||
uint8_t getSamples() const { return mSamples; }
|
||||
uint8_t getLayerCount() const { return mLayerCount; }
|
||||
bool hasDepth() const { return mDepth.texture; }
|
||||
bool isSwapChain() const { return !mOffscreen; }
|
||||
void bindToSwapChain(VulkanSwapChain& surf);
|
||||
@@ -330,6 +331,7 @@ private:
|
||||
VulkanAttachment mMsaaDepthAttachment = {};
|
||||
const bool mOffscreen : 1;
|
||||
uint8_t mSamples : 7;
|
||||
uint8_t mLayerCount = 1;
|
||||
};
|
||||
|
||||
struct VulkanBufferObject;
|
||||
|
||||
@@ -184,6 +184,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
||||
vkRaster.polygonMode = VK_POLYGON_MODE_FILL;
|
||||
vkRaster.cullMode = raster.cullMode;
|
||||
vkRaster.frontFace = raster.frontFace;
|
||||
vkRaster.depthClampEnable = raster.depthClamp;
|
||||
vkRaster.depthBiasEnable = raster.depthBiasEnable;
|
||||
vkRaster.depthBiasConstantFactor = raster.depthBiasConstantFactor;
|
||||
vkRaster.depthBiasClamp = 0.0f;
|
||||
|
||||
@@ -90,7 +90,9 @@ public:
|
||||
VkBlendFactor srcAlphaBlendFactor : 5;
|
||||
VkBlendFactor dstAlphaBlendFactor : 5;
|
||||
VkColorComponentFlags colorWriteMask : 4;
|
||||
uint8_t rasterizationSamples; // offset = 4 bytes
|
||||
uint8_t rasterizationSamples : 4;// offset = 4 bytes
|
||||
uint8_t depthClamp : 1;
|
||||
uint8_t reserved : 3;
|
||||
uint8_t colorTargetCount; // offset = 5 bytes
|
||||
BlendEquation colorBlendOp : 4; // offset = 6 bytes
|
||||
BlendEquation alphaBlendOp : 4;
|
||||
|
||||
@@ -387,12 +387,15 @@ void VulkanTexture::setPrimaryRange(uint32_t minMiplevel, uint32_t maxMiplevel)
|
||||
}
|
||||
|
||||
VkImageView VulkanTexture::getAttachmentView(VkImageSubresourceRange range) {
|
||||
// Attachments should only have one mipmap level and one layer.
|
||||
range.levelCount = 1;
|
||||
range.layerCount = 1;
|
||||
return getImageView(range, VK_IMAGE_VIEW_TYPE_2D, {});
|
||||
}
|
||||
|
||||
VkImageView VulkanTexture::getMultiviewAttachmentView(VkImageSubresourceRange range) {
|
||||
return getImageView(range, VK_IMAGE_VIEW_TYPE_2D_ARRAY, {});
|
||||
}
|
||||
|
||||
VkImageView VulkanTexture::getViewForType(VkImageSubresourceRange const& range, VkImageViewType type) {
|
||||
return getImageView(range, type, mSwizzle);
|
||||
}
|
||||
|
||||
@@ -73,6 +73,10 @@ struct VulkanTexture : public HwTexture, VulkanResource {
|
||||
// and the identity swizzle.
|
||||
VkImageView getAttachmentView(VkImageSubresourceRange range);
|
||||
|
||||
// Gets or creates a cached VkImageView for a single subresource that can be used as a render
|
||||
// target attachment when rendering with multiview.
|
||||
VkImageView getMultiviewAttachmentView(VkImageSubresourceRange range);
|
||||
|
||||
// This is a workaround for the first few frames where we're waiting for the texture to actually
|
||||
// be uploaded. In that case, we bind the sampler to an empty texture, but the corresponding
|
||||
// imageView needs to be of the right type. Hence, we provide an option to indicate the
|
||||
|
||||
@@ -172,7 +172,7 @@ public:
|
||||
UTILS_UNUSED VkResult result = vkAllocateDescriptorSets(mDevice, &allocInfo, &vkSet);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "Failed to allocate descriptor set code=" << result << " size=" << mSize
|
||||
<< " capacity=" << mCapacity << " count=" << "%s";
|
||||
<< " capacity=" << mCapacity << " count=" << mUnusedCount;
|
||||
mSize++;
|
||||
return createSet(layout->bitmask, vkSet);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -325,6 +325,7 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
|
||||
// We could simply enable all supported features, but since that may have performance
|
||||
// consequences let's just enable the features we need.
|
||||
VkPhysicalDeviceFeatures enabledFeatures{
|
||||
.depthClamp = features.depthClamp,
|
||||
.samplerAnisotropy = features.samplerAnisotropy,
|
||||
.textureCompressionETC2 = features.textureCompressionETC2,
|
||||
.textureCompressionBC = features.textureCompressionBC,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -350,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.
|
||||
@@ -893,6 +896,29 @@ public:
|
||||
/** Tells whether an InstanceBuffer object is valid */
|
||||
bool isValid(const InstanceBuffer* UTILS_NULLABLE p) const;
|
||||
|
||||
/**
|
||||
* Retrieve the count of each resource tracked by Engine.
|
||||
* This is intended for debugging.
|
||||
* @{
|
||||
*/
|
||||
size_t getBufferObjectCount() const noexcept;
|
||||
size_t getViewCount() const noexcept;
|
||||
size_t getSceneCount() const noexcept;
|
||||
size_t getSwapChainCount() const noexcept;
|
||||
size_t getStreamCount() const noexcept;
|
||||
size_t getIndexBufferCount() const noexcept;
|
||||
size_t getSkinningBufferCount() const noexcept;
|
||||
size_t getMorphTargetBufferCount() const noexcept;
|
||||
size_t getInstanceBufferCount() const noexcept;
|
||||
size_t getVertexBufferCount() const noexcept;
|
||||
size_t getIndirectLightCount() const noexcept;
|
||||
size_t getMaterialCount() const noexcept;
|
||||
size_t getTextureCount() const noexcept;
|
||||
size_t getSkyboxeCount() const noexcept;
|
||||
size_t getColorGradingCount() const noexcept;
|
||||
size_t getRenderTargetCount() const noexcept;
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Kicks the hardware thread (e.g. the OpenGL, Vulkan or Metal thread) and blocks until
|
||||
* all commands to this point are executed. Note that does guarantee that the
|
||||
|
||||
@@ -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.
|
||||
@@ -238,6 +271,14 @@ public:
|
||||
*/
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Entity.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <math/mathfwd.h>
|
||||
|
||||
@@ -40,6 +41,7 @@ class CallbackHandler;
|
||||
|
||||
class Camera;
|
||||
class ColorGrading;
|
||||
class Engine;
|
||||
class MaterialInstance;
|
||||
class RenderTarget;
|
||||
class Scene;
|
||||
@@ -726,7 +728,7 @@ public:
|
||||
void setDebugCamera(Camera* UTILS_NULLABLE camera) noexcept;
|
||||
|
||||
//! debugging: returns a Camera from the point of view of *the* dominant directional light used for shadowing.
|
||||
Camera const* UTILS_NULLABLE getDirectionalShadowCamera() const noexcept;
|
||||
utils::FixedCapacityVector<Camera const*> getDirectionalShadowCameras() const noexcept;
|
||||
|
||||
|
||||
/** Result of a picking query */
|
||||
@@ -878,6 +880,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
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
#include "details/Engine.h"
|
||||
|
||||
#include "ResourceAllocator.h"
|
||||
|
||||
#include "details/BufferObject.h"
|
||||
#include "details/Camera.h"
|
||||
#include "details/Fence.h"
|
||||
@@ -266,6 +268,71 @@ bool Engine::isValid(const InstanceBuffer* p) const {
|
||||
return downcast(this)->isValid(downcast(p));
|
||||
}
|
||||
|
||||
size_t Engine::getBufferObjectCount() const noexcept {
|
||||
return downcast(this)->getBufferObjectCount();
|
||||
}
|
||||
|
||||
size_t Engine::getViewCount() const noexcept {
|
||||
return downcast(this)->getViewCount();
|
||||
}
|
||||
|
||||
size_t Engine::getSceneCount() const noexcept {
|
||||
return downcast(this)->getSceneCount();
|
||||
}
|
||||
|
||||
size_t Engine::getSwapChainCount() const noexcept {
|
||||
return downcast(this)->getSwapChainCount();
|
||||
}
|
||||
|
||||
size_t Engine::getStreamCount() const noexcept {
|
||||
return downcast(this)->getStreamCount();
|
||||
}
|
||||
|
||||
size_t Engine::getIndexBufferCount() const noexcept {
|
||||
return downcast(this)->getIndexBufferCount();
|
||||
}
|
||||
|
||||
size_t Engine::getSkinningBufferCount() const noexcept {
|
||||
return downcast(this)->getSkinningBufferCount();
|
||||
}
|
||||
|
||||
size_t Engine::getMorphTargetBufferCount() const noexcept {
|
||||
return downcast(this)->getMorphTargetBufferCount();
|
||||
}
|
||||
|
||||
size_t Engine::getInstanceBufferCount() const noexcept {
|
||||
return downcast(this)->getInstanceBufferCount();
|
||||
}
|
||||
|
||||
size_t Engine::getVertexBufferCount() const noexcept {
|
||||
return downcast(this)->getVertexBufferCount();
|
||||
}
|
||||
|
||||
size_t Engine::getIndirectLightCount() const noexcept {
|
||||
return downcast(this)->getIndirectLightCount();
|
||||
}
|
||||
|
||||
size_t Engine::getMaterialCount() const noexcept {
|
||||
return downcast(this)->getMaterialCount();
|
||||
}
|
||||
|
||||
size_t Engine::getTextureCount() const noexcept {
|
||||
return downcast(this)->getTextureCount();
|
||||
}
|
||||
|
||||
size_t Engine::getSkyboxeCount() const noexcept {
|
||||
return downcast(this)->getSkyboxeCount();
|
||||
}
|
||||
|
||||
size_t Engine::getColorGradingCount() const noexcept {
|
||||
return downcast(this)->getColorGradingCount();
|
||||
}
|
||||
|
||||
size_t Engine::getRenderTargetCount() const noexcept {
|
||||
return downcast(this)->getRenderTargetCount();
|
||||
}
|
||||
|
||||
|
||||
void Engine::flushAndWait() {
|
||||
downcast(this)->flushAndWait();
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
#include <fg/FrameGraphTexture.h>
|
||||
|
||||
#include <math/mat4.h>
|
||||
#include <math/vec2.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
|
||||
+149
-55
@@ -16,30 +16,34 @@
|
||||
|
||||
#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/ostream.h>
|
||||
#include <utils/Systrace.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,65 +51,155 @@ 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]);
|
||||
uint64_t elapsed = 0;
|
||||
TimerQueryResult const result = driver.getTimerQueryValue(mQueries[mLast], &elapsed);
|
||||
switch (result) {
|
||||
case TimerQueryResult::NOT_READY:
|
||||
// nothing to do
|
||||
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
|
||||
while (mLast != mIndex) {
|
||||
uint64_t elapsed = 0;
|
||||
TimerQueryResult const result = driver.getTimerQueryValue(mQueries[mLast].handle, &elapsed);
|
||||
switch (result) {
|
||||
case TimerQueryResult::NOT_READY:
|
||||
// nothing to do
|
||||
break;
|
||||
case TimerQueryResult::ERROR:
|
||||
mLast = (mLast + 1) % POOL_COUNT;
|
||||
break;
|
||||
case TimerQueryResult::AVAILABLE: {
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_VALUE32("FrameInfo::elapsed", uint32_t(elapsed));
|
||||
// conversion to our duration happens here
|
||||
pFront = mQueries[mLast].pInfo;
|
||||
pFront->frameTime = std::chrono::duration<uint64_t, std::nano>(elapsed);
|
||||
mLast = (mLast + 1) % POOL_COUNT;
|
||||
denoiseFrameTime(history, config);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result != TimerQueryResult::AVAILABLE) {
|
||||
break;
|
||||
case TimerQueryResult::ERROR:
|
||||
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);
|
||||
break;
|
||||
}
|
||||
// read the pending timer queries until we find one that's not ready
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
update(config, mFrameTime);
|
||||
}
|
||||
|
||||
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
|
||||
auto& history = mFrameTimeHistory;
|
||||
void FrameInfoManager::denoiseFrameTime(FrameHistoryQueue& history, Config const& config) noexcept {
|
||||
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)) {
|
||||
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
|
||||
|
||||
+125
-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,25 @@ public:
|
||||
// call this immediately before "swap buffers"
|
||||
void endFrame(backend::DriverApi& driver) noexcept;
|
||||
|
||||
FrameInfo const& getLastFrameInfo() const noexcept {
|
||||
return mFrameTimeHistory[0];
|
||||
details::FrameInfo getLastFrameInfo() const noexcept {
|
||||
// if pFront is not set yet, return FrameInfo(). But the `valid` field will be false in this case.
|
||||
return pFront ? *pFront : details::FrameInfo{};
|
||||
}
|
||||
|
||||
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;
|
||||
using FrameHistoryQueue = CircularQueue<FrameInfoImpl, MAX_FRAMETIME_HISTORY>;
|
||||
static void denoiseFrameTime(FrameHistoryQueue& history, 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
|
||||
FrameHistoryQueue mFrameTimeHistory;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -16,16 +16,22 @@
|
||||
|
||||
#include "FrameSkipper.h"
|
||||
|
||||
#include <utils/Log.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
using namespace utils;
|
||||
using namespace backend;
|
||||
|
||||
FrameSkipper::FrameSkipper(size_t latency) noexcept
|
||||
: mLast(latency - 1) {
|
||||
: mLast(std::max(latency, MAX_FRAME_LATENCY) - 1) {
|
||||
assert_invariant(latency <= MAX_FRAME_LATENCY);
|
||||
}
|
||||
|
||||
@@ -41,31 +47,32 @@ void FrameSkipper::terminate(DriverApi& driver) noexcept {
|
||||
|
||||
bool FrameSkipper::beginFrame(DriverApi& driver) noexcept {
|
||||
auto& fences = mDelayedFences;
|
||||
auto fence = fences.front();
|
||||
if (fence) {
|
||||
auto status = driver.getFenceStatus(fence);
|
||||
if (status == FenceStatus::TIMEOUT_EXPIRED) {
|
||||
// Sync not ready, skip frame
|
||||
if (fences.front()) {
|
||||
// Do we have a latency old fence?
|
||||
auto status = driver.getFenceStatus(fences.front());
|
||||
if (UTILS_UNLIKELY(status == FenceStatus::TIMEOUT_EXPIRED)) {
|
||||
// The fence hasn't signaled yet, skip this frame
|
||||
return false;
|
||||
}
|
||||
assert_invariant(status == FenceStatus::CONDITION_SATISFIED);
|
||||
driver.destroyFence(fence);
|
||||
}
|
||||
// shift all fences down by 1
|
||||
std::move(fences.begin() + 1, fences.end(), fences.begin());
|
||||
fences.back() = {};
|
||||
return true;
|
||||
}
|
||||
|
||||
void FrameSkipper::endFrame(DriverApi& driver) noexcept {
|
||||
// If the user produced a new frame despite the fact that the previous one wasn't finished
|
||||
// (i.e. FrameSkipper::beginFrame() returned false), we need to make sure to replace
|
||||
// a fence that might be here already)
|
||||
auto& fence = mDelayedFences[mLast];
|
||||
if (fence) {
|
||||
driver.destroyFence(fence);
|
||||
auto& fences = mDelayedFences;
|
||||
size_t const last = mLast;
|
||||
|
||||
// pop the oldest fence and advance the other ones
|
||||
if (fences.front()) {
|
||||
driver.destroyFence(fences.front());
|
||||
}
|
||||
fence = driver.createFence();
|
||||
std::move(fences.begin() + 1, fences.end(), fences.begin());
|
||||
|
||||
// add a new fence to the end
|
||||
assert_invariant(!fences[last]);
|
||||
|
||||
fences[last] = driver.createFence();
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
/*
|
||||
@@ -60,7 +63,7 @@ public:
|
||||
private:
|
||||
using Container = std::array<backend::Handle<backend::HwFence>, MAX_FRAME_LATENCY>;
|
||||
mutable Container mDelayedFences{};
|
||||
size_t mLast;
|
||||
uint8_t const mLast;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <filament/Viewport.h>
|
||||
|
||||
#include <utils/BinaryTreeArray.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Systrace.h>
|
||||
#include <utils/debug.h>
|
||||
|
||||
|
||||
@@ -354,6 +354,12 @@ bool MaterialParser::getSpecularAntiAliasingThreshold(float* value) const noexce
|
||||
return mImpl.getFromSimpleChunk(ChunkType::MaterialSpecularAntiAliasingThreshold, value);
|
||||
}
|
||||
|
||||
bool MaterialParser::getStereoscopicType(backend::StereoscopicType* value) const noexcept {
|
||||
static_assert(sizeof(StereoscopicType) == sizeof(uint8_t),
|
||||
"StereoscopicType expected size is wrong");
|
||||
return mImpl.getFromSimpleChunk(ChunkType::MaterialStereoscopicType, reinterpret_cast<uint8_t*>(value));
|
||||
}
|
||||
|
||||
bool MaterialParser::getRequiredAttributes(AttributeBitset* value) const noexcept {
|
||||
uint32_t rawAttributes = 0;
|
||||
if (!mImpl.getFromSimpleChunk(ChunkType::MaterialRequiredAttributes, &rawAttributes)) {
|
||||
|
||||
@@ -122,6 +122,7 @@ public:
|
||||
bool hasSpecularAntiAliasing(bool* value) const noexcept;
|
||||
bool getSpecularAntiAliasingVariance(float* value) const noexcept;
|
||||
bool getSpecularAntiAliasingThreshold(float* value) const noexcept;
|
||||
bool getStereoscopicType(backend::StereoscopicType*) const noexcept;
|
||||
|
||||
bool getShader(filaflat::ShaderContent& shader, backend::ShaderModel shaderModel,
|
||||
Variant variant, backend::ShaderStage stage) noexcept;
|
||||
|
||||
@@ -494,7 +494,6 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph
|
||||
|
||||
struct StructureMipmapData {
|
||||
FrameGraphId<FrameGraphTexture> depth;
|
||||
uint32_t rt[8];
|
||||
};
|
||||
|
||||
fg.addPass<StructureMipmapData>("StructureMipmap",
|
||||
@@ -505,7 +504,7 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph
|
||||
.level = uint8_t(i)
|
||||
});
|
||||
out = builder.write(out, FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
|
||||
data.rt[i - 1] = builder.declareRenderPass("Structure mip target", {
|
||||
builder.declareRenderPass("Structure mip target", {
|
||||
.attachments = { .depth = out }
|
||||
});
|
||||
}
|
||||
@@ -1965,8 +1964,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 +1977,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) {
|
||||
|
||||
+69
-42
@@ -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>
|
||||
@@ -117,10 +118,11 @@ RenderPass::RenderPass(FEngine& engine, RenderPassBuilder const& builder) noexce
|
||||
uint32_t const customCommandCount =
|
||||
builder.mCustomCommands.has_value() ? builder.mCustomCommands->size() : 0;
|
||||
|
||||
Command* const curr = builder.mArena.alloc<Command>(commandCount + customCommandCount);
|
||||
assert_invariant(curr);
|
||||
Command* const commandBegin = builder.mArena.alloc<Command>(commandCount + customCommandCount);
|
||||
Command* commandEnd = commandBegin + (commandCount + customCommandCount);
|
||||
assert_invariant(commandBegin);
|
||||
|
||||
if (UTILS_UNLIKELY(builder.mArena.getAllocator().isHeapAllocation(curr))) {
|
||||
if (UTILS_UNLIKELY(builder.mArena.getAllocator().isHeapAllocation(commandBegin))) {
|
||||
static bool sLogOnce = true;
|
||||
if (UTILS_UNLIKELY(sLogOnce)) {
|
||||
sLogOnce = false;
|
||||
@@ -129,10 +131,7 @@ RenderPass::RenderPass(FEngine& engine, RenderPassBuilder const& builder) noexce
|
||||
}
|
||||
}
|
||||
|
||||
mCommandBegin = curr;
|
||||
mCommandEnd = curr + commandCount + customCommandCount;
|
||||
|
||||
appendCommands(engine, { curr, commandCount },
|
||||
appendCommands(engine, { commandBegin, commandCount },
|
||||
builder.mUboHandle,
|
||||
builder.mVisibleRenderables,
|
||||
builder.mCommandTypeFlags,
|
||||
@@ -143,33 +142,36 @@ RenderPass::RenderPass(FEngine& engine, RenderPassBuilder const& builder) noexce
|
||||
builder.mCameraForwardVector);
|
||||
|
||||
if (builder.mCustomCommands.has_value()) {
|
||||
Command* p = curr + commandCount;
|
||||
Command* p = commandBegin + commandCount;
|
||||
for (auto [channel, passId, command, order, fn]: builder.mCustomCommands.value()) {
|
||||
appendCustomCommand(p++, channel, passId, command, order, fn);
|
||||
}
|
||||
}
|
||||
|
||||
// sort commands once we're done adding commands
|
||||
sortCommands(builder.mArena);
|
||||
commandEnd = resize(builder.mArena,
|
||||
RenderPass::sortCommands(commandBegin, commandEnd));
|
||||
|
||||
if (engine.isAutomaticInstancingEnabled()) {
|
||||
uint32_t stereoscopicEyeCount = 1;
|
||||
int32_t stereoscopicEyeCount = 1;
|
||||
if (builder.mFlags & IS_INSTANCED_STEREOSCOPIC) {
|
||||
stereoscopicEyeCount *= engine.getConfig().stereoscopicEyeCount;
|
||||
}
|
||||
instanceify(engine, builder.mArena, stereoscopicEyeCount);
|
||||
commandEnd = resize(builder.mArena,
|
||||
instanceify(engine, commandBegin, commandEnd, stereoscopicEyeCount));
|
||||
}
|
||||
|
||||
// these are `const` from this point on...
|
||||
mCommandBegin = commandBegin;
|
||||
mCommandEnd = commandEnd;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
mCommandEnd = mCommandBegin + count;
|
||||
arena.rewind(mCommandEnd);
|
||||
}
|
||||
RenderPass::Command* RenderPass::resize(Arena& arena, Command* const last) noexcept {
|
||||
arena.rewind(last);
|
||||
return last;
|
||||
}
|
||||
|
||||
void RenderPass::appendCommands(FEngine& engine,
|
||||
@@ -221,7 +223,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);
|
||||
}
|
||||
|
||||
@@ -260,18 +262,19 @@ void RenderPass::appendCustomCommand(Command* commands,
|
||||
commands->key = cmd;
|
||||
}
|
||||
|
||||
void RenderPass::sortCommands(Arena& arena) noexcept {
|
||||
SYSTRACE_NAME("sort and trim commands");
|
||||
RenderPass::Command* RenderPass::sortCommands(
|
||||
Command* const begin, Command* const end) noexcept {
|
||||
SYSTRACE_NAME("sort commands");
|
||||
|
||||
std::sort(mCommandBegin, mCommandEnd);
|
||||
std::sort(begin, end);
|
||||
|
||||
// find the last command
|
||||
Command const* const last = std::partition_point(mCommandBegin, mCommandEnd,
|
||||
Command* const last = std::partition_point(begin, end,
|
||||
[](Command const& c) {
|
||||
return c.key != uint64_t(Pass::SENTINEL);
|
||||
});
|
||||
|
||||
resize(arena, uint32_t(last - mCommandBegin));
|
||||
return last;
|
||||
}
|
||||
|
||||
void RenderPass::execute(RenderPass const& pass,
|
||||
@@ -284,7 +287,9 @@ void RenderPass::execute(RenderPass const& pass,
|
||||
driver.endRenderPass();
|
||||
}
|
||||
|
||||
void RenderPass::instanceify(FEngine& engine, Arena& arena, int32_t eyeCount) noexcept {
|
||||
RenderPass::Command* RenderPass::instanceify(FEngine& engine,
|
||||
Command* curr, Command* const last,
|
||||
int32_t eyeCount) const noexcept {
|
||||
SYSTRACE_NAME("instanceify");
|
||||
|
||||
// instanceify works by scanning the **sorted** command stream, looking for repeat draw
|
||||
@@ -297,14 +302,12 @@ void RenderPass::instanceify(FEngine& engine, Arena& arena, int32_t eyeCount) no
|
||||
|
||||
UTILS_UNUSED uint32_t drawCallsSavedCount = 0;
|
||||
|
||||
Command* curr = mCommandBegin;
|
||||
Command* const last = mCommandEnd;
|
||||
|
||||
Command* firstSentinel = nullptr;
|
||||
PerRenderableData const* uboData = nullptr;
|
||||
PerRenderableData* stagingBuffer = nullptr;
|
||||
uint32_t stagingBufferSize = 0;
|
||||
uint32_t instancedPrimitiveOffset = 0;
|
||||
size_t const count = last - curr;
|
||||
|
||||
// TODO: for the case of instancing we could actually use 128 instead of 64 instances
|
||||
constexpr size_t maxInstanceCount = CONFIG_MAX_INSTANCES;
|
||||
@@ -343,7 +346,6 @@ void RenderPass::instanceify(FEngine& engine, Arena& arena, int32_t eyeCount) no
|
||||
if (UTILS_UNLIKELY(!stagingBuffer)) {
|
||||
|
||||
// create a temporary UBO for instancing
|
||||
size_t const count = mCommandEnd - mCommandBegin;
|
||||
mInstancedUboHandle = BufferObjectSharedHandle{
|
||||
engine.getDriverApi().createBufferObject(
|
||||
count * sizeof(PerRenderableData) + sizeof(PerRenderableUib),
|
||||
@@ -385,7 +387,7 @@ void RenderPass::instanceify(FEngine& engine, Arena& arena, int32_t eyeCount) no
|
||||
|
||||
if (UTILS_UNLIKELY(firstSentinel)) {
|
||||
//slog.d << "auto-instancing, saving " << drawCallsSavedCount << " draw calls, out of "
|
||||
// << mCommandEnd - mCommandBegin << io::endl;
|
||||
// << count << io::endl;
|
||||
|
||||
// we have instanced primitives
|
||||
DriverApi& driver = engine.getDriverApi();
|
||||
@@ -401,14 +403,15 @@ void RenderPass::instanceify(FEngine& engine, Arena& arena, int32_t eyeCount) no
|
||||
stagingBuffer = nullptr;
|
||||
|
||||
// remove all the canceled commands
|
||||
auto lastCommand = std::remove_if(firstSentinel, mCommandEnd, [](auto const& command) {
|
||||
auto lastCommand = std::remove_if(firstSentinel, last, [](auto const& command) {
|
||||
return command.key == uint64_t(Pass::SENTINEL);
|
||||
});
|
||||
|
||||
resize(arena, uint32_t(lastCommand - mCommandBegin));
|
||||
return lastCommand;
|
||||
}
|
||||
|
||||
assert_invariant(stagingBuffer == nullptr);
|
||||
return last;
|
||||
}
|
||||
|
||||
|
||||
@@ -416,7 +419,8 @@ void RenderPass::instanceify(FEngine& engine, Arena& arena, int32_t eyeCount) no
|
||||
UTILS_ALWAYS_INLINE // This function exists only to make the code more readable. we want it inlined.
|
||||
inline // and we don't need it in the compilation unit
|
||||
void RenderPass::setupColorCommand(Command& cmdDraw, Variant variant,
|
||||
FMaterialInstance const* const UTILS_RESTRICT mi, bool inverseFrontFaces) noexcept {
|
||||
FMaterialInstance const* const UTILS_RESTRICT mi,
|
||||
bool inverseFrontFaces, bool hasDepthClamp) noexcept {
|
||||
|
||||
FMaterial const * const UTILS_RESTRICT ma = mi->getMaterial();
|
||||
variant = Variant::filterVariant(variant, ma->isVariantLit());
|
||||
@@ -457,6 +461,7 @@ void RenderPass::setupColorCommand(Command& cmdDraw, Variant variant,
|
||||
cmdDraw.info.rasterState.colorWrite = mi->isColorWriteEnabled();
|
||||
cmdDraw.info.rasterState.depthWrite = mi->isDepthWriteEnabled();
|
||||
cmdDraw.info.rasterState.depthFunc = mi->getDepthFunc();
|
||||
cmdDraw.info.rasterState.depthClamp = hasDepthClamp;
|
||||
cmdDraw.info.materialVariant = variant;
|
||||
// we keep "RasterState::colorWrite" to the value set by material (could be disabled)
|
||||
}
|
||||
@@ -555,6 +560,9 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
|
||||
bool const hasInstancedStereo =
|
||||
renderFlags & IS_INSTANCED_STEREOSCOPIC;
|
||||
|
||||
bool const hasDepthClamp =
|
||||
renderFlags & HAS_DEPTH_CLAMP;
|
||||
|
||||
float const cameraPositionDotCameraForward = dot(cameraPosition, cameraForward);
|
||||
|
||||
auto const* const UTILS_RESTRICT soaWorldAABBCenter = soa.data<FScene::WORLD_AABB_CENTER>();
|
||||
@@ -574,6 +582,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
|
||||
cmd.info.rasterState.depthWrite = true;
|
||||
cmd.info.rasterState.depthFunc = RasterState::DepthFunc::GE;
|
||||
cmd.info.rasterState.alphaToCoverage = false;
|
||||
cmd.info.rasterState.depthClamp = hasDepthClamp;
|
||||
}
|
||||
|
||||
for (uint32_t i = range.first; i < range.last; ++i) {
|
||||
@@ -607,7 +616,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
|
||||
// Here, objects close to the camera (but behind) will be drawn first.
|
||||
// An alternative that keeps the mathematical ordering is given here:
|
||||
// distanceBits ^= ((int32_t(distanceBits) >> 31) | 0x80000000u);
|
||||
float const distance = -dot(soaWorldAABBCenter[i], cameraForward) - cameraPositionDotCameraForward;
|
||||
float const distance = -(dot(soaWorldAABBCenter[i], cameraForward) - cameraPositionDotCameraForward);
|
||||
uint32_t const distanceBits = reinterpret_cast<uint32_t const&>(distance);
|
||||
|
||||
// calculate the per-primitive face winding order inversion
|
||||
@@ -671,7 +680,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,11 +692,13 @@ 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);
|
||||
RenderPass::setupColorCommand(cmd, renderableVariant, mi,
|
||||
inverseFrontFaces, hasDepthClamp);
|
||||
const bool blendPass = Pass(cmd.key & PASS_MASK) == Pass::BLENDED;
|
||||
if (blendPass) {
|
||||
// TODO: at least for transparent objects, AABB should be per primitive
|
||||
@@ -885,14 +895,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);
|
||||
|
||||
@@ -284,6 +284,7 @@ public:
|
||||
static constexpr RenderFlags HAS_SHADOWING = 0x01;
|
||||
static constexpr RenderFlags HAS_INVERSE_FRONT_FACES = 0x02;
|
||||
static constexpr RenderFlags IS_INSTANCED_STEREOSCOPIC = 0x04;
|
||||
static constexpr RenderFlags HAS_DEPTH_CLAMP = 0x08;
|
||||
|
||||
// Arena used for commands
|
||||
using Arena = utils::Arena<
|
||||
@@ -408,17 +409,19 @@ private:
|
||||
uint8_t channel, Pass pass, CustomCommand custom, uint32_t order,
|
||||
Executor::CustomCommandFn command);
|
||||
|
||||
void resize(Arena& arena, size_t count) noexcept;
|
||||
static Command* resize(Arena& arena, Command* const last) noexcept;
|
||||
|
||||
// sorts commands then trims sentinels
|
||||
void sortCommands(Arena& arena) noexcept;
|
||||
static Command* sortCommands(
|
||||
Command* begin, Command* end) noexcept;
|
||||
|
||||
// instanceify commands then trims sentinels
|
||||
void instanceify(FEngine& engine, Arena& arena, int32_t eyeCount) noexcept;
|
||||
RenderPass::Command* instanceify(FEngine& engine,
|
||||
Command* begin, Command* end,
|
||||
int32_t eyeCount) const 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;
|
||||
|
||||
@@ -442,17 +445,17 @@ private:
|
||||
uint8_t instancedStereoEyeCount) noexcept;
|
||||
|
||||
static void setupColorCommand(Command& cmdDraw, Variant variant,
|
||||
FMaterialInstance const* mi, bool inverseFrontFaces) noexcept;
|
||||
FMaterialInstance const* mi, bool inverseFrontFaces, bool hasDepthClamp) noexcept;
|
||||
|
||||
static void updateSummedPrimitiveCounts(
|
||||
FScene::RenderableSoa& renderableData, utils::Range<uint32_t> vr) noexcept;
|
||||
|
||||
FScene::RenderableSoa const& mRenderableSoa;
|
||||
backend::Viewport const mScissorViewport;
|
||||
Command* mCommandBegin = nullptr; // Pointer to the first command
|
||||
Command* mCommandEnd = nullptr; // Pointer to one past the last command
|
||||
Command const* /* const */ mCommandBegin = nullptr; // Pointer to the first command
|
||||
Command const* /* const */ mCommandEnd = nullptr; // Pointer to one past the last command
|
||||
// a UBO for instanced primitives
|
||||
BufferObjectSharedHandle mInstancedUboHandle;
|
||||
mutable BufferObjectSharedHandle mInstancedUboHandle;
|
||||
// a vector for our custom commands
|
||||
using CustomCommandVector = std::vector<Executor::CustomCommandFn,
|
||||
utils::STLAllocator<Executor::CustomCommandFn, LinearAllocatorArena>>;
|
||||
|
||||
@@ -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,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "details/Renderer.h"
|
||||
#include <filament/Renderer.h>
|
||||
|
||||
#include "ResourceAllocator.h"
|
||||
|
||||
#include "details/Engine.h"
|
||||
#include "details/Renderer.h"
|
||||
#include "details/View.h"
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
using namespace math;
|
||||
@@ -36,6 +46,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);
|
||||
}
|
||||
@@ -93,4 +107,12 @@ 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
|
||||
|
||||
@@ -75,7 +75,6 @@ FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
|
||||
TargetBufferFlags const clearColorFlags = config.clearFlags & TargetBufferFlags::COLOR;
|
||||
TargetBufferFlags clearDepthFlags = config.clearFlags & TargetBufferFlags::DEPTH;
|
||||
TargetBufferFlags clearStencilFlags = config.clearFlags & TargetBufferFlags::STENCIL;
|
||||
uint8_t layerCount = 0;
|
||||
|
||||
data.shadows = blackboard.get<FrameGraphTexture>("shadows");
|
||||
data.ssao = blackboard.get<FrameGraphTexture>("ssao");
|
||||
@@ -172,9 +171,6 @@ 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) {
|
||||
layerCount = engine.getConfig().stereoscopicEyeCount;
|
||||
}
|
||||
|
||||
/*
|
||||
* There is a bit of magic happening here regarding the viewport used.
|
||||
@@ -196,7 +192,7 @@ FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
|
||||
.stencil = data.stencil },
|
||||
.clearColor = config.clearColor,
|
||||
.samples = config.msaa,
|
||||
.layerCount = layerCount,
|
||||
.layerCount = static_cast<uint8_t>(colorBufferDesc.depth),
|
||||
.clearFlags = clearColorFlags | clearDepthFlags | clearStencilFlags});
|
||||
blackboard["depth"] = data.depth;
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace filament {
|
||||
class ResourceListBase {
|
||||
public:
|
||||
using iterator = typename tsl::robin_set<void*>::iterator;
|
||||
using const_iterator = typename tsl::robin_set<void*>::const_iterator;
|
||||
|
||||
explicit ResourceListBase(const char* typeName);
|
||||
ResourceListBase(ResourceListBase&& rhs) noexcept = default;
|
||||
@@ -58,6 +59,14 @@ public:
|
||||
return mList.end();
|
||||
}
|
||||
|
||||
const_iterator begin() const noexcept {
|
||||
return mList.begin();
|
||||
}
|
||||
|
||||
const_iterator end() const noexcept {
|
||||
return mList.end();
|
||||
}
|
||||
|
||||
protected:
|
||||
void forEach(void(*f)(void* user, void *p), void* user) const noexcept;
|
||||
tsl::robin_set<void*> mList;
|
||||
|
||||
+68
-50
@@ -57,9 +57,6 @@ namespace filament {
|
||||
using namespace math;
|
||||
using namespace backend;
|
||||
|
||||
// do this only if depth-clamp is available
|
||||
static constexpr bool USE_DEPTH_CLAMP = false;
|
||||
|
||||
ShadowMap::ShadowMap(FEngine& engine) noexcept
|
||||
: mPerShadowMapUniforms(engine),
|
||||
mShadowType(ShadowType::DIRECTIONAL),
|
||||
@@ -126,7 +123,8 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
FScene::LightSoa const& lightData, size_t index,
|
||||
filament::CameraInfo const& camera,
|
||||
ShadowMapInfo const& shadowMapInfo,
|
||||
SceneInfo const& sceneInfo) noexcept {
|
||||
SceneInfo const& sceneInfo,
|
||||
bool useDepthClamp) noexcept {
|
||||
|
||||
// reset the visible shadow status
|
||||
mHasVisibleShadows = false;
|
||||
@@ -137,13 +135,20 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
|
||||
const auto direction = lightData.elementAt<FScene::SHADOW_DIRECTION>(index);
|
||||
|
||||
auto [Mv, znear, zfar, lsClippedShadowVolume, vertexCount, visibleShadows] =
|
||||
computeDirectionalShadowBounds(engine, direction, params, camera, sceneInfo);
|
||||
auto const [Mv, znear, zfar, lsClippedShadowVolume, vertexCount, visibleShadows] =
|
||||
computeDirectionalShadowBounds(engine, direction, params, camera, sceneInfo,
|
||||
useDepthClamp);
|
||||
|
||||
if (UTILS_UNLIKELY(!visibleShadows)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// if the 2D bounds of the intersection is empty seen from the light, we have no shadow
|
||||
Aabb const bounds = compute2DBounds(Mv, lsClippedShadowVolume.data(), vertexCount);
|
||||
if (UTILS_UNLIKELY(!(bounds.min.x < bounds.max.x && bounds.min.y < bounds.max.y))) {
|
||||
return {};
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute the light's projection matrix
|
||||
* (directional/point lights, i.e. projection to use, including znear/zfar clip planes)
|
||||
@@ -161,12 +166,17 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
mat4f W, Wp, L;
|
||||
// We can't use LISPSM in stable mode
|
||||
const bool useLispsm = params.options.lispsm && !params.options.stable;
|
||||
if (useLispsm) {
|
||||
|
||||
if (useLispsm ||
|
||||
(!params.options.stable && !engine.debug.shadowmap.disable_light_frustum_align)) {
|
||||
// Orient the shadow map in the direction of the view vector by constructing a
|
||||
const float3 lsCameraFwd = Mv.upperLeft() * camera.getForwardVector();
|
||||
L = computeLightRotation(lsCameraFwd);
|
||||
LMp = L * Mp;
|
||||
LMpMv = LMp * Mv;
|
||||
}
|
||||
|
||||
if (useLispsm) {
|
||||
W = applyLISPSM(Wp, camera, params,
|
||||
LMp, Mv, LMpMv, lsClippedShadowVolume, vertexCount, direction);
|
||||
}
|
||||
@@ -184,8 +194,7 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
// In LiPSM mode, we're using the warped space here.
|
||||
float4 f = computeFocusParams(LMpMv, WLMp,
|
||||
lsClippedShadowVolume, vertexCount,
|
||||
camera, sceneInfo.csNearFar,
|
||||
params.options.shadowFar, params.options.stable);
|
||||
camera, params.options.shadowFar, params.options.stable);
|
||||
|
||||
if (params.options.stable) {
|
||||
const auto lsRef = lightData.elementAt<FScene::SHADOW_REF>(index);
|
||||
@@ -386,7 +395,8 @@ ShadowMap::DirectionalShadowBounds ShadowMap::computeDirectionalShadowBounds(
|
||||
float3 const direction,
|
||||
FLightManager::ShadowParams params,
|
||||
filament::CameraInfo const& camera,
|
||||
SceneInfo const& sceneInfo) noexcept {
|
||||
SceneInfo const& sceneInfo,
|
||||
bool useDepthClamp) noexcept {
|
||||
|
||||
// we use aligned_storage<> here to avoid the default initialization of std::array<>
|
||||
using Storage = std::aligned_storage<sizeof(FrustumBoxIntersection)>::type;
|
||||
@@ -448,36 +458,49 @@ ShadowMap::DirectionalShadowBounds ShadowMap::computeDirectionalShadowBounds(
|
||||
|
||||
// Compute the intersection of the view volume with the intersection of receivers and casters
|
||||
// in light space. This returns a set of points on the convex-hull of the intersection.
|
||||
mat4f const projection = camera.cullingProjection *
|
||||
(camera.view * FCamera::rigidTransformInverse(MvAtOrigin));
|
||||
size_t const vertexCount = intersectFrustumWithBox(lsClippedShadowVolume,
|
||||
camera.cullingProjection * camera.view * FCamera::rigidTransformInverse(MvAtOrigin),
|
||||
sceneInfo.csNearFar, lsLightFrustumBounds);
|
||||
projection, lsLightFrustumBounds);
|
||||
|
||||
if (UTILS_UNLIKELY(vertexCount < 4)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
/*
|
||||
* Adjust the scene's zmax (i.e. Near plane) and zmin (i.e. Far plane) in light space.
|
||||
* (near/far correspond to max/min because the light looks down the -z axis).
|
||||
* - The Near plane is set to the shadow casters max z (i.e. closest to the light)
|
||||
* - The Far plane is set to the closest of the farthest shadow casters and receivers
|
||||
* i.e.: shadow casters behind the last receivers can't cast any shadows
|
||||
*
|
||||
* If "depth clamp" is supported, we can further tighten the near plane to the
|
||||
* shadow receiver.
|
||||
*
|
||||
* Note: L has no influence here, since we're only interested in z values
|
||||
* (L is a rotation around z)
|
||||
*/
|
||||
* Adjust the scene's zmax (i.e. Near plane) and zmin (i.e. Far plane) in light space.
|
||||
* (near/far correspond to max/min because the light looks down the -z axis).
|
||||
*
|
||||
* - For the Far plane, the light frustum is already set to the closest of receivers and
|
||||
* casters (i.e.: shadow casters behind the last receivers can't cast any shadows).
|
||||
* But it can be moved closer up to the farthest point in the camera frustum & light
|
||||
* frustum intersection.
|
||||
*
|
||||
* - The Near plane is already set to the shadow casters max z (i.e. closest to the light)
|
||||
*
|
||||
* If "depth clamp" is supported, we can further tighten the near plane to the
|
||||
* shadow receiver (i.e. to the closest point of camera frustum & light frustum intersection).
|
||||
*
|
||||
* Note: L has no influence here, since we're only interested in z values
|
||||
* (L is a rotation around z)
|
||||
*/
|
||||
|
||||
float lsClippedShadowVolumeFarthest = std::numeric_limits<float>::max();
|
||||
float lsClippedShadowVolumeNearest = std::numeric_limits<float>::lowest();
|
||||
for (size_t i = 0; i < vertexCount; ++i) {
|
||||
float3 const v = lsClippedShadowVolume[i];
|
||||
// far: figure out the farthest shadow receivers
|
||||
lsLightFrustumBounds.min.z = min(lsLightFrustumBounds.min.z, v.z);
|
||||
if constexpr (USE_DEPTH_CLAMP) {
|
||||
lsLightFrustumBounds.max.z = max(lsLightFrustumBounds.max.z, v.z);
|
||||
}
|
||||
lsClippedShadowVolumeFarthest = std::min(lsClippedShadowVolumeFarthest, v.z);
|
||||
lsClippedShadowVolumeNearest = std::max(lsClippedShadowVolumeNearest, v.z);
|
||||
}
|
||||
|
||||
lsLightFrustumBounds.min.z =
|
||||
std::max(lsLightFrustumBounds.min.z, lsClippedShadowVolumeFarthest);
|
||||
|
||||
if (useDepthClamp) {
|
||||
lsLightFrustumBounds.max.z =
|
||||
std::min(lsLightFrustumBounds.max.z, lsClippedShadowVolumeNearest);
|
||||
}
|
||||
|
||||
if (engine.debug.shadowmap.far_uses_shadowcasters) {
|
||||
// far: closest of the farthest shadow casters and receivers
|
||||
lsLightFrustumBounds.min.z =
|
||||
@@ -611,7 +634,7 @@ math::float4 ShadowMap::computeFocusParams(
|
||||
mat4f const& LMpMv,
|
||||
mat4f const& WLMp,
|
||||
FrustumBoxIntersection const& lsShadowVolume, size_t vertexCount,
|
||||
filament::CameraInfo const& camera, float2 const& csNearFar,
|
||||
filament::CameraInfo const& camera,
|
||||
float shadowFar, bool stable) noexcept {
|
||||
float2 s, o;
|
||||
if (stable) {
|
||||
@@ -626,7 +649,7 @@ math::float4 ShadowMap::computeFocusParams(
|
||||
return wsViewVolumeBoundingSphere;
|
||||
} else {
|
||||
mat4f const viewFromClip = inverse(camera.cullingProjection);
|
||||
Corners const wsFrustumVertices = computeFrustumCorners(viewFromClip, csNearFar);
|
||||
Corners const wsFrustumVertices = computeFrustumCorners(viewFromClip);
|
||||
float4 const wsViewVolumeBoundingSphere =
|
||||
computeBoundingSphere(wsFrustumVertices.vertices, 8);
|
||||
return wsViewVolumeBoundingSphere;
|
||||
@@ -794,26 +817,25 @@ Aabb ShadowMap::compute2DBounds(const mat4f& lightView,
|
||||
}
|
||||
|
||||
ShadowMap::Corners ShadowMap::computeFrustumCorners(
|
||||
mat4f const& projectionInverse,
|
||||
float2 csNearFar) noexcept {
|
||||
mat4f const& projectionInverse) noexcept {
|
||||
|
||||
Corners out;
|
||||
|
||||
// compute view frustum in world space (from its NDC)
|
||||
// matrix to convert: ndc -> camera -> world
|
||||
float const near = csNearFar.x;
|
||||
float const far = csNearFar.y;
|
||||
float const near = -1.0f;
|
||||
float const far = 1.0f;
|
||||
|
||||
Corners const csViewFrustumCorners = {
|
||||
.vertices = {
|
||||
{ -1, -1, far },
|
||||
{ 1, -1, far },
|
||||
{ -1, 1, far },
|
||||
{ 1, 1, far },
|
||||
{ 1, -1, far },
|
||||
{ -1, 1, far },
|
||||
{ 1, 1, far },
|
||||
{ -1, -1, near },
|
||||
{ 1, -1, near },
|
||||
{ -1, 1, near },
|
||||
{ 1, 1, near },
|
||||
{ 1, -1, near },
|
||||
{ -1, 1, near },
|
||||
{ 1, 1, near },
|
||||
}
|
||||
};
|
||||
|
||||
@@ -895,11 +917,11 @@ void ShadowMap::snapLightFrustum(float2& s, float2& o,
|
||||
|
||||
size_t ShadowMap::intersectFrustumWithBox(
|
||||
FrustumBoxIntersection& UTILS_RESTRICT outVertices,
|
||||
mat4f const& UTILS_RESTRICT projection, math::float2 const& UTILS_RESTRICT csNearFar,
|
||||
mat4f const& UTILS_RESTRICT projection,
|
||||
Aabb const& UTILS_RESTRICT box)
|
||||
{
|
||||
Frustum const frustum{ projection };
|
||||
Corners const frustumVertices{ computeFrustumCorners(inverse(projection), csNearFar) };
|
||||
Corners const frustumVertices{ computeFrustumCorners(inverse(projection)) };
|
||||
|
||||
constexpr const float EPSILON = 1.0f / 8192.0f; // ~0.012 mm
|
||||
size_t vertexCount = 0;
|
||||
@@ -1203,12 +1225,11 @@ void ShadowMap::visitScene(const FScene& scene, uint32_t visibleLayers,
|
||||
}
|
||||
|
||||
ShadowMap::SceneInfo::SceneInfo(
|
||||
FScene const& scene, uint8_t visibleLayers, mat4f const& viewMatrix) noexcept
|
||||
: vsNearFar(std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max()),
|
||||
visibleLayers(visibleLayers) {
|
||||
FScene const& scene, uint8_t visibleLayers) noexcept
|
||||
: visibleLayers(visibleLayers) {
|
||||
|
||||
// the code below only works with affine transforms
|
||||
assert_invariant(transpose(viewMatrix)[3] == float4(0, 0, 0, 1));
|
||||
// Filament's API requires viewMatrix to be rigid (and thus affine).
|
||||
|
||||
// We assume the light is at the origin to compute the SceneInfo. This is consumed later by
|
||||
// computeShadowCameraDirectional() which takes this into account.
|
||||
@@ -1224,9 +1245,6 @@ ShadowMap::SceneInfo::SceneInfo(
|
||||
[&](Aabb receiver, Culler::result_type) {
|
||||
wsShadowReceiversVolume.min = min(wsShadowReceiversVolume.min, receiver.min);
|
||||
wsShadowReceiversVolume.max = max(wsShadowReceiversVolume.max, receiver.max);
|
||||
auto r = Aabb::transform(viewMatrix.upperLeft(), viewMatrix[3].xyz, receiver);
|
||||
vsNearFar.x = std::max(vsNearFar.x, r.max.z);
|
||||
vsNearFar.y = std::min(vsNearFar.y, r.min.z);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -109,20 +109,13 @@ public:
|
||||
struct SceneInfo {
|
||||
|
||||
SceneInfo() noexcept = default;
|
||||
SceneInfo(FScene const& scene, uint8_t visibleLayers, math::mat4f const& viewMatrix) noexcept;
|
||||
|
||||
// scratch data: The near and far planes, in clip space, to use for this shadow map
|
||||
math::float2 csNearFar = { -1.0f, 1.0f };
|
||||
SceneInfo(FScene const& scene, uint8_t visibleLayers) noexcept;
|
||||
|
||||
// scratch data: light's near/far expressed in light-space, calculated from the scene's
|
||||
// content assuming the light is at the origin.
|
||||
math::float2 lsCastersNearFar;
|
||||
math::float2 lsReceiversNearFar;
|
||||
|
||||
// Viewing camera's near/far expressed in view-space, calculated from the
|
||||
// scene's content.
|
||||
math::float2 vsNearFar;
|
||||
|
||||
// World-space shadow-casters volume
|
||||
Aabb wsShadowCastersVolume;
|
||||
|
||||
@@ -154,7 +147,8 @@ public:
|
||||
const FScene::LightSoa& lightData, size_t index,
|
||||
filament::CameraInfo const& camera,
|
||||
ShadowMapInfo const& shadowMapInfo,
|
||||
SceneInfo const& sceneInfo) noexcept;
|
||||
SceneInfo const& sceneInfo,
|
||||
bool useDepthClamp) noexcept;
|
||||
|
||||
ShaderParameters updateSpot(FEngine& engine,
|
||||
const FScene::LightSoa& lightData, size_t index,
|
||||
@@ -244,7 +238,8 @@ private:
|
||||
math::float3 direction,
|
||||
FLightManager::ShadowParams params,
|
||||
filament::CameraInfo const& camera,
|
||||
SceneInfo const& sceneInfo) noexcept;
|
||||
SceneInfo const& sceneInfo,
|
||||
bool useDepthClamp) noexcept;
|
||||
|
||||
static math::mat4f applyLISPSM(math::mat4f& Wp,
|
||||
filament::CameraInfo const& camera, FLightManager::ShadowParams const& params,
|
||||
@@ -260,7 +255,7 @@ private:
|
||||
math::mat4f const& LMpMv,
|
||||
math::mat4f const& WLMp,
|
||||
FrustumBoxIntersection const& lsShadowVolume, size_t vertexCount,
|
||||
filament::CameraInfo const& camera, math::float2 const& csNearFar,
|
||||
filament::CameraInfo const& camera,
|
||||
float shadowFar, bool stable) noexcept;
|
||||
|
||||
static inline void snapLightFrustum(math::float2& s, math::float2& o,
|
||||
@@ -271,8 +266,7 @@ private:
|
||||
SceneInfo const& sceneInfo,
|
||||
bool stable, bool focusShadowCasters, bool farUsesShadowCasters) noexcept;
|
||||
|
||||
static Corners computeFrustumCorners(const math::mat4f& projectionInverse,
|
||||
math::float2 csNearFar = { -1.0f, 1.0f }) noexcept;
|
||||
static Corners computeFrustumCorners(const math::mat4f& projectionInverse) noexcept;
|
||||
|
||||
static inline math::float2 computeNearFar(math::mat4f const& view,
|
||||
math::float3 const* wsVertices, size_t count) noexcept;
|
||||
@@ -307,7 +301,7 @@ private:
|
||||
|
||||
static size_t intersectFrustumWithBox(
|
||||
FrustumBoxIntersection& outVertices,
|
||||
math::mat4f const& projection, math::float2 const& csNearFar,
|
||||
math::mat4f const& projection,
|
||||
Aabb const& box);
|
||||
|
||||
static math::mat4f warpFrustum(float n, float f) noexcept;
|
||||
|
||||
@@ -39,17 +39,20 @@
|
||||
#include <backend/DriverApiForward.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Range.h>
|
||||
#include <utils/Slice.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/BitmaskEnum.h>
|
||||
#include <utils/Range.h>
|
||||
#include <utils/Slice.h>
|
||||
|
||||
#include <math/half.h>
|
||||
#include <math/mat4.h>
|
||||
#include <math/vec4.h>
|
||||
#include <math/scalar.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <new>
|
||||
@@ -63,12 +66,15 @@ namespace filament {
|
||||
using namespace backend;
|
||||
using namespace math;
|
||||
|
||||
ShadowMapManager::ShadowMapManager(FEngine& engine) {
|
||||
ShadowMapManager::ShadowMapManager(FEngine& engine)
|
||||
: mIsDepthClampSupported(engine.getDriverApi().isDepthClampSupported()) {
|
||||
FDebugRegistry& debugRegistry = engine.getDebugRegistry();
|
||||
debugRegistry.registerProperty("d.shadowmap.visualize_cascades",
|
||||
&engine.debug.shadowmap.visualize_cascades);
|
||||
debugRegistry.registerProperty("d.shadowmap.tightly_bound_scene",
|
||||
&engine.debug.shadowmap.tightly_bound_scene);
|
||||
debugRegistry.registerProperty("d.shadowmap.disable_light_frustum_align",
|
||||
&engine.debug.shadowmap.disable_light_frustum_align);
|
||||
debugRegistry.registerProperty("d.shadowmap.depth_clamp",
|
||||
&engine.debug.shadowmap.depth_clamp);
|
||||
}
|
||||
|
||||
ShadowMapManager::~ShadowMapManager() {
|
||||
@@ -147,7 +153,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::update(
|
||||
calculateTextureRequirements(engine, view, lightData);
|
||||
|
||||
// Compute scene-dependent values shared across all shadow maps
|
||||
ShadowMap::SceneInfo const info{ *view.getScene(), view.getVisibleLayers(), cameraInfo.view };
|
||||
ShadowMap::SceneInfo const info{ *view.getScene(), view.getVisibleLayers() };
|
||||
|
||||
shadowTechnique |= updateCascadeShadowMaps(
|
||||
engine, view, cameraInfo, renderableData, lightData, info);
|
||||
@@ -361,7 +367,22 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
|
||||
|
||||
// generate and sort the commands for rendering the shadow map
|
||||
|
||||
RenderPass::RenderFlags renderPassFlags{};
|
||||
if (view.isFrontFaceWindingInverted()) {
|
||||
renderPassFlags |= RenderPass::HAS_INVERSE_FRONT_FACES;
|
||||
}
|
||||
|
||||
bool const canUseDepthClamp =
|
||||
!view.hasVSM() &&
|
||||
mIsDepthClampSupported &&
|
||||
engine.debug.shadowmap.depth_clamp;
|
||||
|
||||
if (canUseDepthClamp) {
|
||||
renderPassFlags |= RenderPass::HAS_DEPTH_CLAMP;
|
||||
}
|
||||
|
||||
RenderPass const pass = passBuilder
|
||||
.renderFlags(renderPassFlags)
|
||||
.camera(cameraInfo)
|
||||
.visibilityMask(entry.visibilityMask)
|
||||
.geometry(scene->getRenderableData(),
|
||||
@@ -543,65 +564,9 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
FLightManager::ShadowParams const& params = lcm.getShadowParams(directionalLight);
|
||||
|
||||
// Adjust the camera's projection for the light's shadowFar
|
||||
cameraInfo.zf = params.options.shadowFar > 0.0f ? params.options.shadowFar : cameraInfo.zf;
|
||||
if (UTILS_UNLIKELY(params.options.shadowFar > 0.0f)) {
|
||||
cameraInfo.zf = params.options.shadowFar;
|
||||
float const n = cameraInfo.zn;
|
||||
float const f = cameraInfo.zf;
|
||||
|
||||
/*
|
||||
* Updating a projection matrix near and far planes:
|
||||
*
|
||||
* We assume that the near and far plane equations are of the form:
|
||||
* N = { 0, 0, 1, n }
|
||||
* F = { 0, 0, -1, -f }
|
||||
*
|
||||
* We also assume that the lower-left 2x2 of the projection is all 0:
|
||||
* P = A 0 C 0
|
||||
* 0 B D 0
|
||||
* 0 0 E F
|
||||
* 0 0 G H
|
||||
*
|
||||
* It result that we need to calculate E and F while leaving all other parameter unchanged.
|
||||
*
|
||||
* We know that:
|
||||
* with N, F the near/far normalized plane equation parameters
|
||||
* sn, sf arbitrary scale factors (they don't affect the planes)
|
||||
* m is the transpose of projection (see Frustum.cpp)
|
||||
*
|
||||
* sn * N == -m[3] - m[2]
|
||||
* sf * F == -m[3] + m[2]
|
||||
*
|
||||
* sn * N + sf * F == -2 * m[3]
|
||||
* sn * N - sf * F == -2 * m[2]
|
||||
*
|
||||
* sn * N.z + sf * F.z == -2 * m[3].z
|
||||
* sn * N.w + sf * F.w == -2 * m[3].w
|
||||
* sn * N.z - sf * F.z == -2 * m[2].z
|
||||
* sn * N.w - sf * F.w == -2 * m[2].w
|
||||
*
|
||||
* sn * N.z + sf * F.z == -2 * p[2].w
|
||||
* sn * N.w + sf * F.w == -2 * p[3].w
|
||||
* sn * N.z - sf * F.z == -2 * p[2].z
|
||||
* sn * N.w - sf * F.w == -2 * p[3].z
|
||||
*
|
||||
* We now need to solve for { p[2].z, p[3].z, sn, sf } :
|
||||
*
|
||||
* sn == -2 * (p[2].w * F.w - p[3].w * F.z) / (N.z * F.w - N.w * F.z)
|
||||
* sf == -2 * (p[2].w * N.w - p[3].w * N.z) / (F.z * N.w - F.w * N.z)
|
||||
* p[2].z == (sf * F.z - sn * N.z) / 2
|
||||
* p[3].z == (sf * F.w - sn * N.w) / 2
|
||||
*/
|
||||
auto& p = cameraInfo.cullingProjection;
|
||||
float4 const N = { 0, 0, 1, n }; // near plane equation
|
||||
float4 const F = { 0, 0, -1, -f }; // far plane equation
|
||||
// near plane equation scale factor
|
||||
float const sn = -2.0f * (p[2].w * F.w - p[3].w * F.z) / (N.z * F.w - N.w * F.z);
|
||||
// far plane equation scale factor
|
||||
float const sf = -2.0f * (p[2].w * N.w - p[3].w * N.z) / (F.z * N.w - F.w * N.z);
|
||||
// New values for the projection
|
||||
p[2].z = (sf * F.z - sn * N.z) * 0.5f;
|
||||
p[3].z = (sf * F.w - sn * N.w) * 0.5f;
|
||||
updateNearFarPlanes(&cameraInfo.cullingProjection, cameraInfo.zn, cameraInfo.zf);
|
||||
}
|
||||
|
||||
const ShadowMap::ShadowMapInfo shadowMapInfo{
|
||||
@@ -631,8 +596,10 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
// Compute scene-dependent values shared across all cascades
|
||||
ShadowMap::updateSceneInfoDirectional(MvAtOrigin, *scene, sceneInfo);
|
||||
|
||||
// we always do culling without depth clamp, because objects behind the camera
|
||||
// must be rendered regardless
|
||||
shadowMap.updateDirectional(engine,
|
||||
lightData, 0, cameraInfo, shadowMapInfo, sceneInfo);
|
||||
lightData, 0, cameraInfo, shadowMapInfo, sceneInfo, false);
|
||||
|
||||
hasVisibleShadows = shadowMap.hasVisibleShadows();
|
||||
|
||||
@@ -648,15 +615,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
uint32_t cascadeHasVisibleShadows = 0;
|
||||
|
||||
if (hasVisibleShadows) {
|
||||
// Adjust the near and far planes to tightly bound the scene.
|
||||
float vsNear = -cameraInfo.zn;
|
||||
float vsFar = -cameraInfo.zf;
|
||||
if (engine.debug.shadowmap.tightly_bound_scene && !params.options.stable) {
|
||||
vsNear = std::min(vsNear, sceneInfo.vsNearFar.x);
|
||||
vsFar = std::max(vsFar, sceneInfo.vsNearFar.y);
|
||||
}
|
||||
|
||||
const size_t cascadeCount = cascadedShadowMaps.size();
|
||||
uint32_t const cascadeCount = cascadedShadowMaps.size();
|
||||
|
||||
// We divide the camera frustum into N cascades. This gives us N + 1 split positions.
|
||||
// The first split position is the near plane; the last split position is the far plane.
|
||||
@@ -667,9 +626,8 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
}
|
||||
|
||||
const CascadeSplits splits({
|
||||
.proj = cameraInfo.cullingProjection,
|
||||
.near = vsNear,
|
||||
.far = vsFar,
|
||||
.near = -cameraInfo.zn,
|
||||
.far = -cameraInfo.zf,
|
||||
.cascadeCount = cascadeCount,
|
||||
.splitPositions = splitPercentages
|
||||
});
|
||||
@@ -679,10 +637,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
static_assert(CONFIG_MAX_SHADOW_CASCADES <= 5,
|
||||
"At most, a float4 can fit 4 split positions for 5 shadow cascades");
|
||||
float4 wsSplitPositionUniform{ -std::numeric_limits<float>::infinity() };
|
||||
std::copy(splits.beginWs() + 1, splits.endWs(), &wsSplitPositionUniform[0]);
|
||||
|
||||
float csSplitPosition[CONFIG_MAX_SHADOW_CASCADES + 1];
|
||||
std::copy(splits.beginCs(), splits.endCs(), csSplitPosition);
|
||||
std::copy(splits.begin() + 1, splits.end(), &wsSplitPositionUniform[0]);
|
||||
|
||||
mShadowMappingUniforms.cascadeSplits = wsSplitPositionUniform;
|
||||
|
||||
@@ -695,10 +650,20 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
ShadowMap& shadowMap = cascadedShadowMaps[i];
|
||||
assert_invariant(shadowMap.getLightIndex() == 0);
|
||||
|
||||
sceneInfo.csNearFar = { csSplitPosition[i], csSplitPosition[i + 1] };
|
||||
// update cameraInfo culling projection for the cascade
|
||||
float const* nearFarPlanes = splits.begin();
|
||||
cameraInfo.zn = -nearFarPlanes[i];
|
||||
cameraInfo.zf = -nearFarPlanes[i + 1];
|
||||
updateNearFarPlanes(&cameraInfo.cullingProjection, cameraInfo.zn, cameraInfo.zf);
|
||||
|
||||
bool const canUseDepthClamp =
|
||||
!view.hasVSM() &&
|
||||
mIsDepthClampSupported &&
|
||||
engine.debug.shadowmap.depth_clamp;
|
||||
|
||||
auto shaderParameters = shadowMap.updateDirectional(engine,
|
||||
lightData, 0, cameraInfo, shadowMapInfo, sceneInfo);
|
||||
lightData, 0, cameraInfo, shadowMapInfo, sceneInfo,
|
||||
canUseDepthClamp);
|
||||
|
||||
if (shadowMap.hasVisibleShadows()) {
|
||||
const size_t shadowIndex = shadowMap.getShadowIndex();
|
||||
@@ -1069,8 +1034,78 @@ ShadowMapManager::CascadeSplits::CascadeSplits(Params const& params) noexcept
|
||||
: mSplitCount(params.cascadeCount + 1) {
|
||||
for (size_t s = 0; s < mSplitCount; s++) {
|
||||
mSplitsWs[s] = params.near + (params.far - params.near) * params.splitPositions[s];
|
||||
mSplitsCs[s] = mat4f::project(params.proj, float3(0.0f, 0.0f, mSplitsWs[s])).z;
|
||||
}
|
||||
}
|
||||
|
||||
void ShadowMapManager::updateNearFarPlanes(mat4f* projection,
|
||||
float nearDistance, float farDistance) noexcept {
|
||||
float const n = nearDistance;
|
||||
float const f = farDistance;
|
||||
|
||||
/*
|
||||
* Updating a projection matrix near and far planes:
|
||||
*
|
||||
* We assume that the near and far plane equations are of the form:
|
||||
* N = { 0, 0, 1, n }
|
||||
* F = { 0, 0, -1, -f }
|
||||
*
|
||||
* We also assume that the lower-left 2x2 of the projection is all 0:
|
||||
* P = A 0 C 0
|
||||
* 0 B D 0
|
||||
* 0 0 E F
|
||||
* 0 0 G H
|
||||
*
|
||||
* It result that we need to calculate E and F while leaving all other parameter unchanged.
|
||||
*
|
||||
* We know that:
|
||||
* with N, F the near/far normalized plane equation parameters
|
||||
* sn, sf arbitrary scale factors (they don't affect the planes)
|
||||
* m is the transpose of projection (see Frustum.cpp)
|
||||
*
|
||||
* sn * N == -m[3] - m[2]
|
||||
* sf * F == -m[3] + m[2]
|
||||
*
|
||||
* sn * N + sf * F == -2 * m[3]
|
||||
* sn * N - sf * F == -2 * m[2]
|
||||
*
|
||||
* sn * N.z + sf * F.z == -2 * m[3].z
|
||||
* sn * N.w + sf * F.w == -2 * m[3].w
|
||||
* sn * N.z - sf * F.z == -2 * m[2].z
|
||||
* sn * N.w - sf * F.w == -2 * m[2].w
|
||||
*
|
||||
* sn * N.z + sf * F.z == -2 * p[2].w
|
||||
* sn * N.w + sf * F.w == -2 * p[3].w
|
||||
* sn * N.z - sf * F.z == -2 * p[2].z
|
||||
* sn * N.w - sf * F.w == -2 * p[3].z
|
||||
*
|
||||
* We now need to solve for { p[2].z, p[3].z, sn, sf } :
|
||||
*
|
||||
* sn == -2 * (p[2].w * F.w - p[3].w * F.z) / (N.z * F.w - N.w * F.z)
|
||||
* sf == -2 * (p[2].w * N.w - p[3].w * N.z) / (F.z * N.w - F.w * N.z)
|
||||
* p[2].z == (sf * F.z - sn * N.z) / 2
|
||||
* p[3].z == (sf * F.w - sn * N.w) / 2
|
||||
*/
|
||||
auto& p = *projection;
|
||||
float4 const N = { 0, 0, 1, n }; // near plane equation
|
||||
float4 const F = { 0, 0, -1, -f }; // far plane equation
|
||||
// near plane equation scale factor
|
||||
float const sn = -2.0f * (p[2].w * F.w - p[3].w * F.z) / (N.z * F.w - N.w * F.z);
|
||||
// far plane equation scale factor
|
||||
float const sf = -2.0f * (p[2].w * N.w - p[3].w * N.z) / (F.z * N.w - F.w * N.z);
|
||||
// New values for the projection
|
||||
p[2].z = (sf * F.z - sn * N.z) * 0.5f;
|
||||
p[3].z = (sf * F.w - sn * N.w) * 0.5f;
|
||||
}
|
||||
|
||||
utils::FixedCapacityVector<Camera const*>
|
||||
ShadowMapManager::getDirectionalShadowCameras() const noexcept {
|
||||
if (!mInitialized) return {};
|
||||
auto const csm = getCascadedShadowMap();
|
||||
auto result = utils::FixedCapacityVector<Camera const*>::with_capacity(csm.size());
|
||||
for (ShadowMap const& sm : csm) {
|
||||
result.push_back(sm.hasVisibleShadows() ? sm.getDebugCamera() : nullptr);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
|
||||
#include <utils/BitmaskEnum.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Range.h>
|
||||
#include <utils/Slice.h>
|
||||
@@ -58,6 +59,7 @@
|
||||
|
||||
namespace filament {
|
||||
|
||||
class Camera;
|
||||
class FCamera;
|
||||
class FView;
|
||||
class FrameGraph;
|
||||
@@ -138,16 +140,16 @@ public:
|
||||
bool hasSpotShadows() const { return !mSpotShadowMapCount; }
|
||||
|
||||
// for debugging only
|
||||
FCamera const* getDirectionalShadowCamera() const noexcept {
|
||||
if (!mInitialized) return nullptr;
|
||||
return getShadowMap(0).getDebugCamera();
|
||||
}
|
||||
utils::FixedCapacityVector<Camera const*> getDirectionalShadowCameras() const noexcept;
|
||||
|
||||
private:
|
||||
explicit ShadowMapManager(FEngine& engine);
|
||||
|
||||
void terminate(FEngine& engine);
|
||||
|
||||
static void updateNearFarPlanes(math::mat4f* projection,
|
||||
float nearDistance, float farDistance) noexcept;
|
||||
|
||||
ShadowMapManager::ShadowTechnique updateCascadeShadowMaps(FEngine& engine,
|
||||
FView& view, CameraInfo cameraInfo, FScene::RenderableSoa& renderableData,
|
||||
FScene::LightSoa const& lightData, ShadowMap::SceneInfo sceneInfo) noexcept;
|
||||
@@ -186,27 +188,21 @@ private:
|
||||
constexpr static size_t SPLIT_COUNT = CONFIG_MAX_SHADOW_CASCADES + 1;
|
||||
|
||||
struct Params {
|
||||
math::mat4f proj;
|
||||
float near = 0.0f;
|
||||
float far = 0.0f;
|
||||
size_t cascadeCount = 1;
|
||||
uint32_t cascadeCount = 1;
|
||||
std::array<float, SPLIT_COUNT> splitPositions = { 0.0f };
|
||||
};
|
||||
|
||||
explicit CascadeSplits(Params const& params) noexcept;
|
||||
|
||||
// Split positions in world-space.
|
||||
const float* beginWs() const { return mSplitsWs; }
|
||||
const float* endWs() const { return mSplitsWs + mSplitCount; }
|
||||
|
||||
// Split positions in clip-space.
|
||||
const float* beginCs() const { return mSplitsCs; }
|
||||
const float* endCs() const { return mSplitsCs + mSplitCount; }
|
||||
const float* begin() const { return mSplitsWs; }
|
||||
const float* end() const { return mSplitsWs + mSplitCount; }
|
||||
|
||||
private:
|
||||
float mSplitsWs[SPLIT_COUNT];
|
||||
float mSplitsCs[SPLIT_COUNT];
|
||||
size_t mSplitCount;
|
||||
uint32_t mSplitCount;
|
||||
};
|
||||
|
||||
// Atlas requirements, updated in ShadowMapManager::update(),
|
||||
@@ -236,6 +232,7 @@ private:
|
||||
ShadowMapCacheContainer mShadowMapCache;
|
||||
uint32_t mDirectionalShadowMapCount = 0;
|
||||
uint32_t mSpotShadowMapCount = 0;
|
||||
bool const mIsDepthClampSupported;
|
||||
bool mInitialized = false;
|
||||
|
||||
ShadowMap& getShadowMap(size_t index) noexcept {
|
||||
@@ -248,10 +245,14 @@ private:
|
||||
}
|
||||
|
||||
utils::Slice<ShadowMap> getCascadedShadowMap() noexcept {
|
||||
ShadowMap* const p = &getShadowMap(0);
|
||||
ShadowMap const* const p = &getShadowMap(0);
|
||||
return { p, mDirectionalShadowMapCount };
|
||||
}
|
||||
|
||||
utils::Slice<ShadowMap> getCascadedShadowMap() const noexcept {
|
||||
return const_cast<ShadowMapManager*>(this)->getCascadedShadowMap();
|
||||
}
|
||||
|
||||
utils::Slice<ShadowMap> getSpotShadowMaps() noexcept {
|
||||
ShadowMap* const p = &getShadowMap(CONFIG_MAX_SHADOW_CASCADES);
|
||||
return { p, mSpotShadowMapCount };
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
|
||||
#include "details/View.h"
|
||||
#include "filament/View.h"
|
||||
|
||||
|
||||
namespace filament {
|
||||
|
||||
@@ -71,8 +73,8 @@ const char* View::getName() const noexcept {
|
||||
return downcast(this)->getName();
|
||||
}
|
||||
|
||||
Camera const* View::getDirectionalShadowCamera() const noexcept {
|
||||
return downcast(this)->getDirectionalShadowCamera();
|
||||
utils::FixedCapacityVector<Camera const*> View::getDirectionalShadowCameras() const noexcept {
|
||||
return downcast(this)->getDirectionalShadowCameras();
|
||||
}
|
||||
|
||||
void View::setShadowingEnabled(bool enabled) noexcept {
|
||||
@@ -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
|
||||
|
||||
@@ -28,12 +28,6 @@
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#ifndef NDEBUG
|
||||
# define DEBUG_PROPERTIES_WRITABLE true
|
||||
#else
|
||||
# define DEBUG_PROPERTIES_WRITABLE false
|
||||
#endif
|
||||
|
||||
using namespace filament::math;
|
||||
using namespace utils;
|
||||
|
||||
@@ -79,17 +73,15 @@ bool FDebugRegistry::hasProperty(const char* name) const noexcept {
|
||||
|
||||
template<typename T>
|
||||
bool FDebugRegistry::setProperty(const char* name, T v) noexcept {
|
||||
if constexpr (DEBUG_PROPERTIES_WRITABLE) {
|
||||
auto info = getPropertyInfo(name);
|
||||
T* const addr = static_cast<T*>(info.first);
|
||||
if (addr) {
|
||||
auto old = *addr;
|
||||
*addr = v;
|
||||
if (info.second && old != v) {
|
||||
info.second();
|
||||
}
|
||||
return true;
|
||||
auto info = getPropertyInfo(name);
|
||||
T* const addr = static_cast<T*>(info.first);
|
||||
if (addr) {
|
||||
auto old = *addr;
|
||||
*addr = v;
|
||||
if (info.second && old != v) {
|
||||
info.second();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -118,19 +110,32 @@ template bool FDebugRegistry::getProperty<float2>(const char* name, float2* v) c
|
||||
template bool FDebugRegistry::getProperty<float3>(const char* name, float3* v) const noexcept;
|
||||
template bool FDebugRegistry::getProperty<float4>(const char* name, float4* v) const noexcept;
|
||||
|
||||
void FDebugRegistry::registerDataSource(std::string_view name,
|
||||
bool FDebugRegistry::registerDataSource(std::string_view name,
|
||||
void const* data, size_t count) noexcept {
|
||||
auto& dataSourceMap = mDataSourceMap;
|
||||
if (dataSourceMap.find(name) == dataSourceMap.end()) {
|
||||
bool const found = dataSourceMap.find(name) == dataSourceMap.end();
|
||||
if (found) {
|
||||
dataSourceMap[name] = { data, count };
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
void FDebugRegistry::registerDataSource(std::string_view name,
|
||||
bool FDebugRegistry::registerDataSource(std::string_view name,
|
||||
utils::Invocable<DataSource()>&& creator) noexcept {
|
||||
mDataSourceCreatorMap[name] = std::move(creator);
|
||||
auto& dataSourceCreatorMap = mDataSourceCreatorMap;
|
||||
bool const found = dataSourceCreatorMap.find(name) == dataSourceCreatorMap.end();
|
||||
if (found) {
|
||||
dataSourceCreatorMap[name] = std::move(creator);
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
void FDebugRegistry::unregisterDataSource(std::string_view name) noexcept {
|
||||
mDataSourceCreatorMap.erase(name);
|
||||
mDataSourceMap.erase(name);
|
||||
}
|
||||
|
||||
|
||||
DebugRegistry::DataSource FDebugRegistry::getDataSource(const char* name) const noexcept {
|
||||
std::string_view const key{ name };
|
||||
auto& dataSourceMap = mDataSourceMap;
|
||||
|
||||
@@ -101,12 +101,14 @@ public:
|
||||
}
|
||||
|
||||
// registers a DataSource directly
|
||||
void registerDataSource(std::string_view name, void const* data, size_t count) noexcept;
|
||||
bool registerDataSource(std::string_view name, void const* data, size_t count) noexcept;
|
||||
|
||||
// registers a DataSource lazily
|
||||
void registerDataSource(std::string_view name,
|
||||
bool registerDataSource(std::string_view name,
|
||||
utils::Invocable<DataSource()>&& creator) noexcept;
|
||||
|
||||
void unregisterDataSource(std::string_view name) noexcept;
|
||||
|
||||
#if !defined(_MSC_VER)
|
||||
private:
|
||||
#endif
|
||||
|
||||
@@ -71,7 +71,7 @@ struct Engine::BuilderDetails {
|
||||
FeatureLevel mFeatureLevel = FeatureLevel::FEATURE_LEVEL_1;
|
||||
void* mSharedContext = nullptr;
|
||||
bool mPaused = false;
|
||||
static Config validateConfig(const Config* pConfig) noexcept;
|
||||
static Config validateConfig(Config config) noexcept;
|
||||
};
|
||||
|
||||
Engine* FEngine::create(Engine::Builder const& builder) {
|
||||
@@ -163,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();
|
||||
@@ -262,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)
|
||||
@@ -425,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);
|
||||
@@ -440,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!";
|
||||
@@ -460,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
|
||||
@@ -1195,6 +1197,22 @@ bool FEngine::isValid(const FInstanceBuffer* p) const {
|
||||
return isValid(p, mInstanceBuffers);
|
||||
}
|
||||
|
||||
size_t FEngine::getBufferObjectCount() const noexcept { return mBufferObjects.size(); }
|
||||
size_t FEngine::getViewCount() const noexcept { return mViews.size(); }
|
||||
size_t FEngine::getSceneCount() const noexcept { return mScenes.size(); }
|
||||
size_t FEngine::getSwapChainCount() const noexcept { return mSwapChains.size(); }
|
||||
size_t FEngine::getStreamCount() const noexcept { return mStreams.size(); }
|
||||
size_t FEngine::getIndexBufferCount() const noexcept { return mIndexBuffers.size(); }
|
||||
size_t FEngine::getSkinningBufferCount() const noexcept { return mSkinningBuffers.size(); }
|
||||
size_t FEngine::getMorphTargetBufferCount() const noexcept { return mMorphTargetBuffers.size(); }
|
||||
size_t FEngine::getInstanceBufferCount() const noexcept { return mInstanceBuffers.size(); }
|
||||
size_t FEngine::getVertexBufferCount() const noexcept { return mVertexBuffers.size(); }
|
||||
size_t FEngine::getIndirectLightCount() const noexcept { return mIndirectLights.size(); }
|
||||
size_t FEngine::getMaterialCount() const noexcept { return mMaterials.size(); }
|
||||
size_t FEngine::getTextureCount() const noexcept { return mTextures.size(); }
|
||||
size_t FEngine::getSkyboxeCount() const noexcept { return mSkyboxes.size(); }
|
||||
size_t FEngine::getColorGradingCount() const noexcept { return mColorGradings.size(); }
|
||||
size_t FEngine::getRenderTargetCount() const noexcept { return mRenderTargets.size(); }
|
||||
|
||||
void* FEngine::streamAlloc(size_t size, size_t alignment) noexcept {
|
||||
// we allow this only for small allocations
|
||||
@@ -1284,7 +1302,7 @@ Engine::Builder& Engine::Builder::platform(Platform* platform) noexcept {
|
||||
}
|
||||
|
||||
Engine::Builder& Engine::Builder::config(Engine::Config const* config) noexcept {
|
||||
mImpl->mConfig = BuilderDetails::validateConfig(config);
|
||||
mImpl->mConfig = config ? *config : Engine::Config{};
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -1312,22 +1330,15 @@ void Engine::Builder::build(Invocable<void(void*)>&& callback) const {
|
||||
#endif
|
||||
|
||||
Engine* Engine::Builder::build() const {
|
||||
mImpl->mConfig = BuilderDetails::validateConfig(mImpl->mConfig);
|
||||
return FEngine::create(*this);
|
||||
}
|
||||
|
||||
Engine::Config Engine::BuilderDetails::validateConfig(const Config* const pConfig) noexcept {
|
||||
Engine::Config Engine::BuilderDetails::validateConfig(Config config) noexcept {
|
||||
// Rule of thumb: perRenderPassArenaMB must be roughly 1 MB larger than perFrameCommandsMB
|
||||
constexpr uint32_t COMMAND_ARENA_OVERHEAD = 1;
|
||||
constexpr uint32_t CONCURRENT_FRAME_COUNT = 3;
|
||||
|
||||
Config config;
|
||||
if (!pConfig) {
|
||||
return config;
|
||||
}
|
||||
|
||||
// make sure to copy all the fields
|
||||
config = *pConfig;
|
||||
|
||||
// Use at least the defaults set by the build system
|
||||
config.minCommandBufferSizeMB = std::max(
|
||||
config.minCommandBufferSizeMB,
|
||||
|
||||
@@ -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;
|
||||
@@ -346,6 +351,23 @@ public:
|
||||
bool isValid(const FView* p) const;
|
||||
bool isValid(const FInstanceBuffer* p) const;
|
||||
|
||||
size_t getBufferObjectCount() const noexcept;
|
||||
size_t getViewCount() const noexcept;
|
||||
size_t getSceneCount() const noexcept;
|
||||
size_t getSwapChainCount() const noexcept;
|
||||
size_t getStreamCount() const noexcept;
|
||||
size_t getIndexBufferCount() const noexcept;
|
||||
size_t getSkinningBufferCount() const noexcept;
|
||||
size_t getMorphTargetBufferCount() const noexcept;
|
||||
size_t getInstanceBufferCount() const noexcept;
|
||||
size_t getVertexBufferCount() const noexcept;
|
||||
size_t getIndirectLightCount() const noexcept;
|
||||
size_t getMaterialCount() const noexcept;
|
||||
size_t getTextureCount() const noexcept;
|
||||
size_t getSkyboxeCount() const noexcept;
|
||||
size_t getColorGradingCount() const noexcept;
|
||||
size_t getRenderTargetCount() const noexcept;
|
||||
|
||||
void destroy(utils::Entity e);
|
||||
|
||||
bool isPaused() const noexcept;
|
||||
@@ -490,7 +512,7 @@ private:
|
||||
FTransformManager mTransformManager;
|
||||
FLightManager mLightManager;
|
||||
FCameraManager mCameraManager;
|
||||
ResourceAllocator* mResourceAllocator = nullptr;
|
||||
std::shared_ptr<ResourceAllocatorDisposer> mResourceAllocatorDisposer;
|
||||
HwVertexBufferInfoFactory mHwVertexBufferInfoFactory;
|
||||
|
||||
ResourceList<FBufferObject> mBufferObjects{ "BufferObject" };
|
||||
@@ -562,11 +584,14 @@ private:
|
||||
|
||||
std::thread::id mMainThreadId{};
|
||||
|
||||
bool mInitialized = false;
|
||||
|
||||
// Creation parameters
|
||||
Config mConfig;
|
||||
|
||||
public:
|
||||
// these are the debug properties used by FDebug. They're accessed directly by modules who need them.
|
||||
// These are the debug properties used by FDebug.
|
||||
// They're accessed directly by modules who need them.
|
||||
struct {
|
||||
struct {
|
||||
bool debug_directional_shadowmap = false;
|
||||
@@ -574,7 +599,8 @@ public:
|
||||
bool far_uses_shadowcasters = true;
|
||||
bool focus_shadowcasters = true;
|
||||
bool visualize_cascades = false;
|
||||
bool tightly_bound_scene = true;
|
||||
bool disable_light_frustum_align = false;
|
||||
bool depth_clamp = true;
|
||||
float dzn = -1.0f;
|
||||
float dzf = 1.0f;
|
||||
float display_shadow_texture_scale = 0.25f;
|
||||
|
||||
@@ -192,6 +192,22 @@ Material* Material::Builder::build(Engine& engine) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MaterialDomain materialDomain;
|
||||
materialParser->getMaterialDomain(&materialDomain);
|
||||
if (materialDomain == MaterialDomain::SURFACE) {
|
||||
StereoscopicType const engineStereoscopicType = engine.getConfig().stereoscopicType;
|
||||
StereoscopicType materialStereoscopicType = StereoscopicType::NONE;
|
||||
materialParser->getStereoscopicType(&materialStereoscopicType);
|
||||
if (materialStereoscopicType != engineStereoscopicType) {
|
||||
CString name;
|
||||
materialParser->getName(&name);
|
||||
slog.w << "The stereoscopic type in the compiled material '" << name.c_str_safe()
|
||||
<< "' is " << (int)materialStereoscopicType
|
||||
<< ", which is not compatiable with the engine's setting "
|
||||
<< (int)engineStereoscopicType << "." << io::endl;
|
||||
}
|
||||
}
|
||||
|
||||
return downcast(engine).createMaterial(*this, std::move(materialParser));
|
||||
}
|
||||
|
||||
@@ -200,8 +216,7 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder,
|
||||
: mIsDefaultMaterial(builder->mDefaultMaterial),
|
||||
mEngine(engine),
|
||||
mMaterialId(engine.getMaterialId()),
|
||||
mMaterialParser(std::move(materialParser))
|
||||
{
|
||||
mMaterialParser(std::move(materialParser)) {
|
||||
MaterialParser* const parser = mMaterialParser.get();
|
||||
|
||||
UTILS_UNUSED_IN_RELEASE bool const nameOk = parser->getName(&mName);
|
||||
@@ -873,7 +888,7 @@ void FMaterial::processSpecializationConstants(FEngine& engine, Material::Builde
|
||||
engine.getDriverApi().getMaxUniformBufferSize() / 16u);
|
||||
|
||||
bool const staticTextureWorkaround =
|
||||
engine.getDriverApi().isWorkaroundNeeded(Workaround::A8X_STATIC_TEXTURE_TARGET_ERROR);
|
||||
engine.getDriverApi().isWorkaroundNeeded(Workaround::METAL_STATIC_TEXTURE_TARGET_ERROR);
|
||||
|
||||
bool const powerVrShaderWorkarounds =
|
||||
engine.getDriverApi().isWorkaroundNeeded(Workaround::POWER_VR_SHADER_WORKAROUNDS);
|
||||
|
||||
@@ -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() {
|
||||
@@ -236,6 +242,39 @@ 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);
|
||||
|
||||
@@ -305,10 +344,14 @@ bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeN
|
||||
* to ignore the return value and render the frame anyway -- which is perfectly fine.
|
||||
* The remaining work will be done when the first render() call is made.
|
||||
*/
|
||||
auto beginFrameInternal = [this, appVsync]() {
|
||||
auto beginFrameInternal = [this, appVsync, swapChain]() {
|
||||
FEngine& engine = mEngine;
|
||||
FEngine::DriverApi& driver = engine.getDriverApi();
|
||||
|
||||
// we need to re-set mSwapChain here because if a frame was marked as "skip" but the
|
||||
// user ignored us, we'd still want mSwapChain to be set.
|
||||
mSwapChain = swapChain;
|
||||
|
||||
driver.beginFrame(
|
||||
appVsync.time_since_epoch().count(),
|
||||
mDisplayInfo.refreshRate == 0.0 ? 0 : int64_t(
|
||||
@@ -337,6 +380,10 @@ bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeN
|
||||
// so we need to delay this work until that happens.
|
||||
mBeginFrameInternal = beginFrameInternal;
|
||||
|
||||
// clear mSwapChain because the frame should be skipped (it will be re-set if the user
|
||||
// ignores the skip)
|
||||
mSwapChain = nullptr;
|
||||
|
||||
// we need to flush in this case, to make sure the tick() call is executed at some point
|
||||
engine.flush();
|
||||
|
||||
@@ -380,7 +427,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
|
||||
@@ -468,6 +515,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++;
|
||||
@@ -492,8 +542,6 @@ void FRenderer::renderStandaloneView(FView const* view) {
|
||||
void FRenderer::render(FView const* view) {
|
||||
SYSTRACE_CALL();
|
||||
|
||||
assert_invariant(mSwapChain);
|
||||
|
||||
if (UTILS_UNLIKELY(mBeginFrameInternal)) {
|
||||
// this should not happen, the user should not call render() if we returned false from
|
||||
// beginFrame(). But because this is allowed, we handle it gracefully.
|
||||
@@ -501,6 +549,9 @@ void FRenderer::render(FView const* view) {
|
||||
mBeginFrameInternal = {};
|
||||
}
|
||||
|
||||
// after beginFrame() is called, mSwapChain should be true
|
||||
assert_invariant(mSwapChain);
|
||||
|
||||
if (UTILS_LIKELY(view && view->getScene() && view->hasCamera())) {
|
||||
if (mViewRenderedCount) {
|
||||
// This is a good place to kick the GPU, since we've rendered a View before,
|
||||
@@ -747,7 +798,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();
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Allocator.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <math/vec4.h>
|
||||
|
||||
@@ -45,6 +46,7 @@
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include <stddef.h>
|
||||
@@ -52,6 +54,8 @@
|
||||
|
||||
namespace filament {
|
||||
|
||||
class ResourceAllocator;
|
||||
|
||||
namespace backend {
|
||||
class Driver;
|
||||
} // namespace backend
|
||||
@@ -87,6 +91,9 @@ public:
|
||||
|
||||
void setVsyncTime(uint64_t steadyClockTimeNano) noexcept;
|
||||
|
||||
// skip a frame
|
||||
void skipFrame(uint64_t vsyncSteadyClockTimeNano);
|
||||
|
||||
// start a frame
|
||||
bool beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeNano);
|
||||
|
||||
@@ -140,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;
|
||||
@@ -197,6 +212,7 @@ private:
|
||||
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"
|
||||
@@ -30,15 +31,17 @@
|
||||
#include "details/Skybox.h"
|
||||
|
||||
#include <filament/Exposure.h>
|
||||
#include <filament/DebugRegistry.h>
|
||||
#include <filament/TextureSampler.h>
|
||||
#include <filament/View.h>
|
||||
|
||||
#include <private/filament/UibStructs.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Profiler.h>
|
||||
#include <utils/Slice.h>
|
||||
#include <utils/Systrace.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Zip2Iterator.h>
|
||||
|
||||
#include <math/scalar.h>
|
||||
@@ -46,6 +49,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
|
||||
using namespace utils;
|
||||
|
||||
@@ -77,21 +81,29 @@ FView::FView(FEngine& engine)
|
||||
mPidController.setOutputDeadBand(-0.01f, 0.05f);
|
||||
|
||||
#ifndef NDEBUG
|
||||
debugRegistry.registerDataSource("d.view.frame_info",
|
||||
[this]() -> DebugRegistry::DataSource {
|
||||
assert_invariant(!mDebugFrameHistory);
|
||||
mDebugFrameHistory = std::make_unique<std::array<DebugRegistry::FrameHistory, 5*60>>();
|
||||
return { mDebugFrameHistory->data(), mDebugFrameHistory->size() };
|
||||
// This can fail if another view has already registered this data source
|
||||
mDebugState->owner = debugRegistry.registerDataSource("d.view.frame_info",
|
||||
[weak = std::weak_ptr<DebugState>(mDebugState)]() -> DebugRegistry::DataSource {
|
||||
// the View could have been destroyed by the time we do this
|
||||
auto const state = weak.lock();
|
||||
if (!state) {
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
// Lazily allocate the buffer for the debug data source, and mark this
|
||||
// data source as active. It can never go back to inactive.
|
||||
assert_invariant(!state->debugFrameHistory);
|
||||
state->active = true;
|
||||
state->debugFrameHistory =
|
||||
std::make_unique<std::array<DebugRegistry::FrameHistory, 5 * 60>>();
|
||||
return { state->debugFrameHistory->data(), state->debugFrameHistory->size() };
|
||||
});
|
||||
debugRegistry.registerProperty("d.view.pid.kp", &engine.debug.view.pid.kp);
|
||||
debugRegistry.registerProperty("d.view.pid.ki", &engine.debug.view.pid.ki);
|
||||
debugRegistry.registerProperty("d.view.pid.kd", &engine.debug.view.pid.kd);
|
||||
// default parameters for debugging UI
|
||||
engine.debug.view.pid.kp = 1.0f - std::exp(-1.0f / 8.0f);
|
||||
engine.debug.view.pid.ki = PID_CONTROLLER_Ki;
|
||||
engine.debug.view.pid.kd = PID_CONTROLLER_Kd;
|
||||
mPidController.setParallelGains(
|
||||
engine.debug.view.pid.kp, engine.debug.view.pid.ki, engine.debug.view.pid.kd);
|
||||
|
||||
if (UTILS_UNLIKELY(mDebugState->owner)) {
|
||||
// publish the properties (they will be initialized in the main loop)
|
||||
debugRegistry.registerProperty("d.view.pid.kp", &engine.debug.view.pid.kp);
|
||||
debugRegistry.registerProperty("d.view.pid.ki", &engine.debug.view.pid.ki);
|
||||
debugRegistry.registerProperty("d.view.pid.kd", &engine.debug.view.pid.kd);
|
||||
}
|
||||
#endif
|
||||
|
||||
// allocate UBOs
|
||||
@@ -118,13 +130,19 @@ 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);
|
||||
mFroxelizer.terminate(driver);
|
||||
|
||||
engine.getEntityManager().destroy(mFogEntity);
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (UTILS_UNLIKELY(mDebugState->owner)) {
|
||||
engine.getDebugRegistry().unregisterDataSource("d.view.frame_info");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void FView::setViewport(filament::Viewport const& viewport) noexcept {
|
||||
@@ -162,10 +180,20 @@ 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 {
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (UTILS_LIKELY(!mDebugState->active)) {
|
||||
// if we're not active, update the debug properties with the normal values
|
||||
// and use that for configuring the PID controller.
|
||||
engine.debug.view.pid.kp = 1.0f - std::exp(-frameRateOptions.scaleRate);
|
||||
engine.debug.view.pid.ki = PID_CONTROLLER_Ki;
|
||||
engine.debug.view.pid.kd = PID_CONTROLLER_Kd;
|
||||
}
|
||||
#endif
|
||||
|
||||
DynamicResolutionOptions const& options = mDynamicResolution;
|
||||
if (options.enabled) {
|
||||
if (!UTILS_UNLIKELY(info.valid)) {
|
||||
@@ -249,14 +277,15 @@ float2 FView::updateScale(FEngine& engine,
|
||||
|
||||
#ifndef NDEBUG
|
||||
// only for debugging...
|
||||
if (mDebugFrameHistory) {
|
||||
if (UTILS_UNLIKELY(mDebugState->active && mDebugState->debugFrameHistory)) {
|
||||
auto* const debugFrameHistory = mDebugState->debugFrameHistory.get();
|
||||
using namespace std::chrono;
|
||||
using duration_ms = duration<float, std::milli>;
|
||||
const float target = (1000.0f * float(frameRateOptions.interval)) / displayInfo.refreshRate;
|
||||
const float targetWithHeadroom = target * (1.0f - frameRateOptions.headRoomRatio);
|
||||
std::move(mDebugFrameHistory->begin() + 1,
|
||||
mDebugFrameHistory->end(), mDebugFrameHistory->begin());
|
||||
mDebugFrameHistory->back() = {
|
||||
std::move(debugFrameHistory->begin() + 1,
|
||||
debugFrameHistory->end(), debugFrameHistory->begin());
|
||||
debugFrameHistory->back() = {
|
||||
.target = target,
|
||||
.targetWithHeadroom = targetWithHeadroom,
|
||||
.frameTime = duration_cast<duration_ms>(info.frameTime).count(),
|
||||
@@ -1007,20 +1036,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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -201,9 +201,9 @@ public:
|
||||
|
||||
void setStereoscopicOptions(StereoscopicOptions const& options) noexcept;
|
||||
|
||||
FCamera const* getDirectionalShadowCamera() const noexcept {
|
||||
if (!mShadowMapManager) return nullptr;
|
||||
return mShadowMapManager->getDirectionalShadowCamera();
|
||||
utils::FixedCapacityVector<Camera const*> getDirectionalShadowCameras() const noexcept {
|
||||
if (!mShadowMapManager) return {};
|
||||
return mShadowMapManager->getDirectionalShadowCameras();
|
||||
}
|
||||
|
||||
void setRenderTarget(FRenderTarget* renderTarget) noexcept {
|
||||
@@ -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(
|
||||
@@ -576,7 +576,12 @@ private:
|
||||
}};
|
||||
|
||||
#ifndef NDEBUG
|
||||
std::unique_ptr<std::array<DebugRegistry::FrameHistory, 5*60>> mDebugFrameHistory;
|
||||
struct DebugState {
|
||||
std::unique_ptr<std::array<DebugRegistry::FrameHistory, 5*60>> debugFrameHistory{};
|
||||
bool owner = false;
|
||||
bool active = false;
|
||||
};
|
||||
std::shared_ptr<DebugState> mDebugState{ new DebugState };
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "FrameGraphPass.h"
|
||||
#include "FrameGraphRenderPass.h"
|
||||
#include "FrameGraphTexture.h"
|
||||
#include "ResourceAllocator.h"
|
||||
|
||||
#include "details/Engine.h"
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ struct FrameGraphRenderPass {
|
||||
Viewport viewport{};
|
||||
math::float4 clearColor{};
|
||||
uint8_t samples = 0; // # of samples (0 = unset, default)
|
||||
uint8_t layerCount = 0; // # of layer (# > 1 = multiview)
|
||||
uint8_t layerCount = 1; // # of layer (# > 1 = multiview)
|
||||
backend::TargetBufferFlags clearFlags{};
|
||||
backend::TargetBufferFlags discardStart{};
|
||||
};
|
||||
|
||||
@@ -190,10 +190,10 @@ void RenderPassNode::resolve() noexcept {
|
||||
minHeight = std::min(minHeight, h);
|
||||
maxHeight = std::max(maxHeight, h);
|
||||
}
|
||||
// additionally, clear implies discardStart
|
||||
rt.backend.params.flags.discardStart |= (
|
||||
rt.descriptor.clearFlags & rt.targetBufferFlags);
|
||||
}
|
||||
// additionally, clear implies discardStart
|
||||
rt.backend.params.flags.discardStart |= (
|
||||
rt.descriptor.clearFlags & rt.targetBufferFlags);
|
||||
|
||||
assert_invariant(minWidth == maxWidth);
|
||||
assert_invariant(minHeight == maxHeight);
|
||||
|
||||
@@ -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.1"
|
||||
spec.version = "1.54.0"
|
||||
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.1/filament-v1.53.1-ios.tgz" }
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.54.0/filament-v1.54.0-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;
|
||||
|
||||
@@ -92,6 +92,7 @@ enum UTILS_PUBLIC ChunkType : uint64_t {
|
||||
|
||||
MaterialVertexDomain = charTo64bitNum("MAT_VEDO"),
|
||||
MaterialInterpolation = charTo64bitNum("MAT_INTR"),
|
||||
MaterialStereoscopicType = charTo64bitNum("MAT_STER"),
|
||||
|
||||
DictionaryText = charTo64bitNum("DIC_TEXT"),
|
||||
DictionarySpirv = charTo64bitNum("DIC_SPIR"),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user