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