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.hardware 18 19 import android.hardware.HardwareBuffer 20 import android.os.Build 21 import android.util.Log 22 import androidx.annotation.RequiresApi 23 import java.util.concurrent.locks.ReentrantLock 24 import kotlin.concurrent.withLock 25 26 @RequiresApi(Build.VERSION_CODES.O) 27 internal class BufferPool<T : BufferPool.BufferProvider>(val maxPoolSize: Int) { 28 29 private val mPool: ArrayList<Entry<T>> = ArrayList() 30 private val mLock = ReentrantLock() 31 private val mCondition = mLock.newCondition() 32 private var mBuffersAvailable = 0 33 private var mIsClosed = false 34 35 init { 36 if (maxPoolSize <= 0) { 37 throw IllegalArgumentException("Pool size must be at least 1") 38 } 39 } 40 41 fun add(buffer: T) { 42 mPool.add(Entry(buffer)) 43 } 44 45 inline fun obtain(factory: () -> T): T { 46 mLock.withLock { 47 return obtainFromPool() ?: factory.invoke().also { buffer -> insert(buffer) } 48 } 49 } 50 51 private fun insert(buffer: T) { 52 // Insert the buffer into the pool. If we are in single buffer mode mark this buffer 53 // as being available to support front buffered rendering use cases of simultaneous 54 // presentation and rendering 55 val singleBuffered = maxPoolSize == 1 56 if (singleBuffered) { 57 mBuffersAvailable++ 58 } 59 mPool.add(Entry(buffer, isAvailable = singleBuffered)) 60 } 61 62 private fun obtainFromPool(): T? { 63 if (mIsClosed) { 64 throw IllegalStateException( 65 "Attempt to obtain frame buffer from FrameBufferPool " + 66 "that has already been closed" 67 ) 68 } 69 val singleBuffered = maxPoolSize == 1 70 return if (singleBuffered) { 71 val entry = mPool.firstOrNull()?.also { entry -> entry.isAvailable = true } 72 entry?.bufferProvider 73 } else { 74 while (mBuffersAvailable == 0 && mPool.size >= maxPoolSize) { 75 Log.w( 76 TAG, 77 "Waiting for buffer to become available, current allocation " + 78 "count: ${mPool.size}" 79 ) 80 mCondition.await() 81 } 82 val entry = 83 mPool.findEntryWith(isAvailable, signaledFence)?.also { entry -> 84 mBuffersAvailable-- 85 with(entry) { 86 isAvailable = false 87 releaseFence?.let { fence -> 88 fence.awaitForever() 89 fence.close() 90 } 91 bufferProvider 92 } 93 } 94 entry?.bufferProvider 95 } 96 } 97 98 fun release(hardwareBuffer: HardwareBuffer, fence: SyncFenceCompat? = null) { 99 mLock.withLock { 100 val entry = mPool.find { entry -> entry.hardwareBuffer === hardwareBuffer } 101 if (entry != null) { 102 // Only mark the entry as available if it was previously allocated 103 // This protects against the potential for the same buffer to be released 104 // multiple times into the same pool 105 if (!entry.isAvailable) { 106 if (!entry.hardwareBuffer.isClosed) { 107 entry.releaseFence = fence 108 entry.isAvailable = true 109 mBuffersAvailable++ 110 } else { 111 // The consumer closed the buffer before releasing it to the pool. 112 // In this case remove the entry to allocate new buffers when requested. 113 // Because HardwareBuffer instances can be managed by applications, we 114 // should defend against this potential scenario. 115 mPool.remove(entry) 116 } 117 } 118 119 if (!mIsClosed) { 120 mCondition.signal() 121 } else { 122 // If a buffer is attempted to be released after the pool is closed 123 // just remove it from the entries and release it 124 hardwareBuffer.close() 125 if (mBuffersAvailable == mPool.size) { 126 mPool.clear() 127 } 128 } 129 } else if (!hardwareBuffer.isClosed) { 130 // If the FrameBuffer is not previously closed and we don't own this, flag this as 131 // an error as most likely this buffer was attempted to be returned to the wrong 132 // pool 133 throw IllegalArgumentException( 134 "No entry associated with this framebuffer " + 135 "instance. Was this frame buffer created from a different FrameBufferPool?" 136 ) 137 } 138 } 139 } 140 141 /** 142 * Return the current pool allocation size. This will increase until the [maxPoolSize]. This 143 * count will decrease if a buffer is closed before it is returned to the pool as a closed 144 * buffer is no longer re-usable 145 */ 146 val allocationCount: Int 147 get() = mPool.size 148 149 val isClosed: Boolean 150 get() = mLock.withLock { mIsClosed } 151 152 /** 153 * Invokes [BufferProvider.release] on all [Entry] instances currently available within the pool 154 * and clears the pool. This method is thread safe. 155 */ 156 fun close() { 157 mLock.withLock { 158 if (!mIsClosed) { 159 for (entry in mPool) { 160 if (entry.isAvailable) { 161 entry.releaseFence?.let { fence -> 162 fence.awaitForever() 163 fence.close() 164 } 165 entry.bufferProvider.release() 166 } 167 } 168 if (mBuffersAvailable == mPool.size) { 169 mPool.clear() 170 } 171 mIsClosed = true 172 } 173 } 174 } 175 176 private data class Entry<T : BufferProvider>( 177 val bufferProvider: T, 178 var releaseFence: SyncFenceCompat? = null, 179 var isAvailable: Boolean = true 180 ) { 181 val hardwareBuffer: HardwareBuffer 182 get() = bufferProvider.hardwareBuffer 183 } 184 185 interface BufferProvider { 186 val hardwareBuffer: HardwareBuffer 187 188 fun release() 189 } 190 191 internal companion object { 192 private const val TAG = "BufferPool" 193 194 /** 195 * Predicate used to search for the first entry within the pool that is either null or has 196 * already signalled 197 */ 198 private val signaledFence: (Entry<*>) -> Boolean = { entry -> 199 val fence = entry.releaseFence 200 fence == null || fence.getSignalTimeNanos() != SyncFenceCompat.SIGNAL_TIME_PENDING 201 } 202 203 private val isAvailable: (Entry<*>) -> Boolean = { entry -> entry.isAvailable } 204 205 /** 206 * Finds the first element within the ArrayList that satisfies both primary and secondary 207 * conditions. If no entries satisfy the secondary condition, this returns the first entry 208 * that satisfies the primary condition or null if no entries do. 209 */ 210 internal fun <T> ArrayList<T>.findEntryWith( 211 primaryCondition: ((T) -> Boolean), 212 secondaryCondition: ((T) -> Boolean) 213 ): T? { 214 var fallback: T? = null 215 for (entry in this) { 216 if (primaryCondition(entry)) { 217 if (fallback == null) { 218 fallback = entry 219 } 220 if (secondaryCondition(entry)) { 221 return entry 222 } 223 } 224 } 225 // No elements satisfy the condition, return the entry that satisfies the primary 226 // condition if available 227 return fallback 228 } 229 } 230 } 231