rework how dynamic resolution scaling is specified
- now the DynamicResolutionOptions don't specify the target frame rate, they only specify how to scale this given view - there is a new FrameRateOptions setting on Renderer, which is used to specify the desired target frame rate for the whole Renderer - the frame rate is now specified as a "frame interval" in units of the display frame period. - the display frame period is (indirectly) set via DisplayInfo. In other words, the use must set (and update) DisplayInfo properly (the default are reasonable, but assume 60 Hz). They must also set the desired interval (default is 1, which is probably what you want). Finally they must enable dynamic scaling per View, the defaults are also reasonable. Currently Renderer doesn't attempt (yet) to actually target the requested frame rate, so it's up to the caller to push frames at the desired speed. however, dynamic resolution, like before, will attempt to shrink work to fit the target.
This commit is contained in:
committed by
Mathias Agopian
parent
b2dd6683be
commit
746e273336
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 <code>dstSwapChain</code> 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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -26,6 +26,17 @@ namespace filament {
|
||||
using namespace utils;
|
||||
using namespace details;
|
||||
|
||||
namespace details {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
} // 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<uint64_t, std::nano>(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<duration, MAX_FRAMETIME_HISTORY> 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
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "backend/Handle.h"
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
|
||||
#include <assert.h>
|
||||
@@ -31,28 +32,54 @@ namespace details {
|
||||
class FEngine;
|
||||
} // namespace details
|
||||
|
||||
struct FrameInfo {
|
||||
using duration = std::chrono::duration<float>;
|
||||
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<float, std::milli>;
|
||||
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<backend::HwTimerQuery> mQueries[POOL_COUNT];
|
||||
duration mFrameTime{};
|
||||
uint32_t mIndex = 0;
|
||||
uint32_t mLast = 0;
|
||||
|
||||
std::array<FrameInfo, MAX_FRAMETIME_HISTORY> mFrameTimeHistory;
|
||||
size_t mFrameTimeHistorySize = 0;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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<steady_clock> vsyncTp(vsyncSteadyClockTimeNano ? vsync : now);
|
||||
const steady_clock::time_point userVsync{ steady_clock::duration(vsyncSteadyClockTimeNano) };
|
||||
const time_point<steady_clock> 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<double> time(vsync - mUserEpoch);
|
||||
std::chrono::duration<double> 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
|
||||
|
||||
@@ -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<class InputIterator, class OutputIterator>
|
||||
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<float>::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<duration, MAX_FRAMETIME_HISTORY> 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<float, std::milli> 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<float, std::milli>;
|
||||
DynamicResolutionOptions mDynamicResolution;
|
||||
std::array<duration, MAX_FRAMETIME_HISTORY> mFrameTimeHistory;
|
||||
size_t mFrameTimeHistorySize = 0;
|
||||
|
||||
math::float2 mScale = 1.0f;
|
||||
float mDynamicWorkloadScale = 1.0f;
|
||||
bool mIsDynamicResolutionSupported = false;
|
||||
|
||||
RenderQuality mRenderQuality;
|
||||
|
||||
Reference in New Issue
Block a user