[go: nahoru, domu]

blob: 0a820166c5340616ce3720cc895624e4a755fdc0 [file] [log] [blame]
// Copyright 2021 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 "android_webview/browser/gfx/begin_frame_source_webview.h"
#include "android_webview/browser/gfx/gpu_service_webview.h"
#include "android_webview/browser/gfx/hardware_renderer_viz.h"
#include "android_webview/browser/gfx/render_thread_manager.h"
#include "android_webview/browser/gfx/root_frame_sink.h"
#include "android_webview/browser/gfx/root_frame_sink_proxy.h"
#include "android_webview/browser/gfx/scoped_app_gl_state_restore.h"
#include "android_webview/browser/gfx/viz_compositor_thread_runner_webview.h"
#include "base/notreached.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "components/viz/common/quads/solid_color_draw_quad.h"
#include "components/viz/common/quads/surface_draw_quad.h"
#include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
#include "components/viz/test/compositor_frame_helpers.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_surface.h"
#include "ui/gl/init/gl_factory.h"
namespace android_webview {
namespace {
constexpr gfx::Size kFrameSize(100, 100);
constexpr viz::FrameSinkId kRootClientSinkId(1, 1);
constexpr viz::FrameSinkId kChildClientSinkId(2, 1);
void AppendSurfaceDrawQuad(viz::CompositorRenderPass& render_pass,
const viz::SurfaceId& child_id) {
viz::SharedQuadState* quad_state =
render_pass.CreateAndAppendSharedQuadState();
quad_state->quad_to_target_transform = gfx::Transform();
quad_state->quad_layer_rect = gfx::Rect(kFrameSize);
quad_state->visible_quad_layer_rect = gfx::Rect(kFrameSize);
quad_state->clip_rect = gfx::Rect(kFrameSize);
quad_state->opacity = 1.f;
viz::SurfaceDrawQuad* surface_quad =
render_pass.CreateAndAppendDrawQuad<viz::SurfaceDrawQuad>();
surface_quad->SetNew(quad_state, gfx::Rect(quad_state->quad_layer_rect),
gfx::Rect(quad_state->quad_layer_rect),
viz::SurfaceRange(absl::nullopt, child_id),
SK_ColorWHITE, /*stretch_content_to_fill_bounds=*/false);
}
void AppendSolidColorDrawQuad(viz::CompositorRenderPass& render_pass) {
viz::SharedQuadState* quad_state =
render_pass.CreateAndAppendSharedQuadState();
quad_state->quad_to_target_transform = gfx::Transform();
quad_state->quad_layer_rect = gfx::Rect(kFrameSize);
quad_state->visible_quad_layer_rect = gfx::Rect(kFrameSize);
quad_state->clip_rect = gfx::Rect(kFrameSize);
quad_state->opacity = 1.f;
viz::SolidColorDrawQuad* solid_color_quad =
render_pass.CreateAndAppendDrawQuad<viz::SolidColorDrawQuad>();
solid_color_quad->SetNew(quad_state, gfx::Rect(quad_state->quad_layer_rect),
gfx::Rect(quad_state->quad_layer_rect),
SK_ColorWHITE, /*force_anti_aliasing_off=*/false);
}
class VizClient : public viz::mojom::CompositorFrameSinkClient {
public:
VizClient(viz::FrameSinkId frame_sink_id,
int max_pending_frames,
int frame_rate)
: max_pending_frames_(max_pending_frames),
frame_interval_(base::Seconds(1) / frame_rate) {
support_ = std::make_unique<viz::CompositorFrameSinkSupport>(
this,
VizCompositorThreadRunnerWebView::GetInstance()->GetFrameSinkManager(),
frame_sink_id, false);
VizCompositorThreadRunnerWebView::GetInstance()
->GetFrameSinkManager()
->RegisterFrameSinkHierarchy(kRootClientSinkId, frame_sink_id);
local_surface_id_allocator_.GenerateId();
// We always need begin frames.
support_->SetNeedsBeginFrame(true);
}
~VizClient() override {
VizCompositorThreadRunnerWebView::GetInstance()
->GetFrameSinkManager()
->UnregisterFrameSinkHierarchy(kRootClientSinkId,
support_->frame_sink_id());
}
void SubmitFrameIfNeeded() {
if (last_begin_frame_args_.IsValid()) {
bool need_submit = false;
if (pending_frames_ < max_pending_frames_) {
if (last_submitted_time_.is_null()) {
last_submitted_time_ = last_begin_frame_args_.frame_time;
need_submit = true;
} else if (last_begin_frame_args_.frame_time >=
last_submitted_time_ + frame_interval_) {
last_submitted_time_ += frame_interval_;
need_submit = true;
}
}
// If we unsubscribed from begin frames then submit frame now regardless
// of our fps throttling.
if (!needs_begin_frames_) {
DCHECK(stop_submitting_frames_);
DCHECK(pending_frames_ < max_pending_frames_);
need_submit = true;
}
if (need_submit)
SubmitFrame(viz::BeginFrameAck(last_begin_frame_args_, true));
else
DidNotProduceFrame(viz::BeginFrameAck(last_begin_frame_args_, false));
}
last_begin_frame_args_ = viz::BeginFrameArgs();
}
viz::SurfaceId GetSurfaceId() {
return viz::SurfaceId(
support_->frame_sink_id(),
local_surface_id_allocator_.GetCurrentLocalSurfaceId());
}
viz::FrameTimingDetailsMap TakeFrameTimingDetails() {
for (const auto& feedback : support_->TakeFrameTimingDetailsMap()) {
DCHECK(!feedbacks_.contains(feedback.first));
feedbacks_[feedback.first] = feedback.second;
}
viz::FrameTimingDetailsMap result;
std::swap(result, feedbacks_);
return result;
}
size_t frames_submitted() { return frames_submitted_; }
void MakeNextFrameLast() { stop_submitting_frames_ = true; }
// viz::mojom::CompositorFrameSinkClient:
void DidReceiveCompositorFrameAck(
std::vector<viz::ReturnedResource> resources) override {
ReclaimResources(std::move(resources));
DCHECK_GT(pending_frames_, 0);
pending_frames_--;
}
void OnBeginFrame(const viz::BeginFrameArgs& args,
const viz::FrameTimingDetailsMap& feedbacks) override {
for (const auto& feedback : feedbacks) {
DCHECK(!feedbacks_.contains(feedback.first));
feedbacks_[feedback.first] = feedback.second;
}
DCHECK_GE(frame_interval_, args.interval);
if (!needs_begin_frames_) {
DidNotProduceFrame(viz::BeginFrameAck(args, false));
return;
}
last_begin_frame_args_ = args;
if (stop_submitting_frames_) {
needs_begin_frames_ = false;
support_->SetNeedsBeginFrame(false);
}
}
void OnBeginFramePausedChanged(bool paused) override {}
void ReclaimResources(std::vector<viz::ReturnedResource> resources) override {
// No resources in this test
DCHECK(resources.empty());
}
void OnCompositorFrameTransitionDirectiveProcessed(
uint32_t sequence_id) override {}
private:
void SubmitFrame(const viz::BeginFrameAck& ack) {
pending_frames_++;
frames_submitted_++;
auto frame =
viz::CompositorFrameBuilder()
.AddRenderPass(gfx::Rect(kFrameSize), gfx::Rect(kFrameSize))
.SetBeginFrameAck(viz::BeginFrameAck(last_begin_frame_args_, true))
.Build();
AppendSolidColorDrawQuad(*frame.render_pass_list.back());
frame.metadata.frame_token = ack.frame_id.sequence_number;
support_->SubmitCompositorFrame(
local_surface_id_allocator_.GetCurrentLocalSurfaceId(),
std::move(frame), absl::nullopt);
}
void DidNotProduceFrame(const viz::BeginFrameAck& ack) {
support_->DidNotProduceFrame(ack);
}
const int max_pending_frames_;
const base::TimeDelta frame_interval_;
viz::ParentLocalSurfaceIdAllocator local_surface_id_allocator_;
std::unique_ptr<viz::CompositorFrameSinkSupport> support_;
int pending_frames_ = 0;
size_t frames_submitted_ = 0;
bool stop_submitting_frames_ = false;
bool needs_begin_frames_ = true;
viz::BeginFrameArgs last_begin_frame_args_;
base::TimeTicks last_submitted_time_;
viz::FrameTimingDetailsMap feedbacks_;
};
struct PerFrameFlag {
PerFrameFlag(uint64_t bits) : bits(bits) {}
static PerFrameFlag AlwaysTrue() { return {static_cast<uint64_t>(-1)}; }
static PerFrameFlag AlwaysFalse() { return {static_cast<uint64_t>(0)}; }
bool at(int frame) const {
DCHECK_LT(frame, 64);
return (bits & (UINT64_C(1) << frame)) != 0;
}
bool IsAlways() const { return bits == static_cast<uint64_t>(-1); }
bool IsNever() const { return bits == 0; }
std::string ToString() {
if (IsNever())
return "Never";
if (IsAlways())
return "Always";
return "Random";
}
uint64_t bits;
};
enum class AlwaysDrawType {
// No draw happens unless we invalidate for client
kNone,
// Invalidate every frame (e.g if app invalidates or root client draws every
// frame).
kAlwaysInvalidate,
// Invalidate only for client, but draw every frame (e.g other views updated
// in app that intersect webview).
kAlwaysDraw
};
class InvalidateTest
: public testing::TestWithParam<
testing::tuple<PerFrameFlag, PerFrameFlag, AlwaysDrawType>>,
public viz::ExternalBeginFrameSourceClient,
public RootFrameSinkProxyClient {
public:
InvalidateTest()
: task_environment_(std::make_unique<base::test::TaskEnvironment>()) {
begin_frame_source_ = std::make_unique<viz::ExternalBeginFrameSource>(this);
root_frame_sink_proxy_ = std::make_unique<RootFrameSinkProxy>(
base::ThreadTaskRunnerHandle::Get(), this, begin_frame_source_.get());
root_frame_sink_proxy_->AddChildFrameSinkId(kRootClientSinkId);
GpuServiceWebView::GetInstance();
// For purpose of this test we don't care about RT/UI communication, so we
// use single thread for both as we want to control timing of two threads
// explicitly.
render_thread_manager_ = std::make_unique<RenderThreadManager>(
base::ThreadTaskRunnerHandle::Get());
surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size(100, 100));
DCHECK(surface_);
DCHECK(surface_->GetHandle());
context_ = gl::init::CreateGLContext(nullptr, surface_.get(),
gl::GLContextAttribs());
DCHECK(context_);
context_->MakeCurrent(surface_.get());
render_thread_manager_->SetRootFrameSinkGetterForTesting(
root_frame_sink_proxy_->GetRootFrameSinkCallback());
}
~InvalidateTest() override {
VizCompositorThreadRunnerWebView::GetInstance()->PostTaskAndBlock(
FROM_HERE, base::BindOnce(
[](std::unique_ptr<VizClient> client) {
// `client` leaves scope.
},
std::move(client_)));
render_thread_manager_->DestroyHardwareRendererOnRT(false, false);
}
// viz::ExternalBeginFrameSourceClient
void OnNeedsBeginFrames(bool needs_begin_frames) override {
needs_begin_frames_ = needs_begin_frames;
if (set_needs_begin_frames_closure_)
std::move(set_needs_begin_frames_closure_).Run();
}
// RootFrameSinkProxyClient
void Invalidate() override { did_invalidate_ = true; }
void ReturnResourcesFromViz(
viz::FrameSinkId frame_sink_id,
uint32_t layer_tree_frame_sink_id,
std::vector<viz::ReturnedResource> resources) override {
// no resources in this test
DCHECK(resources.empty());
}
protected:
std::unique_ptr<ChildFrame> CreateChildFrame(
std::unique_ptr<content::SynchronousCompositor::Frame> frame,
const viz::BeginFrameArgs& args,
bool invalidated) {
auto future =
base::MakeRefCounted<content::SynchronousCompositor::FrameFuture>();
future->SetFrame(std::move(frame));
auto child_frame = std::make_unique<ChildFrame>(
future, kRootClientSinkId, kFrameSize, gfx::Transform(), false, 1.0f,
CopyOutputRequestQueue(), /*did_invalidate=*/invalidated, args);
return child_frame;
}
void DrawOnUI(std::unique_ptr<ChildFrame> frame) {
render_thread_manager_->SetFrameOnUI(std::move(frame));
}
void Sync() { render_thread_manager_->CommitFrameOnRT(); }
void DrawOnRT(const viz::BeginFrameArgs& args, bool invalidated) {
HardwareRendererDrawParams params{.clip_left = 0,
.clip_top = 0,
.clip_right = 99,
.clip_bottom = 99,
.width = 100,
.height = 100};
params.transform[0] = 1.0f;
params.transform[5] = 1.0f;
params.transform[10] = 1.0f;
params.transform[15] = 1.0f;
params.color_space = gfx::ColorSpace::CreateSRGB();
render_thread_manager_->DrawOnRT(/*save_restore=*/false, params,
OverlaysParams());
if (invalidated)
last_invalidated_draw_bf_ = args;
UpdateFrameTimingDetails();
}
bool BeginFrame(const viz::BeginFrameArgs& args) {
DCHECK(!inside_begin_frame_);
if (needs_begin_frames_) {
inside_begin_frame_ = true;
begin_frame_source_->OnBeginFrame(args);
inside_begin_frame_ = false;
root_begin_frames_count_++;
}
// Client could have unsubscribed from begin frames or called invalidate
// without BF, make sure it was propagated to UI thread.
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
run_loop.QuitClosure());
run_loop.Run();
if (did_invalidate_)
invalidate_count_++;
bool result = did_invalidate_;
did_invalidate_ = false;
return result;
}
void SubmitFrameIfNeeded() {
VizCompositorThreadRunnerWebView::GetInstance()->PostTaskAndBlock(
FROM_HERE, base::BindOnce(&VizClient::SubmitFrameIfNeeded,
base::Unretained(client_.get())));
}
void GetFrameTimingDetailsOnViz(viz::FrameTimingDetailsMap* timings) {
*timings = client_->TakeFrameTimingDetails();
}
void UpdateFrameTimingDetails() {
viz::FrameTimingDetailsMap timings;
VizCompositorThreadRunnerWebView::GetInstance()->PostTaskAndBlock(
FROM_HERE, base::BindOnce(&InvalidateTest::GetFrameTimingDetailsOnViz,
base::Unretained(this), &timings));
for (auto& timing : timings) {
DCHECK(!child_client_timings_.contains(timing.first));
// We never should get ahead of hwui. If we have presentation feedback
// newer then latest invalidated draw, then it must be failed.
if (timing.first > last_invalidated_draw_bf_.frame_id.sequence_number) {
LOG_IF(FATAL, !timing.second.presentation_feedback.failed())
<< "Rendered frame: " << timing.first << " ahead of "
<< last_invalidated_draw_bf_.frame_id.sequence_number;
}
child_client_timings_[timing.first] = timing.second;
}
}
void SetUpAndDrawFirstFrameOnViz(int max_pending_frames,
int frame_rate,
viz::SurfaceId* surface_id) {
client_ = std::make_unique<VizClient>(kChildClientSinkId,
max_pending_frames, frame_rate);
*surface_id = client_->GetSurfaceId();
}
void SetUpAndDrawFirstFrame(int max_pending_frames, int frame_rate) {
// During initialization client will request for begin frames, we need to
// wait until that message will reach UI thread.
base::RunLoop run_loop;
set_needs_begin_frames_closure_ = run_loop.QuitClosure();
viz::SurfaceId child_client_surface_id;
VizCompositorThreadRunnerWebView::GetInstance()->PostTaskAndBlock(
FROM_HERE, base::BindOnce(&InvalidateTest::SetUpAndDrawFirstFrameOnViz,
base::Unretained(this), max_pending_frames,
frame_rate, &child_client_surface_id));
// Wait for OnNeedsBeginFrames to be called.
run_loop.Run();
// Do first draw and to setup embedding.
auto bf_args = NextBeginFrameArgs();
auto compositor_frame =
viz::CompositorFrameBuilder()
.AddRenderPass(gfx::Rect(kFrameSize), gfx::Rect(kFrameSize))
.SetBeginFrameAck(viz::BeginFrameAck(bf_args, true))
.Build();
AppendSurfaceDrawQuad(*compositor_frame.render_pass_list.back(),
child_client_surface_id);
compositor_frame.metadata.referenced_surfaces.push_back(
viz::SurfaceRange(child_client_surface_id));
auto frame = std::make_unique<content::SynchronousCompositor::Frame>();
frame->layer_tree_frame_sink_id = 1;
frame->frame =
std::make_unique<viz::CompositorFrame>(std::move(compositor_frame));
root_local_surface_id_allocator_.GenerateId();
frame->local_surface_id =
root_local_surface_id_allocator_.GetCurrentLocalSurfaceId();
BeginFrame(bf_args);
SubmitFrameIfNeeded();
// First draw is implicitly invalidated.
DrawOnUI(CreateChildFrame(std::move(frame), bf_args, /*invalidated=*/true));
Sync();
DrawOnRT(bf_args, /*invalidated=*/true);
// This draw must always succeed.
ASSERT_EQ(child_client_timings_.size(), 1u);
ASSERT_FALSE(
child_client_timings_.begin()->second.presentation_feedback.failed());
}
viz::BeginFrameArgs NextBeginFrameArgs() {
constexpr auto frame_interval = viz::BeginFrameArgs::DefaultInterval();
auto frame_time = begin_frame_time_;
begin_frame_time_ += frame_interval;
return viz::BeginFrameArgs::Create(
BEGINFRAME_FROM_HERE, 1, next_begin_frame_sequence_++, frame_time,
frame_time + frame_interval, frame_interval,
viz::BeginFrameArgs::NORMAL);
}
void DrawLoop(PerFrameFlag client_slow_param,
PerFrameFlag hwui_slow_param,
int stop_submitting_after_frames = 10000) {
const AlwaysDrawType always_draw = testing::get<2>(GetParam());
viz::BeginFrameArgs delayed_draw_args;
bool delayed_draw_invalidate = false;
for (int i = 0; i < 60; i++) {
const bool client_fast = !client_slow_param.at(i);
const bool hwui_fast = !hwui_slow_param.at(i);
if (i == stop_submitting_after_frames) {
VizCompositorThreadRunnerWebView::GetInstance()->PostTaskAndBlock(
FROM_HERE, base::BindOnce(&VizClient::MakeNextFrameLast,
base::Unretained(client_.get())));
}
auto args = NextBeginFrameArgs();
bool invalidate = BeginFrame(args);
if (always_draw == AlwaysDrawType::kAlwaysInvalidate)
invalidate = true;
// Fast clients submit right after BF.
if (client_fast)
SubmitFrameIfNeeded();
// If we didn't draw last frame, now it's time.
if (delayed_draw_args.IsValid()) {
DrawOnRT(delayed_draw_args, delayed_draw_invalidate);
delayed_draw_args = viz::BeginFrameArgs();
}
if (invalidate) {
DrawOnUI(CreateChildFrame(nullptr, args, /*invalidated=*/invalidate));
}
Sync();
// If webview invalidated or other views requested to draw, try to draw.
if (invalidate || always_draw == AlwaysDrawType::kAlwaysDraw) {
// If this frame hwui is in "fast" mode (i.e RT is not a frame behind)
// then draw a frame now.
if (hwui_fast) {
DrawOnRT(args, invalidate);
DCHECK(!delayed_draw_args.IsValid());
} else {
delayed_draw_args = args;
delayed_draw_invalidate = invalidate;
}
}
// Slow clients submit after RT draw.
if (!client_fast) {
SubmitFrameIfNeeded();
}
UpdateFrameTimingDetails();
}
// Draw one more frame without client submitting to make sure all
// presentation feedback was processed.
auto args = NextBeginFrameArgs();
bool invalidate = BeginFrame(args);
// Finish draw if it's still pending
if (delayed_draw_args.IsValid()) {
DrawOnRT(delayed_draw_args, delayed_draw_invalidate);
}
if (invalidate) {
DrawOnUI(CreateChildFrame(nullptr, args, /*invalidated=*/invalidate));
}
Sync();
DrawOnRT(args, invalidate);
// Make sure we received all presentation feedback.
ASSERT_EQ(client_->frames_submitted(), child_client_timings_.size());
}
int CountDroppedFrames() {
int dropped_frames = 0;
for (auto& timing : child_client_timings_) {
if (timing.second.presentation_feedback.failed())
dropped_frames++;
}
return dropped_frames;
}
std::unique_ptr<base::test::TaskEnvironment> task_environment_;
std::unique_ptr<viz::ExternalBeginFrameSource> begin_frame_source_;
std::unique_ptr<RootFrameSinkProxy> root_frame_sink_proxy_;
std::unique_ptr<RenderThreadManager> render_thread_manager_;
scoped_refptr<gl::GLSurface> surface_;
scoped_refptr<gl::GLContext> context_;
std::unique_ptr<VizClient> client_;
viz::ParentLocalSurfaceIdAllocator root_local_surface_id_allocator_;
uint64_t next_begin_frame_sequence_ =
viz::BeginFrameArgs::kStartingFrameNumber;
base::TimeTicks begin_frame_time_ = base::TimeTicks::Now();
viz::FrameTimingDetailsMap child_client_timings_;
int invalidate_count_ = 0;
int root_begin_frames_count_ = 0;
bool needs_begin_frames_ = false;
bool inside_begin_frame_ = false;
bool did_invalidate_ = false;
viz::BeginFrameArgs last_invalidated_draw_bf_;
base::OnceClosure set_needs_begin_frames_closure_;
};
std::string AlwaysDrawTypeToString(AlwaysDrawType type) {
switch (type) {
case AlwaysDrawType::kNone:
return "";
case AlwaysDrawType::kAlwaysInvalidate:
return "AlwaysInvalidate";
case AlwaysDrawType::kAlwaysDraw:
return "AlwaysDraw";
};
}
std::string TestParamToString(
const testing::TestParamInfo<
testing::tuple<PerFrameFlag, PerFrameFlag, AlwaysDrawType>>&
param_info) {
auto client_slow = testing::get<0>(param_info.param);
auto hwui_slow = testing::get<1>(param_info.param);
auto always_draw = testing::get<2>(param_info.param);
return "ClientSlow" + client_slow.ToString() + "HwuiSlow" +
hwui_slow.ToString() + AlwaysDrawTypeToString(always_draw);
}
INSTANTIATE_TEST_SUITE_P(
Stable,
InvalidateTest,
::testing::Combine(::testing::Values(PerFrameFlag::AlwaysFalse(),
PerFrameFlag::AlwaysTrue()),
::testing::Values(PerFrameFlag::AlwaysFalse(),
PerFrameFlag::AlwaysTrue()),
::testing::Values(AlwaysDrawType::kNone,
AlwaysDrawType::kAlwaysInvalidate,
AlwaysDrawType::kAlwaysDraw)),
TestParamToString);
INSTANTIATE_TEST_SUITE_P(
Random,
InvalidateTest,
::testing::Combine(::testing::Values(PerFrameFlag::AlwaysFalse()),
::testing::Values(PerFrameFlag(0xAAAAAAAAAAAAAAAA)),
::testing::Values(AlwaysDrawType::kNone,
AlwaysDrawType::kAlwaysInvalidate,
AlwaysDrawType::kAlwaysDraw)),
TestParamToString);
TEST_P(InvalidateTest, LowFpsWithMaxFrame1) {
auto always_draw = testing::get<2>(GetParam());
auto client_slow = testing::get<0>(GetParam());
auto hwui_slow = testing::get<1>(GetParam());
// Always draw case is broken because viz runs ahead of hwui.
if (always_draw == AlwaysDrawType::kAlwaysDraw) {
GTEST_SKIP();
}
// If client is faster than hwui, it runs ahead of hwui.
if (client_slow.IsNever() && !hwui_slow.IsNever()) {
GTEST_SKIP();
}
SetUpAndDrawFirstFrame(/*max_pending_frames=*/1, /*frame_rate=*/30);
DrawLoop(client_slow, hwui_slow);
// Due to rounding error (1 / 60 * 2 < 1 / 30) we submit 29 frames instead
// of 30. Total 30 counting frame from first draw.
ASSERT_EQ(child_client_timings_.size(), 30u);
EXPECT_EQ(CountDroppedFrames(), 0);
// If either client or hwui slow we currently invalidate more than we need.
if (client_slow.IsNever() || hwui_slow.IsNever())
EXPECT_LE(invalidate_count_, 31);
}
// Currently we can't reach 60fps with max pending frames 1.
TEST_P(InvalidateTest, DISABLED_HighFpsWithMaxFrame1) {
auto always_draw = testing::get<2>(GetParam());
auto client_slow = testing::get<0>(GetParam());
auto hwui_slow = testing::get<1>(GetParam());
// Always draw case is broken because viz runs ahead of hwui.
if (always_draw == AlwaysDrawType::kAlwaysDraw) {
GTEST_SKIP();
}
// If client is faster than hwui, it runs ahead of hwui.
if (client_slow.IsNever() && !hwui_slow.IsNever()) {
GTEST_SKIP();
}
SetUpAndDrawFirstFrame(/*max_pending_frames=*/1, /*frame_rate=*/60);
DrawLoop(client_slow, hwui_slow);
// We should have submitted 60 frames + 1 from initial draw.
ASSERT_EQ(child_client_timings_.size(), 61u);
EXPECT_EQ(CountDroppedFrames(), 0);
}
TEST_P(InvalidateTest, HighFpsWithMaxFrame2) {
auto always_draw = testing::get<2>(GetParam());
auto client_slow = testing::get<0>(GetParam());
auto hwui_slow = testing::get<1>(GetParam());
// Always draw case is broken because viz runs ahead of hwui.
if (always_draw == AlwaysDrawType::kAlwaysDraw) {
GTEST_SKIP();
}
// If client is faster than hwui, it runs ahead of hwui.
if (client_slow.IsNever() && !hwui_slow.IsNever()) {
GTEST_SKIP();
}
SetUpAndDrawFirstFrame(/*max_pending_frames=*/2, /*frame_rate=*/60);
DrawLoop(client_slow, hwui_slow);
// We should have submitted 60 frames + 1 from initial draw.
ASSERT_EQ(child_client_timings_.size(), 61u);
// Except the case when client is slower than hwui we currently drop first
// frame always.
EXPECT_LE(CountDroppedFrames(), 1);
}
TEST_P(InvalidateTest, LastFrameNotLost) {
auto always_draw = testing::get<2>(GetParam());
auto client_slow = testing::get<0>(GetParam());
auto hwui_slow = testing::get<1>(GetParam());
// Always draw case is broken because viz runs ahead of hwui.
if (always_draw == AlwaysDrawType::kAlwaysDraw) {
GTEST_SKIP();
}
// If client is faster than hwui, it runs ahead of hwui.
if (client_slow.IsNever() && !hwui_slow.IsNever()) {
GTEST_SKIP();
}
SetUpAndDrawFirstFrame(/*max_pending_frames=*/1, /*frame_rate=*/30);
DrawLoop(client_slow, hwui_slow, /*stop_submitting_after_frames=*/30);
ASSERT_EQ(child_client_timings_.size(), 16u);
EXPECT_EQ(CountDroppedFrames(), 0);
// With AlwaysInvalidate we currently trigger more BeginFrames than we need.
// Note, that client unsubscribes at frame 30 leading to 31 BF + 1 from
// initial draw + 1 extra to deliver presentation feedback if we didn't fetch
// it right after draw.
if (always_draw != AlwaysDrawType::kAlwaysInvalidate)
EXPECT_LE(root_begin_frames_count_, 33);
}
TEST_P(InvalidateTest, VeryLateFrame) {
SetUpAndDrawFirstFrame(/*max_pending_frames=*/1, /*frame_rate=*/60);
client_->MakeNextFrameLast();
// Draw until we don't need to draw anymore.
for (int i = 0; i < 10; i++) {
auto args = NextBeginFrameArgs();
bool invalidate = BeginFrame(args);
if (invalidate)
DrawOnUI(CreateChildFrame(nullptr, args, /*invalidated=*/invalidate));
Sync();
if (invalidate)
DrawOnRT(args, invalidate);
}
// Submit frame.
SubmitFrameIfNeeded();
// Client could have subscribed to begin frames, make sure it was
// propagated to UI thread.
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
run_loop.QuitClosure());
run_loop.Run();
for (int i = 0; i < 3; i++) {
auto args = NextBeginFrameArgs();
bool invalidate = BeginFrame(args);
if (invalidate)
DrawOnUI(CreateChildFrame(nullptr, args, /*invalidated=*/invalidate));
Sync();
if (invalidate)
DrawOnRT(args, invalidate);
}
UpdateFrameTimingDetails();
ASSERT_EQ(client_->frames_submitted(), child_client_timings_.size());
ASSERT_EQ(child_client_timings_.size(), 2u);
EXPECT_EQ(CountDroppedFrames(), 0);
}
} // namespace
} // namespace android_webview