/*
* 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 kotlinx.coroutines.runBlocking
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlin.coroutines.resume
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() }
)
}
}