1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.h"
6 
7 #include <utility>
8 
9 #include "base/debug/stack_trace.h"
10 #include "base/single_thread_task_runner.h"
11 #include "components/viz/common/quads/compositor_frame.h"
12 #include "components/viz/common/quads/texture_draw_quad.h"
13 #include "components/viz/common/resources/resource_format.h"
14 #include "components/viz/common/resources/single_release_callback.h"
15 #include "services/viz/public/mojom/compositing/frame_timing_details.mojom-blink.h"
16 #include "services/viz/public/mojom/hit_test/hit_test_region_list.mojom-blink.h"
17 #include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
18 #include "third_party/blink/public/mojom/frame_sinks/embedded_frame_sink.mojom-blink.h"
19 #include "third_party/blink/public/platform/platform.h"
20 #include "third_party/blink/public/platform/web_graphics_context_3d_provider.h"
21 #include "third_party/blink/renderer/platform/graphics/canvas_resource.h"
22 #include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h"
23 #include "third_party/blink/renderer/platform/graphics/offscreen_canvas_placeholder.h"
24 #include "third_party/blink/renderer/platform/instrumentation/histogram.h"
25 #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
26 #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
27 #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
28 #include "ui/gfx/mojom/presentation_feedback.mojom-blink.h"
29 
30 namespace blink {
31 
32 enum {
33   kMaxPendingCompositorFrames = 2,
34   kMaxUnreclaimedPlaceholderFrames = 3,
35 };
36 
37 struct CanvasResourceDispatcher::FrameResource {
38   FrameResource() = default;
~FrameResourceblink::CanvasResourceDispatcher::FrameResource39   ~FrameResource() {
40     if (release_callback)
41       release_callback->Run(sync_token, is_lost);
42   }
43 
44   // TODO(junov):  What does this do?
45   bool spare_lock = true;
46 
47   std::unique_ptr<viz::SingleReleaseCallback> release_callback;
48   gpu::SyncToken sync_token;
49   bool is_lost = false;
50 };
51 
CanvasResourceDispatcher(CanvasResourceDispatcherClient * client,uint32_t client_id,uint32_t sink_id,int canvas_id,const IntSize & size)52 CanvasResourceDispatcher::CanvasResourceDispatcher(
53     CanvasResourceDispatcherClient* client,
54     uint32_t client_id,
55     uint32_t sink_id,
56     int canvas_id,
57     const IntSize& size)
58     : frame_sink_id_(viz::FrameSinkId(client_id, sink_id)),
59       size_(size),
60       change_size_for_next_commit_(false),
61       needs_begin_frame_(false),
62       placeholder_canvas_id_(canvas_id),
63       num_unreclaimed_frames_posted_(0),
64       client_(client) {
65   // Frameless canvas pass an invalid |frame_sink_id_|; don't create mojo
66   // channel for this special case.
67   if (!frame_sink_id_.is_valid())
68     return;
69 
70   DCHECK(!sink_.is_bound());
71   mojo::Remote<mojom::blink::EmbeddedFrameSinkProvider> provider;
72   Platform::Current()->GetBrowserInterfaceBroker()->GetInterface(
73       provider.BindNewPipeAndPassReceiver());
74 
75   DCHECK(provider);
76   provider->CreateCompositorFrameSink(frame_sink_id_,
77                                       receiver_.BindNewPipeAndPassRemote(),
78                                       sink_.BindNewPipeAndPassReceiver());
79   provider->ConnectToEmbedder(frame_sink_id_,
80                               surface_embedder_.BindNewPipeAndPassReceiver());
81 }
82 
83 CanvasResourceDispatcher::~CanvasResourceDispatcher() = default;
84 
85 namespace {
86 
UpdatePlaceholderImage(int placeholder_canvas_id,scoped_refptr<blink::CanvasResource> canvas_resource,viz::ResourceId resource_id)87 void UpdatePlaceholderImage(
88     int placeholder_canvas_id,
89     scoped_refptr<blink::CanvasResource> canvas_resource,
90     viz::ResourceId resource_id) {
91   DCHECK(IsMainThread());
92   OffscreenCanvasPlaceholder* placeholder_canvas =
93       OffscreenCanvasPlaceholder::GetPlaceholderCanvasById(
94           placeholder_canvas_id);
95   if (placeholder_canvas) {
96     placeholder_canvas->SetOffscreenCanvasResource(std::move(canvas_resource),
97                                                    resource_id);
98   }
99 }
100 
UpdatePlaceholderDispatcher(base::WeakPtr<CanvasResourceDispatcher> dispatcher,scoped_refptr<base::SingleThreadTaskRunner> task_runner,int placeholder_canvas_id)101 void UpdatePlaceholderDispatcher(
102     base::WeakPtr<CanvasResourceDispatcher> dispatcher,
103     scoped_refptr<base::SingleThreadTaskRunner> task_runner,
104     int placeholder_canvas_id) {
105   OffscreenCanvasPlaceholder* placeholder_canvas =
106       OffscreenCanvasPlaceholder::GetPlaceholderCanvasById(
107           placeholder_canvas_id);
108   // Note that the placeholder canvas may be destroyed when this post task get
109   // to executed.
110   if (placeholder_canvas)
111     placeholder_canvas->SetOffscreenCanvasDispatcher(dispatcher, task_runner);
112 }
113 
114 }  // namespace
115 
PostImageToPlaceholderIfNotBlocked(scoped_refptr<CanvasResource> canvas_resource,viz::ResourceId resource_id)116 void CanvasResourceDispatcher::PostImageToPlaceholderIfNotBlocked(
117     scoped_refptr<CanvasResource> canvas_resource,
118     viz::ResourceId resource_id) {
119   if (placeholder_canvas_id_ == kInvalidPlaceholderCanvasId) {
120     ReclaimResourceInternal(resource_id);
121     return;
122   }
123   // Determines whether the main thread may be blocked. If unblocked, post
124   // |canvas_resource|. Otherwise, save it but do not post it.
125   if (num_unreclaimed_frames_posted_ < kMaxUnreclaimedPlaceholderFrames) {
126     this->PostImageToPlaceholder(std::move(canvas_resource), resource_id);
127     num_unreclaimed_frames_posted_++;
128   } else {
129     DCHECK(num_unreclaimed_frames_posted_ == kMaxUnreclaimedPlaceholderFrames);
130     if (latest_unposted_image_) {
131       // The previous unposted resource becomes obsolete now.
132       ReclaimResourceInternal(latest_unposted_resource_id_);
133     }
134 
135     latest_unposted_image_ = std::move(canvas_resource);
136     latest_unposted_resource_id_ = resource_id;
137   }
138 }
139 
PostImageToPlaceholder(scoped_refptr<CanvasResource> canvas_resource,viz::ResourceId resource_id)140 void CanvasResourceDispatcher::PostImageToPlaceholder(
141     scoped_refptr<CanvasResource> canvas_resource,
142     viz::ResourceId resource_id) {
143   scoped_refptr<base::SingleThreadTaskRunner> dispatcher_task_runner =
144       Thread::Current()->GetTaskRunner();
145   // After this point, |canvas_resource| can only be used on the main thread,
146   // until it is returned.
147   canvas_resource->Transfer();
148   PostCrossThreadTask(
149       *Thread::MainThread()->Scheduler()->CompositorTaskRunner(), FROM_HERE,
150       CrossThreadBindOnce(UpdatePlaceholderImage, placeholder_canvas_id_,
151                           std::move(canvas_resource), resource_id));
152 }
153 
DispatchFrameSync(scoped_refptr<CanvasResource> canvas_resource,base::TimeTicks commit_start_time,const SkIRect & damage_rect,bool needs_vertical_flip,bool is_opaque)154 void CanvasResourceDispatcher::DispatchFrameSync(
155     scoped_refptr<CanvasResource> canvas_resource,
156     base::TimeTicks commit_start_time,
157     const SkIRect& damage_rect,
158     bool needs_vertical_flip,
159     bool is_opaque) {
160   TRACE_EVENT0("blink", "CanvasResourceDispatcher::DispatchFrameSync");
161   viz::CompositorFrame frame;
162   if (!PrepareFrame(std::move(canvas_resource), commit_start_time, damage_rect,
163                     needs_vertical_flip, is_opaque, &frame)) {
164     return;
165   }
166 
167   pending_compositor_frames_++;
168   WTF::Vector<viz::ReturnedResource> resources;
169   sink_->SubmitCompositorFrameSync(
170       parent_local_surface_id_allocator_.GetCurrentLocalSurfaceIdAllocation()
171           .local_surface_id(),
172       std::move(frame), nullptr, 0, &resources);
173   DidReceiveCompositorFrameAck(resources);
174 }
175 
DispatchFrame(scoped_refptr<CanvasResource> canvas_resource,base::TimeTicks commit_start_time,const SkIRect & damage_rect,bool needs_vertical_flip,bool is_opaque)176 void CanvasResourceDispatcher::DispatchFrame(
177     scoped_refptr<CanvasResource> canvas_resource,
178     base::TimeTicks commit_start_time,
179     const SkIRect& damage_rect,
180     bool needs_vertical_flip,
181     bool is_opaque) {
182   TRACE_EVENT0("blink", "CanvasResourceDispatcher::DispatchFrame");
183   viz::CompositorFrame frame;
184   if (!PrepareFrame(std::move(canvas_resource), commit_start_time, damage_rect,
185                     needs_vertical_flip, is_opaque, &frame)) {
186     return;
187   }
188 
189   pending_compositor_frames_++;
190   sink_->SubmitCompositorFrame(
191       parent_local_surface_id_allocator_.GetCurrentLocalSurfaceIdAllocation()
192           .local_surface_id(),
193       std::move(frame), nullptr, 0);
194 }
195 
PrepareFrame(scoped_refptr<CanvasResource> canvas_resource,base::TimeTicks commit_start_time,const SkIRect & damage_rect,bool needs_vertical_flip,bool is_opaque,viz::CompositorFrame * frame)196 bool CanvasResourceDispatcher::PrepareFrame(
197     scoped_refptr<CanvasResource> canvas_resource,
198     base::TimeTicks commit_start_time,
199     const SkIRect& damage_rect,
200     bool needs_vertical_flip,
201     bool is_opaque,
202     viz::CompositorFrame* frame) {
203   TRACE_EVENT0("blink", "CanvasResourceDispatcher::PrepareFrame");
204   if (!canvas_resource || !VerifyImageSize(canvas_resource->Size())) {
205     return false;
206   }
207 
208   next_resource_id_++;
209 
210   // For frameless canvas, we don't get a valid frame_sink_id and should drop.
211   if (!frame_sink_id_.is_valid()) {
212     PostImageToPlaceholderIfNotBlocked(std::move(canvas_resource),
213                                        next_resource_id_);
214     return false;
215   }
216 
217   // TODO(crbug.com/652931): update the device_scale_factor
218   frame->metadata.device_scale_factor = 1.0f;
219   if (!current_begin_frame_ack_.frame_id.IsSequenceValid()) {
220     // TODO(eseckler): This shouldn't be necessary when OffscreenCanvas no
221     // longer submits CompositorFrames without prior BeginFrame.
222     current_begin_frame_ack_ = viz::BeginFrameAck::CreateManualAckWithDamage();
223   } else {
224     current_begin_frame_ack_.has_damage = true;
225   }
226   frame->metadata.begin_frame_ack = current_begin_frame_ack_;
227 
228   frame->metadata.frame_token = ++next_frame_token_;
229 
230   const gfx::Rect bounds(size_.Width(), size_.Height());
231   constexpr int kRenderPassId = 1;
232   constexpr bool is_clipped = false;
233   std::unique_ptr<viz::RenderPass> pass = viz::RenderPass::Create();
234   pass->SetNew(kRenderPassId, bounds,
235                gfx::Rect(damage_rect.x(), damage_rect.y(), damage_rect.width(),
236                          damage_rect.height()),
237                gfx::Transform());
238 
239   viz::SharedQuadState* sqs = pass->CreateAndAppendSharedQuadState();
240   sqs->SetAll(gfx::Transform(), bounds, bounds, gfx::RRectF(), bounds,
241               is_clipped, is_opaque, 1.f, SkBlendMode::kSrcOver, 0);
242 
243   viz::TransferableResource resource;
244   auto frame_resource = std::make_unique<FrameResource>();
245 
246   bool nearest_neighbor =
247       canvas_resource->FilterQuality() == kNone_SkFilterQuality;
248 
249   canvas_resource->PrepareTransferableResource(
250       &resource, &frame_resource->release_callback, kVerifiedSyncToken);
251   resource.id = next_resource_id_;
252 
253   resources_.insert(next_resource_id_, std::move(frame_resource));
254 
255   // TODO(crbug.com/869913): add unit testing for this.
256   const gfx::Size canvas_resource_size(canvas_resource->Size());
257 
258   PostImageToPlaceholderIfNotBlocked(std::move(canvas_resource),
259                                      next_resource_id_);
260 
261   frame->resource_list.push_back(std::move(resource));
262 
263   viz::TextureDrawQuad* quad =
264       pass->CreateAndAppendDrawQuad<viz::TextureDrawQuad>();
265 
266   const bool needs_blending = !is_opaque;
267   // TODO(crbug.com/645993): this should be inherited from WebGL context's
268   // creation settings.
269   constexpr bool kPremultipliedAlpha = true;
270   constexpr gfx::PointF uv_top_left(0.f, 0.f);
271   constexpr gfx::PointF uv_bottom_right(1.f, 1.f);
272   constexpr float vertex_opacity[4] = {1.f, 1.f, 1.f, 1.f};
273 
274   // Accelerated resources have the origin of coordinates in the upper left
275   // corner while canvases have it in the lower left corner. The DrawQuad is
276   // marked as vertically flipped unless someone else has done the flip for us.
277   const bool yflipped =
278       SharedGpuContext::IsGpuCompositingEnabled() && needs_vertical_flip;
279   quad->SetAll(sqs, bounds, bounds, needs_blending, resource.id,
280                canvas_resource_size, kPremultipliedAlpha, uv_top_left,
281                uv_bottom_right, SK_ColorTRANSPARENT, vertex_opacity, yflipped,
282                nearest_neighbor, /*secure_output_only=*/false,
283                gfx::ProtectedVideoType::kClear);
284 
285   frame->render_pass_list.push_back(std::move(pass));
286 
287   if (change_size_for_next_commit_ ||
288       !parent_local_surface_id_allocator_.HasValidLocalSurfaceIdAllocation()) {
289     parent_local_surface_id_allocator_.GenerateId();
290     surface_embedder_->SetLocalSurfaceId(
291         parent_local_surface_id_allocator_.GetCurrentLocalSurfaceIdAllocation()
292             .local_surface_id());
293     change_size_for_next_commit_ = false;
294   }
295   frame->metadata.local_surface_id_allocation_time =
296       parent_local_surface_id_allocator_.GetCurrentLocalSurfaceIdAllocation()
297           .allocation_time();
298 
299   return true;
300 }
301 
DidReceiveCompositorFrameAck(const WTF::Vector<viz::ReturnedResource> & resources)302 void CanvasResourceDispatcher::DidReceiveCompositorFrameAck(
303     const WTF::Vector<viz::ReturnedResource>& resources) {
304   ReclaimResources(resources);
305   pending_compositor_frames_--;
306   DCHECK_GE(pending_compositor_frames_, 0);
307 }
308 
SetNeedsBeginFrame(bool needs_begin_frame)309 void CanvasResourceDispatcher::SetNeedsBeginFrame(bool needs_begin_frame) {
310   if (needs_begin_frame_ == needs_begin_frame)
311     return;
312   needs_begin_frame_ = needs_begin_frame;
313   if (!suspend_animation_)
314     SetNeedsBeginFrameInternal();
315 }
316 
SetSuspendAnimation(bool suspend_animation)317 void CanvasResourceDispatcher::SetSuspendAnimation(bool suspend_animation) {
318   if (suspend_animation_ == suspend_animation)
319     return;
320   suspend_animation_ = suspend_animation;
321   if (needs_begin_frame_)
322     SetNeedsBeginFrameInternal();
323 }
324 
SetNeedsBeginFrameInternal()325 void CanvasResourceDispatcher::SetNeedsBeginFrameInternal() {
326   if (sink_)
327     sink_->SetNeedsBeginFrame(needs_begin_frame_ && !suspend_animation_);
328 }
329 
HasTooManyPendingFrames() const330 bool CanvasResourceDispatcher::HasTooManyPendingFrames() const {
331   return pending_compositor_frames_ >= kMaxPendingCompositorFrames;
332 }
333 
OnBeginFrame(const viz::BeginFrameArgs & begin_frame_args,WTF::HashMap<uint32_t,::viz::mojom::blink::FrameTimingDetailsPtr>)334 void CanvasResourceDispatcher::OnBeginFrame(
335     const viz::BeginFrameArgs& begin_frame_args,
336     WTF::HashMap<uint32_t, ::viz::mojom::blink::FrameTimingDetailsPtr>) {
337   current_begin_frame_ack_ = viz::BeginFrameAck(begin_frame_args, false);
338   if (HasTooManyPendingFrames() ||
339       (begin_frame_args.type == viz::BeginFrameArgs::MISSED &&
340        base::TimeTicks::Now() > begin_frame_args.deadline)) {
341     sink_->DidNotProduceFrame(current_begin_frame_ack_);
342     return;
343   }
344 
345   // TODO(fserb): should EnqueueMicrotask BeginFrame().
346   // We usually never get to BeginFrame if we are on RAF mode. But it could
347   // still happen that begin frame gets requested and we don't have a frame
348   // anymore, so we shouldn't let the compositor wait.
349   bool submitted_frame = Client() && Client()->BeginFrame();
350   if (!submitted_frame) {
351     sink_->DidNotProduceFrame(current_begin_frame_ack_);
352   }
353 
354   // TODO(fserb): Update this with the correct value if we are on RAF submit.
355   current_begin_frame_ack_.frame_id.sequence_number =
356       viz::BeginFrameArgs::kInvalidFrameNumber;
357 }
358 
ReclaimResources(const WTF::Vector<viz::ReturnedResource> & resources)359 void CanvasResourceDispatcher::ReclaimResources(
360     const WTF::Vector<viz::ReturnedResource>& resources) {
361   for (const auto& resource : resources) {
362     auto it = resources_.find(resource.id);
363 
364     DCHECK(it != resources_.end());
365     if (it == resources_.end())
366       continue;
367 
368     it->value->sync_token = resource.sync_token;
369     it->value->is_lost = resource.lost;
370     ReclaimResourceInternal(it);
371   }
372 }
373 
ReclaimResource(viz::ResourceId resource_id)374 void CanvasResourceDispatcher::ReclaimResource(viz::ResourceId resource_id) {
375   ReclaimResourceInternal(resource_id);
376 
377   num_unreclaimed_frames_posted_--;
378 
379   // The main thread has become unblocked recently and we have an image that
380   // have not been posted yet.
381   if (latest_unposted_image_) {
382     DCHECK(num_unreclaimed_frames_posted_ ==
383            kMaxUnreclaimedPlaceholderFrames - 1);
384     PostImageToPlaceholderIfNotBlocked(std::move(latest_unposted_image_),
385                                        latest_unposted_resource_id_);
386     latest_unposted_resource_id_ = 0;
387   }
388 }
389 
VerifyImageSize(const IntSize image_size)390 bool CanvasResourceDispatcher::VerifyImageSize(const IntSize image_size) {
391   return image_size == size_;
392 }
393 
Reshape(const IntSize & size)394 void CanvasResourceDispatcher::Reshape(const IntSize& size) {
395   if (size_ != size) {
396     size_ = size;
397     change_size_for_next_commit_ = true;
398   }
399 }
400 
DidAllocateSharedBitmap(base::ReadOnlySharedMemoryRegion region,::gpu::mojom::blink::MailboxPtr id)401 void CanvasResourceDispatcher::DidAllocateSharedBitmap(
402     base::ReadOnlySharedMemoryRegion region,
403     ::gpu::mojom::blink::MailboxPtr id) {
404   if (sink_)
405     sink_->DidAllocateSharedBitmap(std::move(region), std::move(id));
406 }
407 
DidDeleteSharedBitmap(::gpu::mojom::blink::MailboxPtr id)408 void CanvasResourceDispatcher::DidDeleteSharedBitmap(
409     ::gpu::mojom::blink::MailboxPtr id) {
410   if (sink_)
411     sink_->DidDeleteSharedBitmap(std::move(id));
412 }
413 
SetFilterQuality(SkFilterQuality filter_quality)414 void CanvasResourceDispatcher::SetFilterQuality(
415     SkFilterQuality filter_quality) {
416   if (Client())
417     Client()->SetFilterQualityInResource(filter_quality);
418 }
419 
SetPlaceholderCanvasDispatcher(int placeholder_canvas_id)420 void CanvasResourceDispatcher::SetPlaceholderCanvasDispatcher(
421     int placeholder_canvas_id) {
422   scoped_refptr<base::SingleThreadTaskRunner> dispatcher_task_runner =
423       Thread::Current()->GetTaskRunner();
424 
425   // If the offscreencanvas is in the same tread as the canvas, we will update
426   // the canvas resource dispatcher directly. So Offscreen Canvas can behave in
427   // a more synchronous way when it's on the main thread.
428   if (IsMainThread()) {
429     UpdatePlaceholderDispatcher(this->GetWeakPtr(), dispatcher_task_runner,
430                                 placeholder_canvas_id);
431   } else {
432     PostCrossThreadTask(
433         *Thread::MainThread()->Scheduler()->CompositorTaskRunner(), FROM_HERE,
434         CrossThreadBindOnce(UpdatePlaceholderDispatcher, this->GetWeakPtr(),
435                             WTF::Passed(std::move(dispatcher_task_runner)),
436                             placeholder_canvas_id));
437   }
438 }
439 
ReclaimResourceInternal(viz::ResourceId resource_id)440 void CanvasResourceDispatcher::ReclaimResourceInternal(
441     viz::ResourceId resource_id) {
442   auto it = resources_.find(resource_id);
443   if (it != resources_.end())
444     ReclaimResourceInternal(it);
445 }
446 
ReclaimResourceInternal(const ResourceMap::iterator & it)447 void CanvasResourceDispatcher::ReclaimResourceInternal(
448     const ResourceMap::iterator& it) {
449   if (it->value->spare_lock) {
450     it->value->spare_lock = false;
451     return;
452   }
453   resources_.erase(it);
454 }
455 
456 }  // namespace blink
457