diff --git a/android/filament-android/src/main/cpp/Renderer.cpp b/android/filament-android/src/main/cpp/Renderer.cpp
index 4f7d88f319..405fddaf1c 100644
--- a/android/filament-android/src/main/cpp/Renderer.cpp
+++ b/android/filament-android/src/main/cpp/Renderer.cpp
@@ -153,3 +153,13 @@ Java_com_google_android_filament_Renderer_nSetDisplayInfo(JNIEnv*, jclass, jlong
.presentationDeadlineNanos = (uint64_t)presentationDeadlineNanos,
.vsyncOffsetNanos = (uint64_t)vsyncOffsetNanos });
}
+
+extern "C" JNIEXPORT void JNICALL
+Java_com_google_android_filament_Renderer_nSetFrameRateOptions(JNIEnv*, jclass,
+ jlong nativeRenderer, jfloat interval, jfloat headRoomRatio, jfloat scaleRate, jint history) {
+ Renderer *renderer = (Renderer *) nativeRenderer;
+ renderer->setFrameRateOptions({ .headRoomRatio = headRoomRatio,
+ .scaleRate = scaleRate,
+ .history = (uint8_t)history,
+ .interval = (uint8_t)interval });
+}
diff --git a/android/filament-android/src/main/cpp/View.cpp b/android/filament-android/src/main/cpp/View.cpp
index e7638988ce..2089fe1b0d 100644
--- a/android/filament-android/src/main/cpp/View.cpp
+++ b/android/filament-android/src/main/cpp/View.cpp
@@ -159,20 +159,15 @@ Java_com_google_android_filament_View_nGetDithering(JNIEnv*, jclass,
}
extern "C" JNIEXPORT void JNICALL
-Java_com_google_android_filament_View_nSetDynamicResolutionOptions(JNIEnv*,
- jclass, jlong nativeView, jboolean enabled, jboolean homogeneousScaling,
- jfloat targetFrameTimeMilli, jfloat headRoomRatio, jfloat scaleRate,
- jfloat minScale, jfloat maxScale, jint history, jint quality) {
+Java_com_google_android_filament_View_nSetDynamicResolutionOptions(JNIEnv*, jclass, jlong nativeView,
+ jboolean enabled, jboolean homogeneousScaling,
+ jfloat minScale, jfloat maxScale, jint quality) {
View* view = (View*)nativeView;
View::DynamicResolutionOptions options;
options.enabled = enabled;
options.homogeneousScaling = homogeneousScaling;
- options.targetFrameTimeMilli = targetFrameTimeMilli;
- options.headRoomRatio = headRoomRatio;
- options.scaleRate = scaleRate;
options.minScale = filament::math::float2{ minScale };
options.maxScale = filament::math::float2{ maxScale };
- options.history = (uint8_t)history;
options.quality = (View::QualityLevel)quality;
view->setDynamicResolutionOptions(options);
}
diff --git a/android/filament-android/src/main/java/com/google/android/filament/Renderer.java b/android/filament-android/src/main/java/com/google/android/filament/Renderer.java
index 5479563a4a..59c93c52c9 100644
--- a/android/filament-android/src/main/java/com/google/android/filament/Renderer.java
+++ b/android/filament-android/src/main/java/com/google/android/filament/Renderer.java
@@ -44,6 +44,8 @@ import java.nio.ReadOnlyBufferException;
public class Renderer {
private final Engine mEngine;
private long mNativeObject;
+ private DisplayInfo mDisplayInfo;
+ private FrameRateOptions mFrameRateOptions;
/**
* Information about the display this renderer is associated to
@@ -68,6 +70,50 @@ public class Renderer {
public long vsyncOffsetNanos = 0;
};
+ /**
+ * Use FrameRateOptions to set the desired frame rate and control how quickly the system
+ * reacts to GPU load changes.
+ *
+ * interval: desired frame interval in multiple of the refresh period, set in DisplayInfo
+ * (as 1 / DisplayInfo.refreshRate)
+ *
+ * The parameters below are relevant when some Views are using dynamic resolution scaling:
+ *
+ * headRoomRatio: additional headroom for the GPU as a ratio of the targetFrameTime.
+ * Useful for taking into account constant costs like post-processing or
+ * GPU drivers on different platforms.
+ * history: History size. higher values, tend to filter more (clamped to 30)
+ * scaleRate: rate at which the gpu load is adjusted to reach the target frame rate
+ * This value can be computed as 1 / N, where N is the number of frames
+ * needed to reach 64% of the target scale factor.
+ * Higher values make the dynamic resolution react faster.
+ *
+ * @see View.DynamicResolutionOptions
+ * @see Renderer.DisplayInfo
+ *
+ */
+ public static class FrameRateOptions {
+ /**
+ * Desired frame interval in unit of 1 / DisplayInfo.refreshRate.
+ */
+ public float interval = 1.0f / 60.0f;
+
+ /**
+ * Additional headroom for the GPU as a ratio of the targetFrameTime.
+ */
+ public float headRoomRatio = 0.0f;
+
+ /**
+ * Rate at which the scale will change to reach the target frame rate.
+ */
+ public float scaleRate = 0.125f;
+
+ /**
+ * History size. higher values, tend to filter more (clamped to 30).
+ */
+ public int history = 9;
+ }
+
/**
* Indicates that the dstSwapChain passed into {@link #copyFrame} should be
* committed after the frame has been copied.
@@ -103,7 +149,43 @@ public class Renderer {
* to accurately compute dynamic-resolution scaling and for frame-pacing.
*/
public void setDisplayInfo(@NonNull DisplayInfo info) {
- nSetDisplayInfo(getNativeObject(), info.refreshRate, info.presentationDeadlineNanos, info.vsyncOffsetNanos);
+ mDisplayInfo = info;
+ nSetDisplayInfo(getNativeObject(),
+ info.refreshRate, info.presentationDeadlineNanos, info.vsyncOffsetNanos);
+ }
+
+ /**
+ * Returns the DisplayInfo object set in {@link #setDisplayInfo} or a new instance otherwise.
+ * @return a DisplayInfo instance
+ */
+ @NonNull
+ public DisplayInfo getDisplayInfo() {
+ if (mDisplayInfo == null) {
+ mDisplayInfo = new DisplayInfo();
+ }
+ return mDisplayInfo;
+ }
+
+ /**
+ * Set options controlling the desired frame-rate.
+ */
+ public void setFrameRateOptions(@NonNull FrameRateOptions options) {
+ mFrameRateOptions = options;
+ nSetFrameRateOptions(getNativeObject(),
+ options.interval, options.headRoomRatio, options.scaleRate, options.history);
+ }
+
+ /**
+ * Returns the FrameRateOptions object set in {@link #setFrameRateOptions} or a new instance
+ * otherwise.
+ * @return a FrameRateOptions instance
+ */
+ @NonNull
+ public FrameRateOptions getFrameRateOptions() {
+ if (mFrameRateOptions == null) {
+ mFrameRateOptions = new FrameRateOptions();
+ }
+ return mFrameRateOptions;
}
/**
@@ -520,4 +602,6 @@ public class Renderer {
private static native void nResetUserTime(long nativeRenderer);
private static native void nSetDisplayInfo(long nativeRenderer,
float refreshRate, long presentationDeadlineNanos, long vsyncOffsetNanos);
+ private static native void nSetFrameRateOptions(long nativeRenderer,
+ float interval, float headRoomRatio, float scaleRate, int history);
}
diff --git a/android/filament-android/src/main/java/com/google/android/filament/View.java b/android/filament-android/src/main/java/com/google/android/filament/View.java
index 7c3fbd4fb9..a5de86fc9a 100644
--- a/android/filament-android/src/main/java/com/google/android/filament/View.java
+++ b/android/filament-android/src/main/java/com/google/android/filament/View.java
@@ -105,21 +105,6 @@ public class View {
*/
public boolean homogeneousScaling = false;
- /**
- * Desired frame time in milliseconds.
- */
- public float targetFrameTimeMilli = 1000.0f / 60.0f;
-
- /**
- * Additional headroom for the GPU as a ratio of the targetFrameTime.
- */
- public float headRoomRatio = 0.0f;
-
- /**
- * Rate at which the scale will change to reach the target frame rate.
- */
- public float scaleRate = 0.125f;
-
/**
* The minimum scale in X and Y this View should use.
*/
@@ -130,11 +115,6 @@ public class View {
*/
public float maxScale = 1.0f;
- /**
- * History size. higher values, tend to filter more (clamped to 30).
- */
- public int history = 9;
-
/**
* Upscaling quality. LOW: 1 bilinear taps, MEDIUM: 4 bilinear taps, HIGH: 9 bilinear taps.
* If minScale needs to be very low, it might help to use MEDIUM or HIGH here.
@@ -714,12 +694,8 @@ public class View {
nSetDynamicResolutionOptions(getNativeObject(),
options.enabled,
options.homogeneousScaling,
- options.targetFrameTimeMilli,
- options.headRoomRatio,
- options.scaleRate,
options.minScale,
options.maxScale,
- options.history,
options.quality.ordinal());
}
@@ -957,10 +933,7 @@ public class View {
private static native int nGetToneMapping(long nativeView);
private static native void nSetDithering(long nativeView, int dithering);
private static native int nGetDithering(long nativeView);
- private static native void nSetDynamicResolutionOptions(long nativeView,
- boolean enabled, boolean homogeneousScaling,
- float targetFrameTimeMilli, float headRoomRatio, float scaleRate,
- float minScale, float maxScale, int history, int quality);
+ private static native void nSetDynamicResolutionOptions(long nativeView, boolean enabled, boolean homogeneousScaling, float minScale, float maxScale, int quality);
private static native void nSetRenderQuality(long nativeView, int hdrColorBufferQuality);
private static native void nSetDynamicLightingOptions(long nativeView, float zLightNear, float zLightFar);
private static native void nSetPostProcessingEnabled(long nativeView, boolean enabled);
diff --git a/android/samples/sample-gltf-viewer/src/main/java/com/google/android/filament/gltf/MainActivity.kt b/android/samples/sample-gltf-viewer/src/main/java/com/google/android/filament/gltf/MainActivity.kt
index 2f0c5542be..c165be69b5 100644
--- a/android/samples/sample-gltf-viewer/src/main/java/com/google/android/filament/gltf/MainActivity.kt
+++ b/android/samples/sample-gltf-viewer/src/main/java/com/google/android/filament/gltf/MainActivity.kt
@@ -23,6 +23,7 @@ import android.view.Choreographer
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.SurfaceView
+import com.google.android.filament.View
import com.google.android.filament.utils.KtxLoader
import com.google.android.filament.utils.ModelViewer
import com.google.android.filament.utils.Utils
@@ -65,6 +66,12 @@ class MainActivity : Activity() {
val options = modelViewer.view.dynamicResolutionOptions
options.enabled = true;
modelViewer.view.dynamicResolutionOptions = options;
+
+ modelViewer.view.ambientOcclusion = View.AmbientOcclusion.SSAO
+
+ val bloom = modelViewer.view.bloomOptions
+ bloom.enabled = true;
+ modelViewer.view.bloomOptions = bloom
}
private fun createRenderables() {
diff --git a/filament/include/filament/Renderer.h b/filament/include/filament/Renderer.h
index ddd77a3ed6..0d564ebcc4 100644
--- a/filament/include/filament/Renderer.h
+++ b/filament/include/filament/Renderer.h
@@ -80,6 +80,35 @@ public:
uint64_t vsyncOffsetNanos = 0;
};
+ /**
+ * Use FrameRateOptions to set the desired frame rate and control how quickly the system
+ * reacts to GPU load changes.
+ *
+ * interval: desired frame interval in multiple of the refresh period, set in DisplayInfo
+ * (as 1 / DisplayInfo::refreshRate)
+ *
+ * The parameters below are relevant when some Views are using dynamic resolution scaling:
+ *
+ * headRoomRatio: additional headroom for the GPU as a ratio of the targetFrameTime.
+ * Useful for taking into account constant costs like post-processing or
+ * GPU drivers on different platforms.
+ * history: History size. higher values, tend to filter more (clamped to 30)
+ * scaleRate: rate at which the gpu load is adjusted to reach the target frame rate
+ * This value can be computed as 1 / N, where N is the number of frames
+ * needed to reach 64% of the target scale factor.
+ * Higher values make the dynamic resolution react faster.
+ *
+ * @see View::DynamicResolutionOptions
+ * @see Renderer::DisplayInfo
+ *
+ */
+ struct FrameRateOptions {
+ float headRoomRatio = 0.0f; //!< additional headroom for the GPU
+ float scaleRate = 0.125f; //!< rate at which the system reacts to load changes
+ uint8_t history = 9; //!< history size
+ uint8_t interval = 1; //!< desired frame interval in unit of 1.0 / DisplayInfo::refreshRate
+ };
+
/**
* Information about the display this Renderer is associated to. This information is needed
* to accurately compute dynamic-resolution scaling and for frame-pacing.
@@ -88,6 +117,13 @@ public:
*/
void setDisplayInfo(const DisplayInfo& info) noexcept;
+ /**
+ * Set options controlling the desired frame-rate.
+ *
+ * @param options
+ */
+ void setFrameRateOptions(FrameRateOptions const& options) noexcept;
+
/**
* Get the Engine that created this Renderer.
*
diff --git a/filament/include/filament/View.h b/filament/include/filament/View.h
index 9a2a76c3ec..3e881714e7 100644
--- a/filament/include/filament/View.h
+++ b/filament/include/filament/View.h
@@ -83,15 +83,6 @@ public:
* enabled: enable or disables dynamic resolution on a View
* homogeneousScaling: by default the system scales the major axis first. Set this to true
* to force homogeneous scaling.
- * scaleRate: rate at which the scale will change to reach the target frame rate
- * This value can be computed as 1 / N, where N is the number of frames
- * needed to reach 64% of the target scale factor.
- * Higher values make the dynamic resolution react faster.
- * targetFrameTimeMilli: desired frame time in milliseconds
- * headRoomRatio: additional headroom for the GPU as a ratio of the targetFrameTime.
- * Useful for taking into account constant costs like post-processing or
- * GPU drivers on different platforms.
- * history: History size. higher values, tend to filter more (clamped to 30)
* minScale: the minimum scale in X and Y this View should use
* maxScale: the maximum scale in X and Y this View should use
* quality: upscaling quality.
@@ -101,25 +92,13 @@ public:
* Dynamic resolution is only supported on platforms where the time to render
* a frame can be measured accurately. Dynamic resolution is currently only
* supported on Android.
+ *
+ * @see Renderer::FrameRateOptions
+ *
*/
struct DynamicResolutionOptions {
- DynamicResolutionOptions() = default;
-
- DynamicResolutionOptions(bool enabled, float scaleRate,
- math::float2 minScale, math::float2 maxScale)
- : minScale(minScale), maxScale(maxScale),
- scaleRate(scaleRate), enabled(enabled) {
- // this one exists for backward compatibility
- }
-
- explicit DynamicResolutionOptions(bool enabled) : enabled(enabled) { }
-
math::float2 minScale = math::float2(0.5f); //!< minimum scale factors in x and y
math::float2 maxScale = math::float2(1.0f); //!< maximum scale factors in x and y
- float scaleRate = 0.125f; //!< rate at which the scale will change
- float targetFrameTimeMilli = 1000.0f / 60.0f; //!< desired frame time, or budget.
- float headRoomRatio = 0.0f; //!< additional headroom for the GPU
- uint8_t history = 9; //!< history size
bool enabled = false; //!< enable or disable dynamic resolution
bool homogeneousScaling = false; //!< set to true to force homogeneous scaling
QualityLevel quality = QualityLevel::LOW; //!< Upscaling quality
diff --git a/filament/src/FrameInfo.cpp b/filament/src/FrameInfo.cpp
index 89084725b2..5a4fb88713 100644
--- a/filament/src/FrameInfo.cpp
+++ b/filament/src/FrameInfo.cpp
@@ -26,6 +26,17 @@ namespace filament {
using namespace utils;
using namespace details;
+namespace details {
+// this is to avoid a call to memmove
+template
+static inline
+void move_backward(InputIterator first, InputIterator last, OutputIterator result) {
+ while (first != last) {
+ *--result = *--last;
+ }
+}
+} // namespace details
+
FrameInfoManager::FrameInfoManager(FEngine& engine) : mEngine(engine) {
backend::DriverApi& driver = mEngine.getDriverApi();
for (auto& query : mQueries) {
@@ -42,14 +53,16 @@ void FrameInfoManager::terminate() {
}
}
-void FrameInfoManager::beginFrame(uint32_t frameId) {
+void FrameInfoManager::beginFrame(Config const& config, uint32_t frameId) {
backend::DriverApi& driver = mEngine.getDriverApi();
driver.beginTimerQuery(mQueries[mIndex]);
uint64_t elapsed = 0;
if (driver.getTimerQueryValue(mQueries[mLast], &elapsed)) {
mLast = (mLast + 1) % POOL_COUNT;
+ // convertion to our duration happens here
mFrameTime = std::chrono::duration(elapsed);
}
+ update(config,mFrameTime);
}
void FrameInfoManager::endFrame() {
@@ -58,5 +71,48 @@ void FrameInfoManager::endFrame() {
mIndex = (mIndex + 1) % POOL_COUNT;
}
+void FrameInfoManager::update(Config const& config, FrameInfoManager::duration lastFrameTime) {
+ const float kFeedbackConstant = (1.0f - std::exp(-config.oneOverTau));
+
+ // keep an history of frame times
+ auto& history = mFrameTimeHistory;
+
+ // this is like doing { pop_back(); push_front(); }
+ details::move_backward(history.begin(), history.end() - 1, history.end());
+ history[0].frameTime = lastFrameTime;
+
+ mFrameTimeHistorySize = std::min(++mFrameTimeHistorySize, size_t(MAX_FRAMETIME_HISTORY));
+ if (UTILS_UNLIKELY(mFrameTimeHistorySize < 3)) {
+ // not enough history to do anything usefull
+ history[0].valid = false;
+ return;
+ }
+
+ // apply a median filter to get a good representation of the frame time of the last
+ // N frames.
+ std::array median; // NOLINT -- it's initialized below
+ size_t size = std::min(mFrameTimeHistorySize, std::min(config.historySize, median.size()));
+ for (size_t i = 0; i < size; ++i) {
+ median[i] = history[i].frameTime;
+ }
+ std::sort(median.begin(), median.begin() + size);
+ duration denoisedFrameTime = median[size / 2];
+
+ history[0].denoisedFrameTime = denoisedFrameTime;
+
+ // how much we need to scale the current workload to fit in our target, at this instant
+ const float targetWithHeadroom = config.targetFrameTime * (1.0f - config.headRoomRatio);
+ const float workload = denoisedFrameTime.count() / targetWithHeadroom;
+ history[0].workLoad = workload;
+ history[0].smoothedWorkLoad = history[1].smoothedWorkLoad +
+ kFeedbackConstant * (workload - history[1].smoothedWorkLoad);
+ history[0].valid = true;
+
+// slog.d << history[0].frameTime.count() << ", "
+// << history[0].denoisedFrameTime.count() << ", "
+// << history[0].workLoad << ", "
+// << history[0].smoothedWorkLoad << io::endl;
+}
+
} // namespace filament
diff --git a/filament/src/FrameInfo.h b/filament/src/FrameInfo.h
index d8b3b2f5dd..85cd7794d7 100644
--- a/filament/src/FrameInfo.h
+++ b/filament/src/FrameInfo.h
@@ -21,6 +21,7 @@
#include "backend/Handle.h"
+#include
#include
#include
@@ -31,28 +32,54 @@ namespace details {
class FEngine;
} // namespace details
+struct FrameInfo {
+ using duration = std::chrono::duration;
+ duration frameTime{}; // frame period
+ duration denoisedFrameTime{}; // frame period (median filter)
+ float workLoad{}; // instant workload (from denoised frame time)
+ float smoothedWorkLoad{}; // filtered workload
+ bool valid = false;
+};
+
class FrameInfoManager {
static constexpr size_t POOL_COUNT = 8;
+ static constexpr size_t MAX_FRAMETIME_HISTORY = 32u;
public:
- using duration = std::chrono::duration;
+ using duration = FrameInfo::duration;
+
+ struct Config {
+ float targetFrameTime;
+ float headRoomRatio;
+ float oneOverTau;
+ size_t historySize;
+ };
explicit FrameInfoManager(details::FEngine& engine);
~FrameInfoManager() noexcept;
void terminate();
- void beginFrame(uint32_t frameId); // call this immediately after "make current"
+ void beginFrame(Config const& config, uint32_t frameId); // call this immediately after "make current"
void endFrame(); // call this immediately before "swap buffers"
- duration getLastFrameTime() const noexcept {
- return mFrameTime;
+ FrameInfo const& getLastFrameInfo() const {
+ return mFrameTimeHistory[0];
}
+ duration getLastFrameTime() const noexcept {
+ return getLastFrameInfo().frameTime;
+ }
+
+
private:
+ void update(Config const& config, duration lastFrameTime);
details::FEngine& mEngine;
backend::Handle mQueries[POOL_COUNT];
duration mFrameTime{};
uint32_t mIndex = 0;
uint32_t mLast = 0;
+
+ std::array mFrameTimeHistory;
+ size_t mFrameTimeHistorySize = 0;
};
diff --git a/filament/src/Renderer.cpp b/filament/src/Renderer.cpp
index f8e233ad7c..d75964d7c8 100644
--- a/filament/src/Renderer.cpp
+++ b/filament/src/Renderer.cpp
@@ -195,7 +195,7 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) {
bool dithering = view.getDithering() == View::Dithering::TEMPORAL;
bool fxaa = view.getAntiAliasing() == View::AntiAliasing::FXAA;
uint8_t msaa = view.getSampleCount();
- float2 scale = view.updateScale(mFrameInfoManager.getLastFrameTime());
+ float2 scale = view.updateScale(mFrameInfoManager.getLastFrameInfo());
const View::QualityLevel upscalingQuality = view.getDynamicResolutionOptions().quality;
auto aoOptions = view.getAmbientOcclusionOptions();
if (!hasPostProcess) {
@@ -695,8 +695,8 @@ bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeN
// get the timestamp as soon as possible
using namespace std::chrono;
const steady_clock::time_point now{ steady_clock::now() };
- const steady_clock::time_point vsync{steady_clock::duration(vsyncSteadyClockTimeNano) };
- const time_point vsyncTp(vsyncSteadyClockTimeNano ? vsync : now);
+ const steady_clock::time_point userVsync{ steady_clock::duration(vsyncSteadyClockTimeNano) };
+ const time_point appVsync(vsyncSteadyClockTimeNano ? userVsync : now);
mFrameId++;
@@ -715,7 +715,7 @@ bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeN
// NOTE: this makes synchronous calls to the driver
driver.updateStreams(&driver);
- driver.beginFrame(vsyncTp.time_since_epoch().count(), mFrameId, callback, user);
+ driver.beginFrame(appVsync.time_since_epoch().count(), mFrameId, callback, user);
if (!mFrameSkipper.beginFrame()) {
driver.endFrame(mFrameId);
@@ -725,10 +725,49 @@ bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeN
// This need to occur after the backend beginFrame() because some backends need to start
// a command buffer before creating a fence.
- mFrameInfoManager.beginFrame(mFrameId);
+ mFrameInfoManager.beginFrame({
+ .targetFrameTime = float(mFrameRateOptions.interval) / mDisplayInfo.refreshRate,
+ .headRoomRatio = mFrameRateOptions.headRoomRatio,
+ .oneOverTau = mFrameRateOptions.scaleRate,
+ .historySize = mFrameRateOptions.history
+ }, mFrameId);
+
+#if 0 // work-in-progress
+ if (vsyncSteadyClockTimeNano) {
+ const size_t interval = mFrameRateOptions.interval; // user requested swap-interval;
+ const steady_clock::duration refreshPeriod(uint64_t(1e9 / mDisplayInfo.refreshRate));
+ const steady_clock::duration presentationDeadline(mDisplayInfo.presentationDeadlineNanos);
+ const steady_clock::duration vsyncOffset(mDisplayInfo.vsyncOffsetNanos);
+
+ // hardware vsync timestamp
+ steady_clock::time_point hwVsync = appVsync - vsyncOffset;
+
+ // compute our desired presentation time. We can't pick a desired presentation time
+ // that's too far, or we won't be able to dequeue buffers.
+ steady_clock::time_point desiredPresentationTime = hwVsync + 2 * interval * refreshPeriod;
+
+ // Compute the deadline. This deadline is when the GPU must be finished.
+ // The deadline has 1ms backed in it on Android.
+ steady_clock::time_point deadline = desiredPresentationTime - presentationDeadline;
+
+ // one important thing is to make sure that the deadline is comfortably later than
+ // when the gpu will finish, otherwise we'll have inconsistent latency/frames.
+
+ // TODO: evaluate if we can make it in time, and if not why.
+ // If the problem is cpu+gpu latency we can try to push the desired presentation time
+ // further away, but this has limits, as only 2 buffers are dequeuable.
+ // If the problem is the gpu is overwhelmed, then we need to
+ // - see if there is more headroom in dynamic resolution
+ // - or start skipping frames. Ideally lower the framerate too.
+
+ // presentation time is set to the middle of the period we're interested in
+ steady_clock::time_point presentationTime = desiredPresentationTime - refreshPeriod / 2;
+ driver.setPresentationTime(presentationTime.time_since_epoch().count());
+ }
+#endif
// latch the frame time
- std::chrono::duration time(vsync - mUserEpoch);
+ std::chrono::duration time(userVsync - mUserEpoch);
float h = float(time.count());
float l = float(time.count() - h);
mShaderUserTime = { h, l, 0, 0 };
@@ -883,4 +922,8 @@ void Renderer::setDisplayInfo(const DisplayInfo& info) noexcept {
upcast(this)->setDisplayInfo(info);
}
+void Renderer::setFrameRateOptions(FrameRateOptions const& options) noexcept {
+ upcast(this)->setFrameRateOptions(options);
+}
+
} // namespace filament
diff --git a/filament/src/View.cpp b/filament/src/View.cpp
index d08d7cc93a..8850f86670 100644
--- a/filament/src/View.cpp
+++ b/filament/src/View.cpp
@@ -118,24 +118,6 @@ void FView::setDynamicResolutionOptions(DynamicResolutionOptions const& options)
if (dynamicResolution.enabled) {
// if enabled, sanitize the parameters
- // History can't be more than 32 frames (~0.5s)
- dynamicResolution.history = std::min(dynamicResolution.history, uint8_t(MAX_FRAMETIME_HISTORY));
-
- // History must at least be 3 frames
- dynamicResolution.history = std::max(dynamicResolution.history, uint8_t(3));
-
- // can't ask more 240 fps
- dynamicResolution.targetFrameTimeMilli =
- std::max(dynamicResolution.targetFrameTimeMilli, 1000.0f / 240.0f);
-
- // can't ask less than 1 fps
- dynamicResolution.targetFrameTimeMilli =
- std::min(dynamicResolution.targetFrameTimeMilli, 1000.0f);
-
- // headroom can't be larger than frame time, or less than 0
- dynamicResolution.headRoomRatio = std::min(dynamicResolution.headRoomRatio, 1.0f);
- dynamicResolution.headRoomRatio = std::max(dynamicResolution.headRoomRatio, 0.0f);
-
// minScale cannot be 0 or negative
dynamicResolution.minScale = max(dynamicResolution.minScale, float2(1.0f / 1024.0f));
@@ -145,11 +127,6 @@ void FView::setDynamicResolutionOptions(DynamicResolutionOptions const& options)
// clamp maxScale to 2x because we're doing bilinear filtering, so super-sampling
// is not useful above that.
dynamicResolution.maxScale = min(dynamicResolution.maxScale, float2(2.0f));
-
- // reset the history, so we start from a known (and current) state
- mFrameTimeHistorySize = 0;
- mScale = 1.0f;
- mDynamicWorkloadScale = 1.0f;
}
}
@@ -157,58 +134,16 @@ void FView::setDynamicLightingOptions(float zLightNear, float zLightFar) noexcep
mFroxelizer.setOptions(zLightNear, zLightFar);
}
-// this is to avoid a call to memmove
-template
-static inline
-void move_backward(InputIterator first, InputIterator last, OutputIterator result) {
- while (first != last) {
- *--result = *--last;
- }
-}
-
-float2 FView::updateScale(duration frameTime) noexcept {
+float2 FView::updateScale(FrameInfo const& info) noexcept {
DynamicResolutionOptions const& options = mDynamicResolution;
if (options.enabled) {
-
- if (UTILS_UNLIKELY(frameTime.count() <= std::numeric_limits::epsilon())) {
+ if (!UTILS_UNLIKELY(info.valid)) {
mScale = 1.0f;
return mScale;
}
- // keep an history of frame times
- auto& history = mFrameTimeHistory;
-
- // this is like doing { pop_back(); push_front(); }
- details::move_backward(history.begin(), history.end() - 1, history.end());
- history.front() = frameTime;
- mFrameTimeHistorySize = std::min(++mFrameTimeHistorySize, size_t(MAX_FRAMETIME_HISTORY));
-
- if (UTILS_UNLIKELY(mFrameTimeHistorySize < 3)) {
- // don't make any decision if we don't have enough data
- mScale = 1.0f;
- return mScale;
- }
-
- // apply a median filter to get a good representation of the frame time of the last
- // N frames.
- std::array median; // NOLINT -- it's initialized below
- size_t size = std::min(mFrameTimeHistorySize, median.size());
- std::uninitialized_copy_n(history.begin(), size, median.begin());
- std::sort(median.begin(), median.begin() + size);
- duration filteredFrameTime = median[size / 2];
-
- // how much we need to scale the current workload to fit in our target, at this instant
- const float targetWithHeadroom = options.targetFrameTimeMilli * (1 - options.headRoomRatio);
- const float workloadScale = targetWithHeadroom / filteredFrameTime.count();
-
- // low-pass: y += b * (x - y)
- const float oneOverTau = options.scaleRate;
- const float x = mScale.x * mScale.y * workloadScale;
- mDynamicWorkloadScale += (1.0f - std::exp(-oneOverTau)) * (x - mDynamicWorkloadScale);
-
// scaling factor we need to apply on the whole surface
- const float scale = mDynamicWorkloadScale;
-
+ const float scale = (mScale.x * mScale.y) / info.smoothedWorkLoad;
const float w = mViewport.width;
const float h = mViewport.height;
if (scale < 1.0f && !options.homogeneousScaling) {
@@ -236,24 +171,22 @@ float2 FView::updateScale(duration frameTime) noexcept {
mScale = std::sqrt(scale);
}
- // now tweak the scaling factor to get multiples of 4 (to help quad-shading)
- mScale = (floor(mScale * float2{ w, h } / 4) * 4) / float2{ w, h };
+ // now tweak the scaling factor to get multiples of 8 (to help quad-shading)
+ // i.e. 8x8=64 fragments, to try to help with warp sizes.
+ mScale = (floor(mScale * float2{ w, h } / 8) * 8) / float2{ w, h };
// always clamp to the min/max scale range
mScale = clamp(mScale, options.minScale, options.maxScale);
//#define DEBUG_DYNAMIC_RESOLUTION
-#if !defined(NDEBUG) && defined(DEBUG_DYNAMIC_RESOLUTION)
+#if defined(DEBUG_DYNAMIC_RESOLUTION)
static int sLogCounter = 15;
if (!--sLogCounter) {
sLogCounter = 15;
- slog.d << frameTime.count()
- << ", " << filteredFrameTime.count()
- << ", " << workloadScale
- << ", " << mDynamicWorkloadScale
+ slog.d << info.denoisedFrameTime.count() * 1000.0f << " ms"
+ << ", " << info.smoothedWorkLoad
<< ", " << mScale.x
<< ", " << mScale.y
- << ", " << mScale.x * mScale.y
<< ", " << mViewport.width * mScale.x
<< ", " << mViewport.height * mScale.y
<< io::endl;
@@ -262,6 +195,7 @@ float2 FView::updateScale(duration frameTime) noexcept {
} else {
mScale = 1.0f;
}
+
return mScale;
}
diff --git a/filament/src/details/Renderer.h b/filament/src/details/Renderer.h
index a663a66823..d80f8d82cf 100644
--- a/filament/src/details/Renderer.h
+++ b/filament/src/details/Renderer.h
@@ -58,6 +58,8 @@ class ShadowMap;
* A concrete implementation of the Renderer Interface.
*/
class FRenderer : public Renderer {
+ static constexpr size_t MAX_FRAMETIME_HISTORY = 32u;
+
public:
explicit FRenderer(FEngine& engine);
~FRenderer() noexcept;
@@ -95,6 +97,24 @@ public:
mDisplayInfo = info;
}
+ void setFrameRateOptions(FrameRateOptions const& options) noexcept {
+ FrameRateOptions& frameRateOptions = mFrameRateOptions;
+ frameRateOptions = options;
+
+ // History can't be more than 32 frames (~0.5s)
+ frameRateOptions.history = std::min(frameRateOptions.history,
+ uint8_t(MAX_FRAMETIME_HISTORY));
+
+ // History must at least be 3 frames
+ frameRateOptions.history = std::max(frameRateOptions.history, uint8_t(3));
+
+ frameRateOptions.interval = std::max(uint8_t(1), frameRateOptions.interval);
+
+ // headroom can't be larger than frame time, or less than 0
+ frameRateOptions.headRoomRatio = std::min(frameRateOptions.headRoomRatio, 1.0f);
+ frameRateOptions.headRoomRatio = std::max(frameRateOptions.headRoomRatio, 0.0f);
+ }
+
private:
friend class Renderer;
using Command = RenderPass::Command;
@@ -159,6 +179,7 @@ private:
Epoch mUserEpoch;
math::float4 mShaderUserTime{};
DisplayInfo mDisplayInfo;
+ FrameRateOptions mFrameRateOptions;
// per-frame arena for this Renderer
LinearAllocatorArena& mPerRenderPassArena;
diff --git a/filament/src/details/View.h b/filament/src/details/View.h
index f1aa2941b9..5d565ca48b 100644
--- a/filament/src/details/View.h
+++ b/filament/src/details/View.h
@@ -21,6 +21,7 @@
#include "upcast.h"
+#include "FrameInfo.h"
#include "UniformBuffer.h"
#include "details/Allocators.h"
@@ -238,7 +239,7 @@ public:
return mHasPostProcessPass;
}
- math::float2 updateScale(std::chrono::duration frameTime) noexcept;
+ math::float2 updateScale(FrameInfo const& info) noexcept;
void setDynamicResolutionOptions(View::DynamicResolutionOptions const& options) noexcept;
@@ -338,8 +339,6 @@ public:
UniformBuffer& getShadowUniforms() const { return mShadowUb; }
private:
- static constexpr size_t MAX_FRAMETIME_HISTORY = 32u;
-
void prepareVisibleRenderables(utils::JobSystem& js,
Frustum const& frustum, FScene::RenderableSoa& renderableData) const noexcept;
@@ -376,12 +375,12 @@ private:
FCamera* mViewingCamera = nullptr;
CameraInfo mViewingCameraInfo;
- Frustum mCullingFrustum;
+ Frustum mCullingFrustum{};
mutable Froxelizer mFroxelizer;
Viewport mViewport;
- LinearColorA mClearColor;
+ LinearColorA mClearColor{};
bool mCulling = true;
bool mFrontFaceWindingInverted = false;
bool mClearTargetColor = true;
@@ -403,13 +402,8 @@ private:
BloomOptions mBloomOptions;
FogOptions mFogOptions;
- using duration = std::chrono::duration;
DynamicResolutionOptions mDynamicResolution;
- std::array mFrameTimeHistory;
- size_t mFrameTimeHistorySize = 0;
-
math::float2 mScale = 1.0f;
- float mDynamicWorkloadScale = 1.0f;
bool mIsDynamicResolutionSupported = false;
RenderQuality mRenderQuality;