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 18 19 import android.graphics.ColorSpace 20 import android.graphics.RenderNode 21 import android.hardware.HardwareBuffer 22 import android.os.Build 23 import android.view.SurfaceControl 24 import androidx.annotation.IntRange 25 import androidx.annotation.RequiresApi 26 import androidx.core.util.Consumer 27 import androidx.graphics.surface.SurfaceControlCompat 28 import androidx.graphics.surface.SurfaceControlCompat.Companion.BufferTransform 29 import androidx.hardware.DefaultFlags 30 import androidx.hardware.DefaultNumBuffers 31 import androidx.hardware.HardwareBufferFormat 32 import androidx.hardware.HardwareBufferUsage 33 import androidx.hardware.SyncFenceCompat 34 import java.util.concurrent.Executor 35 import kotlin.coroutines.resume 36 import kotlinx.coroutines.suspendCancellableCoroutine 37 38 /** 39 * Creates an instance of a hardware-accelerated renderer. This is used to render a scene built from 40 * [RenderNode]s to an output [HardwareBuffer]. There can be as many [CanvasBufferedRenderer] 41 * instances as desired. 42 * 43 * Resources & lifecycle 44 * 45 * All [CanvasBufferedRenderer] instances share a common render thread. Therefore 46 * [CanvasBufferedRenderer] will share common resources and GPU utilization with hardware 47 * accelerated rendering initiated by the UI thread of an application. The render thread contains 48 * the GPU context & resources necessary to do GPU-accelerated rendering. As such, the first 49 * [CanvasBufferedRenderer] created comes with the cost of also creating the associated GPU 50 * contexts, however each incremental [CanvasBufferedRenderer] thereafter is fairly cheap. 51 * 52 * This is useful in situations where a scene built with [RenderNode] 53 * [SurfaceControlCompat.Transaction.setBuffer]. 54 * 55 * [CanvasBufferedRenderer] can optionally persist contents before each draw invocation so previous 56 * contents in the [HardwareBuffer] target will be preserved across renders. This is determined by 57 * the argument provided to [CanvasBufferedRenderer.RenderRequest.preserveContents] which is set to 58 * `false` by default. 59 */ 60 @RequiresApi(Build.VERSION_CODES.Q) 61 class CanvasBufferedRenderer 62 internal constructor( 63 width: Int, 64 height: Int, 65 private val mFormat: Int, 66 private val mUsage: Long, 67 private val mMaxBuffers: Int, 68 useImpl: Int = DEFAULT_IMPL, 69 ) : AutoCloseable { 70 71 private val mImpl: Impl = 72 if ( 73 Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && useImpl == DEFAULT_IMPL 74 ) { 75 CanvasBufferedRendererV34(width, height, mFormat, mUsage, mMaxBuffers) 76 } else { 77 CanvasBufferedRendererV29(width, height, mFormat, mUsage, mMaxBuffers, useImpl) 78 } 79 80 private val mRenderRequest = RenderRequest() 81 82 /** 83 * Returns the number of buffers within the swap chain used for rendering with this 84 * [CanvasBufferedRenderer] 85 */ 86 val maxBuffers: Int 87 get() = mMaxBuffers 88 89 /** 90 * Returns the [HardwareBufferFormat] of the buffers that are being rendered into by this 91 * [CanvasBufferedRenderer] 92 */ 93 @HardwareBufferFormat 94 val bufferFormat: Int 95 get() = mFormat 96 97 /** 98 * Returns the current usage flag hints of the buffers that are being rendered into by this 99 * [CanvasBufferedRenderer] 100 */ 101 @HardwareBufferUsage 102 val usageFlags: Long 103 get() = mUsage 104 105 /** 106 * Releases the resources associated with this [CanvasBufferedRenderer] instance. **Note** this 107 * does not call [HardwareBuffer.close] on the provided [HardwareBuffer] instance. 108 */ 109 override fun close() { 110 mImpl.close() 111 } 112 113 /** 114 * Returns if the [CanvasBufferedRenderer] has already been closed. That is 115 * [CanvasBufferedRenderer.close] has been invoked. 116 */ 117 val isClosed: Boolean 118 get() = mImpl.isClosed() 119 120 /** 121 * Returns a [RenderRequest] that can be used to render into the provided HardwareBuffer. This 122 * is used to synchronize the RenderNode content provided by [setContentRoot]. 123 */ 124 fun obtainRenderRequest(): RenderRequest { 125 mRenderRequest.reset() 126 return mRenderRequest 127 } 128 129 /** 130 * Sets the content root to render. It is not necessary to call this whenever the content 131 * recording changes. Any mutations to the [RenderNode] content, or any of the [RenderNode]s 132 * contained within the content node, will be applied whenever a new [RenderRequest] is issued 133 * via [obtainRenderRequest] and [RenderRequest.drawAsync]. 134 */ 135 fun setContentRoot(renderNode: RenderNode) { 136 mImpl.setContentRoot(renderNode) 137 } 138 139 /** 140 * Configures the ambient & spot shadow alphas. This is the alpha used when the shadow has max 141 * alpha, and ramps down from the values provided to zero. 142 * 143 * These values are typically provided by the current theme, see R.attr.spotShadowAlpha and 144 * R.attr.ambientShadowAlpha. 145 * 146 * This must be set at least once along with [setLightSourceGeometry] before shadows will work. 147 */ 148 fun setLightSourceAlpha( 149 ambientShadowAlpha: Float, 150 spotShadowAlpha: Float, 151 ) { 152 mImpl.setLightSourceAlpha(ambientShadowAlpha, spotShadowAlpha) 153 } 154 155 /** 156 * Sets the center of the light source. The light source point controls the directionality and 157 * shape of shadows rendered by [RenderNode] Z & elevation. 158 * 159 * The light source should be setup both as part of initial configuration, and whenever the 160 * window moves to ensure the light source stays anchored in display space instead of in window 161 * space. 162 * 163 * This must be set at least once along with [setLightSourceAlpha] before shadows will work. 164 */ 165 fun setLightSourceGeometry(lightX: Float, lightY: Float, lightZ: Float, lightRadius: Float) { 166 mImpl.setLightSourceGeometry(lightX, lightY, lightZ, lightRadius) 167 } 168 169 /** 170 * Builder used to construct a [CanvasBufferedRenderer] instance. 171 * 172 * @param width Width of the buffers created by the [CanvasBufferedRenderer] instance 173 * @param height Height of the buffers created by the [CanvasBufferedRenderer] instance 174 */ 175 class Builder(private val width: Int, private val height: Int) { 176 177 private var mBufferFormat = HardwareBuffer.RGBA_8888 178 private var mMaxBuffers = DefaultNumBuffers 179 private var mUsageFlags = DefaultFlags 180 private var mImpl = DEFAULT_IMPL 181 182 init { 183 if (width <= 0 || height <= 0) { 184 throw IllegalArgumentException( 185 "Invalid dimensions provided, width and height must be > 0. " + 186 "width: $width height: $height" 187 ) 188 } 189 } 190 191 /** 192 * Specify the buffer format of the underlying buffers being rendered into by the created 193 * [CanvasBufferedRenderer]. The set of valid formats is implementation-specific. The 194 * particular valid combinations for a given Android version and implementation should be 195 * documented by that version. 196 * 197 * [HardwareBuffer.RGBA_8888] and [HardwareBuffer.RGBX_8888] are guaranteed to be supported. 198 * However, consumers are recommended to query the desired [HardwareBuffer] configuration 199 * using [HardwareBuffer.isSupported]. 200 * 201 * @param format Pixel format of the buffers to be rendered into. The default is RGBA_8888. 202 * @return The builder instance 203 */ 204 fun setBufferFormat(@HardwareBufferFormat format: Int): Builder { 205 mBufferFormat = format 206 return this 207 } 208 209 /** 210 * Specify the maximum number of buffers used within the swap chain of the 211 * [CanvasBufferedRenderer]. If 1 is specified, then the created [CanvasBufferedRenderer] is 212 * running in "single buffer mode". In this case consumption of the buffer content would 213 * need to be coordinated with the [SyncFenceCompat] returned by the callback of 214 * [RenderRequest.drawAsync]. 215 * 216 * @param numBuffers The number of buffers within the swap chain to be consumed by the 217 * created [CanvasBufferedRenderer]. This must be greater than zero. The default number of 218 * buffers used is 3. 219 * @return The builder instance 220 * @see CanvasBufferedRenderer.RenderRequest.drawAsync 221 */ 222 fun setMaxBuffers(@IntRange(from = 1, to = 64) numBuffers: Int): Builder { 223 require(numBuffers > 0) { "Must have at least 1 buffer" } 224 mMaxBuffers = numBuffers 225 return this 226 } 227 228 /** 229 * Specify the usage flags to be configured on the underlying [HardwareBuffer] instances 230 * created by the [CanvasBufferedRenderer]. 231 * 232 * @param usageFlags Usage flags to be configured on the created [HardwareBuffer] instances 233 * that the [CanvasBufferedRenderer] will render into. Must be one of 234 * [HardwareBufferUsage]. Note that the provided flags here are combined with the 235 * following mandatory default flags, [HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE], 236 * [HardwareBuffer.USAGE_GPU_COLOR_OUTPUT] and [HardwareBuffer.USAGE_COMPOSER_OVERLAY] 237 * @return The builder instance 238 */ 239 fun setUsageFlags(@HardwareBufferUsage usageFlags: Long): Builder { 240 mUsageFlags = usageFlags or DefaultFlags 241 return this 242 } 243 244 /** 245 * Internal test method use to verify alternative implementations of 246 * HardwareBufferRenderer.Impl as well as internal algorithms for persisting rendered 247 * content 248 */ 249 internal fun setImpl(impl: Int): Builder { 250 mImpl = impl 251 return this 252 } 253 254 /** 255 * Create the [CanvasBufferedRenderer] with the specified parameters on this [Builder] 256 * instance. 257 * 258 * @return The newly created [CanvasBufferedRenderer] instance. 259 */ 260 fun build(): CanvasBufferedRenderer { 261 return CanvasBufferedRenderer( 262 width, 263 height, 264 mBufferFormat, 265 mUsageFlags, 266 mMaxBuffers, 267 mImpl 268 ) 269 } 270 } 271 272 /** 273 * Sets the parameters that can be used to control a render request for a 274 * [CanvasBufferedRenderer]. This is not thread-safe and must not be held on to for longer than 275 * a single request. 276 */ 277 inner class RenderRequest internal constructor() { 278 279 private var mColorSpace = DefaultColorSpace 280 private var mTransform = SurfaceControlCompat.BUFFER_TRANSFORM_IDENTITY 281 private var mPreserveContents = false 282 283 internal val preserveContents: Boolean 284 get() = mPreserveContents 285 286 internal val colorSpace: ColorSpace 287 get() = mColorSpace 288 289 internal val transform: Int 290 get() = mTransform 291 292 internal fun reset() { 293 mColorSpace = DefaultColorSpace 294 mTransform = SurfaceControlCompat.BUFFER_TRANSFORM_IDENTITY 295 mPreserveContents = false 296 } 297 298 /** 299 * Syncs the [RenderNode] tree to the render thread and requests content to be drawn. This 300 * [RenderRequest] instance should no longer be used after calling this method. The system 301 * internally may reuse instances of [RenderRequest] to reduce allocation churn. 302 * 303 * @throws IllegalStateException if this method is invoked after the 304 * [CanvasBufferedRenderer] has been closed. 305 */ 306 fun drawAsync(executor: Executor, callback: Consumer<RenderResult>) { 307 if (isClosed) { 308 throw IllegalStateException("Attempt to draw after renderer has been closed") 309 } 310 mImpl.draw(this, executor, callback) 311 } 312 313 /** 314 * Syncs the [RenderNode] tree to the render thread and requests content to be drawn 315 * synchronously. This [RenderRequest] instance should no longer be used after calling this 316 * method. The system internally may reuse instances of [RenderRequest] to reduce allocation 317 * churn. 318 * 319 * @param waitForFence Optional flag to determine if the [SyncFenceCompat] is also waited 320 * upon before returning as a convenience in order to enable callers to consume the 321 * [HardwareBuffer] returned in the [RenderResult] immediately after this method returns. 322 * Passing `false` here on Android T and below is a no-op as the graphics rendering 323 * pipeline internally blocks on the fence before returning. 324 */ 325 suspend fun draw(waitForFence: Boolean = true): RenderResult { 326 check(!isClosed) { "Attempt to draw after renderer has been closed" } 327 328 return suspendCancellableCoroutine { continuation -> 329 drawAsync(Runnable::run) { result -> 330 if (waitForFence) { 331 result.fence?.apply { 332 awaitForever() 333 close() 334 } 335 } 336 continuation.resume(result) 337 } 338 } 339 } 340 341 /** 342 * Specifies a transform to be applied before content is rendered. This is useful for 343 * pre-rotating content for the current display orientation to increase performance of 344 * displaying the associated buffer. This transformation will also adjust the light source 345 * position for the specified rotation. 346 * 347 * @throws IllegalArgumentException if [bufferTransform] is not one of: 348 * [SurfaceControlCompat.BUFFER_TRANSFORM_IDENTITY], 349 * [SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_90], 350 * [SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_180], or 351 * [SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_270] 352 * @see SurfaceControl.Transaction#setBufferTransform(SurfaceControl, int) 353 */ 354 fun setBufferTransform(@BufferTransform bufferTransform: Int): RenderRequest { 355 val validTransform = 356 bufferTransform == SurfaceControlCompat.BUFFER_TRANSFORM_IDENTITY || 357 bufferTransform == SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_90 || 358 bufferTransform == SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_180 || 359 bufferTransform == SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_270 360 if (validTransform) { 361 mTransform = bufferTransform 362 } else { 363 throw IllegalArgumentException( 364 "Invalid transform provided, must be one of the " + 365 "SurfaceControlCompat.BufferTransform values received: " + 366 bufferTransform 367 ) 368 } 369 return this 370 } 371 372 /** 373 * Configures the color space which the content should be rendered in. This affects how the 374 * framework will interpret the color at each pixel. The color space provided here must be 375 * non-null, RGB based and leverage an ICC parametric curve. The min/max values of the 376 * components should not reduce the numerical range compared to the previously assigned 377 * color space. If left unspecified, the default color space of SRGB will be used. 378 * 379 * **NOTE** this method is only supported on Android U and above and is ignored on older 380 * Android versions 381 */ 382 fun setColorSpace(colorSpace: ColorSpace?): RenderRequest { 383 mColorSpace = colorSpace ?: DefaultColorSpace 384 return this 385 } 386 387 /** 388 * Determines whether or not previous buffer contents will be persisted across render 389 * requests. If false then contents are cleared before issuing drawing instructions, 390 * otherwise contents will remain. 391 * 392 * If contents are known in advance to be completely opaque and cover all pixels within the 393 * buffer, setting this flag to true will slightly improve performance as the clear 394 * operation will be skipped. 395 * 396 * For low latency use cases (ex applications that support drawing with a stylus), setting 397 * this value to true alongside single buffered rendering by configuring 398 * [CanvasBufferedRenderer.Builder.setMaxBuffers] to 1 allows for reduced latency by 399 * allowing consumers to only render the deltas across frames as the previous content would 400 * be persisted. 401 * 402 * The default setting is false. 403 */ 404 fun preserveContents(preserve: Boolean): RenderRequest { 405 mPreserveContents = preserve 406 return this 407 } 408 } 409 410 /** 411 * Releases the [HardwareBuffer] back into the allocation pool to be reused in subsequent 412 * renders. The [HardwareBuffer] instance released here must be one that was originally obtained 413 * from this [CanvasBufferedRenderer] instance. This method also takes in an optional 414 * [SyncFenceCompat] instance that will be internally waited upon before re-using the buffer. 415 * This is useful in conjunction with [SurfaceControlCompat.Transaction.setBuffer] where the 416 * system will return a release fence that should be waited upon before the corresponding buffer 417 * can be re-used. 418 * 419 * @param hardwareBuffer [HardwareBuffer] to return back to the allocation pool 420 * @param fence Optional [SyncFenceCompat] that should be waited upon before the buffer is 421 * reused. 422 */ 423 @JvmOverloads 424 fun releaseBuffer(hardwareBuffer: HardwareBuffer, fence: SyncFenceCompat? = null) { 425 mImpl.releaseBuffer(hardwareBuffer, fence) 426 } 427 428 /** 429 * Class that contains data regarding the result of the render request. Consumers are to wait on 430 * the provided [SyncFenceCompat] if it is non null before consuming the [HardwareBuffer] 431 * provided to as well as verify that the status returned by [RenderResult.status] returns 432 * [RenderResult.SUCCESS]. 433 * 434 * For example: 435 * ``` 436 * fun handleRenderResult(result: RenderResult) { 437 * // block on the fence if it is non-null 438 * result.fence?.let { fence -> 439 * fence.awaitForever() 440 * fence.close() 441 * } 442 * // consume contents of RenderResult.hardwareBuffer 443 * } 444 * ``` 445 */ 446 class RenderResult( 447 private val buffer: HardwareBuffer, 448 private val mFence: SyncFenceCompat?, 449 private val mStatus: Int 450 ) { 451 452 /** 453 * [HardwareBuffer] that contains the result of the render request. Consumers should be sure 454 * to block on the [SyncFenceCompat] instance provided in [fence] if it is non-null before 455 * consuming the contents of this buffer. If [fence] returns null then this [HardwareBuffer] 456 * can be consumed immediately. 457 */ 458 val hardwareBuffer: HardwareBuffer 459 get() = buffer 460 461 /** 462 * Optional fence that should be waited upon before consuming [hardwareBuffer]. On Android U 463 * and above, requests to render will return sooner and include this fence as a way to 464 * signal that the result of the render request is reflected in the contents of the buffer. 465 * This is done to reduce latency and provide opportunities for other systems to block on 466 * the fence on the behalf of the application. For example, 467 * [SurfaceControlCompat.Transaction.setBuffer] can be invoked with 468 * [RenderResult.hardwareBuffer] and [RenderResult.fence] respectively without the 469 * application having to explicitly block on this fence. For older Android versions, the 470 * rendering pipeline will automatically block on this fence and this value will return 471 * null. 472 */ 473 val fence: SyncFenceCompat? 474 get() = mFence 475 476 /** 477 * Status code for the [RenderResult] either [SUCCESS] if rendering completed or 478 * [ERROR_UNKNOWN] if the rendering could not be completed. 479 */ 480 val status: Int 481 get() = mStatus 482 483 companion object { 484 /** Render request was completed successfully */ 485 const val SUCCESS = 0 486 487 /** Render request failed with an unknown error */ 488 const val ERROR_UNKNOWN = 1 489 } 490 } 491 492 internal interface Impl : AutoCloseable { 493 494 override fun close() 495 496 fun isClosed(): Boolean 497 498 fun draw(request: RenderRequest, executor: Executor, callback: Consumer<RenderResult>) 499 500 fun releaseBuffer(hardwareBuffer: HardwareBuffer, syncFence: SyncFenceCompat?) 501 502 fun setContentRoot(renderNode: RenderNode) 503 504 fun setLightSourceAlpha( 505 ambientShadowAlpha: Float, 506 spotShadowAlpha: Float, 507 ) 508 509 fun setLightSourceGeometry(lightX: Float, lightY: Float, lightZ: Float, lightRadius: Float) 510 } 511 512 internal companion object { 513 514 val DefaultColorSpace = ColorSpace.get(ColorSpace.Named.SRGB) 515 516 /** 517 * Test flag to use the optimal implementation for the corresponding Android platform 518 * version 519 */ 520 internal const val DEFAULT_IMPL = 0 521 522 /** 523 * Test flag used to verify the V29 implementation that leverages the redraw strategy on 524 * devices that do not persist contents of opaque renders 525 */ 526 internal const val USE_V29_IMPL_WITH_REDRAW = 1 527 528 /** 529 * Test flag used to verify the V29 implementation that leverages the default single 530 * buffered restoration strategy 531 */ 532 internal const val USE_V29_IMPL_WITH_SINGLE_BUFFER = 2 533 } 534 } 535