1 /* <lambda>null2 * Copyright 2023 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 androidx.graphics.lowlatency 18 19 import android.graphics.Canvas 20 import android.graphics.RenderNode 21 import android.graphics.SurfaceTexture 22 import android.os.Build 23 import android.os.Handler 24 import android.os.HandlerThread 25 import androidx.annotation.AnyThread 26 import androidx.annotation.RequiresApi 27 import androidx.annotation.WorkerThread 28 import androidx.graphics.SurfaceTextureRenderer 29 import androidx.graphics.utils.post 30 31 /** 32 * Class responsible for the producing side of SurfaceTextures that are rendered with content 33 * provided from a canvas. This class handles proxying all requests to an internal thread as well as 34 * throttles production of frames based on consumption rate. 35 */ 36 @RequiresApi(Build.VERSION_CODES.Q) 37 internal class TextureProducer<T>(val width: Int, val height: Int, val callbacks: Callbacks<T>) { 38 39 interface Callbacks<T> { 40 fun onTextureAvailable(texture: SurfaceTexture) 41 42 fun render(canvas: Canvas, width: Int, height: Int, param: T) 43 } 44 45 private var mIsReleasing = false 46 private val mParams = ArrayList<T>() 47 private var mPendingRenders = 0 48 private val mProducerThread = HandlerThread("producerThread").apply { start() } 49 private val mProducerHandler = Handler(mProducerThread.looper) 50 51 private val mCancelPendingRunnable = Runnable { mParams.clear() } 52 53 @WorkerThread // ProducerThread 54 private fun teardown(releaseCallback: (() -> Unit)? = null) { 55 releaseCallback?.invoke() 56 mSurfaceTextureRenderer.release() 57 mProducerThread.quit() 58 } 59 60 @WorkerThread // ProducerThread 61 private fun isPendingRendering() = mParams.isNotEmpty() || mPendingRenders > 0 62 63 private val mRenderNode = 64 RenderNode("node").apply { 65 setPosition(0, 0, this@TextureProducer.width, this@TextureProducer.height) 66 } 67 68 private inline fun RenderNode.record(block: (Canvas) -> Unit) { 69 val canvas = beginRecording() 70 block(canvas) 71 endRecording() 72 } 73 74 private val mSurfaceTextureRenderer = 75 SurfaceTextureRenderer(mRenderNode, width, height, mProducerHandler) { texture -> 76 callbacks.onTextureAvailable(texture) 77 } 78 79 @WorkerThread // ProducerThread 80 private fun doRender() { 81 if (mPendingRenders < MAX_PENDING_RENDERS) { 82 if (mParams.isNotEmpty()) { 83 mRenderNode.record { canvas -> 84 for (p in mParams) { 85 callbacks.render(canvas, width, height, p) 86 } 87 } 88 mParams.clear() 89 mPendingRenders++ 90 mSurfaceTextureRenderer.renderFrame() 91 } 92 } 93 } 94 95 @AnyThread 96 fun requestRender(param: T) { 97 mProducerHandler.post(RENDER) { 98 if (!mIsReleasing) { 99 mParams.add(param) 100 doRender() 101 } 102 } 103 } 104 105 @AnyThread 106 fun cancelPending() { 107 mProducerHandler.removeCallbacksAndMessages(RENDER) 108 mProducerHandler.post(CANCEL_PENDING, mCancelPendingRunnable) 109 } 110 111 @AnyThread 112 fun markTextureConsumed() { 113 mProducerHandler.post(TEXTURE_CONSUMED) { 114 mPendingRenders-- 115 if (mIsReleasing && !isPendingRendering()) { 116 teardown() 117 } else { 118 doRender() 119 } 120 } 121 } 122 123 @AnyThread 124 fun execute(runnable: Runnable) { 125 mProducerHandler.post(runnable) 126 } 127 128 @AnyThread 129 fun remove(runnable: Runnable) { 130 mProducerHandler.removeCallbacks(runnable) 131 } 132 133 @AnyThread 134 fun release(cancelPending: Boolean, onReleaseComplete: (() -> Unit)? = null) { 135 if (cancelPending) { 136 cancelPending() 137 } 138 mProducerHandler.post(RELEASE) { 139 mIsReleasing = true 140 if (!isPendingRendering()) { 141 teardown(onReleaseComplete) 142 } 143 } 144 } 145 146 private companion object { 147 /** 148 * Constant to indicate a request to render new content into a SurfaceTexture for 149 * consumption. 150 */ 151 const val RENDER = 0 152 153 /** Constant to indicate that a previously produced frame has been consumed. */ 154 const val TEXTURE_CONSUMED = 1 155 156 /** 157 * Cancel all pending requests to render and clear all parameters that are to be consumed 158 * for an upcoming frame 159 */ 160 const val CANCEL_PENDING = 2 161 162 /** Release the resources associated with this [TextureProducer] instance */ 163 const val RELEASE = 3 164 165 /** 166 * Maximum number of frames to produce before the producer pauses. Subsequent attempts to 167 * render will batch parameters and continue to produce frames when the consumer signals 168 * that the corresponding textures have been consumed. 169 */ 170 const val MAX_PENDING_RENDERS = 2 171 } 172 } 173