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