// Copyright 2014 The Chromium Authors
// 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/hardware_renderer.h"
#include <algorithm>
#include <iterator>
#include <memory>
#include <utility>
#include "android_webview/browser/gfx/aw_gl_surface.h"
#include "android_webview/browser/gfx/display_scheduler_webview.h"
#include "android_webview/browser/gfx/display_webview.h"
#include "android_webview/browser/gfx/gpu_service_webview.h"
#include "android_webview/browser/gfx/overlay_processor_webview.h"
#include "android_webview/browser/gfx/parent_compositor_draw_constraints.h"
#include "android_webview/browser/gfx/render_thread_manager.h"
#include "android_webview/browser/gfx/root_frame_sink.h"
#include "android_webview/browser/gfx/skia_output_surface_dependency_webview.h"
#include "android_webview/browser/gfx/task_queue_webview.h"
#include "android_webview/browser/gfx/viz_compositor_thread_runner_webview.h"
#include "android_webview/common/aw_features.h"
#include "android_webview/common/aw_switches.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/functional/callback_helpers.h"
#include "base/functional/overloaded.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/trace_event/trace_event.h"
#include "components/viz/common/display/renderer_settings.h"
#include "components/viz/common/features.h"
#include "components/viz/common/frame_sinks/begin_frame_source.h"
#include "components/viz/common/frame_sinks/copy_output_request.h"
#include "components/viz/common/frame_timing_details_map.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "components/viz/common/quads/solid_color_draw_quad.h"
#include "components/viz/common/quads/surface_draw_quad.h"
#include "components/viz/common/surfaces/local_surface_id.h"
#include "components/viz/common/surfaces/parent_local_surface_id_allocator.h"
#include "components/viz/service/display/display_client.h"
#include "components/viz/service/display/display_scheduler.h"
#include "components/viz/service/display/frame_interval_decider.h"
#include "components/viz/service/display/overlay_processor_stub.h"
#include "components/viz/service/display_embedder/skia_output_surface_dependency.h"
#include "components/viz/service/display_embedder/skia_output_surface_impl.h"
#include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
#include "gpu/config/gpu_finch_features.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_share_group.h"
#include "ui/gl/gl_surface_egl.h"
#include "ui/gl/init/gl_factory.h"
namespace android_webview {
namespace {
BASE_FEATURE(kWebViewUseOutputSurfaceClipRect,
"WebViewUseOutputSurfaceClipRect",
base::FEATURE_ENABLED_BY_DEFAULT);
BASE_FEATURE(kDrawAndSwapInjectLatency,
"DrawAndSwapInjectLatency",
base::FEATURE_DISABLED_BY_DEFAULT);
class ScopedAcquireExternalContext {
public:
ScopedAcquireExternalContext(gpu::SharedContextState* state,
gl::GLSurface* surface,
bool is_angle)
: state_(state), surface_(surface), is_angle_(is_angle) {
if (is_angle_) {
// When using ANGLE, need to make sure ANGLE's internals are in sync
// with the external context.
// If the context has changed, make sure it gets current now.
if (!state_->context()->IsCurrent(surface_)) {
state_->MakeCurrent(surface_);
}
eglAcquireExternalContextANGLE(state_->display()->GetDisplay(),
surface_->GetHandle());
} else {
// When not using ANGLE, fake context and surface are used, so the
// MakeCurrent calls are cheap.
state_->MakeCurrent(surface_);
}
}
~ScopedAcquireExternalContext() {
if (is_angle_) {
eglReleaseExternalContextANGLE(state_->display()->GetDisplay());
} else {
state_->ReleaseCurrent(surface_);
}
}
private:
const raw_ptr<gpu::SharedContextState> state_;
raw_ptr<gl::GLSurface> surface_;
const bool is_angle_;
};
void MoveCopyRequests(CopyOutputRequestQueue* from,
CopyOutputRequestQueue* to) {
std::move(from->begin(), from->end(), std::back_inserter(*to));
from->clear();
}
viz::BeginFrameArgs NewerBeginFrameArgs(const viz::BeginFrameArgs& args1,
const viz::BeginFrameArgs& args2) {
return args1.frame_id.IsNextInSequenceTo(args2.frame_id) ? args1 : args2;
}
enum WebViewDrawAndSubmissionType : uint8_t {
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
kNoInvalidateNoSubmissionSameParams = 0,
kNoInvalidateNoSubmissionDifferentParams = 1,
kNoInvalidateSubmittedFrameSameParams = 2,
kNoInvalidateSubmittedFrameDifferentParams = 3,
kInvalidateNoSubmissionSameParams = 4,
kInvalidateNoSubmissionDifferentParams = 5,
kInvalidateSubmittedFrameSameParams = 6,
kInvalidateSubmittedFrameDifferentParams = 7,
kMaxValue = kInvalidateSubmittedFrameDifferentParams
};
WebViewDrawAndSubmissionType GetDrawAndSubmissionType(bool invalidated,
bool submitted_frame,
bool params_changed) {
if (invalidated) {
if (submitted_frame) {
return params_changed ? kInvalidateSubmittedFrameDifferentParams
: kInvalidateSubmittedFrameSameParams;
} else {
return params_changed ? kInvalidateNoSubmissionDifferentParams
: kInvalidateNoSubmissionSameParams;
}
} else {
if (submitted_frame) {
return params_changed ? kNoInvalidateSubmittedFrameDifferentParams
: kNoInvalidateSubmittedFrameSameParams;
} else {
return params_changed ? kNoInvalidateNoSubmissionDifferentParams
: kNoInvalidateNoSubmissionSameParams;
}
}
}
} // namespace
class HardwareRenderer::OnViz : public viz::DisplayClient {
public:
OnViz(OutputSurfaceProviderWebView* output_surface_provider,
const scoped_refptr<RootFrameSink>& root_frame_sink);
OnViz(const OnViz&) = delete;
OnViz& operator=(const OnViz&) = delete;
~OnViz() override;
void DrawAndSwapOnViz(const gfx::Size& viewport,
const gfx::Rect& clip,
const gfx::Transform& transform,
const viz::SurfaceId& child_id,
float device_scale_factor,
const gfx::ColorSpace& color_space,
bool overlays_enabled_by_hwui,
ChildFrame* child_frame);
void PostDrawOnViz(viz::FrameTimingDetailsMap* timing_details,
std::vector<pid_t>* rendering_thread_ids,
base::TimeDelta* preferred_frame_interval);
void RemoveOverlaysOnViz();
void MarkAllowContextLossOnViz();
OverlayProcessorWebView* overlay_processor() {
return overlay_processor_webview_;
}
// viz::DisplayClient overrides.
void DisplayOutputSurfaceLost() override;
void DisplayWillDrawAndSwap(
bool will_draw_and_swap,
viz::AggregatedRenderPassList* render_passes) override;
void DisplayDidDrawAndSwap() override {}
void DisplayDidReceiveCALayerParams(
const gfx::CALayerParams& ca_layer_params) override {}
void DisplayDidCompleteSwapWithSize(const gfx::Size& pixel_size) override {}
void DisplayAddChildWindowToBrowser(
gpu::SurfaceHandle child_window) override {}
void SetWideColorEnabled(bool enabled) override {}
void SetPreferredFrameInterval(base::TimeDelta interval) override {}
base::TimeDelta GetPreferredFrameIntervalForFrameSinkId(
const viz::FrameSinkId& id,
viz::mojom::CompositorFrameSinkType* type) override;
private:
viz::FrameSinkManagerImpl* GetFrameSinkManager();
scoped_refptr<RootFrameSink> without_gpu_;
const viz::FrameSinkId frame_sink_id_;
viz::LocalSurfaceId root_local_surface_id_;
std::unique_ptr<viz::BeginFrameSource> stub_begin_frame_source_;
std::unique_ptr<DisplayWebView> display_;
std::unique_ptr<viz::HitTestAggregator> hit_test_aggregator_;
viz::SurfaceId child_surface_id_;
const bool viz_frame_submission_;
const bool use_new_invalidate_heuristic_;
bool expect_context_loss_ = false;
// Initialized in ctor and never changes, so it's safe to access from both
// threads. Can be null, if overlays are disabled.
raw_ptr<OverlayProcessorWebView> overlay_processor_webview_ = nullptr;
base::PlatformThreadId browser_io_thread_id_ = base::kInvalidThreadId;
base::TimeDelta preferred_frame_interval_;
THREAD_CHECKER(viz_thread_checker_);
};
HardwareRenderer::OnViz::OnViz(
OutputSurfaceProviderWebView* output_surface_provider,
const scoped_refptr<RootFrameSink>& root_frame_sink)
: without_gpu_(root_frame_sink),
frame_sink_id_(without_gpu_->root_frame_sink_id()),
viz_frame_submission_(::features::IsUsingVizFrameSubmissionForWebView()),
use_new_invalidate_heuristic_(
::features::UseWebViewNewInvalidateHeuristic()) {
DCHECK_CALLED_ON_VALID_THREAD(viz_thread_checker_);
std::unique_ptr<viz::DisplayCompositorMemoryAndTaskController>
display_controller = output_surface_provider->CreateDisplayController();
std::unique_ptr<viz::OutputSurface> output_surface =
output_surface_provider->CreateOutputSurface(display_controller.get());
stub_begin_frame_source_ = std::make_unique<viz::StubBeginFrameSource>();
display_ = DisplayWebView::Create(
output_surface_provider->renderer_settings(),
output_surface_provider->debug_settings(), frame_sink_id_,
std::move(display_controller), std::move(output_surface),
GetFrameSinkManager(), without_gpu_.get());
display_->Initialize(this, GetFrameSinkManager()->surface_manager());
overlay_processor_webview_ = display_->overlay_processor();
display_->SetVisible(true);
display_->DisableGPUAccessByDefault();
if (viz::FrameIntervalDecider* decider = display_->frame_interval_decider()) {
viz::FrameIntervalDecider::Settings settings;
std::vector<std::unique_ptr<viz::FrameIntervalMatcher>> matchers;
matchers.push_back(std::make_unique<viz::InputBoostMatcher>());
matchers.push_back(std::make_unique<viz::OnlyVideoMatcher>());
matchers.push_back(std::make_unique<viz::OnlyAnimatingImageMatcher>());
// Raw `self` pointer is safe because this owns viz::Display which owns
// viz::FrameIntervalDecider. So this pointer is guaranteed to be valid for
// the lifetime of viz::FrameIntervalDecider.
settings.result_callback = base::BindRepeating(
[](HardwareRenderer::OnViz* self,
viz::FrameIntervalDecider::Result result,
viz::FrameIntervalMatcherType matcher_type) {
self->preferred_frame_interval_ = absl::visit(
base::Overloaded(
[](viz::FrameIntervalDecider::FrameIntervalClass
frame_interval_class) {
// Zero currently is interpreted by WebView as no opinion,
// which allows system to use its default heuristics.
return base::Milliseconds(0);
},
[](base::TimeDelta interval) { return interval; }),
result);
},
this);
decider->UpdateSettings(std::move(settings), std::move(matchers));
}
}
HardwareRenderer::OnViz::~OnViz() {
DCHECK_CALLED_ON_VALID_THREAD(viz_thread_checker_);
if (child_surface_id_.is_valid()) {
without_gpu_->EvictChildSurface(child_surface_id_);
}
if (root_local_surface_id_.is_valid()) {
without_gpu_->EvictRootSurface(root_local_surface_id_);
}
GetFrameSinkManager()->surface_manager()->GarbageCollectSurfaces();
}
void HardwareRenderer::OnViz::DrawAndSwapOnViz(
const gfx::Size& viewport,
const gfx::Rect& clip,
const gfx::Transform& transform,
const viz::SurfaceId& child_id,
float device_scale_factor,
const gfx::ColorSpace& color_space,
bool overlays_enabled_by_hwui,
ChildFrame* child_frame) {
TRACE_EVENT1("android_webview", "HardwareRenderer::DrawAndSwap", "child_id",
child_id.ToString());
DCHECK_CALLED_ON_VALID_THREAD(viz_thread_checker_);
DCHECK(child_id.is_valid());
DCHECK(child_frame);
if (child_frame->frame) {
DCHECK(!viz_frame_submission_);
DCHECK(!child_frame->rendered);
// Browser thread is trusted, and can be saved straight away.
// Renderer threads are not trusted, and need to go through verification
// in SubmitChildCompositorFrame before being reported to the ADPF session.
browser_io_thread_id_ = child_frame->browser_io_thread_id;
without_gpu_->SubmitChildCompositorFrame(child_frame);
}
gfx::Size frame_size = without_gpu_->GetChildFrameSize();
if (!child_frame->copy_requests.empty()) {
viz::FrameSinkManagerImpl* manager = GetFrameSinkManager();
CopyOutputRequestQueue requests;
requests.swap(child_frame->copy_requests);
for (auto& copy_request : requests) {
manager->RequestCopyOfOutput(child_id, std::move(copy_request),
/*capture_exact_surface_id=*/false);
}
}
if (overlay_processor_webview_) {
overlay_processor_webview_->SetOverlaysEnabledByHWUI(
overlays_enabled_by_hwui);
}
gfx::DisplayColorSpaces display_color_spaces(
color_space.IsValid() ? color_space : gfx::ColorSpace::CreateSRGB());
display_->SetDisplayColorSpaces(display_color_spaces);
// Create a frame with a single SurfaceDrawQuad referencing the child
// Surface and transformed using the given transform.
auto render_pass = viz::CompositorRenderPass::Create();
render_pass->SetNew(viz::CompositorRenderPassId{1}, gfx::Rect(viewport), clip,
gfx::Transform());
render_pass->has_transparent_background = false;
const bool use_output_surface_clip_rect =
base::FeatureList::IsEnabled(kWebViewUseOutputSurfaceClipRect);
viz::SharedQuadState* quad_state =
render_pass->CreateAndAppendSharedQuadState();
quad_state->quad_to_target_transform = transform;
quad_state->quad_layer_rect = gfx::Rect(frame_size);
quad_state->visible_quad_layer_rect = gfx::Rect(frame_size);
quad_state->opacity = 1.f;
// We don't need to clip render pass if we apply clip on the viz::Display
// level.
if (!use_output_surface_clip_rect) {
quad_state->clip_rect = clip;
}
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(std::nullopt, child_id),
SkColors::kWhite,
/*stretch_content_to_fill_bounds=*/false);
viz::CompositorFrame frame;
// We draw synchronously, so acknowledge a manual BeginFrame.
frame.metadata.begin_frame_ack =
viz::BeginFrameAck::CreateManualAckWithDamage();
frame.render_pass_list.push_back(std::move(render_pass));
frame.metadata.device_scale_factor = device_scale_factor;
if (child_surface_id_ != child_id) {
if (child_surface_id_.frame_sink_id() != child_id.frame_sink_id()) {
hit_test_aggregator_ = std::make_unique<viz::HitTestAggregator>(
GetFrameSinkManager()->hit_test_manager(), GetFrameSinkManager(),
display_.get(), child_id.frame_sink_id());
}
child_surface_id_ = child_id;
GetFrameSinkManager()->surface_manager()->GarbageCollectSurfaces();
}
{
std::vector<viz::SurfaceRange> child_ranges;
child_ranges.emplace_back(child_surface_id_);
frame.metadata.referenced_surfaces = std::move(child_ranges);
}
const auto& local_surface_id =
without_gpu_->SubmitRootCompositorFrame(std::move(frame));
if (use_new_invalidate_heuristic_) {
auto root_surface_id =
viz::SurfaceId(without_gpu_->root_frame_sink_id(), local_surface_id);
const auto& current_frame_id = child_frame->begin_frame_args.frame_id;
const auto& root_frame_sink_id = root_surface_id.frame_sink_id();
const auto& child_frame_sink_id = child_surface_id_.frame_sink_id();
// Each OnDraw on UI we get new ChildFrame. Without OnDraw we can't modify
// contents of the webview or it will break HWUI damage tracking, so only
// commit if the frame is new.
const bool commit_child_frames = !child_frame->rendered;
base::flat_set<viz::SurfaceId> manual_surfaces;
auto commit_predicate = [&](const viz::SurfaceId& surface_id,
const viz::BeginFrameId& frame_id) {
const bool is_root_surface =
surface_id.frame_sink_id() == root_frame_sink_id;
const bool is_main_renderer_surface =
surface_id.frame_sink_id() == child_frame_sink_id;
// If we have uncommitted main renderer frame, `commit_child_frames`
// must be true.
CHECK(!is_main_renderer_surface || commit_child_frames);
if (!commit_child_frames) {
// Commit only root frame, all child surfaces can be committed only
// if we did have Draw on UI thread.
return is_root_surface;
}
// Always commit frame from different begin frame sources, because we
// can't order with them.
if (frame_id.source_id != current_frame_id.source_id) {
// We always should have single source_id except for the manual
// acks.
DCHECK_EQ(frame_id.source_id, viz::BeginFrameArgs::kManualSourceId);
// For manual acks commit only one frame at time to avoid excessive
// frame drops.
auto [_, inserted] = manual_surfaces.insert(surface_id);
return inserted;
}
// Commit all frames that are older than current one.
if (frame_id.sequence_number < current_frame_id.sequence_number) {
return true;
}
// All clients except main renderer and root surface are frame behind.
const bool is_frame_behind =
!is_main_renderer_surface && !is_root_surface;
// If this surface is not frame behind, commit it for current frame
// too.
if (!is_frame_behind &&
frame_id.sequence_number == current_frame_id.sequence_number) {
return true;
}
return false;
};
GetFrameSinkManager()->surface_manager()->CommitFramesInRangeRecursively(
viz::SurfaceRange(root_surface_id), commit_predicate);
}
if (root_local_surface_id_ != local_surface_id) {
root_local_surface_id_ = local_surface_id;
display_->SetLocalSurfaceId(local_surface_id, device_scale_factor);
}
display_->Resize(viewport);
if (use_output_surface_clip_rect) {
display_->SetOutputSurfaceClipRect(clip);
}
auto now = base::TimeTicks::Now();
display_->DrawAndSwap({now, now});
child_frame->rendered = true;
without_gpu_->SetContainedSurfaces(display_->GetContainedSurfaceIds());
}
void HardwareRenderer::OnViz::PostDrawOnViz(
viz::FrameTimingDetailsMap* timing_details,
std::vector<pid_t>* rendering_thread_ids,
base::TimeDelta* preferred_frame_interval) {
*timing_details = without_gpu_->TakeChildFrameTimingDetailsMap();
auto renderer_thread_ids = without_gpu_->GetChildFrameRendererThreadIds();
*rendering_thread_ids = std::vector<pid_t>(renderer_thread_ids.begin(),
renderer_thread_ids.end());
auto gpu_thread_ids =
VizCompositorThreadRunnerWebView::GetInstance()->GetThreadIds();
std::copy(gpu_thread_ids.begin(), gpu_thread_ids.end(),
std::back_inserter(*rendering_thread_ids));
if (browser_io_thread_id_ != base::kInvalidThreadId) {
rendering_thread_ids->push_back(browser_io_thread_id_);
}
*preferred_frame_interval = preferred_frame_interval_;
}
void HardwareRenderer::OnViz::RemoveOverlaysOnViz() {
if (overlay_processor_webview_) {
overlay_processor_webview_->RemoveOverlays();
}
}
void HardwareRenderer::OnViz::MarkAllowContextLossOnViz() {
DCHECK_CALLED_ON_VALID_THREAD(viz_thread_checker_);
expect_context_loss_ = true;
}
viz::FrameSinkManagerImpl* HardwareRenderer::OnViz::GetFrameSinkManager() {
DCHECK_CALLED_ON_VALID_THREAD(viz_thread_checker_);
return VizCompositorThreadRunnerWebView::GetInstance()->GetFrameSinkManager();
}
void HardwareRenderer::OnViz::DisplayOutputSurfaceLost() {
DCHECK_CALLED_ON_VALID_THREAD(viz_thread_checker_);
if (!expect_context_loss_) {
// Android WebView does not handle real context loss.
LOG(FATAL) << "Render thread context loss";
}
}
void HardwareRenderer::OnViz::DisplayWillDrawAndSwap(
bool will_draw_and_swap,
viz::AggregatedRenderPassList* render_passes) {
DCHECK_CALLED_ON_VALID_THREAD(viz_thread_checker_);
hit_test_aggregator_->Aggregate(child_surface_id_);
}
base::TimeDelta
HardwareRenderer::OnViz::GetPreferredFrameIntervalForFrameSinkId(
const viz::FrameSinkId& id,
viz::mojom::CompositorFrameSinkType* type) {
DCHECK_CALLED_ON_VALID_THREAD(viz_thread_checker_);
return GetFrameSinkManager()->GetPreferredFrameIntervalForFrameSinkId(id,
type);
}
// static
ChildFrameQueue HardwareRenderer::WaitAndPruneFrameQueue(
ChildFrameQueue* child_frames_ptr) {
ChildFrameQueue& child_frames = *child_frames_ptr;
ChildFrameQueue pruned_frames;
if (child_frames.empty()) {
return pruned_frames;
}
// First find the last non-empty frame.
int remaining_frame_index = -1;
for (size_t i = 0; i < child_frames.size(); ++i) {
auto& child_frame = *child_frames[i];
child_frame.WaitOnFutureIfNeeded();
if (child_frame.frame) {
remaining_frame_index = i;
}
}
// If all empty, keep the last one.
if (remaining_frame_index < 0) {
remaining_frame_index = child_frames.size() - 1;
}
// Prune end.
while (child_frames.size() > static_cast<size_t>(remaining_frame_index + 1)) {
std::unique_ptr<ChildFrame> frame = std::move(child_frames.back());
child_frames.pop_back();
MoveCopyRequests(&frame->copy_requests,
&child_frames[remaining_frame_index]->copy_requests);
// If we're dropping frames at the end, we need update begin frame args.
child_frames[remaining_frame_index]->begin_frame_args = NewerBeginFrameArgs(
child_frames[remaining_frame_index]->begin_frame_args,
frame->begin_frame_args);
// We shouldn't get rendered frames here.
DCHECK(!frame->frame);
DCHECK(!frame->rendered);
}
DCHECK_EQ(static_cast<size_t>(remaining_frame_index),
child_frames.size() - 1);
// Prune front.
while (child_frames.size() > 1) {
std::unique_ptr<ChildFrame> frame = std::move(child_frames.front());
child_frames.pop_front();
MoveCopyRequests(&frame->copy_requests,
&child_frames.back()->copy_requests);
// We shouldn't drop newer frames.
DCHECK(!frame->begin_frame_args.frame_id.IsNextInSequenceTo(
child_frames.back()->begin_frame_args.frame_id));
// We shouldn't get rendered frames here.
DCHECK(!frame->rendered);
if (frame->frame) {
pruned_frames.emplace_back(std::move(frame));
}
}
return pruned_frames;
}
bool HardwareRendererDrawParams::operator==(
const HardwareRendererDrawParams& other) const {
return clip_left == other.clip_left && clip_top == other.clip_top &&
clip_right == other.clip_right && clip_bottom == other.clip_bottom &&
width == other.width && height == other.height &&
color_space == other.color_space &&
!memcmp(transform, other.transform, sizeof(transform));
}
bool HardwareRendererDrawParams::operator!=(
const HardwareRendererDrawParams& other) const {
return !(*this == other);
}
HardwareRenderer::HardwareRenderer(RenderThreadManager* state,
RootFrameSinkGetter root_frame_sink_getter,
AwVulkanContextProvider* context_provider)
: render_thread_manager_(state),
last_egl_context_(eglGetCurrentContext()),
output_surface_provider_(context_provider),
report_rendering_threads_(
base::FeatureList::IsEnabled(::features::kWebViewEnableADPF)) {
DCHECK_CALLED_ON_VALID_THREAD(render_thread_checker_);
VizCompositorThreadRunnerWebView::GetInstance()->ScheduleOnVizAndBlock(
base::BindOnce(&HardwareRenderer::InitializeOnViz, base::Unretained(this),
std::move(root_frame_sink_getter)));
}
void HardwareRenderer::InitializeOnViz(
RootFrameSinkGetter root_frame_sink_getter) {
scoped_refptr<RootFrameSink> root_frame_sink =
std::move(root_frame_sink_getter).Run();
if (root_frame_sink) {
on_viz_ = std::make_unique<OnViz>(&output_surface_provider_,
std::move(root_frame_sink));
}
}
HardwareRenderer::~HardwareRenderer() {
DCHECK_CALLED_ON_VALID_THREAD(render_thread_checker_);
// Do not crash for context loss during destruction. It's possible functor is
// being destroyed due to an already-detected lost context.
MarkAllowContextLoss();
output_surface_provider_.shared_context_state()->MakeCurrent(nullptr);
VizCompositorThreadRunnerWebView::GetInstance()->ScheduleOnVizAndBlock(
base::DoNothingWithBoundArgs(std::move(on_viz_)));
// Reset draw constraints.
if (child_frame_) {
render_thread_manager_->PostParentDrawDataToChildCompositorOnRT(
ParentCompositorDrawConstraints(), child_frame_->frame_sink_id,
viz::FrameTimingDetailsMap(), 0u, preferred_frame_interval_);
}
for (auto& child_frame : child_frame_queue_) {
child_frame->WaitOnFutureIfNeeded();
ReturnChildFrame(std::move(child_frame));
}
}
bool HardwareRenderer::IsUsingVulkan() const {
DCHECK_CALLED_ON_VALID_THREAD(render_thread_checker_);
DCHECK(output_surface_provider_.shared_context_state());
return output_surface_provider_.shared_context_state()->GrContextIsVulkan();
}
bool HardwareRenderer::IsUsingANGLEOverGL() const {
return !IsUsingVulkan() && gl::GLSurfaceEGL::GetGLDisplayEGL()
->IsANGLEExternalContextAndSurfaceSupported();
}
void HardwareRenderer::DrawAndSwap(
const HardwareRendererDrawParams& params,
const OverlaysParams& overlays_params,
ReportRenderingThreadsCallback report_rendering_threads_callback) {
TRACE_EVENT1("android_webview", "HardwareRenderer::Draw", "vulkan",
IsUsingVulkan());
DCHECK_CALLED_ON_VALID_THREAD(render_thread_checker_);
if (base::FeatureList::IsEnabled(kDrawAndSwapInjectLatency)) {
usleep(1000);
}
// Ensure that the context is synced from external and synced back before
// returning. This is only necessary when using ANGLE to keep its internals
// synced with the external context
ScopedAcquireExternalContext scoped_acquire(
output_surface_provider_.shared_context_state().get(),
output_surface_provider_.gl_surface().get(), IsUsingANGLEOverGL());
viz::FrameTimingDetailsMap timing_details;
gfx::Transform transform = gfx::Transform::ColMajorF(params.transform);
transform.Translate(scroll_offset_.x(), scroll_offset_.y());
gfx::Size viewport(params.width, params.height);
// Need to post the new transform matrix back to child compositor
// because there is no onDraw during a Render Thread animation, and child
// compositor might not have the tiles rasterized as the animation goes on.
ParentCompositorDrawConstraints draw_constraints =
ParentCompositorDrawConstraints(viewport, transform);
bool need_to_update_draw_constraints =
!child_frame_.get() || draw_constraints.NeedUpdate(*child_frame_);
if (child_frame_) {
viz::SurfaceId child_surface_id = child_frame_->GetSurfaceId();
if (child_surface_id.is_valid() && child_surface_id != surface_id_) {
surface_id_ = child_surface_id;
device_scale_factor_ = child_frame_->device_scale_factor;
}
}
if (!surface_id_.is_valid()) {
if (need_to_update_draw_constraints) {
// FrameSinkId is used only for FrameTimingDetails and we want to update
// only draw constraints here.
// TODO(vasilyt): Move frame timing details delivery over to
// RootFrameSink.
render_thread_manager_->PostParentDrawDataToChildCompositorOnRT(
draw_constraints, viz::FrameSinkId(), viz::FrameTimingDetailsMap(), 0,
preferred_frame_interval_);
}
return;
}
gfx::Rect clip(params.clip_left, params.clip_top,
params.clip_right - params.clip_left,
params.clip_bottom - params.clip_top);
output_surface_provider_.gl_surface()->RecalculateClipAndTransform(
&viewport, &clip, &transform);
// Reset Skia's state if not using ANGLE. For ANGLE, it is in general not
// necessary as ANGLE will restore GL context state for us as long as Chrome
// hasn't mucked with it outside ANGLE's knowledge. There is only one case
// where Chrome does so: the complex clip case. That case is rare and
// we can't know a priori whether we are going to hit it for this frame.
// Hence, rather than resetting Skia state on every frame for ANGLE, we
// instead detect whether this frame has hit the complex clip case at the end
// of this function and reset the GR context as needed for ANGLE there.
if (!gl::GLSurfaceEGL::GetGLDisplayEGL()
->IsANGLEExternalContextAndSurfaceSupported()) {
DCHECK(output_surface_provider_.shared_context_state());
output_surface_provider_.shared_context_state()
->PessimisticallyResetGrContext();
}
std::optional<OverlayProcessorWebView::ScopedSurfaceControlAvailable>
allow_surface_control;
auto* overlay_processor = on_viz_->overlay_processor();
const bool can_use_overlays =
overlays_params.overlays_mode == OverlaysParams::Mode::Enabled &&
!output_surface_provider_.gl_surface()->IsDrawingToFBO();
if (can_use_overlays && overlay_processor) {
DCHECK(overlays_params.get_surface_control);
allow_surface_control.emplace(overlay_processor,
overlays_params.get_surface_control);
}
VizCompositorThreadRunnerWebView::GetInstance()->ScheduleOnVizAndBlock(
base::BindOnce(&HardwareRenderer::OnViz::DrawAndSwapOnViz,
base::Unretained(on_viz_.get()), viewport, clip, transform,
surface_id_, device_scale_factor_, params.color_space,
can_use_overlays, child_frame_.get()));
MergeTransactionIfNeeded(overlays_params.merge_transaction);
output_surface_provider_.gl_surface()->MaybeDidPresent(
gfx::PresentationFeedback(base::TimeTicks::Now(), base::TimeDelta(),
0 /* flags */));
// Implement proper damage tracking, then deliver FrameTimingDetails
// through the common begin frame path.
std::vector<pid_t> rendering_thread_ids;
base::TimeDelta preferred_frame_interval;
VizCompositorThreadRunnerWebView::GetInstance()->ScheduleOnVizAndBlock(
base::BindOnce(&HardwareRenderer::OnViz::PostDrawOnViz,
base::Unretained(on_viz_.get()), &timing_details,
&rendering_thread_ids, &preferred_frame_interval));
if (report_rendering_threads_ && report_rendering_threads_callback) {
std::move(report_rendering_threads_callback)
.Run(rendering_thread_ids.data(), rendering_thread_ids.size());
}
bool frame_interval_changed =
preferred_frame_interval_ != preferred_frame_interval;
preferred_frame_interval_ = preferred_frame_interval;
if (need_to_update_draw_constraints || !timing_details.empty() ||
frame_interval_changed) {
// |frame_token| will be reported through the FrameSinkManager so we pass 0
// here.
render_thread_manager_->PostParentDrawDataToChildCompositorOnRT(
draw_constraints, child_frame_->frame_sink_id,
std::move(timing_details), 0, preferred_frame_interval_);
}
// If using ANGLE we have not reset Skia's state at the beginning of the draw,
// as in general ANGLE will take care of saving/restoring GL state. However,
// it is necessary to reset Skia's state in the complex clip case, as Chrome
// mucks with GL state outside of ANGLE's knowledge in handling this case. We
// need to do this check at the end of the frame as it is only at this point
// that we know whether this frame hit the complex clip case. (For non-ANGLE
// we need to reset Skia's state at the beginning of each draw in any case, so
// doing it here would be redundant).
if (gl::GLSurfaceEGL::GetGLDisplayEGL()
->IsANGLEExternalContextAndSurfaceSupported() &&
output_surface_provider_.gl_surface()->IsDrawingToFBO()) {
DCHECK(output_surface_provider_.shared_context_state());
output_surface_provider_.shared_context_state()
->PessimisticallyResetGrContext();
}
}
void HardwareRenderer::RemoveOverlays(
OverlaysParams::MergeTransactionFn merge_transaction) {
VizCompositorThreadRunnerWebView::GetInstance()->ScheduleOnVizAndBlock(
base::BindOnce(&HardwareRenderer::OnViz::RemoveOverlaysOnViz,
base::Unretained(on_viz_.get())));
MergeTransactionIfNeeded(merge_transaction);
}
void HardwareRenderer::MergeTransactionIfNeeded(
OverlaysParams::MergeTransactionFn merge_transaction) {
auto* overlay_processor = on_viz_->overlay_processor();
if (overlay_processor) {
auto transaction = overlay_processor->TakeSurfaceTransactionOnRT();
if (transaction) {
DCHECK(merge_transaction);
merge_transaction(transaction->GetTransaction());
}
}
}
void HardwareRenderer::AbandonContext() {
MarkAllowContextLoss();
output_surface_provider_.shared_context_state()->MarkContextLost(
gpu::error::ContextLostReason::kUnknown);
}
void HardwareRenderer::MarkAllowContextLoss() {
if (on_viz_) {
VizCompositorThreadRunnerWebView::GetInstance()->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&HardwareRenderer::OnViz::MarkAllowContextLossOnViz,
base::Unretained(on_viz_.get())));
}
output_surface_provider_.MarkAllowContextLoss();
}
void HardwareRenderer::CommitFrame() {
TRACE_EVENT0("android_webview", "CommitFrame");
scroll_offset_ = render_thread_manager_->GetScrollOffsetOnRT();
ChildFrameQueue child_frames = render_thread_manager_->PassFramesOnRT();
// |child_frames| should have at most one non-empty frame, and one current
// and unwaited frame, in that order.
DCHECK_LE(child_frames.size(), 2u);
if (child_frames.empty()) {
return;
}
// Insert all except last, ie current frame.
while (child_frames.size() > 1u) {
child_frame_queue_.emplace_back(std::move(child_frames.front()));
child_frames.pop_front();
}
for (auto& pruned_frame : WaitAndPruneFrameQueue(&child_frame_queue_)) {
ReturnChildFrame(std::move(pruned_frame));
}
DCHECK_LE(child_frame_queue_.size(), 1u);
child_frame_queue_.emplace_back(std::move(child_frames.front()));
}
void HardwareRenderer::ReportDrawMetric(
const HardwareRendererDrawParams& params) {
const bool params_changed = last_draw_params_ == params;
auto type = GetDrawAndSubmissionType(
did_invalidate_, did_submit_compositor_frame_, params_changed);
UMA_HISTOGRAM_ENUMERATION("Android.WebView.Gfx.HardwareDrawType", type);
last_draw_params_ = params;
did_invalidate_ = false;
did_submit_compositor_frame_ = false;
}
void HardwareRenderer::Draw(
const HardwareRendererDrawParams& params,
const OverlaysParams& overlays_params,
ReportRenderingThreadsCallback report_rendering_threads_callback) {
TRACE_EVENT0("android_webview", "HardwareRenderer::Draw");
for (auto& pruned_frame : WaitAndPruneFrameQueue(&child_frame_queue_)) {
ReturnChildFrame(std::move(pruned_frame));
}
DCHECK_LE(child_frame_queue_.size(), 1u);
if (!child_frame_queue_.empty()) {
child_frame_ = std::move(child_frame_queue_.front());
child_frame_queue_.clear();
did_invalidate_ = child_frame_->did_invalidate;
did_submit_compositor_frame_ = !!child_frame_->frame;
}
// 0u is not a valid frame_sink_id, but can happen when renderer did not
// produce a frame. Keep the existing id in that case.
if (child_frame_ && child_frame_->layer_tree_frame_sink_id > 0u) {
last_committed_layer_tree_frame_sink_id_ =
child_frame_->layer_tree_frame_sink_id;
}
ReportDrawMetric(params);
if (last_egl_context_ && !IsUsingVulkan() && !IsUsingANGLEOverGL()) {
// We need to watch if the current Android context has changed and enforce a
// clean-up in the compositor. This is only necessary for the validating
// command decoder.
EGLContext current_context = eglGetCurrentContext();
DCHECK(current_context) << "Draw called without EGLContext";
// TODO(boliu): Handle context loss.
if (last_egl_context_ != current_context) {
DLOG(WARNING) << "EGLContextChanged";
}
}
DrawAndSwap(params, overlays_params,
std::move(report_rendering_threads_callback));
}
void HardwareRenderer::ReturnChildFrame(
std::unique_ptr<ChildFrame> child_frame) {
if (!child_frame || !child_frame->frame) {
return;
}
std::vector<viz::ReturnedResource> resources_to_return =
viz::TransferableResource::ReturnResources(
child_frame->frame->resource_list);
// The child frame's frame_sink_id is not necessarily same as
// |child_frame_sink_id_|.
ReturnResourcesToCompositor(std::move(resources_to_return),
child_frame->frame_sink_id,
child_frame->layer_tree_frame_sink_id);
}
void HardwareRenderer::ReturnResourcesToCompositor(
std::vector<viz::ReturnedResource> resources,
const viz::FrameSinkId& frame_sink_id,
uint32_t layer_tree_frame_sink_id) {
render_thread_manager_->InsertReturnedResourcesOnRT(
std::move(resources), frame_sink_id, layer_tree_frame_sink_id);
}
void HardwareRenderer::SetChildFrameForTesting(
std::unique_ptr<ChildFrame> child_frame) {
child_frame_ = std::move(child_frame);
}
} // namespace android_webview