[go: nahoru, domu]

blob: 60cf00dd7786289c6b277942b202d8b7f239c29f [file] [log] [blame]
/*
* Copyright 2023 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 androidx.graphics.lowlatency
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorSpace
import android.hardware.HardwareBuffer
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.graphics.drawSquares
import androidx.graphics.isAllColor
import androidx.graphics.opengl.egl.supportsNativeAndroidFence
import androidx.graphics.surface.SurfaceControlCompat
import androidx.graphics.surface.SurfaceControlCompat.Companion.BUFFER_TRANSFORM_IDENTITY
import androidx.graphics.verifyQuadrants
import androidx.graphics.withEgl
import androidx.hardware.SyncFenceCompat
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
import androidx.test.filters.SmallTest
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@SmallTest
class SingleBufferedCanvasRendererV29Test {
companion object {
const val TEST_WIDTH = 20
const val TEST_HEIGHT = 20
}
data class RectColors(
val topLeft: Int,
val topRight: Int,
val bottomLeft: Int,
val bottomRight: Int
)
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
@Test
fun testRenderFrameRotate0() {
testRenderWithTransform(
BUFFER_TRANSFORM_IDENTITY,
RectColors(
topLeft = Color.RED,
topRight = Color.YELLOW,
bottomRight = Color.BLUE,
bottomLeft = Color.GREEN
),
RectColors(
topLeft = Color.RED,
topRight = Color.YELLOW,
bottomRight = Color.BLUE,
bottomLeft = Color.GREEN
)
)
}
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
@Test
fun testRenderFrameRotate90() {
testRenderWithTransform(
SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_90,
RectColors(
topLeft = Color.RED,
topRight = Color.YELLOW,
bottomRight = Color.BLUE,
bottomLeft = Color.GREEN
),
RectColors(
topLeft = Color.YELLOW,
topRight = Color.BLUE,
bottomRight = Color.GREEN,
bottomLeft = Color.RED
)
)
}
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
@Test
fun testRenderFrameRotate180() {
testRenderWithTransform(
SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_180,
RectColors(
topLeft = Color.RED,
topRight = Color.YELLOW,
bottomRight = Color.BLUE,
bottomLeft = Color.GREEN
),
RectColors(
topLeft = Color.BLUE,
topRight = Color.GREEN,
bottomRight = Color.RED,
bottomLeft = Color.YELLOW
)
)
}
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
@Test
fun testRenderFrameRotate270() {
testRenderWithTransform(
SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_270,
RectColors(
topLeft = Color.RED,
topRight = Color.YELLOW,
bottomRight = Color.BLUE,
bottomLeft = Color.GREEN
),
RectColors(
topLeft = Color.GREEN,
topRight = Color.RED,
bottomRight = Color.YELLOW,
bottomLeft = Color.BLUE
)
)
}
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
@Test
fun testClearRenderer() {
val transformer = BufferTransformer().apply {
computeTransform(TEST_WIDTH, TEST_HEIGHT, BUFFER_TRANSFORM_IDENTITY)
}
val executor = Executors.newSingleThreadExecutor()
val firstRenderLatch = CountDownLatch(1)
val clearLatch = CountDownLatch(2)
var buffer: HardwareBuffer? = null
val renderer = SingleBufferedCanvasRendererV29(
TEST_WIDTH,
TEST_HEIGHT,
transformer,
executor,
object : SingleBufferedCanvasRenderer.RenderCallbacks<Unit> {
override fun render(canvas: Canvas, width: Int, height: Int, param: Unit) {
canvas.drawColor(Color.RED)
}
override fun onBufferReady(
hardwareBuffer: HardwareBuffer,
syncFenceCompat: SyncFenceCompat?
) {
syncFenceCompat?.awaitForever()
buffer = hardwareBuffer
firstRenderLatch.countDown()
clearLatch.countDown()
}
}).apply {
// See: b/236394768 Workaround for ANGLE issue where FBOs with HardwareBuffer
// attachments are not executed until a glReadPixels call is made
forceFlush.set(true)
}
try {
renderer.render(Unit)
firstRenderLatch.await(3000, TimeUnit.MILLISECONDS)
renderer.clear()
assertTrue(clearLatch.await(3000, TimeUnit.MILLISECONDS))
assertNotNull(buffer)
val colorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_SRGB)
val bitmap = Bitmap.wrapHardwareBuffer(buffer!!, colorSpace)
?.copy(Bitmap.Config.ARGB_8888, false)
assertNotNull(bitmap)
assertTrue(bitmap!!.isAllColor(Color.TRANSPARENT))
} finally {
val latch = CountDownLatch(1)
renderer.release(true) {
executor.shutdownNow()
latch.countDown()
}
assertTrue(latch.await(3000, TimeUnit.MILLISECONDS))
}
}
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
@Test
fun testCancelPending() {
val transformer = BufferTransformer().apply {
computeTransform(TEST_WIDTH, TEST_HEIGHT, BUFFER_TRANSFORM_IDENTITY)
}
val executor = Executors.newSingleThreadExecutor()
var buffer: HardwareBuffer? = null
val initialDrawLatch = CountDownLatch(1)
val bufferReadyLatch = CountDownLatch(1)
val waitForRequestLatch = CountDownLatch(1)
var drawCancelledRequestLatch: CountDownLatch? = null
val renderer = SingleBufferedCanvasRendererV29(
TEST_WIDTH,
TEST_HEIGHT,
transformer,
executor,
object : SingleBufferedCanvasRenderer.RenderCallbacks<Int> {
override fun render(canvas: Canvas, width: Int, height: Int, param: Int) {
canvas.drawColor(param)
initialDrawLatch.countDown()
}
override fun onBufferReady(
hardwareBuffer: HardwareBuffer,
syncFenceCompat: SyncFenceCompat?
) {
syncFenceCompat?.awaitForever()
buffer = hardwareBuffer
bufferReadyLatch.countDown()
drawCancelledRequestLatch?.countDown()
}
}).apply {
// See: b/236394768 Workaround for ANGLE issue where FBOs with HardwareBuffer
// attachments are not executed until a glReadPixels call is made
forceFlush.set(true)
}
try {
renderer.render(Color.RED)
assertTrue(initialDrawLatch.await(3000, TimeUnit.MILLISECONDS))
drawCancelledRequestLatch = CountDownLatch(2)
renderer.render(Color.GREEN)
renderer.render(Color.YELLOW)
renderer.cancelPending()
waitForRequestLatch.countDown()
assertTrue(bufferReadyLatch.await(3000, TimeUnit.MILLISECONDS))
// Because the requests were cancelled this latch should not be signalled
assertFalse(drawCancelledRequestLatch.await(1000, TimeUnit.MILLISECONDS))
assertNotNull(buffer)
val colorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_SRGB)
val bitmap = Bitmap.wrapHardwareBuffer(buffer!!, colorSpace)
?.copy(Bitmap.Config.ARGB_8888, false)
assertNotNull(bitmap)
assertTrue(bitmap!!.isAllColor(Color.RED))
} finally {
val latch = CountDownLatch(1)
renderer.release(true) {
executor.shutdownNow()
latch.countDown()
}
assertTrue(latch.await(3000, TimeUnit.MILLISECONDS))
}
}
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
@Test
fun testMultiReleasesDoesNotCrash() {
val transformer = BufferTransformer()
val executor = Executors.newSingleThreadExecutor()
val renderer = SingleBufferedCanvasRendererV29(
TEST_WIDTH,
TEST_HEIGHT,
transformer,
executor,
object : SingleBufferedCanvasRenderer.RenderCallbacks<Void> {
override fun render(canvas: Canvas, width: Int, height: Int, param: Void) {
// NO-OP
}
override fun onBufferReady(
hardwareBuffer: HardwareBuffer,
syncFenceCompat: SyncFenceCompat?
) {
// NO-OP
}
}).apply {
// See: b/236394768 Workaround for ANGLE issue where FBOs with HardwareBuffer
// attachments are not executed until a glReadPixels call is made
forceFlush.set(true)
}
try {
val latch = CountDownLatch(1)
renderer.release(true) {
executor.shutdownNow()
latch.countDown()
}
assertTrue(latch.await(3000, TimeUnit.MILLISECONDS))
renderer.release(true)
} finally {
if (!executor.isShutdown) {
executor.shutdownNow()
}
}
}
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
@Test
fun testRendererVisibleFlag() {
var supportsNativeAndroidFence = false
withEgl { eglManager ->
supportsNativeAndroidFence = eglManager.supportsNativeAndroidFence()
}
if (!supportsNativeAndroidFence) {
return
}
val transformer = BufferTransformer().apply {
computeTransform(TEST_WIDTH, TEST_HEIGHT, BUFFER_TRANSFORM_IDENTITY)
}
val executor = Executors.newSingleThreadExecutor()
var syncFenceNull = false
var drawLatch: CountDownLatch? = null
val renderer = SingleBufferedCanvasRendererV29(
TEST_WIDTH,
TEST_HEIGHT,
transformer,
executor,
object : SingleBufferedCanvasRenderer.RenderCallbacks<Int> {
override fun render(canvas: Canvas, width: Int, height: Int, param: Int) {
canvas.drawColor(param)
}
override fun onBufferReady(
hardwareBuffer: HardwareBuffer,
syncFenceCompat: SyncFenceCompat?
) {
syncFenceNull = syncFenceCompat == null
syncFenceCompat?.awaitForever()
drawLatch?.countDown()
}
}).apply {
// See: b/236394768 Workaround for ANGLE issue where FBOs with HardwareBuffer
// attachments are not executed until a glReadPixels call is made
forceFlush.set(true)
}
try {
renderer.isVisible = false
drawLatch = CountDownLatch(1)
renderer.render(Color.RED)
assertTrue(drawLatch.await(3000, TimeUnit.MILLISECONDS))
assertFalse(syncFenceNull)
renderer.isVisible = true
drawLatch = CountDownLatch(1)
renderer.render(Color.BLUE)
assertTrue(drawLatch.await(3000, TimeUnit.MILLISECONDS))
assertTrue(syncFenceNull)
} finally {
val latch = CountDownLatch(1)
renderer.release(true) {
executor.shutdownNow()
latch.countDown()
}
assertTrue(latch.await(3000, TimeUnit.MILLISECONDS))
}
}
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
@Test
fun testBatchedRenders() {
val transformer = BufferTransformer()
transformer.computeTransform(TEST_WIDTH, TEST_HEIGHT, BUFFER_TRANSFORM_IDENTITY)
val executor = Executors.newSingleThreadExecutor()
val renderCount = AtomicInteger(0)
val renderer = SingleBufferedCanvasRendererV29(
TEST_WIDTH,
TEST_HEIGHT,
transformer,
executor,
object : SingleBufferedCanvasRenderer.RenderCallbacks<Int> {
override fun render(canvas: Canvas, width: Int, height: Int, param: Int) {
canvas.drawColor(param)
renderCount.incrementAndGet()
}
override fun onBufferReady(
hardwareBuffer: HardwareBuffer,
syncFenceCompat: SyncFenceCompat?
) {
// NO-OP
}
}).apply {
// See: b/236394768 Workaround for ANGLE issue where FBOs with HardwareBuffer
// attachments are not executed until a glReadPixels call is made
forceFlush.set(true)
}
try {
renderer.render(Color.RED)
renderer.render(Color.BLUE)
renderer.render(Color.YELLOW)
} finally {
val latch = CountDownLatch(1)
renderer.release(false) {
executor.shutdownNow()
latch.countDown()
}
assertTrue(latch.await(3000, TimeUnit.MILLISECONDS))
assertEquals(3, renderCount.get())
}
}
@RequiresApi(Build.VERSION_CODES.Q)
private fun testRenderWithTransform(
transform: Int,
actualColors: RectColors,
expectedColors: RectColors
) {
val transformer = BufferTransformer()
transformer.computeTransform(TEST_WIDTH, TEST_HEIGHT, transform)
val executor = Executors.newSingleThreadExecutor()
var buffer: HardwareBuffer? = null
val renderLatch = CountDownLatch(1)
val renderer = SingleBufferedCanvasRendererV29(
TEST_WIDTH,
TEST_HEIGHT,
transformer,
executor,
object : SingleBufferedCanvasRenderer.RenderCallbacks<Int> {
override fun render(canvas: Canvas, width: Int, height: Int, param: Int) {
drawSquares(
canvas,
width,
height,
actualColors.topLeft,
actualColors.topRight,
actualColors.bottomLeft,
actualColors.bottomRight
)
}
override fun onBufferReady(
hardwareBuffer: HardwareBuffer,
syncFenceCompat: SyncFenceCompat?
) {
syncFenceCompat?.awaitForever()
buffer = hardwareBuffer
renderLatch.countDown()
}
}).apply {
// See: b/236394768 Workaround for ANGLE issue where FBOs with HardwareBuffer
// attachments are not executed until a glReadPixels call is made
forceFlush.set(true)
}
try {
renderer.render(0)
assertTrue(renderLatch.await(3000, TimeUnit.MILLISECONDS))
assertNotNull(buffer)
val colorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_SRGB)
val bitmap = Bitmap.wrapHardwareBuffer(buffer!!, colorSpace)
?.copy(Bitmap.Config.ARGB_8888, false)
assertNotNull(bitmap)
bitmap!!.verifyQuadrants(
expectedColors.topLeft,
expectedColors.topRight,
expectedColors.bottomLeft,
expectedColors.bottomRight
)
} finally {
val latch = CountDownLatch(1)
renderer.release(true) {
executor.shutdownNow()
latch.countDown()
}
assertTrue(latch.await(3000, TimeUnit.MILLISECONDS))
}
}
}