Commit 2dbccf3e authored by Klaus Weidner's avatar Klaus Weidner Committed by Commit Bot

WebXR: add recommendedViewportScale

This uses an estimated GPU utilization ratio from ArCoreGl. The goal is
to reduce the recommended viewport scale if the web app is unable to hit
a target framerate due to excessive GPU work, but not do so if it's slow
due to excessive JS computations or similar where reducing the viewport
wouldn't help. This is opt-in, the scale only takes effect if the app
calls view.requestViewportScale(view.recommendedViewportScale) followed
by xrwebgllayer.getViewport(view).

(Dynamic viewport scaling is only available if WebXRIncubations is
enabled.)

Spec link for context:
https://immersive-web.github.io/webxr/#dom-xrview-recommendedviewportscale

Change-Id: I0a8598d8bd03f27fd1d2cc6c542ff79812825d74
Bug: 1133381
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2213113
Commit-Queue: Klaus Weidner <klausw@chromium.org>
Reviewed-by: default avatarSam McNally <sammc@chromium.org>
Reviewed-by: default avatarPiotr Bialecki <bialpio@chromium.org>
Cr-Commit-Position: refs/heads/master@{#816813}
parent 926cd142
...@@ -482,6 +482,9 @@ void ArCoreGl::GetFrameData( ...@@ -482,6 +482,9 @@ void ArCoreGl::GetFrameData(
frame_data->pose = std::move(pose); frame_data->pose = std::move(pose);
frame_data->time_delta = now - base::TimeTicks(); frame_data->time_delta = now - base::TimeTicks();
if (rendering_time_ratio_ > 0) {
frame_data->rendering_time_ratio = rendering_time_ratio_;
}
fps_meter_.AddFrame(now); fps_meter_.AddFrame(now);
TRACE_COUNTER1("gpu", "WebXR FPS", fps_meter_.GetFPS()); TRACE_COUNTER1("gpu", "WebXR FPS", fps_meter_.GetFPS());
...@@ -548,8 +551,9 @@ base::TimeDelta ArCoreGl::EstimatedArCoreFrameTime() { ...@@ -548,8 +551,9 @@ base::TimeDelta ArCoreGl::EstimatedArCoreFrameTime() {
// 30fps from ARCore and expecting a single-value range. Revisit this estimate // 30fps from ARCore and expecting a single-value range. Revisit this estimate
// (i.e. based on camera timestamp difference) when adding 60fps modes which // (i.e. based on camera timestamp difference) when adding 60fps modes which
// may result in ranges such as 30-60fps. // may result in ranges such as 30-60fps.
return base::TimeDelta::FromSecondsD(1.0f / float framerate_max = arcore_->GetTargetFramerateRange().max;
arcore_->GetTargetFramerateRange().min); DCHECK_GT(framerate_max, 0.0f);
return base::TimeDelta::FromSecondsD(1.0f / framerate_max);
} }
base::TimeDelta ArCoreGl::WaitTimeForArCoreUpdate() { base::TimeDelta ArCoreGl::WaitTimeForArCoreUpdate() {
...@@ -697,6 +701,21 @@ void ArCoreGl::GetRenderedFrameStats() { ...@@ -697,6 +701,21 @@ void ArCoreGl::GetRenderedFrameStats() {
average_render_time_.AddSample(completion_time - frame->time_copied); average_render_time_.AddSample(completion_time - frame->time_copied);
// Save a GPU load estimate for use in GetFrameData. This is somewhat
// arbitrary, use the most recent rendering time divided by the nominal frame
// time. If this is greater than 1.0, it's not possible to hit the target
// framerate and the application should reduce its workload.
// (Intentionally not using averages here since the blink side is expected
// to do its own smoothing when using this data.)
base::TimeDelta copied_to_completion = completion_time - frame->time_copied;
base::TimeDelta arcore_frametime = EstimatedArCoreFrameTime();
DCHECK(!arcore_frametime.is_zero());
rendering_time_ratio_ = copied_to_completion / arcore_frametime;
DVLOG(3) << __func__
<< ": rendering time ratio (%)=" << rendering_time_ratio_ * 100;
TRACE_COUNTER1("xr", "WebXR rendering time ratio (%)",
rendering_time_ratio_ * 100);
// Add Animating/Processing/Rendering async annotations to event traces. // Add Animating/Processing/Rendering async annotations to event traces.
// Trace IDs need to be unique. Since frame->index is an 8-bit wrapping value, // Trace IDs need to be unique. Since frame->index is an 8-bit wrapping value,
......
...@@ -264,6 +264,8 @@ class ArCoreGl : public mojom::XRFrameDataProvider, ...@@ -264,6 +264,8 @@ class ArCoreGl : public mojom::XRFrameDataProvider,
device::SlidingTimeDeltaAverage average_process_time_; device::SlidingTimeDeltaAverage average_process_time_;
device::SlidingTimeDeltaAverage average_render_time_; device::SlidingTimeDeltaAverage average_render_time_;
float rendering_time_ratio_ = 0.0f;
FPSMeter fps_meter_; FPSMeter fps_meter_;
mojo::Receiver<mojom::XRFrameDataProvider> frame_data_receiver_{this}; mojo::Receiver<mojom::XRFrameDataProvider> frame_data_receiver_{this};
......
...@@ -687,6 +687,14 @@ struct XRFrameData { ...@@ -687,6 +687,14 @@ struct XRFrameData {
// Hit test subscription results. Only present if the session supports // Hit test subscription results. Only present if the session supports
// environment integration. // environment integration.
XRHitTestSubscriptionResultsData? hit_test_subscription_results; XRHitTestSubscriptionResultsData? hit_test_subscription_results;
// If nonzero, an estimate of how much of the available render time budget
// was used for GPU rendering for the most recent measured frame. A value
// above 1.0 means that the application is dropping frames due to GPU load,
// and a value well below 1.0 means that GPU utilization is low. This is
// intended to be used as input for renderer-side adaptive viewport sizing.
// A value of zero means the ratio is unknown and must not be used.
float rendering_time_ratio;
}; };
// Used primarily in logging to indicate why a session was rejecting to aid // Used primarily in logging to indicate why a session was rejecting to aid
......
...@@ -76,6 +76,8 @@ blink_modules_sources("xr") { ...@@ -76,6 +76,8 @@ blink_modules_sources("xr") {
"xr_session.h", "xr_session.h",
"xr_session_event.cc", "xr_session_event.cc",
"xr_session_event.h", "xr_session_event.h",
"xr_session_viewport_scaler.cc",
"xr_session_viewport_scaler.h",
"xr_setlike.h", "xr_setlike.h",
"xr_space.cc", "xr_space.cc",
"xr_space.h", "xr_space.h",
......
...@@ -44,6 +44,7 @@ ...@@ -44,6 +44,7 @@
#include "third_party/blink/renderer/modules/xr/xr_reference_space.h" #include "third_party/blink/renderer/modules/xr/xr_reference_space.h"
#include "third_party/blink/renderer/modules/xr/xr_render_state.h" #include "third_party/blink/renderer/modules/xr/xr_render_state.h"
#include "third_party/blink/renderer/modules/xr/xr_session_event.h" #include "third_party/blink/renderer/modules/xr/xr_session_event.h"
#include "third_party/blink/renderer/modules/xr/xr_session_viewport_scaler.h"
#include "third_party/blink/renderer/modules/xr/xr_system.h" #include "third_party/blink/renderer/modules/xr/xr_system.h"
#include "third_party/blink/renderer/modules/xr/xr_transient_input_hit_test_source.h" #include "third_party/blink/renderer/modules/xr/xr_transient_input_hit_test_source.h"
#include "third_party/blink/renderer/modules/xr/xr_utils.h" #include "third_party/blink/renderer/modules/xr/xr_utils.h"
...@@ -1584,6 +1585,24 @@ void XRSession::UpdatePresentationFrameState( ...@@ -1584,6 +1585,24 @@ void XRSession::UpdatePresentationFrameState(
if (ended_) if (ended_)
return; return;
if (supports_viewport_scaling_) {
float gpu_load = frame_data->rendering_time_ratio;
base::Optional<double> scale = base::nullopt;
if (gpu_load > 0.0f) {
if (!viewport_scaler_) {
// Lazily create an instance of the viewport scaler on first use.
viewport_scaler_ = std::make_unique<XRSessionViewportScaler>();
}
viewport_scaler_->UpdateRenderingTimeRatio(gpu_load);
scale = viewport_scaler_->Scale();
DVLOG(3) << __func__ << ": gpu_load=" << gpu_load << " scale=" << *scale;
}
for (XRViewData* view : views_) {
view->SetRecommendedViewportScale(scale);
}
}
mojo_from_viewer_ = getPoseMatrix(frame_pose); mojo_from_viewer_ = getPoseMatrix(frame_pose);
DVLOG(2) << __func__ << " : mojo_from_viewer_ valid? " DVLOG(2) << __func__ << " : mojo_from_viewer_ valid? "
<< (mojo_from_viewer_ ? true : false); << (mojo_from_viewer_ ? true : false);
......
...@@ -49,6 +49,7 @@ class XRLightProbe; ...@@ -49,6 +49,7 @@ class XRLightProbe;
class XRReferenceSpace; class XRReferenceSpace;
class XRRenderState; class XRRenderState;
class XRRenderStateInit; class XRRenderStateInit;
class XRSessionViewportScaler;
class XRSpace; class XRSpace;
class XRSystem; class XRSystem;
class XRTransientInputHitTestOptionsInit; class XRTransientInputHitTestOptionsInit;
...@@ -566,6 +567,8 @@ class XRSession final ...@@ -566,6 +567,8 @@ class XRSession final
// Corresponds to mojo XRSession.supportsViewportScaling // Corresponds to mojo XRSession.supportsViewportScaling
bool supports_viewport_scaling_ = false; bool supports_viewport_scaling_ = false;
std::unique_ptr<XRSessionViewportScaler> viewport_scaler_;
// Indicates that this is a sensorless session which should only support the // Indicates that this is a sensorless session which should only support the
// identity reference space. // identity reference space.
bool sensorless_session_ = false; bool sensorless_session_ = false;
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/modules/xr/xr_session_viewport_scaler.h"
#include <algorithm>
#include <cmath>
#include "base/numerics/ranges.h"
namespace blink {
namespace {
// Minimum and maximum viewport scale factors.
constexpr float kMinScale = 0.25f;
constexpr float kMaxScale = 1.0f;
// With this scale step, the resulting scales include powers of 1/2:
// [1, 0.841, 0.707, 0.595, 0.5, 0.420, 0.354, 0.297, 0.25]
constexpr float kScaleStep = 0.840896415256f; // sqrt(sqrt(1/2))
// Thresholds for high/low load values to trigger a scale change.
constexpr float kLoadHigh = 1.25f;
constexpr float kLoadLow = 0.9f;
// Maximum change allowed for a single update. Helps avoid glitches for
// outliers.
constexpr float kMaxChange = 0.5f;
// Load average decay value, smaller values are smoother but react
// slower. Higher values react quicker but may oscillate.
// Must be between 0 and 1.
constexpr float kLoadDecay = 0.3f;
// A power of two used to round the floating point value to a certain number
// of significant bits. This ensures that scale values exactly equal the
// appropriate powers of 2 (1, 0.5, 0.25). We don't want rounding errors to
// result in a scale of 0.99999 instead of 1.0 after multiple iterations of
// scaling up and down.
constexpr float kRound = 65536.0f;
} // namespace
void XRSessionViewportScaler::ResetLoad() {
gpu_load_ = (kLoadHigh + kLoadLow) / 2;
}
void XRSessionViewportScaler::UpdateRenderingTimeRatio(float new_value) {
gpu_load_ += base::ClampToRange(kLoadDecay * (new_value - gpu_load_),
-kMaxChange, kMaxChange);
float old_scale = scale_;
if (gpu_load_ > kLoadHigh && scale_ > kMinScale) {
scale_ *= kScaleStep;
scale_ = round(scale_ * kRound) / kRound;
} else if (gpu_load_ < kLoadLow && scale_ < kMaxScale) {
scale_ /= kScaleStep;
scale_ = round(scale_ * kRound) / kRound;
}
scale_ = base::ClampToRange(scale_, kMinScale, kMaxScale);
if (scale_ != old_scale) {
ResetLoad();
}
}
} // namespace blink
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_SESSION_VIEWPORT_SCALER_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_SESSION_VIEWPORT_SCALER_H_
namespace blink {
class XRSessionViewportScaler final {
public:
XRSessionViewportScaler() { ResetLoad(); }
void UpdateRenderingTimeRatio(float value);
float Scale() { return scale_; }
void ResetLoad();
private:
float gpu_load_ = 1.0f;
float scale_ = 1.0f;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_SESSION_VIEWPORT_SCALER_H_
...@@ -157,7 +157,7 @@ XRRigidTransform* XRView::transform() const { ...@@ -157,7 +157,7 @@ XRRigidTransform* XRView::transform() const {
} }
base::Optional<double> XRView::recommendedViewportScale() const { base::Optional<double> XRView::recommendedViewportScale() const {
return base::nullopt; return view_data_->recommendedViewportScale();
} }
void XRView::requestViewportScale(base::Optional<double> scale) { void XRView::requestViewportScale(base::Optional<double> scale) {
...@@ -173,7 +173,7 @@ void XRView::Trace(Visitor* visitor) const { ...@@ -173,7 +173,7 @@ void XRView::Trace(Visitor* visitor) const {
} }
base::Optional<double> XRViewData::recommendedViewportScale() const { base::Optional<double> XRViewData::recommendedViewportScale() const {
return base::nullopt; return recommended_viewport_scale_;
} }
void XRViewData::requestViewportScale(base::Optional<double> scale) { void XRViewData::requestViewportScale(base::Optional<double> scale) {
......
...@@ -89,6 +89,9 @@ class MODULES_EXPORT XRViewData final : public GarbageCollected<XRViewData> { ...@@ -89,6 +89,9 @@ class MODULES_EXPORT XRViewData final : public GarbageCollected<XRViewData> {
} }
base::Optional<double> recommendedViewportScale() const; base::Optional<double> recommendedViewportScale() const;
void SetRecommendedViewportScale(base::Optional<double> scale) {
recommended_viewport_scale_ = scale;
}
void requestViewportScale(base::Optional<double> scale); void requestViewportScale(base::Optional<double> scale);
...@@ -111,6 +114,7 @@ class MODULES_EXPORT XRViewData final : public GarbageCollected<XRViewData> { ...@@ -111,6 +114,7 @@ class MODULES_EXPORT XRViewData final : public GarbageCollected<XRViewData> {
TransformationMatrix inv_projection_; TransformationMatrix inv_projection_;
TransformationMatrix head_from_eye_; TransformationMatrix head_from_eye_;
bool inv_projection_dirty_ = true; bool inv_projection_dirty_ = true;
base::Optional<double> recommended_viewport_scale_ = base::nullopt;
double requested_viewport_scale_ = 1.0; double requested_viewport_scale_ = 1.0;
double current_viewport_scale_ = 1.0; double current_viewport_scale_ = 1.0;
bool viewport_modifiable_ = false; bool viewport_modifiable_ = false;
......
...@@ -209,6 +209,8 @@ XRViewport* XRWebGLLayer::getViewport(XRView* view) { ...@@ -209,6 +209,8 @@ XRViewport* XRWebGLLayer::getViewport(XRView* view) {
view_data->SetCurrentViewportScale(view_data->RequestedViewportScale()); view_data->SetCurrentViewportScale(view_data->RequestedViewportScale());
viewports_dirty_ = true; viewports_dirty_ = true;
} }
TRACE_COUNTER1("xr", "XR viewport scale (%)",
view_data->CurrentViewportScale() * 100);
view_data->SetViewportModifiable(false); view_data->SetViewportModifiable(false);
return GetViewportForEye(view->EyeValue()); return GetViewportForEye(view->EyeValue());
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment