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 <memory>
8
9 #include "components/viz/common/quads/texture_draw_quad.h"
10 #include "mojo/public/cpp/bindings/receiver.h"
11 #include "services/viz/public/mojom/hit_test/hit_test_region_list.mojom-blink.h"
12 #include "testing/gmock/include/gmock/gmock.h"
13 #include "testing/gtest/include/gtest/gtest.h"
14 #include "third_party/blink/public/mojom/frame_sinks/embedded_frame_sink.mojom-blink.h"
15 #include "third_party/blink/renderer/platform/graphics/canvas_resource.h"
16 #include "third_party/blink/renderer/platform/graphics/canvas_resource_provider.h"
17 #include "third_party/blink/renderer/platform/graphics/test/mock_compositor_frame_sink.h"
18 #include "third_party/blink/renderer/platform/graphics/test/mock_embedded_frame_sink_provider.h"
19 #include "third_party/blink/renderer/platform/testing/testing_platform_support.h"
20 #include "third_party/blink/renderer/platform/wtf/functional.h"
21 #include "third_party/skia/include/core/SkSurface.h"
22 #include "ui/gfx/mojom/presentation_feedback.mojom-blink.h"
23
24 using testing::_;
25 using testing::Mock;
26 using testing::ValuesIn;
27
28 namespace blink {
29
30 namespace {
31 constexpr uint32_t kClientId = 2;
32 constexpr uint32_t kSinkId = 1;
33
34 constexpr size_t kWidth = 10;
35 constexpr size_t kHeight = 10;
36
37 struct TestParams {
38 bool context_alpha;
39 bool vertical_flip;
40 };
41 } // namespace
42
43 class MockCanvasResourceDispatcher : public CanvasResourceDispatcher {
44 public:
MockCanvasResourceDispatcher()45 MockCanvasResourceDispatcher()
46 : CanvasResourceDispatcher(nullptr /* client */,
47 kClientId,
48 kSinkId,
49 0 /* placeholder_canvas_id* */,
50 {kWidth, kHeight} /* canvas_size */) {}
51
52 MOCK_METHOD2(PostImageToPlaceholder,
53 void(scoped_refptr<CanvasResource>, unsigned resource_id));
54 };
55
56 class CanvasResourceDispatcherTest
57 : public testing::Test,
58 public ::testing::WithParamInterface<TestParams> {
59 public:
DispatchOneFrame()60 void DispatchOneFrame() {
61 dispatcher_->DispatchFrame(resource_provider_->ProduceCanvasResource(),
62 base::TimeTicks(), SkIRect::MakeEmpty(),
63 false /* needs_vertical_flip */,
64 false /* is-opaque */);
65 }
66
GetNumUnreclaimedFramesPosted()67 unsigned GetNumUnreclaimedFramesPosted() {
68 return dispatcher_->num_unreclaimed_frames_posted_;
69 }
70
GetLatestUnpostedImage()71 CanvasResource* GetLatestUnpostedImage() {
72 return dispatcher_->latest_unposted_image_.get();
73 }
74
GetLatestUnpostedResourceId()75 viz::ResourceId GetLatestUnpostedResourceId() {
76 return dispatcher_->latest_unposted_resource_id_;
77 }
78
GetCurrentResourceId()79 viz::ResourceId GetCurrentResourceId() {
80 return dispatcher_->next_resource_id_;
81 }
82
GetSize() const83 const IntSize& GetSize() const { return dispatcher_->size_; }
84
85 protected:
86 CanvasResourceDispatcherTest() = default;
87
CreateCanvasResourceDispatcher()88 void CreateCanvasResourceDispatcher() {
89 dispatcher_ = std::make_unique<MockCanvasResourceDispatcher>();
90 resource_provider_ = CanvasResourceProvider::Create(
91 IntSize(kWidth, kHeight),
92 CanvasResourceProvider::ResourceUsage::kSoftwareCompositedResourceUsage,
93 nullptr, // context_provider_wrapper
94 0, // msaa_sample_count
95 kLow_SkFilterQuality, CanvasColorParams(),
96 CanvasResourceProvider::kDefaultPresentationMode,
97 dispatcher_->GetWeakPtr());
98 }
99
Dispatcher()100 MockCanvasResourceDispatcher* Dispatcher() { return dispatcher_.get(); }
101
102 private:
103 scoped_refptr<StaticBitmapImage> PrepareStaticBitmapImage();
104 std::unique_ptr<MockCanvasResourceDispatcher> dispatcher_;
105 std::unique_ptr<CanvasResourceProvider> resource_provider_;
106 };
107
TEST_F(CanvasResourceDispatcherTest,PlaceholderRunsNormally)108 TEST_F(CanvasResourceDispatcherTest, PlaceholderRunsNormally) {
109 CreateCanvasResourceDispatcher();
110 /* We allow OffscreenCanvas to post up to 3 frames without hearing a response
111 * from placeholder. */
112 // Post first frame
113 unsigned post_resource_id = 1u;
114 EXPECT_CALL(*(Dispatcher()), PostImageToPlaceholder(_, post_resource_id));
115 DispatchOneFrame();
116 EXPECT_EQ(1u, GetNumUnreclaimedFramesPosted());
117 EXPECT_EQ(1u, GetCurrentResourceId());
118 Mock::VerifyAndClearExpectations(Dispatcher());
119
120 // Post second frame
121 post_resource_id++;
122 EXPECT_CALL(*(Dispatcher()), PostImageToPlaceholder(_, post_resource_id));
123 DispatchOneFrame();
124 EXPECT_EQ(2u, GetNumUnreclaimedFramesPosted());
125 EXPECT_EQ(2u, GetCurrentResourceId());
126 Mock::VerifyAndClearExpectations(Dispatcher());
127
128 // Post third frame
129 post_resource_id++;
130 EXPECT_CALL(*(Dispatcher()), PostImageToPlaceholder(_, post_resource_id));
131 DispatchOneFrame();
132 EXPECT_EQ(3u, GetNumUnreclaimedFramesPosted());
133 EXPECT_EQ(3u, GetCurrentResourceId());
134 EXPECT_EQ(nullptr, GetLatestUnpostedImage());
135 Mock::VerifyAndClearExpectations(Dispatcher());
136
137 /* We mock the behavior of placeholder on main thread here, by reclaiming
138 * the resources in order. */
139 // Reclaim first frame
140 unsigned reclaim_resource_id = 1u;
141 Dispatcher()->ReclaimResource(reclaim_resource_id);
142 EXPECT_EQ(2u, GetNumUnreclaimedFramesPosted());
143
144 // Reclaim second frame
145 reclaim_resource_id++;
146 Dispatcher()->ReclaimResource(reclaim_resource_id);
147 EXPECT_EQ(1u, GetNumUnreclaimedFramesPosted());
148
149 // Reclaim third frame
150 reclaim_resource_id++;
151 Dispatcher()->ReclaimResource(reclaim_resource_id);
152 EXPECT_EQ(0u, GetNumUnreclaimedFramesPosted());
153 }
154
TEST_F(CanvasResourceDispatcherTest,PlaceholderBeingBlocked)155 TEST_F(CanvasResourceDispatcherTest, PlaceholderBeingBlocked) {
156 CreateCanvasResourceDispatcher();
157 /* When main thread is blocked, attempting to post more than 3 frames will
158 * result in only 3 PostImageToPlaceholder. The latest unposted image will
159 * be saved. */
160 EXPECT_CALL(*(Dispatcher()), PostImageToPlaceholder(_, _)).Times(3);
161
162 // Attempt to post 4 times
163 DispatchOneFrame();
164 DispatchOneFrame();
165 DispatchOneFrame();
166 DispatchOneFrame();
167 unsigned post_resource_id = 4u;
168 EXPECT_EQ(3u, GetNumUnreclaimedFramesPosted());
169 EXPECT_EQ(post_resource_id, GetCurrentResourceId());
170 EXPECT_TRUE(GetLatestUnpostedImage());
171 EXPECT_EQ(post_resource_id, GetLatestUnpostedResourceId());
172
173 // Attempt to post the 5th time. The latest unposted image will be replaced.
174 post_resource_id++;
175 DispatchOneFrame();
176 EXPECT_EQ(3u, GetNumUnreclaimedFramesPosted());
177 EXPECT_EQ(post_resource_id, GetCurrentResourceId());
178 EXPECT_TRUE(GetLatestUnpostedImage());
179 EXPECT_EQ(post_resource_id, GetLatestUnpostedResourceId());
180
181 Mock::VerifyAndClearExpectations(Dispatcher());
182
183 /* When main thread becomes unblocked, the first reclaim called by placeholder
184 * will trigger CanvasResourceDispatcher to post the last saved image.
185 * Resource reclaim happens in the same order as frame posting. */
186 unsigned reclaim_resource_id = 1u;
187 EXPECT_CALL(*(Dispatcher()), PostImageToPlaceholder(_, post_resource_id));
188 Dispatcher()->ReclaimResource(reclaim_resource_id);
189 // Reclaim 1 frame and post 1 frame, so numPostImagesUnresponded remains as 3
190 EXPECT_EQ(3u, GetNumUnreclaimedFramesPosted());
191 // Not generating new resource Id
192 EXPECT_EQ(post_resource_id, GetCurrentResourceId());
193 EXPECT_FALSE(GetLatestUnpostedImage());
194 EXPECT_EQ(0u, GetLatestUnpostedResourceId());
195 Mock::VerifyAndClearExpectations(Dispatcher());
196
197 reclaim_resource_id++;
198 Dispatcher()->ReclaimResource(reclaim_resource_id);
199 EXPECT_EQ(2u, GetNumUnreclaimedFramesPosted());
200 }
201
TEST_P(CanvasResourceDispatcherTest,DispatchFrame)202 TEST_P(CanvasResourceDispatcherTest, DispatchFrame) {
203 ScopedTestingPlatformSupport<TestingPlatformSupport> platform;
204 ::testing::InSequence s;
205
206 // To intercept SubmitCompositorFrame/SubmitCompositorFrameSync messages sent
207 // by theCanvasResourceDispatcher, we have to override the Mojo
208 // EmbeddedFrameSinkProvider interface impl and its CompositorFrameSinkClient.
209 MockEmbeddedFrameSinkProvider mock_embedded_frame_sink_provider;
210 mojo::Receiver<mojom::blink::EmbeddedFrameSinkProvider>
211 embedded_frame_sink_provider_receiver(&mock_embedded_frame_sink_provider);
212 auto override =
213 mock_embedded_frame_sink_provider.CreateScopedOverrideMojoInterface(
214 &embedded_frame_sink_provider_receiver);
215
216 CreateCanvasResourceDispatcher();
217
218 // CanvasResourceDispatcher ctor will cause a CreateCompositorFrameSink() to
219 // be issued.
220 EXPECT_CALL(mock_embedded_frame_sink_provider,
221 CreateCompositorFrameSink_(viz::FrameSinkId(kClientId, kSinkId)));
222 platform->RunUntilIdle();
223
224 auto canvas_resource = CanvasResourceSharedBitmap::Create(
225 GetSize(), CanvasColorParams(), nullptr /* provider */,
226 kLow_SkFilterQuality);
227 EXPECT_TRUE(!!canvas_resource);
228 EXPECT_EQ(canvas_resource->Size(), GetSize());
229
230 const bool context_alpha = GetParam().context_alpha;
231 const bool vertical_flip = GetParam().vertical_flip;
232
233 constexpr size_t kDamageWidth = 8;
234 constexpr size_t kDamageHeight = 6;
235 ASSERT_LE(kDamageWidth, kWidth);
236 ASSERT_LE(kDamageHeight, kHeight);
237
238 EXPECT_CALL(*(Dispatcher()), PostImageToPlaceholder(_, _));
239 EXPECT_CALL(mock_embedded_frame_sink_provider.mock_compositor_frame_sink(),
240 SubmitCompositorFrame_(_))
241 .WillOnce(::testing::WithArg<0>(
242 ::testing::Invoke([context_alpha](const viz::CompositorFrame* frame) {
243 const viz::RenderPass* render_pass =
244 frame->render_pass_list[0].get();
245
246 EXPECT_EQ(render_pass->transform_to_root_target, gfx::Transform());
247 EXPECT_EQ(render_pass->output_rect, gfx::Rect(kWidth, kHeight));
248 EXPECT_EQ(render_pass->damage_rect,
249 gfx::Rect(kDamageWidth, kDamageHeight));
250
251 const auto* quad = render_pass->quad_list.front();
252 EXPECT_EQ(quad->material, viz::DrawQuad::Material::kTextureContent);
253 EXPECT_EQ(quad->rect, gfx::Rect(kWidth, kHeight));
254 EXPECT_EQ(quad->visible_rect, gfx::Rect(kWidth, kHeight));
255
256 EXPECT_EQ(quad->needs_blending, context_alpha);
257
258 const auto* texture_quad =
259 static_cast<const viz::TextureDrawQuad*>(quad);
260 EXPECT_TRUE(texture_quad->premultiplied_alpha);
261 EXPECT_EQ(texture_quad->uv_top_left, gfx::PointF(0.0f, 0.0f));
262 EXPECT_EQ(texture_quad->uv_bottom_right, gfx::PointF(1.0f, 1.0f));
263 EXPECT_THAT(texture_quad->vertex_opacity,
264 ::testing::ElementsAre(1.f, 1.f, 1.f, 1.f));
265 // |y_flipped| should follow |vertical_flip| on GPU compositing; but
266 // we don't have that in unit tests, so it's always false.
267 EXPECT_FALSE(texture_quad->y_flipped);
268 })));
269
270 constexpr SkIRect damage_rect = SkIRect::MakeWH(kDamageWidth, kDamageHeight);
271 Dispatcher()->DispatchFrame(canvas_resource, base::TimeTicks::Now(),
272 damage_rect, vertical_flip,
273 !context_alpha /* is_opaque */);
274 platform->RunUntilIdle();
275 }
276
277 const TestParams kTestCases[] = {
278 {false /* context_alpha */, false /* vertical_flip */},
279 {false, true},
280 {true, false},
281 {true, true}};
282
283 INSTANTIATE_TEST_SUITE_P(All,
284 CanvasResourceDispatcherTest,
285 ValuesIn(kTestCases));
286 } // namespace blink
287