renaming to Thermion

This commit is contained in:
Nick Fisher
2024-06-15 15:05:34 +08:00
parent 1a5f573bc0
commit fe62a70e29
719 changed files with 7291 additions and 3946 deletions

View File

@@ -0,0 +1,8 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures

View File

@@ -0,0 +1,17 @@
cmake_minimum_required(VERSION 3.10)
include_directories(../../../thermion_dart/native/include)
include_directories(src/main/cpp)
# link_directories(src/main/jniLibs/${ANDROID_ABI})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17")
set(ANDROID_STL c++_shared)
add_library(thermion_flutter_android SHARED
"${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp/ThermionFlutterAndroid.cpp"
)
target_link_libraries(
thermion_flutter_android
-landroid
-llog
)

View File

@@ -0,0 +1,63 @@
group 'app.polyvox.filament'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.7.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
rootProject.allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 33
ndkVersion '25.2.9519653'
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
minSdkVersion 22
ndk {
abiFilters 'arm64-v8a' // 'x86_64' 'armeabi-v7a'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
version "3.18.1"
}
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'net.java.dev.jna:jna:5.10.0@aar'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
}

View File

@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip

View File

@@ -0,0 +1 @@
rootProject.name = 'thermion_flutter'

View File

@@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="app.polyvox.filament">
</manifest>

View File

@@ -0,0 +1,31 @@
#include <android/native_window_jni.h>
#include <android/native_activity.h>
#include "ThermionDartFFIApi.h"
extern "C" {
void* get_native_window_from_surface(
jobject surface,
JNIEnv* env
) {
void* window = ANativeWindow_fromSurface(env, surface);
return window;
}
ResourceLoaderWrapper* make_resource_loader_wrapper_android(LoadFilamentResourceFromOwner loadFn, FreeFilamentResourceFromOwner freeFn, void* owner) {
ResourceLoaderWrapper *rlw = (ResourceLoaderWrapper *)malloc(sizeof(ResourceLoaderWrapper));
rlw->loadToOut = nullptr;
rlw->freeResource = nullptr;
rlw->loadResource = nullptr;
rlw->loadFromOwner = loadFn;
rlw->freeFromOwner = freeFn;
rlw->owner = owner;
return rlw;
}
// this does nothing, but we need it for JNA to return the correct pointer
FilamentRenderCallback make_render_callback_fn_pointer(FilamentRenderCallback callback) {
return callback;
}
}

View File

@@ -0,0 +1,39 @@
package app.polyvox.filament
import com.sun.jna.ptr.PointerByReference
import com.sun.jna.ptr.IntByReference
import android.view.Surface
import android.content.res.AssetManager
import com.sun.jna.*
open class ResourceBuffer: Structure(), Structure.ByValue {
@JvmField var data: Pointer = Pointer(0);
@JvmField var size: Int = 0;
@JvmField var id: Int = 0;
override fun getFieldOrder(): List<String> {
return listOf("data", "size", "id")
}
}
interface LoadFilamentResourceFromOwner : Callback {
fun loadResourceFromOwner(resourceName: String?, owner: Pointer?): ResourceBuffer
}
interface FreeFilamentResourceFromOwner : Callback {
fun freeResourceFromOwner(rb: ResourceBuffer, owner: Pointer?)
}
interface RenderCallback : Callback {
fun renderCallback(owner:Pointer?)
}
interface FilamentInterop : Library {
fun get_native_window_from_surface(surface:Object, env:JNIEnv) : Pointer?;
fun make_render_callback_fn_pointer(renderCallback:RenderCallback) : Pointer
fun make_resource_loader_wrapper_android(loadResourceFromOwner: LoadFilamentResourceFromOwner, freeResource: FreeFilamentResourceFromOwner, owner:Pointer?) : Pointer;
}

View File

@@ -0,0 +1,28 @@
import java.io.*
import java.nio.file.FileSystems
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.absolutePathString
import kotlin.io.path.listDirectoryEntries
import android.util.Log
class HotReloadPathHelper {
companion object {
fun getAssetPath(path: String, packageName: String): String? {
val packagePath = "/data/user/0/${packageName}/code_cache/"
Log.v("thermion_flutter", "Looking for hot reloaded asset ${path} under package path ${packagePath}")
val files = File(packagePath).walkBottomUp().filter {
it.path.endsWith(path)
}.sortedBy {
it.lastModified()
}.toList()
if(files.size > 0) {
Log.v("thermion_flutter", "Using hot reloaded asset at ${files.last().path}")
return files.last().path;
}
Log.v("thermion_flutter", "No hot reloaded asset found.")
return null;
}
}
}

View File

@@ -0,0 +1,229 @@
package app.polyvox.filament
import HotReloadPathHelper
import android.app.Activity
import android.content.res.AssetManager
import android.graphics.*
import android.os.Build
import android.util.Log
import android.view.Choreographer
import android.view.Surface
import androidx.annotation.NonNull
import androidx.annotation.RequiresApi
import androidx.lifecycle.Lifecycle
import com.sun.jna.*
import io.flutter.FlutterInjector
import io.flutter.embedding.engine.FlutterJNI
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.embedding.engine.plugins.lifecycle.HiddenLifecycleReference
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.view.TextureRegistry.SurfaceTextureEntry
import java.io.File
import java.util.*
class LoadFilamentResourceFromOwnerImpl(plugin:ThermionFlutterPlugin) : LoadFilamentResourceFromOwner {
var plugin = plugin
override fun loadResourceFromOwner(path: String?, owner: Pointer?): ResourceBuffer {
return plugin.loadResourceFromOwner(path, owner)
}
}
class FreeFilamentResourceFromOwnerImpl(plugin:ThermionFlutterPlugin) : FreeFilamentResourceFromOwner {
var plugin = plugin
override fun freeResourceFromOwner(rb: ResourceBuffer, owner: Pointer?) {
plugin.freeResourceFromOwner(rb, owner)
}
}
class RenderCallbackImpl(plugin:ThermionFlutterPlugin) : RenderCallback {
var plugin = plugin
override fun renderCallback(owner:Pointer?) {
plugin.renderCallback();
if(!plugin._surface!!.isValid) {
Log.e("thermion_flutter", "Error: surface is no longer valid")
}
}
}
/** ThermionFlutterPlugin */
class ThermionFlutterPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, LoadFilamentResourceFromOwner, FreeFilamentResourceFromOwner {
companion object {
const val CHANNEL_NAME = "app.polyvox.filament/event"
const val TAG = "FilamentPlugin"
}
private lateinit var channel : MethodChannel
private lateinit var flutterPluginBinding : FlutterPlugin.FlutterPluginBinding
private var lifecycle: Lifecycle? = null
private lateinit var _lib : FilamentInterop
var _surfaceTexture: SurfaceTexture? = null
private var _surfaceTextureEntry: SurfaceTextureEntry? = null
var _surface: Surface? = null
private lateinit var activity:Activity
private var loadResourceWrapper:LoadFilamentResourceFromOwnerImpl = LoadFilamentResourceFromOwnerImpl(this)
private var freeResourceWrapper:FreeFilamentResourceFromOwnerImpl = FreeFilamentResourceFromOwnerImpl(this)
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
this.flutterPluginBinding = flutterPluginBinding
channel = MethodChannel(flutterPluginBinding.binaryMessenger, CHANNEL_NAME)
channel.setMethodCallHandler(this)
_lib = Native.loadLibrary("thermion_flutter_android", FilamentInterop::class.java, Collections.singletonMap(Library.OPTION_ALLOW_OBJECTS, true))
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
lifecycle = (binding.lifecycle as? HiddenLifecycleReference)?.lifecycle
activity = binding.activity
activity.window.setFormat(PixelFormat.RGBA_8888)
}
val _resources:MutableMap<ResourceBuffer,Memory> = mutableMapOf();
var _lastId = 1
override fun loadResourceFromOwner(path: String?, owner: Pointer?): ResourceBuffer {
Log.i("thermion_flutter", "Loading resource from path $path")
var data:ByteArray? = null
if(path!!.startsWith("file://")) {
data = File(path!!.substring(6)).readBytes()
} else {
var assetPath = path
if(assetPath.startsWith("asset://")) {
assetPath = assetPath!!.substring(8)
}
val loader = FlutterInjector.instance().flutterLoader()
val key = loader.getLookupKeyForAsset(assetPath)
val hotReloadPath = HotReloadPathHelper.getAssetPath(key, activity.getPackageName())
if (hotReloadPath != null) {
data = File(hotReloadPath).readBytes()
} else {
Log.i("thermion_flutter", "Loading resource from main asset bundle at ${assetPath}")
val assetManager: AssetManager = activity.assets
try {
data = assetManager.open(key).readBytes()
Log.i("thermion_flutter", "Loaded ${data.size} bytes")
} catch (e:Exception) {
Log.e("thermion_flutter", "Failed to open asset at ${assetPath}", null)
}
}
}
val rb = ResourceBuffer();
try {
if (data != null) {
val dataPtr = Memory(data.size.toLong())
dataPtr.write(0, data, 0, data.size)
rb.data = dataPtr
rb.size = data.size
rb.id = _lastId
_resources[rb] = dataPtr;
_lastId++
} else {
rb.id = 0
rb.size = 0
rb.data = Pointer(0)
}
} catch(e:Exception) {
Log.e("thermion_flutter", "Error setting resource buffer : $e", null);
}
rb.write();
return rb;
}
override fun freeResourceFromOwner(rb: ResourceBuffer, owner: Pointer?) {
_resources.remove(rb)
}
fun renderCallback() {
// noop, log or check surface.valid() is you want
}
@RequiresApi(Build.VERSION_CODES.M)
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
Log.e("thermion_flutter", call.method, null)
when (call.method) {
"createTexture" -> {
if(_surfaceTextureEntry != null) {
result.error("TEXTURE_EXISTS", "Texture already exist. Make sure you call destroyTexture first", null)
return
}
val args = call.arguments as List<*>
val width = args[0] as Int
val height = args[1] as Int
if(width <1 || height < 1) {
result.error("DIMENSION_MISMATCH","Both dimensions must be greater than zero (you provided $width x $height)", null);
return;
}
Log.i("thermion_flutter", "Creating SurfaceTexture ${width}x${height}");
_surfaceTextureEntry = flutterPluginBinding.textureRegistry.createSurfaceTexture()
_surfaceTexture = _surfaceTextureEntry!!.surfaceTexture();
_surfaceTexture!!.setDefaultBufferSize(width, height)
_surface = Surface(_surfaceTexture)
if(!_surface!!.isValid) {
result.error("SURFACE_INVALID", "Failed to create valid surface", null)
} else {
val nativeWindow = _lib.get_native_window_from_surface(_surface!! as Object, JNIEnv.CURRENT)
result.success(listOf(_surfaceTextureEntry!!.id(), null, Pointer.nativeValue(nativeWindow)))
}
}
"getResourceLoaderWrapper" -> {
val resourceLoader = _lib.make_resource_loader_wrapper_android(loadResourceWrapper, freeResourceWrapper, Pointer(0))
result.success(Pointer.nativeValue(resourceLoader))
}
"getDriverPlatform" -> {
result.success(null)
}
"getSharedContext" -> {
result.success(null)
}
"getRenderCallback" -> {
val renderCallbackFnPointer = _lib.make_render_callback_fn_pointer(RenderCallbackImpl(this))
result.success(listOf(Pointer.nativeValue(renderCallbackFnPointer), 0))
}
"destroyTexture" -> {
_surface!!.release();
_surfaceTextureEntry!!.release();
_surface = null
_surfaceTextureEntry = null
result.success(true)
}
else -> {
result.notImplemented()
}
}
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
onAttachedToActivity(binding)
}
override fun onDetachedFromActivityForConfigChanges() {
onDetachedFromActivity()
}
override fun onDetachedFromActivity() {
lifecycle = null
}
}