blob: 0f82370e3b41913f22555188c349aa58c95dc3ec [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.uirendering.cts.testclasses
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.HardwareRenderer
import android.graphics.Outline
import android.graphics.Paint
import android.graphics.PixelFormat
import android.graphics.RecordingCanvas
import android.graphics.Rect
import android.graphics.RenderNode
import android.hardware.HardwareBuffer
import android.media.Image
import android.media.ImageReader
import android.os.Debug
import android.uirendering.cts.bitmapverifiers.BitmapVerifier
import android.uirendering.cts.bitmapverifiers.ColorVerifier
import android.uirendering.cts.bitmapverifiers.PerPixelBitmapVerifier
import android.uirendering.cts.bitmapverifiers.RectVerifier
import android.uirendering.cts.bitmapverifiers.RegionVerifier
import android.uirendering.cts.testinfrastructure.ActivityTestBase
import android.uirendering.cts.util.CompareUtils
import android.util.Log
import androidx.test.filters.LargeTest
import androidx.test.filters.MediumTest
import androidx.test.runner.AndroidJUnit4
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
const val TEST_WIDTH = ActivityTestBase.TEST_WIDTH
const val TEST_HEIGHT = ActivityTestBase.TEST_HEIGHT
class CaptureResult(
val bitmap: IntArray,
val offset: Int,
val stride: Int,
val width: Int,
val height: Int
)
data class RendererTest(
var verifier: BitmapVerifier? = null,
var onPrepare: (HardwareRenderer.() -> Unit)? = null,
var onDraw: (RecordingCanvas.() -> Unit)? = null
)
private fun verify(verifier: BitmapVerifier, setup: HardwareRenderer.() -> Unit) {
val reader = ImageReader.newInstance(
TEST_WIDTH, TEST_HEIGHT, PixelFormat.RGBA_8888, 1,
HardwareBuffer.USAGE_CPU_READ_OFTEN or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT)
val renderer = HardwareRenderer()
var image: Image? = null
try {
renderer.setSurface(reader.surface)
renderer.notifyFramePending()
setup.invoke(renderer)
val syncResult = renderer.createRenderRequest()
.setWaitForPresent(true)
.syncAndDraw()
assertEquals(HardwareRenderer.SYNC_OK, syncResult)
image = reader.acquireNextImage()
assertNotNull(image)
val planes = image.planes
assertNotNull(planes)
assertEquals(1, planes.size)
val plane = planes[0]
assertEquals(4, plane.pixelStride)
assertTrue((ActivityTestBase.TEST_WIDTH * 4) <= plane.rowStride)
val buffer = plane.buffer
val channels = ByteArray(buffer.remaining())
buffer.get(channels, 0, channels.size)
val pixels = IntArray(channels.size / 4)
var pixelIndex = 0
var channelIndex = 0
// Need to switch from RGBA (the pixel format on the reader) to ARGB (what bitmaps use)
while (channelIndex < channels.size) {
val red = channels[channelIndex++].toInt() and 0xFF
val green = channels[channelIndex++].toInt() and 0xFF
val blue = channels[channelIndex++].toInt() and 0xFF
val alpha = channels[channelIndex++].toInt() and 0xFF
pixels[pixelIndex++] = Color.argb(alpha, red, green, blue)
}
val result = CaptureResult(pixels, 0, plane.rowStride / plane.pixelStride,
TEST_WIDTH, TEST_WIDTH)
assertTrue(verifier.verify(
result.bitmap, result.offset, result.stride, result.width, result.height))
} finally {
image?.close()
renderer.destroy()
reader.close()
}
}
private fun rendererTest(setup: RendererTest.() -> Unit) {
val spec = RendererTest()
setup.invoke(spec)
assertNotNull(spec.verifier, "Missing BitmapVerifier")
assertNotNull(spec.onDraw, "Missing onDraw callback")
verify(spec.verifier!!) {
spec.onPrepare?.invoke(this)
val content = RenderNode("content")
content.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT)
spec.onDraw!!.invoke(content.beginRecording())
content.endRecording()
setContentRoot(content)
}
}
private fun fetchMemoryInfo(): Debug.MemoryInfo {
Runtime.getRuntime().apply {
gc()
runFinalization()
gc()
runFinalization()
gc()
}
val meminfo = Debug.MemoryInfo()
Debug.getMemoryInfo(meminfo)
return meminfo
}
@MediumTest
@RunWith(AndroidJUnit4::class)
class HardwareRendererTests : ActivityTestBase() {
@Test
fun testBasicDrawCpuConsumer() {
val reader = ImageReader.newInstance(TEST_WIDTH, TEST_HEIGHT, PixelFormat.RGBA_8888, 1,
HardwareBuffer.USAGE_CPU_READ_OFTEN or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT)
assertNotNull(reader)
val renderer = HardwareRenderer()
var image: Image? = null
try {
val content = RenderNode("content")
content.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT)
val canvas = content.beginRecording()
canvas.drawColor(Color.BLUE)
content.endRecording()
renderer.setContentRoot(content)
renderer.setSurface(reader.surface)
val syncResult = renderer.createRenderRequest()
.setWaitForPresent(true)
.syncAndDraw()
assertEquals(HardwareRenderer.SYNC_OK, syncResult)
image = reader.acquireNextImage()
assertNotNull(image)
val planes = image.planes
assertNotNull(planes)
assertEquals(1, planes.size)
val plane = planes[0]
assertEquals(4, plane.pixelStride)
assertTrue((TEST_WIDTH * 4) <= plane.rowStride)
val buffer = plane.buffer
val red = buffer.get()
val green = buffer.get()
val blue = buffer.get()
val alpha = buffer.get()
assertEquals(0, red, "red")
assertEquals(0, green, "green")
assertEquals(0xFF.toByte(), blue, "blue")
assertEquals(0xFF.toByte(), alpha, "alpha")
} finally {
image?.close()
renderer.destroy()
reader.close()
}
}
@Test
fun testBasicDrawGpuConsumer() {
val reader = ImageReader.newInstance(TEST_WIDTH, TEST_HEIGHT, PixelFormat.RGBA_8888, 1,
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT)
assertNotNull(reader)
val renderer = HardwareRenderer()
var image: Image? = null
try {
val content = RenderNode("content")
content.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT)
val canvas = content.beginRecording()
canvas.drawColor(Color.BLUE)
content.endRecording()
renderer.setContentRoot(content)
renderer.setSurface(reader.surface)
val syncResult = renderer.createRenderRequest()
.setWaitForPresent(true)
.syncAndDraw()
assertEquals(HardwareRenderer.SYNC_OK, syncResult)
image = reader.acquireNextImage()
assertNotNull(image)
val buffer = image.hardwareBuffer
assertNotNull(buffer)
Log.d("HardwareRenderer", "buffer usage bits: " +
java.lang.Long.toHexString(buffer.usage))
val bitmap = Bitmap.wrapHardwareBuffer(buffer, null)!!
.copy(Bitmap.Config.ARGB_8888, false)
assertEquals(TEST_WIDTH, bitmap.width)
assertEquals(TEST_HEIGHT, bitmap.height)
assertEquals(0xFF0000FF.toInt(), bitmap.getPixel(0, 0))
} finally {
image?.close()
renderer.destroy()
reader.close()
}
}
@Test
fun testSetOpaque() = rendererTest {
val rect = Rect(10, 10, 30, 30)
onPrepare = {
assertTrue(isOpaque)
isOpaque = false
assertFalse(isOpaque)
}
onDraw = {
val paint = Paint()
paint.color = Color.RED
drawRect(rect, paint)
}
verifier = RectVerifier(Color.TRANSPARENT, Color.RED, rect)
}
@Test
fun testSetStopped() {
val reader = ImageReader.newInstance(TEST_WIDTH, TEST_HEIGHT, PixelFormat.RGBA_8888, 1,
HardwareBuffer.USAGE_CPU_READ_OFTEN or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT)
assertNotNull(reader)
val renderer = HardwareRenderer()
try {
renderer.setSurface(reader.surface)
assertEquals(HardwareRenderer.SYNC_OK,
renderer.createRenderRequest().syncAndDraw())
renderer.stop()
assertEquals(HardwareRenderer.SYNC_CONTEXT_IS_STOPPED
or HardwareRenderer.SYNC_FRAME_DROPPED,
renderer.createRenderRequest().syncAndDraw())
reader.acquireLatestImage()?.close()
renderer.start()
val result = renderer.createRenderRequest().syncAndDraw()
assertEquals(0, result and HardwareRenderer.SYNC_CONTEXT_IS_STOPPED)
} finally {
renderer.destroy()
reader.close()
}
}
@Test
@Ignore // TODO: Re-enable, see b/124520175
fun testNoSurface() {
val reader = ImageReader.newInstance(TEST_WIDTH, TEST_HEIGHT, PixelFormat.RGBA_8888, 1,
HardwareBuffer.USAGE_CPU_READ_OFTEN or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT)
assertNotNull(reader)
val renderer = HardwareRenderer()
try {
assertEquals(
HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND
or HardwareRenderer.SYNC_FRAME_DROPPED,
renderer.createRenderRequest().syncAndDraw())
renderer.setSurface(reader.surface)
assertEquals(HardwareRenderer.SYNC_OK,
renderer.createRenderRequest().syncAndDraw())
reader.close()
Thread.sleep(32)
assertEquals(HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND
or HardwareRenderer.SYNC_FRAME_DROPPED,
renderer.createRenderRequest().syncAndDraw())
} finally {
renderer.destroy()
reader.close()
}
}
@LargeTest
@Test
fun testClearContent() {
val reader = ImageReader.newInstance(TEST_WIDTH, TEST_HEIGHT, PixelFormat.RGBA_8888, 1,
HardwareBuffer.USAGE_CPU_READ_OFTEN or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT)
assertNotNull(reader)
val renderer = HardwareRenderer()
val content = RenderNode("content")
val canvas = content.beginRecording()
run {
for (i in 0..5) {
val bitmap = Bitmap.createBitmap(1024, 1024, Bitmap.Config.ARGB_8888)
bitmap.eraseColor(Color.RED)
canvas.drawBitmap(bitmap, 0f, 0f, Paint())
}
}
content.endRecording()
try {
renderer.setSurface(reader.surface)
renderer.setContentRoot(content)
assertEquals(HardwareRenderer.SYNC_OK,
renderer.createRenderRequest()
.setWaitForPresent(true)
.syncAndDraw())
val infoBeforeClear = fetchMemoryInfo()
renderer.clearContent()
val infoAfterClear = fetchMemoryInfo()
assertNotEquals(0, infoBeforeClear.totalPss)
assertNotEquals(0, infoAfterClear.totalPss)
val pssDifference = infoBeforeClear.totalPss - infoAfterClear.totalPss
// Use a rather generous margin of error in case the only thing freed is the bitmap
// while other memroy was allocated in the process of checking that. pss is in kB
val minimalDifference = 5 * 1024
assertTrue(pssDifference > minimalDifference,
"pssDifference: $pssDifference less than expected of at least $minimalDifference")
} finally {
renderer.destroy()
reader.close()
}
}
@Test
fun testDestroy() {
val reader = ImageReader.newInstance(TEST_WIDTH, TEST_HEIGHT, PixelFormat.RGBA_8888, 1,
HardwareBuffer.USAGE_CPU_READ_OFTEN or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT)
assertNotNull(reader)
val renderer = HardwareRenderer()
try {
renderer.setSurface(reader.surface)
assertEquals(HardwareRenderer.SYNC_OK,
renderer.createRenderRequest()
.setWaitForPresent(true)
.syncAndDraw())
renderer.destroy()
assertEquals(
HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND
or HardwareRenderer.SYNC_FRAME_DROPPED,
renderer.createRenderRequest().syncAndDraw())
renderer.setSurface(reader.surface)
assertEquals(HardwareRenderer.SYNC_OK,
renderer.createRenderRequest()
.setWaitForPresent(true)
.syncAndDraw())
} finally {
renderer.destroy()
reader.close()
}
}
@Test
fun testSpotShadowSetup() = rendererTest {
val childRect = Rect(25, 25, 65, 65)
onPrepare = {
setLightSourceAlpha(0.0f, 1.0f)
setLightSourceGeometry(TEST_WIDTH / 2f, 0f, 800.0f, 20.0f)
}
onDraw = {
val childNode = RenderNode("shadowCaster")
childNode.setPosition(childRect)
val outline = Outline()
outline.setRect(Rect(0, 0, childRect.width(), childRect.height()))
outline.alpha = 1f
childNode.setOutline(outline)
val childCanvas = childNode.beginRecording()
childCanvas.drawColor(Color.RED)
childNode.endRecording()
childNode.elevation = 20f
drawColor(Color.WHITE)
enableZ()
drawRenderNode(childNode)
disableZ()
}
verifier = RegionVerifier()
.addVerifier(childRect, ColorVerifier(Color.RED, 10))
.addVerifier(
Rect(childRect.left, childRect.bottom, childRect.right, childRect.bottom + 10),
object : PerPixelBitmapVerifier() {
override fun verifyPixel(x: Int, y: Int, observedColor: Int): Boolean {
return CompareUtils.verifyPixelGrayScale(observedColor, 1)
}
})
}
@Test
fun testLotsOfBuffers() {
val colorForIndex = { i: Int ->
Color.argb(255, 10 * i, 6 * i, 2 * i)
}
val testColors = IntArray(20, colorForIndex)
val reader = ImageReader.newInstance(
TEST_WIDTH, TEST_HEIGHT, PixelFormat.RGBA_8888, testColors.size,
HardwareBuffer.USAGE_CPU_READ_OFTEN or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT)
assertNotNull(reader)
val renderer = HardwareRenderer()
val images = ArrayList<Image>()
try {
val content = RenderNode("content")
content.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT)
renderer.setContentRoot(content)
renderer.setSurface(reader.surface)
testColors.forEach {
val canvas = content.beginRecording()
canvas.drawColor(it)
content.endRecording()
val syncResult = renderer.createRenderRequest()
.setWaitForPresent(true)
.syncAndDraw()
assertEquals(HardwareRenderer.SYNC_OK, syncResult)
// TODO: Add API to avoid this
Thread.sleep(32)
}
for (i in 0 until testColors.size) {
val image = reader.acquireNextImage()
assertNotNull(image)
images.add(image)
}
assertEquals(testColors.size, images.size)
images.forEachIndexed { index, image ->
val planes = image.planes
assertNotNull(planes)
assertEquals(1, planes.size)
val plane = planes[0]
assertEquals(4, plane.pixelStride)
assertTrue((TEST_WIDTH * 4) <= plane.rowStride)
val buffer = plane.buffer
val red = buffer.get().toInt() and 0xFF
val green = buffer.get().toInt() and 0xFF
val blue = buffer.get().toInt() and 0xFF
val alpha = buffer.get().toInt() and 0xFF
val expectedColor = colorForIndex(index)
assertEquals(Color.red(expectedColor), red, "red")
assertEquals(Color.green(expectedColor), green, "green")
assertEquals(Color.blue(expectedColor), blue, "blue")
assertEquals(255, alpha, "alpha")
}
} finally {
images.forEach {
try {
it.close()
} catch (ex: Throwable) {}
}
images.clear()
renderer.destroy()
reader.close()
}
}
}