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