/*
* Copyright 2021 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.wear.watchface
import android.opengl.EGL14
import android.view.SurfaceHolder
import androidx.annotation.IntRange
import androidx.annotation.Px
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.wear.watchface.Renderer.SharedAssets
import androidx.wear.watchface.style.CurrentUserStyleRepository
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture
import kotlin.coroutines.resume
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
internal val EGL_CONFIG_ATTRIB_LIST =
intArrayOf(
EGL14.EGL_RENDERABLE_TYPE,
EGL14.EGL_OPENGL_ES2_BIT,
EGL14.EGL_RED_SIZE,
8,
EGL14.EGL_GREEN_SIZE,
8,
EGL14.EGL_BLUE_SIZE,
8,
EGL14.EGL_ALPHA_SIZE,
8,
EGL14.EGL_NONE
)
internal val EGL_SURFACE_ATTRIB_LIST = intArrayOf(EGL14.EGL_NONE)
/**
* [ListenableFuture]-based compatibility wrapper around [Renderer.GlesRenderer]'s suspending
* methods.
*/
@Deprecated(message = "Use ListenableGlesRenderer2 instead")
@Suppress("Deprecation")
public abstract class ListenableGlesRenderer
@Throws(GlesException::class)
@JvmOverloads
constructor(
surfaceHolder: SurfaceHolder,
currentUserStyleRepository: CurrentUserStyleRepository,
watchState: WatchState,
@IntRange(from = 0, to = 60000) interactiveDrawModeUpdateDelayMillis: Long,
eglConfigAttribList: IntArray = EGL_CONFIG_ATTRIB_LIST,
eglSurfaceAttribList: IntArray = EGL_SURFACE_ATTRIB_LIST
) :
Renderer.GlesRenderer(
surfaceHolder,
currentUserStyleRepository,
watchState,
interactiveDrawModeUpdateDelayMillis,
eglConfigAttribList,
eglSurfaceAttribList
) {
/**
* Inside of a [Mutex] this function sets the GL context associated with the
* [WatchFaceService.getBackgroundThreadHandler]'s looper thread as the current one, executes
* [runnable] and finally unsets the GL context.
*
* Access to the GL context this way is necessary because GL contexts are not shared between
* renderers and there can be multiple watch face instances existing concurrently (e.g. headless
* and interactive, potentially from different watch faces if an APK contains more than one
* [WatchFaceService]).
*
* NB this function is called by the library before running [runBackgroundThreadGlCommands] so
* there's no need to use this directly in client code unless you need to make GL calls outside
* of those methods.
*
* @throws [IllegalStateException] if the calls to [EGL14.eglMakeCurrent] fails
*/
@WorkerThread
public fun runBackgroundThreadGlCommands(runnable: Runnable) {
runBlocking { runBackgroundThreadGlCommands { runnable.run() } }
}
/**
* Inside of a [Mutex] this function sets the UiThread GL context as the current one, executes
* [runnable] and finally unsets the GL context.
*
* Access to the GL context this way is necessary because GL contexts are not shared between
* renderers and there can be multiple watch face instances existing concurrently (e.g. headless
* and interactive, potentially from different watch faces if an APK contains more than one
* [WatchFaceService]).
*
* @throws [IllegalStateException] if the calls to [EGL14.eglMakeCurrent] fails
*/
@UiThread
public fun runUiThreadGlCommands(runnable: Runnable) {
runBlocking { runUiThreadGlCommands { runnable.run() } }
}
/**
* Called once a background thread when a new GL context is created on the background thread,
* before any subsequent calls to [render]. Note this function is called inside a lambda passed
* to [runBackgroundThreadGlCommands] which has synchronized access to the GL context. Note
* cancellation of the returned future is not supported.
*
* @return A ListenableFuture<Unit> which is resolved when background thread work has completed.
* Rendering will be blocked until this has resolved.
*/
@Suppress("AsyncSuffixFuture") // This is the guava wrapper for a suspend function
protected open fun onBackgroundThreadGlContextCreatedFuture(): ListenableFuture<Unit> {
return SettableFuture.create<Unit>().apply { set(Unit) }
}
override suspend fun onBackgroundThreadGlContextCreated(): Unit = suspendCancellableCoroutine {
val future = onBackgroundThreadGlContextCreatedFuture()
future.addListener({ it.resume(future.get()) }, { runnable -> runnable.run() })
}
/**
* Called when a new GL surface is created on the UiThread, before any subsequent calls to
* [render] and in response to [SurfaceHolder.Callback.surfaceChanged]. Note this function is
* called inside a lambda passed to [Renderer.GlesRenderer.runUiThreadGlCommands] which has
* synchronized access to the GL context. Note cancellation of the returned future is not
* supported.
*
* @param width width of surface in pixels
* @param height height of surface in pixels
* @return A ListenableFuture<Unit> which is resolved when UI thread work has completed.
* Rendering will be blocked until this has resolved.
*/
@UiThread
@Suppress("AsyncSuffixFuture") // This is the guava wrapper for a suspend function
protected open fun onUiThreadGlSurfaceCreatedFuture(
@Px width: Int,
@Px height: Int
): ListenableFuture<Unit> {
return SettableFuture.create<Unit>().apply { set(Unit) }
}
override suspend fun onUiThreadGlSurfaceCreated(@Px width: Int, @Px height: Int): Unit =
suspendCancellableCoroutine {
val future = onUiThreadGlSurfaceCreatedFuture(width, height)
future.addListener({ it.resume(future.get()) }, { runnable -> runnable.run() })
}
}
/**
* [ListenableFuture]-based compatibility wrapper around [Renderer.GlesRenderer]'s suspending
* methods.
*/
public abstract class ListenableGlesRenderer2<SharedAssetsT>
@Throws(GlesException::class)
@JvmOverloads
constructor(
surfaceHolder: SurfaceHolder,
currentUserStyleRepository: CurrentUserStyleRepository,
watchState: WatchState,
@IntRange(from = 0, to = 60000) interactiveDrawModeUpdateDelayMillis: Long,
eglConfigAttribList: IntArray = EGL_CONFIG_ATTRIB_LIST,
eglSurfaceAttribList: IntArray = EGL_SURFACE_ATTRIB_LIST
) :
Renderer.GlesRenderer2<SharedAssetsT>(
surfaceHolder,
currentUserStyleRepository,
watchState,
interactiveDrawModeUpdateDelayMillis,
eglConfigAttribList,
eglSurfaceAttribList
) where SharedAssetsT : SharedAssets {
/**
* Inside of a [Mutex] this function sets the GL context associated with the
* [WatchFaceService.getBackgroundThreadHandler]'s looper thread as the current one, executes
* [runnable] and finally unsets the GL context.
*
* Access to the GL context this way is necessary because GL contexts are not shared between
* renderers and there can be multiple watch face instances existing concurrently (e.g. headless
* and interactive, potentially from different watch faces if an APK contains more than one
* [WatchFaceService]).
*
* NB this function is called by the library before running [runBackgroundThreadGlCommands] so
* there's no need to use this directly in client code unless you need to make GL calls outside
* of those methods.
*
* @throws [IllegalStateException] if the calls to [EGL14.eglMakeCurrent] fails
*/
@WorkerThread
public fun runBackgroundThreadGlCommands(runnable: Runnable) {
runBlocking { runBackgroundThreadGlCommands { runnable.run() } }
}
/**
* Inside of a [Mutex] this function sets the UiThread GL context as the current one, executes
* [runnable] and finally unsets the GL context.
*
* Access to the GL context this way is necessary because GL contexts are not shared between
* renderers and there can be multiple watch face instances existing concurrently (e.g. headless
* and interactive, potentially from different watch faces if an APK contains more than one
* [WatchFaceService]).
*
* @throws [IllegalStateException] if the calls to [EGL14.eglMakeCurrent] fails
*/
@UiThread
public fun runUiThreadGlCommands(runnable: Runnable) {
runBlocking { runUiThreadGlCommands { runnable.run() } }
}
/**
* Called once a background thread when a new GL context is created on the background thread,
* before any subsequent calls to [render]. Note this function is called inside a lambda passed
* to [runBackgroundThreadGlCommands] which has synchronized access to the GL context. Note
* cancellation of the returned future is not supported.
*
* @return A ListenableFuture<Unit> which is resolved when background thread work has completed.
* Rendering will be blocked until this has resolved.
*/
@Suppress("AsyncSuffixFuture") // This is the guava wrapper for a suspend function
protected open fun onBackgroundThreadGlContextCreatedFuture(): ListenableFuture<Unit> {
return SettableFuture.create<Unit>().apply { set(Unit) }
}
final override suspend fun onBackgroundThreadGlContextCreated(): Unit =
suspendCancellableCoroutine {
val future = onBackgroundThreadGlContextCreatedFuture()
future.addListener({ it.resume(future.get()) }, { runnable -> runnable.run() })
}
/**
* Called when a new GL surface is created on the UiThread, before any subsequent calls to
* [render] and in response to [SurfaceHolder.Callback.surfaceChanged]. Note this function is
* called inside a lambda passed to [Renderer.GlesRenderer.runUiThreadGlCommands] which has
* synchronized access to the GL context. Note cancellation of the returned future is not
* supported.
*
* @param width width of surface in pixels
* @param height height of surface in pixels
* @return A ListenableFuture<Unit> which is resolved when UI thread work has completed.
* Rendering will be blocked until this has resolved.
*/
@UiThread
@Suppress("AsyncSuffixFuture") // This is the guava wrapper for a suspend function
protected open fun onUiThreadGlSurfaceCreatedFuture(
@Px width: Int,
@Px height: Int
): ListenableFuture<Unit> {
return SettableFuture.create<Unit>().apply { set(Unit) }
}
final override suspend fun onUiThreadGlSurfaceCreated(@Px width: Int, @Px height: Int): Unit =
suspendCancellableCoroutine {
val future = onUiThreadGlSurfaceCreatedFuture(width, height)
future.addListener({ it.resume(future.get()) }, { runnable -> runnable.run() })
}
/**
* When editing multiple [WatchFaceService] instances and hence Renderers can exist concurrently
* (e.g. a headless instance and an interactive instance) and using [SharedAssets] allows memory
* to be saved by sharing immutable data (e.g. Bitmaps, shaders, etc...) between them.
*
* To take advantage of SharedAssets, override this method. The constructed SharedAssets are
* passed into the [render] as an argument (NB you'll have to cast this to your type). It is
* safe to make GLES calls within this method.
*
* When all instances using SharedAssets have been closed, [SharedAssets.onDestroy] will be
* called.
*
* Note that while SharedAssets are constructed on a background thread, they'll typically be
* used on the main thread and subsequently destroyed there. The watch face library constructs
* shared GLES contexts to allow resource sharing between threads.
*
* @return A [ListenableFuture] for the [SharedAssetsT] that will be passed into [render] and
* [renderHighlightLayer]
*/
@WorkerThread
@Suppress("AsyncSuffixFuture") // This is the guava wrapper for a suspend function
public abstract fun createSharedAssetsFuture(): ListenableFuture<SharedAssetsT>
final override suspend fun createSharedAssets(): SharedAssetsT = suspendCancellableCoroutine {
val future = createSharedAssetsFuture()
future.addListener({ it.resume(future.get()) }, { runnable -> runnable.run() })
}
}