diff options
Diffstat (limited to 'libs/bufferstreams')
56 files changed, 2300 insertions, 0 deletions
diff --git a/libs/bufferstreams/Android.bp b/libs/bufferstreams/Android.bp new file mode 100644 index 0000000000..365fc457d1 --- /dev/null +++ b/libs/bufferstreams/Android.bp @@ -0,0 +1,36 @@ +// Copyright (C) 2023 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 { + default_applicable_licenses: ["frameworks_native_license"], +} + +aconfig_declarations { + name: "bufferstreams_flags", + package: "com.android.graphics.bufferstreams.flags", + srcs: [ + "aconfig/bufferstreams_flags.aconfig", + ], +} + +rust_aconfig_library { + name: "libbufferstreams_flags_rust", + crate_name: "bufferstreams_flags", + aconfig_declarations: "bufferstreams_flags", +} + +cc_aconfig_library { + name: "libbufferstreams_flags_cc", + aconfig_declarations: "bufferstreams_flags", +} diff --git a/libs/bufferstreams/OWNERS b/libs/bufferstreams/OWNERS new file mode 100644 index 0000000000..32b72b8592 --- /dev/null +++ b/libs/bufferstreams/OWNERS @@ -0,0 +1,7 @@ +carlosmr@google.com +hibrian@google.com +jreck@google.com +jshargo@google.com + +file:/services/surfaceflinger/OWNERS + diff --git a/libs/bufferstreams/README.md b/libs/bufferstreams/README.md new file mode 100644 index 0000000000..860adef281 --- /dev/null +++ b/libs/bufferstreams/README.md @@ -0,0 +1,13 @@ +# libbufferstreams: Reactive Streams for Graphics Buffers + +This library is currently **experimental** and **under active development**. +It is not production ready yet. + +For more information on reactive streams, please see <https://www.reactive-streams.org/> + +## Contributing + +This library is natively written in Rust and exposes a C API. If you make changes to the Rust API, +you **must** update the C API in turn. To do so, with cbindgen installed, run: + +```$ ./update_include.sh``` diff --git a/libs/bufferstreams/aconfig/bufferstreams_flags.aconfig b/libs/bufferstreams/aconfig/bufferstreams_flags.aconfig new file mode 100644 index 0000000000..e258725e3d --- /dev/null +++ b/libs/bufferstreams/aconfig/bufferstreams_flags.aconfig @@ -0,0 +1,65 @@ +package: "com.android.graphics.bufferstreams.flags" + +flag { + name: "bufferstreams_steel_thread" + namespace: "core_graphics" + description: "Flag for bufferstreams steel thread milestone" + bug: "296101122" +} + +flag { + name: "bufferstreams_local" + namespace: "core_graphics" + description: "Flag for bufferstreams single-process functionality milestone" + bug: "296100790" +} + +flag { + name: "bufferstreams_pooling" + namespace: "core_graphics" + description: "Flag for bufferstreams buffer pooling milestone" + bug: "296101127" +} + +flag { + name: "bufferstreams_ipc" + namespace: "core_graphics" + description: "Flag for bufferstreams IPC milestone" + bug: "296099728" +} + +flag { + name: "bufferstreams_cpp" + namespace: "core_graphics" + description: "Flag for bufferstreams C/C++ milestone" + bug: "296100536" +} + +flag { + name: "bufferstreams_utils" + namespace: "core_graphics" + description: "Flag for bufferstreams extra utilities milestone" + bug: "285322189" +} + +flag { + name: "bufferstreams_demo" + namespace: "core_graphics" + description: "Flag for bufferstreams demo milestone" + bug: "297242965" +} + +flag { + name: "bufferstreams_perf" + namespace: "core_graphics" + description: "Flag for bufferstreams performance enhancement milestone" + bug: "297242843" +} + +flag { + name: "bufferstreams_tooling" + namespace: "core_graphics" + description: "Flag for bufferstreams tooling milestone" + bug: "297243180" +} + diff --git a/libs/bufferstreams/examples/app/Android.bp b/libs/bufferstreams/examples/app/Android.bp new file mode 100644 index 0000000000..bb573c596c --- /dev/null +++ b/libs/bufferstreams/examples/app/Android.bp @@ -0,0 +1,49 @@ +// Copyright (C) 2023 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. + +android_app { + name: "BufferStreamsDemoApp", + srcs: ["java/**/*.kt"], + sdk_version: "current", + + jni_uses_platform_apis: true, + jni_libs: ["libbufferstreamdemoapp"], + use_embedded_native_libs: true, + kotlincflags: [ + "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api", + ], + optimize: { + proguard_flags_files: ["proguard-rules.pro"], + }, + + resource_dirs: ["res"], + + static_libs: [ + "androidx.activity_activity-compose", + "androidx.appcompat_appcompat", + "androidx.compose.foundation_foundation", + "androidx.compose.material3_material3", + "androidx.compose.runtime_runtime", + "androidx.compose.ui_ui", + "androidx.compose.ui_ui-graphics", + "androidx.compose.ui_ui-tooling-preview", + "androidx.core_core-ktx", + "androidx.lifecycle_lifecycle-runtime-ktx", + "androidx.navigation_navigation-common-ktx", + "androidx.navigation_navigation-compose", + "androidx.navigation_navigation-fragment-ktx", + "androidx.navigation_navigation-runtime-ktx", + "androidx.navigation_navigation-ui-ktx", + ], +} diff --git a/libs/bufferstreams/examples/app/AndroidManifest.xml b/libs/bufferstreams/examples/app/AndroidManifest.xml new file mode 100644 index 0000000000..a5e2fa8a63 --- /dev/null +++ b/libs/bufferstreams/examples/app/AndroidManifest.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.graphics.bufferstreamsdemoapp" + xmlns:tools="http://schemas.android.com/tools"> + + <application + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + android:theme="@style/Theme.Jetpack" + tools:targetApi="34"> + <activity + android:name=".MainActivity" + android:exported="true" + android:label="@string/app_name" + android:theme="@style/Theme.Jetpack"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> + +</manifest>
\ No newline at end of file diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferDemosAppBar.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferDemosAppBar.kt new file mode 100644 index 0000000000..ff3ae5a090 --- /dev/null +++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferDemosAppBar.kt @@ -0,0 +1,40 @@ +package com.android.graphics.bufferstreamsdemoapp + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource + +@Composable +fun BufferDemosAppBar( + currentScreen: BufferDemoScreen, + canNavigateBack: Boolean, + navigateUp: () -> Unit, + modifier: Modifier = Modifier +) { + TopAppBar( + title = { Text(stringResource(currentScreen.title)) }, + colors = + TopAppBarDefaults.mediumTopAppBarColors( + containerColor = MaterialTheme.colorScheme.primaryContainer + ), + modifier = modifier, + navigationIcon = { + if (canNavigateBack) { + IconButton(onClick = navigateUp) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource(R.string.back_button) + ) + } + } + } + ) +}
\ No newline at end of file diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferStreamJNI.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferStreamJNI.kt new file mode 100644 index 0000000000..ede77938de --- /dev/null +++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferStreamJNI.kt @@ -0,0 +1,27 @@ +package com.android.graphics.bufferstreamsdemoapp + +class BufferStreamJNI { + // Used to load the 'bufferstreamsdemoapp' library on application startup. + init { + System.loadLibrary("bufferstreamdemoapp") + } + + /** + * A native method that is implemented by the 'bufferstreamsdemoapp' native library, which is + * packaged with this application. + */ + external fun stringFromJNI(): String; + external fun testBufferQueueCreation(); + + companion object { + fun companion_stringFromJNI(): String { + val instance = BufferStreamJNI() + return instance.stringFromJNI() + } + + fun companion_testBufferQueueCreation() { + val instance = BufferStreamJNI() + return instance.testBufferQueueCreation() + } + } +}
\ No newline at end of file diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen1.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen1.kt new file mode 100644 index 0000000000..95e415ecd5 --- /dev/null +++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen1.kt @@ -0,0 +1,35 @@ +package com.android.graphics.bufferstreamsdemoapp + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun DemoScreen1(modifier: Modifier = Modifier) { + Column(modifier = modifier, verticalArrangement = Arrangement.SpaceBetween) { + LogOutput.getInstance().LogOutputComposable() + Row(modifier = Modifier.weight(1f, false).padding(16.dp)) { + Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { + Button( + modifier = Modifier.fillMaxWidth(), + onClick = { BufferStreamJNI.companion_testBufferQueueCreation() }) { + Text("Run") + } + + OutlinedButton( + modifier = Modifier.fillMaxWidth(), + onClick = { LogOutput.getInstance().clearText() }) { + Text("Clear") + } + } + } + } +} diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen2.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen2.kt new file mode 100644 index 0000000000..5efee92f76 --- /dev/null +++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen2.kt @@ -0,0 +1,7 @@ +package com.android.graphics.bufferstreamsdemoapp + +import androidx.compose.runtime.Composable + +@Composable +fun DemoScreen2() { +}
\ No newline at end of file diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen3.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen3.kt new file mode 100644 index 0000000000..8cba857737 --- /dev/null +++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen3.kt @@ -0,0 +1,7 @@ +package com.android.graphics.bufferstreamsdemoapp + +import androidx.compose.runtime.Composable + +@Composable +fun DemoScreen3() { +}
\ No newline at end of file diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/LogOutput.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/LogOutput.kt new file mode 100644 index 0000000000..3f0926f497 --- /dev/null +++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/LogOutput.kt @@ -0,0 +1,65 @@ +package com.android.graphics.bufferstreamsdemoapp + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Card +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import java.util.Collections + +/* +LogOutput centralizes logging: storing, displaying, adding, and clearing log messages with +thread safety. It is a singleton that's also accessed from C++. The private constructor will +not allow this class to be initialized, limiting it to getInstance(). + */ +class LogOutput private constructor() { + val logs = Collections.synchronizedList(mutableStateListOf<String>()) + + @Composable + fun LogOutputComposable() { + val rlogs = remember { logs } + + Card(modifier = Modifier.fillMaxWidth().padding(16.dp).height(400.dp)) { + Column( + modifier = + Modifier.padding(10.dp).size(380.dp).verticalScroll(rememberScrollState())) { + for (log in rlogs) { + Text(log, modifier = Modifier.padding(0.dp)) + } + } + } + } + + fun clearText() { + logs.clear() + } + + fun addLog(log: String) { + logs.add(log) + } + + companion object { + @Volatile private var instance: LogOutput? = null + + @JvmStatic + fun getInstance(): LogOutput { + if (instance == null) { + synchronized(this) { + if (instance == null) { + instance = LogOutput() + } + } + } + return instance!! + } + } +} diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.kt new file mode 100644 index 0000000000..2ccd8d75ef --- /dev/null +++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.kt @@ -0,0 +1,132 @@ +package com.android.graphics.bufferstreamsdemoapp + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.annotation.StringRes +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.widthIn +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import com.android.graphics.bufferstreamsdemoapp.ui.theme.JetpackTheme +import java.util.* + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + JetpackTheme { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background) { + BufferDemosApp() + } + } + } + } +} + +enum class BufferDemoScreen(val route: String, @StringRes val title: Int) { + Start(route = "start", title = R.string.start), + Demo1(route = "demo1", title = R.string.demo1), + Demo2(route = "demo2", title = R.string.demo2), + Demo3(route = "demo3", title = R.string.demo3); + + companion object { + fun findByRoute(route: String): BufferDemoScreen { + return values().find { it.route == route }!! + } + } +} + +@Composable +fun BufferDemosApp() { + var navController: NavHostController = rememberNavController() + // Get current back stack entry + val backStackEntry by navController.currentBackStackEntryAsState() + // Get the name of the current screen + val currentScreen = + BufferDemoScreen.findByRoute( + backStackEntry?.destination?.route ?: BufferDemoScreen.Start.route) + + Scaffold( + topBar = { + BufferDemosAppBar( + currentScreen = currentScreen, + canNavigateBack = navController.previousBackStackEntry != null, + navigateUp = { navController.navigateUp() }) + }) { + NavHost( + navController = navController, + startDestination = BufferDemoScreen.Start.route, + modifier = Modifier.padding(10.dp)) { + composable(route = BufferDemoScreen.Start.route) { + DemoList( + onButtonClicked = { navController.navigate(it) }, + ) + } + composable(route = BufferDemoScreen.Demo1.route) { + DemoScreen1(modifier = Modifier.fillMaxHeight().padding(top = 100.dp)) + } + composable(route = BufferDemoScreen.Demo2.route) { DemoScreen2() } + composable(route = BufferDemoScreen.Demo3.route) { DemoScreen3() } + } + } +} + +@Composable +fun DemoList(onButtonClicked: (String) -> Unit) { + var modifier = Modifier.fillMaxSize().padding(16.dp) + + Column(modifier = modifier, verticalArrangement = Arrangement.SpaceBetween) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp)) { + Spacer(modifier = Modifier.height(100.dp)) + Text(text = "Buffer Demos", style = MaterialTheme.typography.titleLarge) + Spacer(modifier = Modifier.height(8.dp)) + } + Row(modifier = Modifier.weight(2f, false)) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp)) { + for (item in BufferDemoScreen.values()) { + if (item.route != BufferDemoScreen.Start.route) + SelectDemoButton( + name = stringResource(item.title), + onClick = { onButtonClicked(item.route) }) + } + } + } + } +} + +@Composable +fun SelectDemoButton(name: String, onClick: () -> Unit, modifier: Modifier = Modifier) { + Button(onClick = onClick, modifier = modifier.widthIn(min = 250.dp)) { Text(name) } +} diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Color.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Color.kt new file mode 100644 index 0000000000..d85ea724de --- /dev/null +++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Color.kt @@ -0,0 +1,11 @@ +package com.android.graphics.bufferstreamsdemoapp.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260)
\ No newline at end of file diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Theme.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Theme.kt new file mode 100644 index 0000000000..fccd93a10b --- /dev/null +++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Theme.kt @@ -0,0 +1,60 @@ +package com.android.graphics.bufferstreamsdemoapp.ui.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.core.view.WindowCompat + +private val DarkColorScheme = darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80 +) + +private val LightColorScheme = lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40 +) + +@Composable +fun JetpackTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + val window = (view.context as Activity).window + window.statusBarColor = colorScheme.primary.toArgb() + WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme + } + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +}
\ No newline at end of file diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Type.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Type.kt new file mode 100644 index 0000000000..06814ead8b --- /dev/null +++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Type.kt @@ -0,0 +1,18 @@ +package com.android.graphics.bufferstreamsdemoapp.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) +)
\ No newline at end of file diff --git a/libs/bufferstreams/examples/app/jni/Android.bp b/libs/bufferstreams/examples/app/jni/Android.bp new file mode 100644 index 0000000000..67910a1c4d --- /dev/null +++ b/libs/bufferstreams/examples/app/jni/Android.bp @@ -0,0 +1,28 @@ +// Copyright (C) 2023 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. + +cc_library_shared { + name: "libbufferstreamdemoapp", + cflags: [ + "-Werror", + "-Wno-error=unused-parameter", + ], + shared_libs: [ + "libgui", + "libbase", + "libutils", + ], + header_libs: ["jni_headers"], + srcs: ["*.cpp"], +} diff --git a/libs/bufferstreams/examples/app/jni/main.cpp b/libs/bufferstreams/examples/app/jni/main.cpp new file mode 100644 index 0000000000..550ad22ae8 --- /dev/null +++ b/libs/bufferstreams/examples/app/jni/main.cpp @@ -0,0 +1,53 @@ +// Copyright (C) 2023 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. + +#include <jni.h> +#include <string> + +#include <gui/BufferQueue.h> + +void log(JNIEnv* env, std::string l) { + jclass clazz = env->FindClass("com/android/graphics/bufferstreamsdemoapp/LogOutput"); + jmethodID getInstance = env->GetStaticMethodID(clazz, "getInstance", + "()Lcom/android/graphics/bufferstreamsdemoapp/LogOutput;"); + jmethodID addLog = env->GetMethodID(clazz, "addLog", "(Ljava/lang/String;)V"); + jobject dmg = env->CallStaticObjectMethod(clazz, getInstance); + + jstring jlog = env->NewStringUTF(l.c_str()); + env->CallVoidMethod(dmg, addLog, jlog); +} + +extern "C" { + +JNIEXPORT jstring JNICALL +Java_com_android_graphics_bufferstreamsdemoapp_BufferStreamJNI_stringFromJNI(JNIEnv* env, + jobject /* this */) { + const char* hello = "Hello from C++"; + return env->NewStringUTF(hello); +} + +JNIEXPORT void JNICALL +Java_com_android_graphics_bufferstreamsdemoapp_BufferStreamJNI_testBufferQueueCreation( + JNIEnv* env, jobject /* thiz */) { + + log(env, "Calling testBufferQueueCreation."); + android::sp<android::IGraphicBufferProducer> producer; + log(env, "Created producer."); + android::sp<android::IGraphicBufferConsumer> consumer; + log(env, "Created consumer."); + android::BufferQueue::createBufferQueue(&producer, &consumer); + log(env, "Created BufferQueue successfully."); + log(env, "Done!"); +} +}
\ No newline at end of file diff --git a/libs/bufferstreams/examples/app/proguard-rules.pro b/libs/bufferstreams/examples/app/proguard-rules.pro new file mode 100644 index 0000000000..7a987fc7c4 --- /dev/null +++ b/libs/bufferstreams/examples/app/proguard-rules.pro @@ -0,0 +1,23 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +-keep,allowoptimization,allowobfuscation class com.android.graphics.bufferstreamsdemoapp.** { *; } diff --git a/libs/bufferstreams/examples/app/res/drawable/ic_launcher_background.xml b/libs/bufferstreams/examples/app/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000000..07d5da9cbf --- /dev/null +++ b/libs/bufferstreams/examples/app/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + <path + android:fillColor="#3DDC84" + android:pathData="M0,0h108v108h-108z" /> + <path + android:fillColor="#00000000" + android:pathData="M9,0L9,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,0L19,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M29,0L29,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M39,0L39,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M49,0L49,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M59,0L59,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M69,0L69,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M79,0L79,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M89,0L89,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M99,0L99,108" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,9L108,9" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,19L108,19" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,29L108,29" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,39L108,39" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,49L108,49" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,59L108,59" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,69L108,69" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,79L108,79" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,89L108,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M0,99L108,99" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,29L89,29" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,39L89,39" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,49L89,49" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,59L89,59" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,69L89,69" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M19,79L89,79" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M29,19L29,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M39,19L39,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M49,19L49,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M59,19L59,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M69,19L69,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> + <path + android:fillColor="#00000000" + android:pathData="M79,19L79,89" + android:strokeWidth="0.8" + android:strokeColor="#33FFFFFF" /> +</vector> diff --git a/libs/bufferstreams/examples/app/res/drawable/ic_launcher_foreground.xml b/libs/bufferstreams/examples/app/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000000..2b068d1146 --- /dev/null +++ b/libs/bufferstreams/examples/app/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z"> + <aapt:attr name="android:fillColor"> + <gradient + android:endX="85.84757" + android:endY="92.4963" + android:startX="42.9492" + android:startY="49.59793" + android:type="linear"> + <item + android:color="#44000000" + android:offset="0.0" /> + <item + android:color="#00000000" + android:offset="1.0" /> + </gradient> + </aapt:attr> + </path> + <path + android:fillColor="#FFFFFF" + android:fillType="nonZero" + android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z" + android:strokeWidth="1" + android:strokeColor="#00000000" /> +</vector>
\ No newline at end of file diff --git a/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher.xml b/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher.xml new file mode 100644 index 0000000000..6f3b755bf5 --- /dev/null +++ b/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background" /> + <foreground android:drawable="@drawable/ic_launcher_foreground" /> + <monochrome android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon>
\ No newline at end of file diff --git a/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher_round.xml b/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher_round.xml new file mode 100644 index 0000000000..6f3b755bf5 --- /dev/null +++ b/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher_round.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background" /> + <foreground android:drawable="@drawable/ic_launcher_foreground" /> + <monochrome android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon>
\ No newline at end of file diff --git a/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher.webp Binary files differnew file mode 100644 index 0000000000..c209e78ecd --- /dev/null +++ b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher.webp diff --git a/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 0000000000..b2dfe3d1ba --- /dev/null +++ b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher_round.webp diff --git a/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher.webp Binary files differnew file mode 100644 index 0000000000..4f0f1d64e5 --- /dev/null +++ b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher.webp diff --git a/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 0000000000..62b611da08 --- /dev/null +++ b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher_round.webp diff --git a/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher.webp Binary files differnew file mode 100644 index 0000000000..948a3070fe --- /dev/null +++ b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher.webp diff --git a/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 0000000000..1b9a6956b3 --- /dev/null +++ b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher_round.webp diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher.webp Binary files differnew file mode 100644 index 0000000000..28d4b77f9f --- /dev/null +++ b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher.webp diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 0000000000..9287f50836 --- /dev/null +++ b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher_round.webp diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher.webp Binary files differnew file mode 100644 index 0000000000..aa7d6427e6 --- /dev/null +++ b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher.webp diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 0000000000..9126ae37cb --- /dev/null +++ b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher_round.webp diff --git a/libs/bufferstreams/examples/app/res/values/colors.xml b/libs/bufferstreams/examples/app/res/values/colors.xml new file mode 100644 index 0000000000..f8c6127d32 --- /dev/null +++ b/libs/bufferstreams/examples/app/res/values/colors.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="purple_200">#FFBB86FC</color> + <color name="purple_500">#FF6200EE</color> + <color name="purple_700">#FF3700B3</color> + <color name="teal_200">#FF03DAC5</color> + <color name="teal_700">#FF018786</color> + <color name="black">#FF000000</color> + <color name="white">#FFFFFFFF</color> +</resources>
\ No newline at end of file diff --git a/libs/bufferstreams/examples/app/res/values/strings.xml b/libs/bufferstreams/examples/app/res/values/strings.xml new file mode 100644 index 0000000000..75c8ab5e1c --- /dev/null +++ b/libs/bufferstreams/examples/app/res/values/strings.xml @@ -0,0 +1,8 @@ +<resources> + <string name="app_name">Buffer Demos</string> + <string name="start">Start</string> + <string name="demo1">Demo 1</string> + <string name="demo2">Demo 2</string> + <string name="demo3">Demo 3</string> + <string name="back_button">Back</string> +</resources>
\ No newline at end of file diff --git a/libs/bufferstreams/examples/app/res/values/themes.xml b/libs/bufferstreams/examples/app/res/values/themes.xml new file mode 100644 index 0000000000..eeb308ae44 --- /dev/null +++ b/libs/bufferstreams/examples/app/res/values/themes.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <style name="Theme.Jetpack" parent="android:Theme.Material.Light.NoActionBar" /> +</resources>
\ No newline at end of file diff --git a/libs/bufferstreams/include/bufferstreams.h b/libs/bufferstreams/include/bufferstreams.h new file mode 100644 index 0000000000..5308de24c0 --- /dev/null +++ b/libs/bufferstreams/include/bufferstreams.h @@ -0,0 +1,13 @@ +/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */ + +#include <stdarg.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> + + +/** + * This function will print Hello World. + */ +bool hello(void); diff --git a/libs/bufferstreams/rust/Android.bp b/libs/bufferstreams/rust/Android.bp new file mode 100644 index 0000000000..7fcb222085 --- /dev/null +++ b/libs/bufferstreams/rust/Android.bp @@ -0,0 +1,37 @@ +// Copyright (C) 2023 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. + +rust_defaults { + name: "libbufferstreams_defaults", + srcs: ["src/lib.rs"], + rustlibs: [ + "libanyhow", + "libnativewindow_rs", + ], + edition: "2021", +} + +rust_library { + name: "libbufferstreams", + crate_name: "bufferstreams", + defaults: ["libbufferstreams_defaults"], + min_sdk_version: "30", +} + +rust_test { + name: "libbufferstreams-internal_test", + crate_name: "bufferstreams", + defaults: ["libbufferstreams_defaults"], + test_suites: ["general-tests"], +} diff --git a/libs/bufferstreams/rust/Cargo.lock b/libs/bufferstreams/rust/Cargo.lock new file mode 100644 index 0000000000..4482dba6cd --- /dev/null +++ b/libs/bufferstreams/rust/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bufferstreams" +version = "0.1.0" diff --git a/libs/bufferstreams/rust/Cargo.toml b/libs/bufferstreams/rust/Cargo.toml new file mode 100644 index 0000000000..d30c55c551 --- /dev/null +++ b/libs/bufferstreams/rust/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "bufferstreams" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/libs/bufferstreams/rust/cbindgen.toml b/libs/bufferstreams/rust/cbindgen.toml new file mode 100644 index 0000000000..eda837f360 --- /dev/null +++ b/libs/bufferstreams/rust/cbindgen.toml @@ -0,0 +1,149 @@ +# See https://github.com/eqrion/cbindgen/blob/master/docs.md#cbindgentoml +# for detailed documentation of every option here. + + + +language = "C" + + + +############## Options for Wrapping the Contents of the Header ################# + +# header = "/* Text to put at the beginning of the generated file. Probably a license. */" +# trailer = "/* Text to put at the end of the generated file */" +# include_guard = "my_bindings_h" +# pragma_once = true +autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" +include_version = false +# namespace = "my_namespace" +namespaces = [] +using_namespaces = [] +sys_includes = [] +includes = [] +no_includes = false +after_includes = "" + + + + +############################ Code Style Options ################################ + +braces = "SameLine" +line_length = 100 +tab_width = 2 +documentation = true +documentation_style = "auto" +documentation_length = "full" +line_endings = "LF" # also "CR", "CRLF", "Native" + + + + +############################# Codegen Options ################################## + +style = "both" +sort_by = "Name" # default for `fn.sort_by` and `const.sort_by` +usize_is_size_t = true + + + +[defines] +# "target_os = freebsd" = "DEFINE_FREEBSD" +# "feature = serde" = "DEFINE_SERDE" + + + +[export] +include = [] +exclude = [] +# prefix = "CAPI_" +item_types = [] +renaming_overrides_prefixing = false + + + +[export.rename] + + + +[export.body] + + +[export.mangle] + + +[fn] +rename_args = "None" +# must_use = "MUST_USE_FUNC" +# no_return = "NO_RETURN" +# prefix = "START_FUNC" +# postfix = "END_FUNC" +args = "auto" +sort_by = "Name" + + + + +[struct] +rename_fields = "None" +# must_use = "MUST_USE_STRUCT" +derive_constructor = false +derive_eq = false +derive_neq = false +derive_lt = false +derive_lte = false +derive_gt = false +derive_gte = false + + + + +[enum] +rename_variants = "None" +# must_use = "MUST_USE_ENUM" +add_sentinel = false +prefix_with_name = false +derive_helper_methods = false +derive_const_casts = false +derive_mut_casts = false +# cast_assert_name = "ASSERT" +derive_tagged_enum_destructor = false +derive_tagged_enum_copy_constructor = false +enum_class = true +private_default_tagged_enum_constructor = false + + + + +[const] +allow_static_const = true +allow_constexpr = false +sort_by = "Name" + + + + +[macro_expansion] +bitflags = false + + + + + + +############## Options for How Your Rust library Should Be Parsed ############## + +[parse] +parse_deps = false +# include = [] +exclude = [] +clean = false +extra_bindings = [] + + + +[parse.expand] +crates = [] +all_features = false +default_features = true +features = []
\ No newline at end of file diff --git a/libs/bufferstreams/rust/src/buffers/buffer.rs b/libs/bufferstreams/rust/src/buffers/buffer.rs new file mode 100644 index 0000000000..0a8516e8e3 --- /dev/null +++ b/libs/bufferstreams/rust/src/buffers/buffer.rs @@ -0,0 +1,80 @@ +// Copyright (C) 2023 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. + +//! Wrapper around the HardwareBuffer + +use nativewindow::*; + +use super::{buffer_owner::NoBufferOwner, BufferOwner}; + +/// A wrapper for a hardware buffer. +/// +/// This buffer may be associated with a buffer pool to which it will be returned to it when dropped. +pub struct Buffer { + buffer_owner: Box<dyn BufferOwner>, + hardware_buffer: HardwareBuffer, +} + +impl Buffer { + /// Create new buffer with a custom [BufferOwner]. + pub fn new(buffer_owner: Box<dyn BufferOwner>, hardware_buffer: HardwareBuffer) -> Self { + Self { buffer_owner, hardware_buffer } + } + + /// Create a new buffer with no association to any buffer pool. + pub fn new_unowned(hardware_buffer: HardwareBuffer) -> Self { + Self { buffer_owner: Box::new(NoBufferOwner), hardware_buffer } + } + + /// Get the id of the underlying buffer. + pub fn id(&self) -> u64 { + self.hardware_buffer.id() + } + + /// Get a reference to the underlying hardware buffer. + pub fn buffer(&self) -> &HardwareBuffer { + &self.hardware_buffer + } +} + +impl Drop for Buffer { + fn drop(&mut self) { + self.buffer_owner.on_return(self); + } +} + +#[cfg(test)] +mod test { + use super::*; + + use crate::StreamConfig; + + const STREAM_CONFIG: StreamConfig = StreamConfig { + width: 1, + height: 1, + layers: 1, + format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, + usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN, + stride: 0, + }; + + #[test] + fn test_get_buffer_id() { + let hardware_buffer = STREAM_CONFIG.create_hardware_buffer().unwrap(); + let buffer_id = hardware_buffer.id(); + + let buffer = Buffer::new_unowned(hardware_buffer); + assert_eq!(buffer_id, buffer.id()); + } +} diff --git a/libs/bufferstreams/rust/src/buffers/buffer_owner.rs b/libs/bufferstreams/rust/src/buffers/buffer_owner.rs new file mode 100644 index 0000000000..a4abb9d3b7 --- /dev/null +++ b/libs/bufferstreams/rust/src/buffers/buffer_owner.rs @@ -0,0 +1,28 @@ +// Copyright (C) 2023 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. + +use super::Buffer; + +/// Trait that represents an owner of a buffer that might need to handle events such as a buffer +/// being dropped. +pub trait BufferOwner { + /// Called when a buffer is dropped. + fn on_return(&self, buffer: &Buffer); +} + +pub(super) struct NoBufferOwner; + +impl BufferOwner for NoBufferOwner { + fn on_return(&self, _buffer: &Buffer) {} +} diff --git a/libs/bufferstreams/rust/src/buffers/buffer_pool.rs b/libs/bufferstreams/rust/src/buffers/buffer_pool.rs new file mode 100644 index 0000000000..05804e2e3a --- /dev/null +++ b/libs/bufferstreams/rust/src/buffers/buffer_pool.rs @@ -0,0 +1,137 @@ +// Copyright (C) 2023 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. + +//! A Buffer Pool containing and managing HardwareBuffers + +use std::{ + collections::HashMap, + sync::{Arc, Mutex, Weak}, +}; + +use nativewindow::*; + +use crate::StreamConfig; + +use super::{Buffer, BufferOwner}; + +pub(super) struct BufferPoolInner { + size: usize, + hardware_buffers: HashMap<u64, HardwareBuffer>, + available_buffers: Vec<u64>, +} + +impl BufferPoolInner { + pub(super) fn return_buffer(&mut self, buffer_id: u64) { + assert!(self.hardware_buffers.contains_key(&buffer_id)); + assert!(!self.available_buffers.contains(&buffer_id)); + + self.available_buffers.push(buffer_id); + } +} + +struct BufferPoolOwner(Weak<Mutex<BufferPoolInner>>); + +impl BufferOwner for BufferPoolOwner { + fn on_return(&self, buffer: &Buffer) { + if let Some(locked_buffer_pool) = self.0.upgrade() { + let mut buffer_pool = locked_buffer_pool.lock().unwrap(); + + buffer_pool.return_buffer(buffer.id()); + } + } +} + +/// A thread-safe collection of buffers. +/// +/// A buffer pool can be of arbitrary size. It creates and then holds references to all buffers +/// associated with it. +pub struct BufferPool(Arc<Mutex<BufferPoolInner>>); + +impl BufferPool { + /// Creates a new buffer pool of size pool_size. All buffers will be created according to + /// the stream config. + /// + /// This constructor creates all buffers at initialization. + pub fn new(pool_size: usize, stream_config: StreamConfig) -> Option<Self> { + let mut hardware_buffers = HashMap::new(); + let mut available_buffers = Vec::new(); + for _ in 0..pool_size { + if let Some(buffer) = stream_config.create_hardware_buffer() { + available_buffers.push(buffer.id()); + hardware_buffers.insert(buffer.id(), buffer); + } else { + return None; + } + } + Some(Self(Arc::new(Mutex::new(BufferPoolInner { + size: pool_size, + hardware_buffers, + available_buffers, + })))) + } + + /// Try to acquire the next available buffer in the buffer pool. + /// + /// If all buffers are in use it will return None. + pub fn next_buffer(&mut self) -> Option<Buffer> { + let mut inner = self.0.lock().unwrap(); + if let Some(buffer_id) = inner.available_buffers.pop() { + Some(Buffer::new( + Box::new(BufferPoolOwner(Arc::downgrade(&self.0))), + inner.hardware_buffers[&buffer_id].clone(), + )) + } else { + None + } + } + + /// Gets the size of the buffer pool. + pub fn size(&self) -> usize { + let inner = self.0.lock().unwrap(); + inner.size + } +} + +#[cfg(test)] +mod test { + use super::*; + + const STREAM_CONFIG: StreamConfig = StreamConfig { + width: 1, + height: 1, + layers: 1, + format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, + usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN, + stride: 0, + }; + + #[test] + fn buffer_pool_next_buffer() { + let mut buffer_pool = BufferPool::new(1, STREAM_CONFIG).unwrap(); + let next_buffer = buffer_pool.next_buffer(); + + assert!(next_buffer.is_some()); + assert!(buffer_pool.next_buffer().is_none()); + } + + #[test] + fn drop_buffer_returns_to_pool() { + let mut buffer_pool = BufferPool::new(1, STREAM_CONFIG).unwrap(); + let next_buffer = buffer_pool.next_buffer(); + + assert!(next_buffer.is_some()); + drop(next_buffer); + assert!(buffer_pool.next_buffer().is_some()); + } +} diff --git a/libs/bufferstreams/rust/src/buffers/mod.rs b/libs/bufferstreams/rust/src/buffers/mod.rs new file mode 100644 index 0000000000..83360d6c00 --- /dev/null +++ b/libs/bufferstreams/rust/src/buffers/mod.rs @@ -0,0 +1,23 @@ +// Copyright (C) 2023 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. + +//! Module containing Buffers and BufferPools + +mod buffer; +mod buffer_owner; +mod buffer_pool; + +pub use buffer::*; +pub use buffer_owner::*; +pub use buffer_pool::*; diff --git a/libs/bufferstreams/rust/src/lib.rs b/libs/bufferstreams/rust/src/lib.rs new file mode 100644 index 0000000000..be1525d41f --- /dev/null +++ b/libs/bufferstreams/rust/src/lib.rs @@ -0,0 +1,257 @@ +// Copyright (C) 2023 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. + +//! libbufferstreams: Reactive Streams for Graphics Buffers + +pub mod buffers; +pub mod publishers; +mod stream_config; +pub mod subscribers; +pub mod subscriptions; + +use buffers::Buffer; +pub use stream_config::*; + +use std::time::Instant; + +/// This function will print Hello World. +#[no_mangle] +pub extern "C" fn hello() -> bool { + println!("Hello world."); + true +} + +/// BufferPublishers provide buffers to BufferSusbscribers. Depending on the +/// particular object in question, these could be allocated locally or provided +/// over IPC. +/// +/// BufferPublishers are required to adhere to the following, based on the +/// reactive streams specification: +/// * The total number of on_next´s signalled by a Publisher to a Subscriber +/// MUST be less than or equal to the total number of elements requested by that +/// Subscriber´s Subscription at all times. +/// * A Publisher MAY signal fewer on_next than requested and terminate the +/// Subscription by calling on_complete or on_error. +/// * on_subscribe, on_next, on_error and on_complete signaled to a Subscriber +/// MUST be signaled serially. +/// * If a Publisher fails it MUST signal an on_error. +/// * If a Publisher terminates successfully (finite stream) it MUST signal an +/// on_complete. +/// * If a Publisher signals either on_error or on_complete on a Subscriber, +/// that Subscriber’s Subscription MUST be considered cancelled. +/// * Once a terminal state has been signaled (on_error, on_complete) it is +/// REQUIRED that no further signals occur. +/// * If a Subscription is cancelled its Subscriber MUST eventually stop being +/// signaled. +/// * A Publisher MAY support multiple Subscribers and decides whether each +/// Subscription is unicast or multicast. +pub trait BufferPublisher { + /// Returns the StreamConfig of buffers that publisher creates. + fn get_publisher_stream_config(&self) -> StreamConfig; + /// This function will create the subscription between the publisher and + /// the subscriber. + fn subscribe(&mut self, subscriber: impl BufferSubscriber + 'static); +} + +/// BufferSubscribers can subscribe to BufferPublishers. They can request Frames +/// via the BufferSubscription they get from the publisher, then receive Frames +/// via on_next. +/// +/// BufferSubcribers are required to adhere to the following, based on the +/// reactive streams specification: +/// * The total number of on_next´s signalled by a Publisher to a Subscriber +/// MUST be less than or equal to the total number of elements requested by that +/// Subscriber´s Subscription at all times. +/// * A Publisher MAY signal fewer on_next than requested and terminate the +/// Subscription by calling on_complete or on_error. +/// * on_subscribe, on_next, on_error and on_complete signaled to a Subscriber +/// MUST be signaled serially. +/// * If a Publisher fails it MUST signal an on_error. +/// * If a Publisher terminates successfully (finite stream) it MUST signal an +/// on_complete. +/// * If a Publisher signals either on_error or on_complete on a Subscriber, +/// that Subscriber’s Subscription MUST be considered cancelled. +/// * Once a terminal state has been signaled (on_error, on_complete) it is +/// REQUIRED that no further signals occur. +/// * If a Subscription is cancelled its Subscriber MUST eventually stop being +/// signaled. +/// * Publisher.subscribe MAY be called as many times as wanted but MUST be +/// with a different Subscriber each time. +/// * A Publisher MAY support multiple Subscribers and decides whether each +/// Subscription is unicast or multicast. +pub trait BufferSubscriber { + /// The StreamConfig of buffers that this subscriber expects. + fn get_subscriber_stream_config(&self) -> StreamConfig; + /// This function will be called at the beginning of the subscription. + fn on_subscribe(&mut self, subscription: Box<dyn BufferSubscription>); + /// This function will be called for buffer that comes in. + fn on_next(&mut self, frame: Frame); + /// This function will be called in case of an error. + fn on_error(&mut self, error: BufferError); + /// This function will be called on finite streams when done. + fn on_complete(&mut self); +} + +/// BufferSubscriptions serve as the bridge between BufferPublishers and +/// BufferSubscribers. BufferSubscribers receive a BufferSubscription when they +/// subscribe to a BufferPublisher via on_subscribe. +/// This object is to be used by the BufferSubscriber to cancel its subscription +/// or request more buffers. +/// +/// BufferSubcriptions are required to adhere to the following, based on the +/// reactive streams specification: +/// * Subscription.request and Subscription.cancel MUST only be called inside +/// of its Subscriber context. +/// * The Subscription MUST allow the Subscriber to call Subscription.request +/// synchronously from within on_next or on_subscribe. +/// * Subscription.request MUST place an upper bound on possible synchronous +/// recursion between Publisher and Subscriber. +/// * Subscription.request SHOULD respect the responsivity of its caller by +/// returning in a timely manner. +/// * Subscription.cancel MUST respect the responsivity of its caller by +/// returning in a timely manner, MUST be idempotent and MUST be thread-safe. +/// * After the Subscription is cancelled, additional +/// Subscription.request(n: u64) MUST be NOPs. +/// * After the Subscription is cancelled, additional Subscription.cancel() +/// MUST be NOPs. +/// * While the Subscription is not cancelled, Subscription.request(n: u64) +/// MUST register the given number of additional elements to be produced to the +/// respective subscriber. +/// * While the Subscription is not cancelled, Subscription.request(n: u64) +/// MUST signal on_error if the argument is <= 0. The cause message SHOULD +/// explain that non-positive request signals are illegal. +/// * While the Subscription is not cancelled, Subscription.request(n: u64) +/// MAY synchronously call on_next on this (or other) subscriber(s). +/// * While the Subscription is not cancelled, Subscription.request(n: u64) +/// MAY synchronously call on_complete or on_error on this (or other) +/// subscriber(s). +/// * While the Subscription is not cancelled, Subscription.cancel() MUST +/// request the Publisher to eventually stop signaling its Subscriber. The +/// operation is NOT REQUIRED to affect the Subscription immediately. +/// * While the Subscription is not cancelled, Subscription.cancel() MUST +/// request the Publisher to eventually drop any references to the corresponding +/// subscriber. +/// * While the Subscription is not cancelled, calling Subscription.cancel MAY +/// cause the Publisher, if stateful, to transition into the shut-down state if +/// no other Subscription exists at this point. +/// * Calling Subscription.cancel MUST return normally. +/// * Calling Subscription.request MUST return normally. +pub trait BufferSubscription { + /// request + fn request(&self, n: u64); + /// cancel + fn cancel(&self); +} + +/// Type used to describe errors produced by subscriptions. +pub type BufferError = anyhow::Error; + +/// Struct used to contain the buffer. +pub struct Frame { + /// A buffer to be used this frame. + pub buffer: Buffer, + /// The time at which the buffer was dispatched. + pub present_time: Instant, + /// A fence used for reading/writing safely. + pub fence: i32, +} + +#[cfg(test)] +mod test { + #![allow(warnings, unused)] + use super::*; + + use anyhow::anyhow; + use buffers::Buffer; + use nativewindow::{AHardwareBuffer_Format, AHardwareBuffer_UsageFlags}; + use std::borrow::BorrowMut; + use std::error::Error; + use std::ops::Add; + use std::sync::Arc; + use std::time::Duration; + + use crate::publishers::testing::*; + use crate::subscribers::{testing::*, SharedSubscriber}; + + const STREAM_CONFIG: StreamConfig = StreamConfig { + width: 1, + height: 1, + layers: 1, + format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, + usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN, + stride: 0, + }; + + fn make_frame() -> Frame { + Frame { + buffer: Buffer::new_unowned( + STREAM_CONFIG + .create_hardware_buffer() + .expect("Unable to create hardware buffer for test"), + ), + present_time: Instant::now() + Duration::from_secs(1), + fence: 0, + } + } + + #[test] + fn test_test_implementations_next() { + let subscriber = SharedSubscriber::new(TestSubscriber::new(STREAM_CONFIG)); + let mut publisher = TestPublisher::new(STREAM_CONFIG); + + publisher.subscribe(subscriber.clone()); + assert!(subscriber.map_inner(|s| s.has_subscription())); + assert!(publisher.has_subscriber()); + + publisher.send_frame(make_frame()); + let events = subscriber.map_inner_mut(|s| s.take_events()); + assert!(!matches!(events.last().unwrap(), TestingSubscriberEvent::Next(_))); + + subscriber.map_inner(|s| s.request(1)); + assert_eq!(publisher.pending_requests(), 1); + + publisher.send_frame(make_frame()); + let events = subscriber.map_inner_mut(|s| s.take_events()); + assert!(matches!(events.last().unwrap(), TestingSubscriberEvent::Next(_))); + assert_eq!(publisher.pending_requests(), 0); + } + + #[test] + fn test_test_implementations_complete() { + let subscriber = SharedSubscriber::new(TestSubscriber::new(STREAM_CONFIG)); + let mut publisher = TestPublisher::new(STREAM_CONFIG); + + publisher.subscribe(subscriber.clone()); + assert!(subscriber.map_inner(|s| s.has_subscription())); + assert!(publisher.has_subscriber()); + + publisher.send_complete(); + let events = subscriber.map_inner_mut(|s| s.take_events()); + assert!(matches!(events.last().unwrap(), TestingSubscriberEvent::Complete)); + } + + #[test] + fn test_test_implementations_error() { + let subscriber = SharedSubscriber::new(TestSubscriber::new(STREAM_CONFIG)); + let mut publisher = TestPublisher::new(STREAM_CONFIG); + + publisher.subscribe(subscriber.clone()); + assert!(subscriber.map_inner(|s| s.has_subscription())); + assert!(publisher.has_subscriber()); + + publisher.send_error(anyhow!("error")); + let events = subscriber.map_inner_mut(|s| s.take_events()); + assert!(matches!(events.last().unwrap(), TestingSubscriberEvent::Error(_))); + } +} diff --git a/libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs b/libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs new file mode 100644 index 0000000000..846105dacd --- /dev/null +++ b/libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs @@ -0,0 +1,112 @@ +// Copyright (C) 2023 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. + +//! + +use std::time::Instant; + +use crate::{ + buffers::BufferPool, subscriptions::SharedBufferSubscription, BufferPublisher, + BufferSubscriber, Frame, StreamConfig, +}; + +/// The [BufferPoolPublisher] submits buffers from a pool over to the subscriber. +pub struct BufferPoolPublisher { + stream_config: StreamConfig, + buffer_pool: BufferPool, + subscription: SharedBufferSubscription, + subscriber: Option<Box<dyn BufferSubscriber>>, +} + +impl BufferPoolPublisher { + /// The [BufferPoolPublisher] needs to initialize a [BufferPool], the [BufferPool] will create + /// all buffers at initialization using the stream_config. + pub fn new(stream_config: StreamConfig, size: usize) -> Option<Self> { + BufferPool::new(size, stream_config).map(|buffer_pool| Self { + stream_config, + buffer_pool, + subscription: SharedBufferSubscription::new(), + subscriber: None, + }) + } + + /// If the [SharedBufferSubscription] is ready for a [Frame], a buffer will be requested from + /// [BufferPool] and sent over to the [BufferSubscriber]. + pub fn send_next_frame(&mut self, present_time: Instant) -> bool { + if let Some(subscriber) = self.subscriber.as_mut() { + if self.subscription.take_request() { + if let Some(buffer) = self.buffer_pool.next_buffer() { + let frame = Frame { buffer, present_time, fence: 0 }; + + subscriber.on_next(frame); + return true; + } + } + } + false + } +} + +impl BufferPublisher for BufferPoolPublisher { + fn get_publisher_stream_config(&self) -> StreamConfig { + self.stream_config + } + + fn subscribe(&mut self, subscriber: impl BufferSubscriber + 'static) { + assert!(self.subscriber.is_none()); + + self.subscriber = Some(Box::new(subscriber)); + self.subscriber.as_mut().unwrap().on_subscribe(self.subscription.clone_for_subscriber()); + } +} + +#[cfg(test)] +mod test { + use nativewindow::{AHardwareBuffer_Format, AHardwareBuffer_UsageFlags}; + + use super::*; + + use crate::{ + subscribers::{ + testing::{TestSubscriber, TestingSubscriberEvent}, + SharedSubscriber, + }, + StreamConfig, + }; + + const STREAM_CONFIG: StreamConfig = StreamConfig { + width: 1, + height: 1, + layers: 1, + format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, + usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN, + stride: 0, + }; + + #[test] + fn test_send_next_frame() { + let subscriber = SharedSubscriber::new(TestSubscriber::new(STREAM_CONFIG)); + + let mut buffer_pool_publisher = BufferPoolPublisher::new(STREAM_CONFIG, 1).unwrap(); + buffer_pool_publisher.subscribe(subscriber.clone()); + + subscriber.map_inner(|s| s.request(1)); + + assert!(buffer_pool_publisher.send_next_frame(Instant::now())); + + let events = subscriber.map_inner_mut(|s| s.take_events()); + assert!(matches!(events.last().unwrap(), TestingSubscriberEvent::Next(_))); + assert_eq!(buffer_pool_publisher.subscription.pending_requests(), 0); + } +} diff --git a/libs/bufferstreams/rust/src/publishers/mod.rs b/libs/bufferstreams/rust/src/publishers/mod.rs new file mode 100644 index 0000000000..8ed3ba0e00 --- /dev/null +++ b/libs/bufferstreams/rust/src/publishers/mod.rs @@ -0,0 +1,20 @@ +// Copyright (C) 2023 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. + +//! This module provides [BufferSubscriber] implementations and helpers. + +mod buffer_pool_publisher; +pub mod testing; + +pub use buffer_pool_publisher::*; diff --git a/libs/bufferstreams/rust/src/publishers/testing.rs b/libs/bufferstreams/rust/src/publishers/testing.rs new file mode 100644 index 0000000000..1593b18d7f --- /dev/null +++ b/libs/bufferstreams/rust/src/publishers/testing.rs @@ -0,0 +1,103 @@ +// Copyright (C) 2023 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. + +//! Provides useful publishers for testing specifically. These should not be used in normal code. + +use crate::{subscriptions::SharedBufferSubscription, *}; + +/// A [BufferPublisher] specifically for testing. +/// +/// Provides users the ability to send events and read the state of the subscription. +pub struct TestPublisher { + config: StreamConfig, + subscriber: Option<Box<dyn BufferSubscriber>>, + subscription: SharedBufferSubscription, +} + +impl TestPublisher { + /// Create a new [TestPublisher]. + pub fn new(config: StreamConfig) -> Self { + Self { config, subscriber: None, subscription: SharedBufferSubscription::new() } + } + + /// Send a [BufferSubscriber::on_next] event to an owned [BufferSubscriber] if it has any + /// requested and returns true. Drops the frame and returns false otherwise. + /// + /// # Panics + /// + /// This will panic if there is no owned subscriber. + pub fn send_frame(&mut self, frame: Frame) -> bool { + let subscriber = + self.subscriber.as_deref_mut().expect("Tried to send_frame with no subscriber"); + + if self.subscription.take_request() { + subscriber.on_next(frame); + true + } else { + false + } + } + + /// Send a [BufferSubscriber::on_complete] event to an owned [BufferSubscriber]. + /// + /// # Panics + /// + /// This will panic if there is no owned subscriber. + pub fn send_complete(&mut self) { + let subscriber = + self.subscriber.as_deref_mut().expect("Tried to send_complete with no subscriber"); + subscriber.on_complete(); + } + + /// Send a [BufferSubscriber::on_error] event to an owned [BufferSubscriber]. + /// + /// # Panics + /// + /// This will panic if there is no owned subscriber. + pub fn send_error(&mut self, error: BufferError) { + let subscriber = + self.subscriber.as_deref_mut().expect("Tried to send_error with no subscriber"); + subscriber.on_error(error); + } + + /// Returns whether this [BufferPublisher] owns a subscriber. + pub fn has_subscriber(&self) -> bool { + self.subscriber.is_some() + } + + /// Returns the nummber of frames requested by the [BufferSubscriber]. + pub fn pending_requests(&self) -> u64 { + self.subscription.pending_requests() + } + + /// Returns whether the [BufferSubscriber] has cancelled the subscription. + pub fn is_cancelled(&self) -> bool { + self.subscription.is_cancelled() + } +} + +impl BufferPublisher for TestPublisher { + fn get_publisher_stream_config(&self) -> crate::StreamConfig { + self.config + } + + fn subscribe(&mut self, subscriber: impl BufferSubscriber + 'static) { + assert!(self.subscriber.is_none(), "TestingPublishers can only take one subscriber"); + self.subscriber = Some(Box::new(subscriber)); + + if let Some(ref mut subscriber) = self.subscriber { + subscriber.on_subscribe(self.subscription.clone_for_subscriber()); + } + } +} diff --git a/libs/bufferstreams/rust/src/stream_config.rs b/libs/bufferstreams/rust/src/stream_config.rs new file mode 100644 index 0000000000..454bdf144e --- /dev/null +++ b/libs/bufferstreams/rust/src/stream_config.rs @@ -0,0 +1,67 @@ +// Copyright (C) 2023 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. + +use nativewindow::*; + +/// The configuration of the buffers published by a [BufferPublisher] or +/// expected by a [BufferSubscriber]. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct StreamConfig { + /// Width in pixels of streaming buffers. + pub width: u32, + /// Height in pixels of streaming buffers. + pub height: u32, + /// Number of layers of streaming buffers. + pub layers: u32, + /// Format of streaming buffers. + pub format: AHardwareBuffer_Format::Type, + /// Usage of streaming buffers. + pub usage: AHardwareBuffer_UsageFlags, + /// Stride of streaming buffers. + pub stride: u32, +} + +impl StreamConfig { + /// Tries to create a new HardwareBuffer from settings in a [StreamConfig]. + pub fn create_hardware_buffer(&self) -> Option<HardwareBuffer> { + HardwareBuffer::new(self.width, self.height, self.layers, self.format, self.usage) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_create_hardware_buffer() { + let config = StreamConfig { + width: 123, + height: 456, + layers: 1, + format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, + usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN + | AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, + stride: 0, + }; + + let maybe_buffer = config.create_hardware_buffer(); + assert!(maybe_buffer.is_some()); + + let buffer = maybe_buffer.unwrap(); + assert_eq!(config.width, buffer.width()); + assert_eq!(config.height, buffer.height()); + assert_eq!(config.format, buffer.format()); + assert_eq!(config.usage, buffer.usage()); + } +} diff --git a/libs/bufferstreams/rust/src/subscribers/mod.rs b/libs/bufferstreams/rust/src/subscribers/mod.rs new file mode 100644 index 0000000000..dd038c6c32 --- /dev/null +++ b/libs/bufferstreams/rust/src/subscribers/mod.rs @@ -0,0 +1,20 @@ +// Copyright (C) 2023 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. + +//! This module provides [BufferSubscriber] implementations and helpers. + +mod shared; +pub mod testing; + +pub use shared::*; diff --git a/libs/bufferstreams/rust/src/subscribers/shared.rs b/libs/bufferstreams/rust/src/subscribers/shared.rs new file mode 100644 index 0000000000..46c58dc04a --- /dev/null +++ b/libs/bufferstreams/rust/src/subscribers/shared.rs @@ -0,0 +1,94 @@ +// Copyright (C) 2023 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. + +//! This module provides [BufferSubscriber] implementations and helpers. + +use std::sync::{Arc, Mutex}; + +use crate::*; + +/// A [BufferSubscriber] wrapper that provides shared access. +/// +/// Normally, [BufferSubscriber]s are fully owned by the publisher that they are attached to. With +/// [SharedSubscriber], a +/// +/// # Panics +/// +/// [BufferSubscriber::on_subscribe] on a [SharedSubscriber] can only be called once, otherwise it +/// will panic. This is to prevent accidental and unsupported sharing between multiple publishers to +/// reflect the usual behavior where a publisher takes full ownership of a subscriber. +pub struct SharedSubscriber<S: BufferSubscriber>(Arc<Mutex<SharedSubscriberInner<S>>>); + +struct SharedSubscriberInner<S: BufferSubscriber> { + subscriber: S, + is_subscribed: bool, +} + +impl<S: BufferSubscriber> SharedSubscriber<S> { + /// Create a new wrapper around a [BufferSubscriber]. + pub fn new(subscriber: S) -> Self { + Self(Arc::new(Mutex::new(SharedSubscriberInner { subscriber, is_subscribed: false }))) + } + + /// Provides access to an immutable reference to the wrapped [BufferSubscriber]. + pub fn map_inner<R, F: FnOnce(&S) -> R>(&self, f: F) -> R { + let inner = self.0.lock().unwrap(); + f(&inner.subscriber) + } + + /// Provides access to a mutable reference to the wrapped [BufferSubscriber]. + pub fn map_inner_mut<R, F: FnOnce(&mut S) -> R>(&self, f: F) -> R { + let mut inner = self.0.lock().unwrap(); + f(&mut inner.subscriber) + } +} + +impl<S: BufferSubscriber> Clone for SharedSubscriber<S> { + fn clone(&self) -> Self { + Self(Arc::clone(&self.0)) + } +} + +impl<S: BufferSubscriber> BufferSubscriber for SharedSubscriber<S> { + fn get_subscriber_stream_config(&self) -> StreamConfig { + let inner = self.0.lock().unwrap(); + inner.subscriber.get_subscriber_stream_config() + } + + fn on_subscribe(&mut self, subscription: Box<dyn BufferSubscription>) { + let mut inner = self.0.lock().unwrap(); + assert!( + !inner.is_subscribed, + "A SharedSubscriber can not be shared between two BufferPublishers" + ); + inner.is_subscribed = true; + + inner.subscriber.on_subscribe(subscription); + } + + fn on_next(&mut self, frame: Frame) { + let mut inner = self.0.lock().unwrap(); + inner.subscriber.on_next(frame); + } + + fn on_error(&mut self, error: BufferError) { + let mut inner = self.0.lock().unwrap(); + inner.subscriber.on_error(error); + } + + fn on_complete(&mut self) { + let mut inner = self.0.lock().unwrap(); + inner.subscriber.on_complete(); + } +} diff --git a/libs/bufferstreams/rust/src/subscribers/testing.rs b/libs/bufferstreams/rust/src/subscribers/testing.rs new file mode 100644 index 0000000000..b7e970579e --- /dev/null +++ b/libs/bufferstreams/rust/src/subscribers/testing.rs @@ -0,0 +1,106 @@ +// Copyright (C) 2023 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. + +//! Provides useful subscribers for testing specifically. These should not be used in normal code. + +use crate::*; + +/// Represents a callback called by a [BufferPublisher] on a [BufferSubscriber]. +pub enum TestingSubscriberEvent { + /// Represents a call to [BufferSubscriber::on_subscribe]. + Subscribe, + /// Represents a call to [BufferSubscriber::on_next]. + Next(Frame), + /// Represents a call to [BufferSubscriber::on_error]. + Error(BufferError), + /// Represents a call to [BufferSubscriber::on_complete]. + Complete, +} + +/// A [BufferSubscriber] specifically for testing. Logs events as they happen which can be retrieved +/// by the test to ensure appropriate behavior. +pub struct TestSubscriber { + config: StreamConfig, + subscription: Option<Box<dyn BufferSubscription>>, + events: Vec<TestingSubscriberEvent>, +} + +impl TestSubscriber { + /// Create a new [TestSubscriber]. + pub fn new(config: StreamConfig) -> Self { + Self { config, subscription: None, events: Vec::new() } + } + + /// Returns true if this [BufferSubscriber] has an active subscription. + pub fn has_subscription(&self) -> bool { + self.subscription.is_some() + } + + /// Make a request on behalf of this test subscriber. + /// + /// This will panic if there is no owned subscription. + pub fn request(&self, n: u64) { + let subscription = self + .subscription + .as_deref() + .expect("Tried to request on a TestSubscriber with no subscription"); + subscription.request(n); + } + + /// Cancel on behalf of this test subscriber. + /// + /// # Panics + /// + /// This will panic if there is no owned subscription. + pub fn cancel(&self) { + let subscription = self + .subscription + .as_deref() + .expect("Tried to cancel a TestSubscriber with no subscription"); + subscription.cancel(); + } + + /// Gets all of the events that have happened to this [BufferSubscriber] since the last call + /// to this function or it was created. + pub fn take_events(&mut self) -> Vec<TestingSubscriberEvent> { + let mut out = Vec::new(); + out.append(&mut self.events); + out + } +} + +impl BufferSubscriber for TestSubscriber { + fn get_subscriber_stream_config(&self) -> StreamConfig { + self.config + } + + fn on_subscribe(&mut self, subscription: Box<dyn BufferSubscription>) { + assert!(self.subscription.is_none(), "TestSubscriber must only be subscribed to once"); + self.subscription = Some(subscription); + + self.events.push(TestingSubscriberEvent::Subscribe); + } + + fn on_next(&mut self, frame: Frame) { + self.events.push(TestingSubscriberEvent::Next(frame)); + } + + fn on_error(&mut self, error: BufferError) { + self.events.push(TestingSubscriberEvent::Error(error)); + } + + fn on_complete(&mut self) { + self.events.push(TestingSubscriberEvent::Complete); + } +} diff --git a/libs/bufferstreams/rust/src/subscriptions/mod.rs b/libs/bufferstreams/rust/src/subscriptions/mod.rs new file mode 100644 index 0000000000..e046dbbda3 --- /dev/null +++ b/libs/bufferstreams/rust/src/subscriptions/mod.rs @@ -0,0 +1,19 @@ +// Copyright (C) 2023 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. + +//! This module provides [BufferSubscription] implementations and helpers. + +mod shared_buffer_subscription; + +pub use shared_buffer_subscription::*; diff --git a/libs/bufferstreams/rust/src/subscriptions/shared_buffer_subscription.rs b/libs/bufferstreams/rust/src/subscriptions/shared_buffer_subscription.rs new file mode 100644 index 0000000000..90275c7320 --- /dev/null +++ b/libs/bufferstreams/rust/src/subscriptions/shared_buffer_subscription.rs @@ -0,0 +1,84 @@ +// Copyright (C) 2023 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. + +use std::sync::{Arc, Mutex}; + +use crate::*; + +/// A simple sharable helper that can be used as a [BufferSubscription] by a [BufferSubscriber] and +/// as a state tracker by a [BufferPublisher]. +#[derive(Clone, Debug)] +pub struct SharedBufferSubscription(Arc<Mutex<BufferSubscriptionData>>); + +#[derive(Debug, Default)] +struct BufferSubscriptionData { + requests: u64, + is_cancelled: bool, +} + +impl SharedBufferSubscription { + /// Create a new [SharedBufferSubscription]. + pub fn new() -> Self { + SharedBufferSubscription::default() + } + + /// Clone this [SharedBufferSubscription] so it can be passed into + /// [BufferSubscriber::on_subscribe]. + pub fn clone_for_subscriber(&self) -> Box<dyn BufferSubscription> { + Box::new(self.clone()) as Box<dyn BufferSubscription> + } + + /// If possible (not cancelled and with requests pending), take + pub fn take_request(&self) -> bool { + let mut data = self.0.lock().unwrap(); + + if data.is_cancelled || data.requests == 0 { + false + } else { + data.requests -= 1; + true + } + } + + /// Get the number of pending requests made by the [BufferSubscriber] via + /// [BufferSubscription::request]. + pub fn pending_requests(&self) -> u64 { + self.0.lock().unwrap().requests + } + + /// Get get whether the [BufferSubscriber] has called [BufferSubscription::cancel]. + pub fn is_cancelled(&self) -> bool { + self.0.lock().unwrap().is_cancelled + } +} + +impl Default for SharedBufferSubscription { + fn default() -> Self { + Self(Arc::new(Mutex::new(BufferSubscriptionData::default()))) + } +} + +impl BufferSubscription for SharedBufferSubscription { + fn request(&self, n: u64) { + let mut data = self.0.lock().unwrap(); + if !data.is_cancelled { + data.requests = data.requests.saturating_add(n); + } + } + + fn cancel(&self) { + let mut data = self.0.lock().unwrap(); + data.is_cancelled = true; + } +} diff --git a/libs/bufferstreams/update_include.sh b/libs/bufferstreams/update_include.sh new file mode 100755 index 0000000000..e986e9fb08 --- /dev/null +++ b/libs/bufferstreams/update_include.sh @@ -0,0 +1,2 @@ +cd rust +cbindgen --config cbindgen.toml --crate bufferstreams --output ../include/bufferstreams.h |