1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.uirendering.cts.testclasses 18 19 import android.graphics.Bitmap 20 import android.graphics.Color 21 import android.graphics.HardwareRenderer 22 import android.graphics.Outline 23 import android.graphics.Paint 24 import android.graphics.PixelFormat 25 import android.graphics.RecordingCanvas 26 import android.graphics.Rect 27 import android.graphics.RenderNode 28 import android.hardware.HardwareBuffer 29 import android.media.Image 30 import android.media.ImageReader 31 import android.os.Debug 32 import android.uirendering.cts.bitmapverifiers.BitmapVerifier 33 import android.uirendering.cts.bitmapverifiers.ColorVerifier 34 import android.uirendering.cts.bitmapverifiers.PerPixelBitmapVerifier 35 import android.uirendering.cts.bitmapverifiers.RectVerifier 36 import android.uirendering.cts.bitmapverifiers.RegionVerifier 37 import android.uirendering.cts.testinfrastructure.ActivityTestBase 38 import android.uirendering.cts.util.CompareUtils 39 import android.util.Log 40 import androidx.test.filters.LargeTest 41 import androidx.test.filters.MediumTest 42 import androidx.test.runner.AndroidJUnit4 43 import org.junit.Ignore 44 import org.junit.Test 45 import org.junit.runner.RunWith 46 import kotlin.test.assertEquals 47 import kotlin.test.assertFalse 48 import kotlin.test.assertNotEquals 49 import kotlin.test.assertNotNull 50 import kotlin.test.assertTrue 51 52 const val TEST_WIDTH = ActivityTestBase.TEST_WIDTH 53 const val TEST_HEIGHT = ActivityTestBase.TEST_HEIGHT 54 55 class CaptureResult( 56 val bitmap: IntArray, 57 val offset: Int, 58 val stride: Int, 59 val width: Int, 60 val height: Int 61 ) 62 63 data class RendererTest( 64 var verifier: BitmapVerifier? = null, 65 var onPrepare: (HardwareRenderer.() -> Unit)? = null, 66 var onDraw: (RecordingCanvas.() -> Unit)? = null 67 ) 68 69 private fun verify(verifier: BitmapVerifier, setup: HardwareRenderer.() -> Unit) { 70 val reader = ImageReader.newInstance( 71 TEST_WIDTH, TEST_HEIGHT, PixelFormat.RGBA_8888, 1, 72 HardwareBuffer.USAGE_CPU_READ_OFTEN or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT) 73 val renderer = HardwareRenderer() 74 var image: Image? = null 75 try { 76 renderer.setSurface(reader.surface) 77 renderer.notifyFramePending() 78 setup.invoke(renderer) 79 val syncResult = renderer.createRenderRequest() 80 .setWaitForPresent(true) 81 .syncAndDraw() 82 assertEquals(HardwareRenderer.SYNC_OK, syncResult) 83 image = reader.acquireNextImage() 84 assertNotNull(image) 85 val planes = image.planes 86 assertNotNull(planes) 87 assertEquals(1, planes.size) 88 val plane = planes[0] 89 assertEquals(4, plane.pixelStride) 90 assertTrue((ActivityTestBase.TEST_WIDTH * 4) <= plane.rowStride) 91 val buffer = plane.buffer 92 val channels = ByteArray(buffer.remaining()) 93 buffer.get(channels, 0, channels.size) 94 val pixels = IntArray(channels.size / 4) 95 var pixelIndex = 0 96 var channelIndex = 0 97 // Need to switch from RGBA (the pixel format on the reader) to ARGB (what bitmaps use) 98 while (channelIndex < channels.size) { 99 val red = channels[channelIndex++].toInt() and 0xFF 100 val green = channels[channelIndex++].toInt() and 0xFF 101 val blue = channels[channelIndex++].toInt() and 0xFF 102 val alpha = channels[channelIndex++].toInt() and 0xFF 103 pixels[pixelIndex++] = Color.argb(alpha, red, green, blue) 104 } 105 val result = CaptureResult(pixels, 0, plane.rowStride / plane.pixelStride, 106 TEST_WIDTH, TEST_WIDTH) 107 assertTrue(verifier.verify( 108 result.bitmap, result.offset, result.stride, result.width, result.height)) 109 } finally { 110 image?.close() 111 renderer.destroy() 112 reader.close() 113 } 114 } 115 116 private fun rendererTest(setup: RendererTest.() -> Unit) { 117 val spec = RendererTest() 118 setup.invoke(spec) 119 assertNotNull(spec.verifier, "Missing BitmapVerifier") 120 assertNotNull(spec.onDraw, "Missing onDraw callback") 121 verify(spec.verifier!!) { 122 spec.onPrepare?.invoke(this) 123 val content = RenderNode("content") 124 content.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT) 125 spec.onDraw!!.invoke(content.beginRecording()) 126 content.endRecording() 127 setContentRoot(content) 128 } 129 } 130 131 private fun fetchMemoryInfo(): Debug.MemoryInfo { 132 Runtime.getRuntime().apply { 133 gc() 134 runFinalization() 135 gc() 136 runFinalization() 137 gc() 138 } 139 val meminfo = Debug.MemoryInfo() 140 Debug.getMemoryInfo(meminfo) 141 return meminfo 142 } 143 144 @MediumTest 145 @RunWith(AndroidJUnit4::class) 146 class HardwareRendererTests : ActivityTestBase() { 147 @Test 148 fun testBasicDrawCpuConsumer() { 149 val reader = ImageReader.newInstance(TEST_WIDTH, TEST_HEIGHT, PixelFormat.RGBA_8888, 1, 150 HardwareBuffer.USAGE_CPU_READ_OFTEN or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT) 151 assertNotNull(reader) 152 val renderer = HardwareRenderer() 153 var image: Image? = null 154 155 try { 156 val content = RenderNode("content") 157 content.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT) 158 val canvas = content.beginRecording() 159 canvas.drawColor(Color.BLUE) 160 content.endRecording() 161 renderer.setContentRoot(content) 162 163 renderer.setSurface(reader.surface) 164 165 val syncResult = renderer.createRenderRequest() 166 .setWaitForPresent(true) 167 .syncAndDraw() 168 169 assertEquals(HardwareRenderer.SYNC_OK, syncResult) 170 171 image = reader.acquireNextImage() 172 assertNotNull(image) 173 val planes = image.planes 174 assertNotNull(planes) 175 assertEquals(1, planes.size) 176 val plane = planes[0] 177 assertEquals(4, plane.pixelStride) 178 assertTrue((TEST_WIDTH * 4) <= plane.rowStride) 179 180 val buffer = plane.buffer 181 val red = buffer.get() 182 val green = buffer.get() 183 val blue = buffer.get() 184 val alpha = buffer.get() 185 assertEquals(0, red, "red") 186 assertEquals(0, green, "green") 187 assertEquals(0xFF.toByte(), blue, "blue") 188 assertEquals(0xFF.toByte(), alpha, "alpha") 189 } finally { 190 image?.close() 191 renderer.destroy() 192 reader.close() 193 } 194 } 195 196 @Test 197 fun testBasicDrawGpuConsumer() { 198 val reader = ImageReader.newInstance(TEST_WIDTH, TEST_HEIGHT, PixelFormat.RGBA_8888, 1, 199 HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT) 200 assertNotNull(reader) 201 val renderer = HardwareRenderer() 202 var image: Image? = null 203 204 try { 205 val content = RenderNode("content") 206 content.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT) 207 val canvas = content.beginRecording() 208 canvas.drawColor(Color.BLUE) 209 content.endRecording() 210 renderer.setContentRoot(content) 211 212 renderer.setSurface(reader.surface) 213 214 val syncResult = renderer.createRenderRequest() 215 .setWaitForPresent(true) 216 .syncAndDraw() 217 218 assertEquals(HardwareRenderer.SYNC_OK, syncResult) 219 220 image = reader.acquireNextImage() 221 assertNotNull(image) 222 val buffer = image.hardwareBuffer 223 assertNotNull(buffer) 224 Log.d("HardwareRenderer", "buffer usage bits: " + 225 java.lang.Long.toHexString(buffer.usage)) 226 val bitmap = Bitmap.wrapHardwareBuffer(buffer, null) 227 .copy(Bitmap.Config.ARGB_8888, false) 228 229 assertEquals(TEST_WIDTH, bitmap.width) 230 assertEquals(TEST_HEIGHT, bitmap.height) 231 assertEquals(0xFF0000FF.toInt(), bitmap.getPixel(0, 0)) 232 } finally { 233 image?.close() 234 renderer.destroy() 235 reader.close() 236 } 237 } 238 239 @Test 240 fun testSetOpaque() = rendererTest { 241 val rect = Rect(10, 10, 30, 30) 242 onPrepare = { 243 assertTrue(isOpaque) 244 isOpaque = false 245 assertFalse(isOpaque) 246 } 247 onDraw = { 248 val paint = Paint() 249 paint.color = Color.RED 250 drawRect(rect, paint) 251 } 252 verifier = RectVerifier(Color.TRANSPARENT, Color.RED, rect) 253 } 254 255 @Test 256 fun testSetStopped() { 257 val reader = ImageReader.newInstance(TEST_WIDTH, TEST_HEIGHT, PixelFormat.RGBA_8888, 1, 258 HardwareBuffer.USAGE_CPU_READ_OFTEN or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT) 259 assertNotNull(reader) 260 val renderer = HardwareRenderer() 261 try { 262 renderer.setSurface(reader.surface) 263 assertEquals(HardwareRenderer.SYNC_OK, 264 renderer.createRenderRequest().syncAndDraw()) 265 renderer.stop() 266 assertEquals(HardwareRenderer.SYNC_CONTEXT_IS_STOPPED 267 or HardwareRenderer.SYNC_FRAME_DROPPED, 268 renderer.createRenderRequest().syncAndDraw()) 269 reader.acquireLatestImage()?.close() 270 renderer.start() 271 val result = renderer.createRenderRequest().syncAndDraw() 272 assertEquals(0, result and HardwareRenderer.SYNC_CONTEXT_IS_STOPPED) 273 } finally { 274 renderer.destroy() 275 reader.close() 276 } 277 } 278 279 @Test 280 @Ignore // TODO: Re-enable, see b/124520175 281 fun testNoSurface() { 282 val reader = ImageReader.newInstance(TEST_WIDTH, TEST_HEIGHT, PixelFormat.RGBA_8888, 1, 283 HardwareBuffer.USAGE_CPU_READ_OFTEN or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT) 284 assertNotNull(reader) 285 val renderer = HardwareRenderer() 286 try { 287 assertEquals( 288 HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND 289 or HardwareRenderer.SYNC_FRAME_DROPPED, 290 renderer.createRenderRequest().syncAndDraw()) 291 renderer.setSurface(reader.surface) 292 assertEquals(HardwareRenderer.SYNC_OK, 293 renderer.createRenderRequest().syncAndDraw()) 294 reader.close() 295 Thread.sleep(32) 296 assertEquals(HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND 297 or HardwareRenderer.SYNC_FRAME_DROPPED, 298 renderer.createRenderRequest().syncAndDraw()) 299 } finally { 300 renderer.destroy() 301 reader.close() 302 } 303 } 304 305 @LargeTest 306 @Test 307 fun testClearContent() { 308 val reader = ImageReader.newInstance(TEST_WIDTH, TEST_HEIGHT, PixelFormat.RGBA_8888, 1, 309 HardwareBuffer.USAGE_CPU_READ_OFTEN or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT) 310 assertNotNull(reader) 311 val renderer = HardwareRenderer() 312 val content = RenderNode("content") 313 val canvas = content.beginRecording() 314 315 run { 316 for (i in 0..5) { 317 val bitmap = Bitmap.createBitmap(1024, 1024, Bitmap.Config.ARGB_8888) 318 bitmap.eraseColor(Color.RED) 319 canvas.drawBitmap(bitmap, 0f, 0f, Paint()) 320 } 321 } 322 323 content.endRecording() 324 try { 325 renderer.setSurface(reader.surface) 326 renderer.setContentRoot(content) 327 assertEquals(HardwareRenderer.SYNC_OK, 328 renderer.createRenderRequest() 329 .setWaitForPresent(true) 330 .syncAndDraw()) 331 val infoBeforeClear = fetchMemoryInfo() 332 renderer.clearContent() 333 val infoAfterClear = fetchMemoryInfo() 334 assertNotEquals(0, infoBeforeClear.totalPss) 335 assertNotEquals(0, infoAfterClear.totalPss) 336 337 val pssDifference = infoBeforeClear.totalPss - infoAfterClear.totalPss 338 // Use a rather generous margin of error in case the only thing freed is the bitmap 339 // while other memroy was allocated in the process of checking that. pss is in kB 340 val minimalDifference = 5 * 1024 341 assertTrue(pssDifference > minimalDifference, 342 "pssDifference: $pssDifference less than expected of at least $minimalDifference") 343 } finally { 344 renderer.destroy() 345 reader.close() 346 } 347 } 348 349 @Test 350 fun testDestroy() { 351 val reader = ImageReader.newInstance(TEST_WIDTH, TEST_HEIGHT, PixelFormat.RGBA_8888, 1, 352 HardwareBuffer.USAGE_CPU_READ_OFTEN or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT) 353 assertNotNull(reader) 354 val renderer = HardwareRenderer() 355 try { 356 renderer.setSurface(reader.surface) 357 assertEquals(HardwareRenderer.SYNC_OK, 358 renderer.createRenderRequest() 359 .setWaitForPresent(true) 360 .syncAndDraw()) 361 renderer.destroy() 362 assertEquals( 363 HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND 364 or HardwareRenderer.SYNC_FRAME_DROPPED, 365 renderer.createRenderRequest().syncAndDraw()) 366 367 renderer.setSurface(reader.surface) 368 assertEquals(HardwareRenderer.SYNC_OK, 369 renderer.createRenderRequest() 370 .setWaitForPresent(true) 371 .syncAndDraw()) 372 } finally { 373 renderer.destroy() 374 reader.close() 375 } 376 } 377 378 @Test 379 fun testSpotShadowSetup() = rendererTest { 380 val childRect = Rect(25, 25, 65, 65) 381 onPrepare = { 382 setLightSourceAlpha(0.0f, 1.0f) 383 setLightSourceGeometry(TEST_WIDTH / 2f, 0f, 800.0f, 20.0f) 384 } 385 onDraw = { 386 val childNode = RenderNode("shadowCaster") 387 childNode.setPosition(childRect) 388 val outline = Outline() 389 outline.setRect(Rect(0, 0, childRect.width(), childRect.height())) 390 outline.alpha = 1f 391 childNode.setOutline(outline) 392 val childCanvas = childNode.beginRecording() 393 childCanvas.drawColor(Color.RED) 394 childNode.endRecording() 395 childNode.elevation = 20f 396 397 drawColor(Color.WHITE) 398 enableZ() 399 drawRenderNode(childNode) 400 disableZ() 401 } 402 verifier = RegionVerifier() 403 .addVerifier(childRect, ColorVerifier(Color.RED, 10)) 404 .addVerifier( 405 Rect(childRect.left, childRect.bottom, childRect.right, childRect.bottom + 10), 406 object : PerPixelBitmapVerifier() { 407 override fun verifyPixel(x: Int, y: Int, observedColor: Int): Boolean { 408 return CompareUtils.verifyPixelGrayScale(observedColor, 1) 409 } 410 }) 411 } 412 413 @Test 414 fun testLotsOfBuffers() { 415 val colorForIndex = { i: Int -> 416 Color.argb(255, 10 * i, 6 * i, 2 * i) 417 } 418 val testColors = IntArray(20, colorForIndex) 419 420 val reader = ImageReader.newInstance( 421 TEST_WIDTH, TEST_HEIGHT, PixelFormat.RGBA_8888, testColors.size, 422 HardwareBuffer.USAGE_CPU_READ_OFTEN or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT) 423 assertNotNull(reader) 424 val renderer = HardwareRenderer() 425 val images = ArrayList<Image>() 426 427 try { 428 val content = RenderNode("content") 429 content.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT) 430 renderer.setContentRoot(content) 431 renderer.setSurface(reader.surface) 432 433 testColors.forEach { 434 val canvas = content.beginRecording() 435 canvas.drawColor(it) 436 content.endRecording() 437 438 val syncResult = renderer.createRenderRequest() 439 .setWaitForPresent(true) 440 .syncAndDraw() 441 assertEquals(HardwareRenderer.SYNC_OK, syncResult) 442 // TODO: Add API to avoid this 443 Thread.sleep(32) 444 } 445 446 for (i in 0 until testColors.size) { 447 val image = reader.acquireNextImage() 448 assertNotNull(image) 449 images.add(image) 450 } 451 452 assertEquals(testColors.size, images.size) 453 454 images.forEachIndexed { index, image -> 455 val planes = image.planes 456 assertNotNull(planes) 457 assertEquals(1, planes.size) 458 val plane = planes[0] 459 assertEquals(4, plane.pixelStride) 460 assertTrue((TEST_WIDTH * 4) <= plane.rowStride) 461 462 val buffer = plane.buffer 463 val red = buffer.get().toInt() and 0xFF 464 val green = buffer.get().toInt() and 0xFF 465 val blue = buffer.get().toInt() and 0xFF 466 val alpha = buffer.get().toInt() and 0xFF 467 468 val expectedColor = colorForIndex(index) 469 470 assertEquals(Color.red(expectedColor), red, "red") 471 assertEquals(Color.green(expectedColor), green, "green") 472 assertEquals(Color.blue(expectedColor), blue, "blue") 473 assertEquals(255, alpha, "alpha") 474 } 475 } finally { 476 images.forEach { 477 try { 478 it.close() 479 } catch (ex: Throwable) {} 480 } 481 images.clear() 482 renderer.destroy() 483 reader.close() 484 } 485 } 486 }