| // 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/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/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 { |
| |
| class ScopedCurrentContext { |
| public: |
| explicit ScopedCurrentContext(gpu::SharedContextState* state, |
| gl::GLSurface* surface) |
| : state_(state), surface_(surface) { |
| state_->MakeCurrent(surface_); |
| } |
| ~ScopedCurrentContext() { state_->ReleaseCurrent(surface_); } |
| |
| private: |
| const raw_ptr<gpu::SharedContextState> state_; |
| gl::GLSurface* surface_; |
| }; |
| |
| 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); |
| void RemoveOverlaysOnViz(); |
| void MarkExpectContextLossOnViz(); |
| |
| 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; |
| |
| 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_(base::FeatureList::IsEnabled( |
| ::features::kWebViewNewInvalidateHeuristic)) { |
| 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(), true); |
| overlay_processor_webview_ = display_->overlay_processor(); |
| |
| display_->SetVisible(true); |
| display_->DisableGPUAccessByDefault(); |
| } |
| |
| 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_); |
| 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)); |
| } |
| } |
| |
| 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; |
| |
| 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->clip_rect = clip; |
| 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), |
| 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); |
| |
| auto commit_predicate = base::BindRepeating( |
| [](const viz::BeginFrameId& current_frame_id, |
| const viz::FrameSinkId& root_frame_sink_id, |
| const viz::FrameSinkId& child_frame_sink_id, |
| const viz::SurfaceId& surface_id, |
| const viz::BeginFrameId& frame_id) { |
| // 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); |
| return true; |
| } |
| |
| // Commit all frames that are older than current one. |
| if (frame_id.sequence_number < current_frame_id.sequence_number) { |
| return true; |
| } |
| |
| // All clients except root renderer and root surface are frame behind. |
| const bool is_frame_behind = |
| surface_id.frame_sink_id() != root_frame_sink_id && |
| surface_id.frame_sink_id() != child_frame_sink_id; |
| |
| // 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; |
| }, |
| child_frame->begin_frame_args.frame_id, root_surface_id.frame_sink_id(), |
| child_surface_id_.frame_sink_id()); |
| |
| 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); |
| auto now = base::TimeTicks::Now(); |
| display_->DrawAndSwap({now, now}); |
| |
| without_gpu_->SetContainedSurfaces(display_->GetContainedSurfaceIds()); |
| } |
| |
| void HardwareRenderer::OnViz::PostDrawOnViz( |
| viz::FrameTimingDetailsMap* timing_details) { |
| *timing_details = without_gpu_->TakeChildFrameTimingDetailsMap(); |
| } |
| |
| void HardwareRenderer::OnViz::RemoveOverlaysOnViz() { |
| if (overlay_processor_webview_) { |
| overlay_processor_webview_->RemoveOverlays(); |
| } |
| } |
| |
| void HardwareRenderer::OnViz::MarkExpectContextLossOnViz() { |
| 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); |
| DCHECK(!frame->frame); |
| } |
| 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)); |
| 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) { |
| 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_); |
| 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); |
| } |
| 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(); |
| } |
| |
| void HardwareRenderer::DrawAndSwap(const HardwareRendererDrawParams& params, |
| const OverlaysParams& overlays_params) { |
| TRACE_EVENT1("android_webview", "HardwareRenderer::Draw", "vulkan", |
| IsUsingVulkan()); |
| |
| if (!IsUsingVulkan()) { |
| UMA_HISTOGRAM_BOOLEAN( |
| "Android.WebView.Gfx.GLDrawWasToFBO", |
| output_surface_provider_.gl_surface()->IsDrawingToFBO()); |
| } |
| |
| DCHECK_CALLED_ON_VALID_THREAD(render_thread_checker_); |
| |
| // Ensure that the context is current and that it is released before |
| // returning. When using ANGLE, the former is not guaranteed to be true and |
| // the latter is required for the external ANGLE context. For non-ANGLE case, |
| // fake context and surface are used, so releasing current context should be |
| // very cheap. |
| ScopedCurrentContext scoped_context( |
| output_surface_provider_.shared_context_state().get(), |
| output_surface_provider_.gl_surface().get()); |
| |
| 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); |
| } |
| 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(); |
| } |
| |
| absl::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. |
| VizCompositorThreadRunnerWebView::GetInstance()->ScheduleOnVizAndBlock( |
| base::BindOnce(&HardwareRenderer::OnViz::PostDrawOnViz, |
| base::Unretained(on_viz_.get()), &timing_details)); |
| |
| if (need_to_update_draw_constraints || !timing_details.empty()) { |
| // |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); |
| } |
| |
| // 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() { |
| VizCompositorThreadRunnerWebView::GetInstance()->task_runner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&HardwareRenderer::OnViz::MarkExpectContextLossOnViz, |
| base::Unretained(on_viz_.get()))); |
| output_surface_provider_.MarkExpectContextLoss(); |
| output_surface_provider_.shared_context_state()->MarkContextLost( |
| gpu::error::ContextLostReason::kUnknown); |
| } |
| |
| 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) { |
| 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_) { |
| // We need to watch if the current Android context has changed and enforce a |
| // clean-up in the compositor. |
| 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); |
| } |
| |
| 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) { |
| if (!base::FeatureList::IsEnabled(features::kWebViewCheckReturnResources) && |
| layer_tree_frame_sink_id != last_committed_layer_tree_frame_sink_id_) { |
| return; |
| } |
| 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 |