Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(9)

Side by Side Diff: chrome/android/junit/src/org/chromium/chrome/browser/compositor/CompositorSurfaceManagerTest.java

Issue 2201483002: Improve transition between opaque and translucent compositor views. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: now without VR-breaking badness Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2017 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 package org.chromium.chrome.browser.compositor;
6
7 import android.app.Activity;
8 import android.graphics.PixelFormat;
9 import android.view.Surface;
10 import android.view.SurfaceHolder;
11 import android.view.SurfaceView;
12 import android.widget.FrameLayout;
13
14 import static org.hamcrest.Matchers.lessThan;
15 import static org.junit.Assert.assertEquals;
16 import static org.junit.Assert.assertNotNull;
17 import static org.junit.Assert.assertThat;
18 import static org.junit.Assert.assertTrue;
19 import static org.mockito.ArgumentMatchers.anyInt;
20 import static org.mockito.ArgumentMatchers.eq;
21 import static org.mockito.Mockito.times;
22 import static org.mockito.Mockito.verify;
23
24 import org.junit.Before;
25 import org.junit.Test;
26 import org.junit.runner.RunWith;
27 import org.mockito.ArgumentMatchers;
28 import org.mockito.Mock;
29 import org.mockito.MockitoAnnotations;
30 import org.robolectric.Robolectric;
31 import org.robolectric.Shadows;
32 import org.robolectric.annotation.Config;
33 import org.robolectric.annotation.Implementation;
34 import org.robolectric.annotation.Implements;
35 import org.robolectric.shadows.ShadowLooper;
36 import org.robolectric.shadows.ShadowSurfaceView;
37
38 import org.chromium.base.test.util.Feature;
39 import org.chromium.testing.local.LocalRobolectricTestRunner;
40
41 import java.util.Set;
42
43 /**
44 * Unit tests for the CompositorSurfaceManager.
45 */
46 @RunWith(LocalRobolectricTestRunner.class)
47 @Config(manifest = Config.NONE)
48 public class CompositorSurfaceManagerTest {
49 @Mock
50 private SurfaceHolder.Callback mCallback;
51
52 private CompositorSurfaceManager mManager;
53
54 private FrameLayout mLayout;
55
56 /**
57 * Implementation of a SurfaceView shadow that provides additional functiona lity for controlling
58 * the state of the underlying (fake) Surface.
59 */
60 @Implements(SurfaceView.class)
61 public static class MyShadowSurfaceView extends ShadowSurfaceView {
62 private final MyFakeSurfaceHolder mHolder = new MyFakeSurfaceHolder();
63
64 /**
65 * Robolectric's FakeSurfaceHolder doesn't keep track of the format, etc .
66 */
67 public static class MyFakeSurfaceHolder extends ShadowSurfaceView.FakeSu rfaceHolder {
68 /**
69 * Fake surface that lets us control whether it's valid or not.
70 */
71 public static class MyFakeSurface extends Surface {
72 public boolean valid = false;
73
74 @Override
75 public boolean isValid() {
76 return valid;
77 }
78 }
79
80 private int mFormat = PixelFormat.UNKNOWN;
81 private final MyFakeSurface mSurface = new MyFakeSurface();
82
83 @Implementation
84 public void setFormat(int format) {
85 mFormat = format;
86 }
87
88 public int getFormat() {
89 return mFormat;
90 }
91
92 // Return a surface that we can control if it's valid or not.
93 @Override
94 public Surface getSurface() {
95 return getFakeSurface();
96 }
97
98 public MyFakeSurface getFakeSurface() {
99 return mSurface;
100 }
101 }
102
103 public MyShadowSurfaceView() {}
104
105 @Implementation
106 public SurfaceHolder getHolder() {
107 return getMyFakeSurfaceHolder();
108 }
109
110 @Override
111 public FakeSurfaceHolder getFakeSurfaceHolder() {
112 return getMyFakeSurfaceHolder();
113 }
114
115 public MyFakeSurfaceHolder getMyFakeSurfaceHolder() {
116 return mHolder;
117 }
118 }
119
120 @Before
121 public void beforeTest() {
122 MockitoAnnotations.initMocks(this);
123 Activity activity = Robolectric.buildActivity(Activity.class).setup().ge t();
124 mLayout = new FrameLayout(activity);
125 mManager = new CompositorSurfaceManager(mLayout, mCallback);
126 }
127
128 private void runDelayedTasks() {
129 ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
130 }
131
132 /**
133 * Return the callback for |view|, or null. Will get mad if there's more th an one.
134 */
135 private SurfaceHolder.Callback callbackFor(SurfaceView view) {
136 MyShadowSurfaceView viewShadow = (MyShadowSurfaceView) Shadows.shadowOf( view);
137 ShadowSurfaceView.FakeSurfaceHolder viewHolder = viewShadow.getFakeSurfa ceHolder();
138 Set<SurfaceHolder.Callback> callbacks = viewHolder.getCallbacks();
139 // Zero or one is okay.
140 assertThat(callbacks.size(), lessThan(2));
141
142 if (callbacks.size() == 1) return callbacks.iterator().next();
143
144 return null;
145 }
146
147 private MyShadowSurfaceView.MyFakeSurfaceHolder fakeHolderFor(SurfaceView vi ew) {
148 MyShadowSurfaceView viewShadow = (MyShadowSurfaceView) Shadows.shadowOf( view);
149 return viewShadow.getMyFakeSurfaceHolder();
150 }
151
152 private void setSurfaceValid(SurfaceView view, boolean valid) {
153 fakeHolderFor(view).getFakeSurface().valid = valid;
154 }
155
156 /**
157 * Find and return the SurfaceView with format |format|.
158 */
159 private SurfaceView findSurface(int format) {
160 final int childCount = mLayout.getChildCount();
161 for (int i = 0; i < childCount; i++) {
162 final SurfaceView child = (SurfaceView) mLayout.getChildAt(i);
163 if (fakeHolderFor(child).getFormat() == format) return child;
164 }
165
166 return null;
167 }
168
169 /**
170 * Request the pixel format |format|, and return the SurfaceView for it if i t's attached. You
171 * are responsible for sending surfaceCreated / Changed to |mManager| if you want it to think
172 * that Android has provided the Surface.
173 */
174 private SurfaceView requestSurface(int format) {
175 mManager.requestSurface(format);
176 runDelayedTasks();
177
178 return findSurface(format);
179 }
180
181 /**
182 * Request format |format|, and send created / changed callbacks to |mManage r| as if Android
183 * had provided the underlying Surface.
184 */
185 private SurfaceView requestThenCreateSurface(int format) {
186 SurfaceView view = requestSurface(format);
187 setSurfaceValid(view, true);
188 callbackFor(view).surfaceCreated(view.getHolder());
189 final int actualFormat =
190 (format == PixelFormat.OPAQUE) ? PixelFormat.RGB_565 : PixelForm at.RGBA_8888;
191 final int width = 320;
192 final int height = 240;
193 callbackFor(view).surfaceChanged(view.getHolder(), actualFormat, width, height);
194
195 return view;
196 }
197
198 @Test
199 @Feature("Compositor")
200 @Config(shadows = {MyShadowSurfaceView.class})
201 public void testRequestOpaqueSurface() {
202 // Request a SurfaceView, and test in detail that it worked.
203 SurfaceView opaque = requestSurface(PixelFormat.OPAQUE);
204 verify(mCallback, times(0)).surfaceCreated(ArgumentMatchers.<SurfaceHold er>any());
205 verify(mCallback, times(0))
206 .surfaceChanged(
207 ArgumentMatchers.<SurfaceHolder>any(), anyInt(), anyInt( ), anyInt());
208 verify(mCallback, times(0)).surfaceDestroyed(ArgumentMatchers.<SurfaceHo lder>any());
209
210 // Check that there's an opaque SurfaceView .
211 assertEquals(1, mLayout.getChildCount());
212 assertTrue(fakeHolderFor(opaque).getFormat() == PixelFormat.OPAQUE);
213
214 // Verify that we are notified when the surface is created.
215 callbackFor(opaque).surfaceCreated(opaque.getHolder());
216 verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
217 verify(mCallback, times(0)).surfaceDestroyed(ArgumentMatchers.<SurfaceHo lder>any());
218
219 // Verify that we are notified when the surface is changed.
220 final int format = PixelFormat.RGB_565;
221 final int width = 320;
222 final int height = 240;
223 callbackFor(opaque).surfaceChanged(opaque.getHolder(), format, width, he ight);
224 verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
225 verify(mCallback, times(1)).surfaceChanged(opaque.getHolder(), format, w idth, height);
226 verify(mCallback, times(0)).surfaceDestroyed(ArgumentMatchers.<SurfaceHo lder>any());
227
228 // Verify that we are notified when the surface is destroyed.
229 callbackFor(opaque).surfaceDestroyed(opaque.getHolder());
230 verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
231 verify(mCallback, times(1)).surfaceChanged(opaque.getHolder(), format, w idth, height);
232 verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
233 }
234
235 @Test
236 @Feature("Compositor")
237 @Config(shadows = {MyShadowSurfaceView.class})
238 public void testRequestOpaqueThenTranslucentSurface() {
239 // Request opaque then translucent.
240 SurfaceView opaque = requestThenCreateSurface(PixelFormat.OPAQUE);
241 SurfaceView translucent = requestThenCreateSurface(PixelFormat.TRANSLUCE NT);
242
243 // Verify that we received a destroy for |opaque| and created / changed for |translucent|.
244 verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
245 verify(mCallback, times(1)).surfaceCreated(translucent.getHolder());
246 verify(mCallback, times(1))
247 .surfaceChanged(eq(translucent.getHolder()), anyInt(), anyInt(), anyInt());
248
249 // Both views should be present.
250 assertEquals(2, mLayout.getChildCount());
251
252 // Only the translucent surface should be left. Note that the old view is still valid.
253 mManager.doneWithUnownedSurface();
254 runDelayedTasks();
255 assertEquals(1, mLayout.getChildCount());
256 assertNotNull(findSurface(PixelFormat.TRANSLUCENT));
257 }
258
259 @Test
260 @Feature("Compositor")
261 @Config(shadows = {MyShadowSurfaceView.class})
262 public void testRequestSameSurface() {
263 // Request an opaque surface, get it, then request it again. Verify tha t we get synthetic
264 // create / destroy callbacks.
265 SurfaceView opaque = requestThenCreateSurface(PixelFormat.OPAQUE);
266 verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
267 verify(mCallback, times(1))
268 .surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyI nt());
269 verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder());
270
271 // Surface is curerntly valid. Request again. We should get back a des troy and create.
272 assertEquals(opaque, requestSurface(PixelFormat.OPAQUE));
273 verify(mCallback, times(2)).surfaceCreated(opaque.getHolder());
274 verify(mCallback, times(2))
275 .surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyI nt());
276 verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
277 assertEquals(1, mLayout.getChildCount());
278 }
279
280 @Test
281 @Feature("Compositor")
282 @Config(shadows = {MyShadowSurfaceView.class})
283 public void testRequestSameSurfaceBeforeReady() {
284 // Request an opaque surface, then request it again before the first one shows up.
285 SurfaceView opaque = requestSurface(PixelFormat.OPAQUE);
286 verify(mCallback, times(0)).surfaceCreated(opaque.getHolder());
287 verify(mCallback, times(0))
288 .surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyI nt());
289 verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder());
290
291 // Request again. We shouldn't get any callbacks, since the surface is still pending.
292 assertEquals(opaque, requestSurface(PixelFormat.OPAQUE));
293 verify(mCallback, times(0)).surfaceCreated(opaque.getHolder());
294 verify(mCallback, times(0))
295 .surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyI nt());
296 verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder());
297
298 // Only the opaque view should be attached.
299 assertEquals(1, mLayout.getChildCount());
300
301 // When the surface is created, we should get notified created / changed , but not destroyed.
302 callbackFor(opaque).surfaceCreated(opaque.getHolder());
303 verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
304
305 callbackFor(opaque).surfaceChanged(opaque.getHolder(), PixelFormat.RGB_5 65, 320, 240);
306 verify(mCallback, times(1))
307 .surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyI nt());
308 verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder());
309 }
310
311 @Test
312 @Feature("Compositor")
313 @Config(shadows = {MyShadowSurfaceView.class})
314 public void testRequestDifferentSurfacesBeforeReady() {
315 // Request an opaque surface, then request the translucent one before th e it one shows up.
316 SurfaceView opaque = requestSurface(PixelFormat.OPAQUE);
317 verify(mCallback, times(0)).surfaceCreated(opaque.getHolder());
318 verify(mCallback, times(0))
319 .surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyI nt());
320 verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder());
321
322 // Request translucent. We should get no callbacks, but both views shou ld be attached.
323 SurfaceView translucent = requestSurface(PixelFormat.TRANSLUCENT);
324 verify(mCallback, times(0)).surfaceCreated(opaque.getHolder());
325 verify(mCallback, times(0)).surfaceCreated(translucent.getHolder());
326 assertEquals(2, mLayout.getChildCount());
327
328 // If the opaque surface arrives, we shouldn't hear about it. It should be detached, since
329 // we've requested the other one.
330 callbackFor(opaque).surfaceCreated(opaque.getHolder());
331 runDelayedTasks();
332 assertEquals(1, mLayout.getChildCount());
333 verify(mCallback, times(0)).surfaceCreated(opaque.getHolder());
334 verify(mCallback, times(0)).surfaceCreated(translucent.getHolder());
335 verify(mCallback, times(0)).surfaceDestroyed(opaque.getHolder());
336
337 // When we create the translucent surface, we should be notified.
338 callbackFor(translucent).surfaceCreated(translucent.getHolder());
339 verify(mCallback, times(0)).surfaceCreated(opaque.getHolder());
340 verify(mCallback, times(1)).surfaceCreated(translucent.getHolder());
341 }
342
343 @Test
344 @Feature("Compositor")
345 @Config(shadows = {MyShadowSurfaceView.class})
346 public void testPendingSurfaceChangedCallback() {
347 // Request an opaque surface, and request it again between 'created' and 'changed'. We
348 // should get a synthetic 'created', but a real 'changed' callback.
349 SurfaceView opaque = requestSurface(PixelFormat.OPAQUE);
350 callbackFor(opaque).surfaceCreated(opaque.getHolder());
351 runDelayedTasks();
352
353 // Sanity check.
354 verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
355 verify(mCallback, times(0))
356 .surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyI nt());
357
358 // Re-request while 'changed' is still pending. We should get a synthet ic 'destroyed' and
359 // synthetic 'created'.
360 assertEquals(opaque, requestSurface(PixelFormat.OPAQUE));
361 verify(mCallback, times(2)).surfaceCreated(opaque.getHolder());
362 verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
363 verify(mCallback, times(0))
364 .surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyI nt());
365
366 // Send 'changed', and expect that we'll receive it.
367 callbackFor(opaque).surfaceChanged(opaque.getHolder(), PixelFormat.RGB_5 65, 320, 240);
368 verify(mCallback, times(1))
369 .surfaceChanged(eq(opaque.getHolder()), anyInt(), anyInt(), anyI nt());
370 }
371
372 @Test
373 @Feature("Compositor")
374 @Config(shadows = {MyShadowSurfaceView.class})
375 public void testJellyBeanWorkaround() {
376 // See if recreateSurfaceForJellyBean destroys / re-creates the surface.
377 // should get a synthetic 'created', but a real 'changed' callback.
378 SurfaceView opaque = requestThenCreateSurface(PixelFormat.OPAQUE);
379 verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
380 assertEquals(1, mLayout.getChildCount());
381
382 // We should be notified that the surface was destroyed via synthetic ca llback, and the
383 // surface should be detached.
384 mManager.recreateSurfaceForJellyBean();
385 verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
386 verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
387 assertEquals(0, mLayout.getChildCount());
388
389 // When the surface really is destroyed, it should be re-attached. We s hould not be
390 // notified again, though.
391 callbackFor(opaque).surfaceDestroyed(opaque.getHolder());
392 verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
393 verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
394 assertEquals(1, mLayout.getChildCount());
395
396 // When the surface is re-created, we should be notified.
397 callbackFor(opaque).surfaceCreated(opaque.getHolder());
398 verify(mCallback, times(2)).surfaceCreated(opaque.getHolder());
399 verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
400 assertEquals(1, mLayout.getChildCount());
401 }
402
403 @Test
404 @Feature("Compositor")
405 @Config(shadows = {MyShadowSurfaceView.class})
406 public void testRequestSurfaceDuringDestruction() {
407 // If we re-request a surface while we're tearing it down, it should be re-attached and
408 // given back to us once the destruction completes.
409 SurfaceView opaque = requestThenCreateSurface(PixelFormat.OPAQUE);
410 SurfaceView translucent = requestThenCreateSurface(PixelFormat.TRANSLUCE NT);
411 mManager.doneWithUnownedSurface();
412
413 // The transparent surface should be attached, and the opaque one detach ed.
414 assertEquals(1, mLayout.getChildCount());
415 assertNotNull(findSurface(PixelFormat.TRANSLUCENT));
416
417 // Re-request the opaque surface. Nothing should happen until it's dest royed. It should
418 // not be re-attached, since that is also deferred until destruction.
419 assertEquals(null, requestSurface(PixelFormat.OPAQUE));
420 assertEquals(1, mLayout.getChildCount());
421 assertNotNull(findSurface(PixelFormat.TRANSLUCENT));
422
423 // When the opaque surface is destroyed, then it should be re-attached. No callbacks shoud
424 // have arrived yet, except for initial creation and (synthetic) destroy ed when we got the
425 // translucent surface.
426 callbackFor(opaque).surfaceDestroyed(opaque.getHolder());
427 assertEquals(2, mLayout.getChildCount());
428 verify(mCallback, times(1)).surfaceCreated(opaque.getHolder());
429 verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
430 verify(mCallback, times(0)).surfaceDestroyed(translucent.getHolder());
431
432 // When the opaque surface becomes available, we'll get the synthetic de stroy for the
433 // translucent one that we lost ownership of, and the real create for th e opaque one.
434 callbackFor(opaque).surfaceCreated(opaque.getHolder());
435 assertEquals(2, mLayout.getChildCount());
436 verify(mCallback, times(2)).surfaceCreated(opaque.getHolder());
437 verify(mCallback, times(1)).surfaceDestroyed(opaque.getHolder());
438 verify(mCallback, times(1)).surfaceDestroyed(translucent.getHolder());
439 }
440 }
OLDNEW
« no previous file with comments | « chrome/android/java_sources.gni ('k') | content/browser/renderer_host/compositor_impl_android.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698