-
-
Save chaosgoo/dd26cbe819852957e3c9d4bd8e7f0900 to your computer and use it in GitHub Desktop.
3mf renderer in flutter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.chaosgoo.metasequoia.egl | |
| import android.opengl.EGL14 | |
| import android.opengl.EGLConfig | |
| import android.util.Log | |
| import android.view.Surface | |
| /** | |
| * Kotlin层的EGL核心类,负责管理EGL上下文、EGL显示和EGL表面等资源。 | |
| * EGL = Embedded Graphic Library | |
| */ | |
| class EglCore(surface: Surface) { | |
| private val TAG = this::class.java.simpleName | |
| private var eglDisplay = EGL14.EGL_NO_DISPLAY | |
| private var eglContext = EGL14.EGL_NO_CONTEXT | |
| private var eglSurface = EGL14.EGL_NO_SURFACE | |
| init { | |
| eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY) | |
| val version = IntArray(2) | |
| EGL14.eglInitialize(eglDisplay, version, 0, version, 1) | |
| Log.i(TAG, "EGL version: ${version[0]}.${version[1]}") | |
| val configAttribs = intArrayOf( | |
| EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, | |
| EGL14.EGL_RED_SIZE, 8, | |
| EGL14.EGL_GREEN_SIZE, 8, | |
| EGL14.EGL_BLUE_SIZE, 8, | |
| EGL14.EGL_DEPTH_SIZE, 16, | |
| EGL14.EGL_NONE | |
| ) | |
| val configs = arrayOfNulls<EGLConfig>(1) | |
| val numConfigs = IntArray(1) | |
| EGL14.eglChooseConfig(eglDisplay, configAttribs, 0, configs, 0, 1, numConfigs, 0) | |
| eglContext = EGL14.eglCreateContext( | |
| eglDisplay, | |
| configs[0], | |
| EGL14.EGL_NO_CONTEXT, | |
| intArrayOf(EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, EGL14.EGL_NONE), | |
| 0 | |
| ) | |
| val surfaceAttribs = intArrayOf(EGL14.EGL_NONE) | |
| eglSurface = | |
| EGL14.eglCreateWindowSurface(eglDisplay, configs[0], surface, surfaceAttribs, 0) | |
| } | |
| fun makeCurrent() = EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext) | |
| fun swapBuffer() = EGL14.eglSwapBuffers(eglDisplay, eglSurface) | |
| fun release() { | |
| EGL14.eglDestroyContext(eglDisplay, eglContext) | |
| EGL14.eglDestroySurface(eglDisplay, eglSurface) | |
| eglDisplay = EGL14.EGL_NO_DISPLAY | |
| eglContext = EGL14.EGL_NO_CONTEXT | |
| eglSurface = EGL14.EGL_NO_SURFACE | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.chaosgoo.metasequoia.texture | |
| import android.opengl.GLES30 | |
| import android.os.Handler | |
| import android.os.HandlerThread | |
| import android.os.Message | |
| import android.util.Log | |
| import android.view.Choreographer | |
| import android.view.Surface | |
| import com.chaosgoo.metasequoia.egl.EglCore | |
| import com.chaosgoo.metasequoia.renderer.NativeDrawerRenderer | |
| import io.flutter.view.TextureRegistry | |
| class GLTextureHandler2( | |
| private val registry: TextureRegistry, | |
| private val shaderParams: Map<String, String> | |
| ) { | |
| private val TAG = this::class.java.simpleName | |
| private val entry = registry.createSurfaceTexture() | |
| private val surfaceTexture = entry.surfaceTexture() | |
| private var surface: Surface? = null | |
| private var eglCore: EglCore? = null | |
| private var render: NativeDrawerRenderer? = null | |
| private var isRendering = false | |
| private val choreographer = Choreographer.getInstance() | |
| private val renderThread = HandlerThread("RenderThread").apply { start() } | |
| var renderMode = GLES30.GL_TRIANGLES | |
| var rotateX = 0.0 | |
| var rotateY = 0.0 | |
| val INIT = 0 | |
| val DRAW = 1 | |
| val STOP = 2 | |
| val UPDATE_SIZE = 3 | |
| val LOAD_MODEL = 4 | |
| val ROTATE = 5 | |
| val cb = Handler.Callback { msg -> | |
| when (msg.what) { | |
| INIT -> { | |
| Log.i(TAG, "INIT CMD") | |
| Surface(surfaceTexture).apply { | |
| surface = this | |
| eglCore = EglCore(this).apply { | |
| makeCurrent() | |
| } | |
| render = NativeDrawerRenderer() | |
| render?.onSurfaceCreated(null, null) | |
| val (width, height) = msg.obj as? Pair<Int, Int> ?: (0 to 0) | |
| render?.onSurfaceChanged(null, width, height) | |
| } | |
| start() | |
| } | |
| DRAW -> { | |
| render?.onDrawFrame(null) | |
| eglCore?.swapBuffer() | |
| } | |
| STOP -> { | |
| } | |
| UPDATE_SIZE -> { | |
| Log.i(TAG, "UPDATE_SIZE CMD") | |
| val (width, height) = msg.obj as? Pair<Int, Int> ?: (0 to 0) | |
| surfaceTexture.setDefaultBufferSize(width, height) | |
| render?.onSurfaceChanged(null, width, height) | |
| } | |
| LOAD_MODEL -> { | |
| Log.i(TAG, "LOAD_MODEL CMD") | |
| val modelPath = msg.obj as? String ?: "" | |
| if (render !is NativeDrawerRenderer) return@Callback true | |
| (render as? NativeDrawerRenderer)?.loadModel(modelPath) | |
| } | |
| ROTATE -> { | |
| render?.rotate(rotateX.toFloat(), rotateY.toFloat()) | |
| } | |
| } | |
| return@Callback true | |
| } | |
| private val renderThreadHandler = Handler(renderThread.looper, cb) | |
| private val frameCallback = object : Choreographer.FrameCallback { | |
| override fun doFrame(frameTimeNanos: Long) { | |
| if (!isRendering) { | |
| Log.d(TAG, "return") | |
| return | |
| } | |
| renderThreadHandler.sendEmptyMessage(DRAW) | |
| choreographer.postFrameCallback(this) | |
| } | |
| } | |
| fun getTextureId() = entry.id() | |
| fun setup(width: Int, height: Int) { | |
| Log.d(TAG, "setup") | |
| surfaceTexture.setDefaultBufferSize(width, height) | |
| renderThreadHandler.sendMessage( | |
| Message().apply { | |
| this.what = INIT | |
| this.obj = width to height | |
| } | |
| ) | |
| } | |
| fun updateTextureSize(width: Int, height: Int) { | |
| Log.d(TAG, "updateTextureSize") | |
| surfaceTexture.setDefaultBufferSize(width, height) | |
| renderThreadHandler.sendMessage( | |
| Message().apply { | |
| this.what = UPDATE_SIZE | |
| this.obj = width to height | |
| } | |
| ) | |
| } | |
| fun start() { | |
| Log.d(TAG, "start") | |
| if (isRendering) { | |
| Log.d(TAG, "return") | |
| return | |
| } | |
| isRendering = true | |
| choreographer.postFrameCallback(frameCallback) | |
| } | |
| fun stop() { | |
| isRendering = false | |
| choreographer.removeFrameCallback(frameCallback) | |
| } | |
| fun release() { | |
| stop() | |
| entry.release() | |
| surface?.release() | |
| eglCore?.release() | |
| } | |
| fun loadModel(modelPath: String) { | |
| renderThreadHandler.sendMessage( | |
| Message().apply { | |
| this.what = LOAD_MODEL | |
| this.obj = modelPath | |
| } | |
| ) | |
| } | |
| fun rotate(x: Float, y: Float) { | |
| rotateX = x.toDouble() | |
| rotateY = y.toDouble() | |
| renderThreadHandler.sendMessage( | |
| Message().apply { | |
| this.what = ROTATE | |
| } | |
| ) | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.chaosgoo.metasequoia.texture | |
| import android.util.Log | |
| import io.flutter.embedding.engine.plugins.FlutterPlugin | |
| import io.flutter.plugin.common.BinaryMessenger | |
| import io.flutter.plugin.common.MethodChannel | |
| import io.flutter.view.TextureRegistry | |
| class GLTexturePlugin2 : FlutterPlugin { | |
| private val TAG = this::class.java.simpleName | |
| private lateinit var channel: MethodChannel | |
| private lateinit var textureRegistry: TextureRegistry | |
| private lateinit var messenger: BinaryMessenger | |
| private val textureHandlers = mutableMapOf<Long, GLTextureHandler2>() | |
| override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { | |
| textureRegistry = binding.textureRegistry | |
| messenger = binding.binaryMessenger | |
| channel = MethodChannel(messenger, "com.chaosgoo.metasequoia/gl_texture2") | |
| channel.setMethodCallHandler { call, result -> | |
| when (call.method) { | |
| "createGLTexture" -> { | |
| val handler = GLTextureHandler2( | |
| textureRegistry, | |
| emptyMap() | |
| ) | |
| val width = call.argument<Int>("width") ?: 640 | |
| val height = call.argument<Int>("height") ?: 640 | |
| val textureId = handler.getTextureId() | |
| textureHandlers[textureId] = handler | |
| handler.setup(width, height) | |
| result.success(textureId) | |
| } | |
| "changeRenderMode" -> { | |
| // val textureId = call.argument<Int>("textureId") ?: return@setMethodCallHandler | |
| // val renderMode = call.argument<String>("renderMode") ?: "GL_TRIANGLES" | |
| // Log.i(TAG, "changeRenderMode to $renderMode") | |
| // textureHandlers[textureId.toLong()]?.run { | |
| // this.renderMode = when (renderMode) { | |
| // "GL_TRIANGLES" -> { | |
| // GLES30.GL_TRIANGLES | |
| // } | |
| // "GL_LINES" -> { | |
| // GLES30.GL_LINES | |
| // } | |
| // "GL_POINTS" -> { | |
| // GLES30.GL_POINTS | |
| // } | |
| // else -> { | |
| // GLES30.GL_TRIANGLES | |
| // } | |
| // } | |
| // } | |
| } | |
| "loadModel"->{ | |
| val textureId = call.argument<Int>("textureId") ?: return@setMethodCallHandler | |
| val modelPath = call.argument<String>("modelPath") ?: return@setMethodCallHandler | |
| Log.i(TAG, "loadModel to $modelPath") | |
| textureHandlers[textureId.toLong()]?.loadModel(modelPath) | |
| } | |
| "updateTextureSize"->{ | |
| val textureId = call.argument<Int>("textureId") ?: return@setMethodCallHandler | |
| val width = call.argument<Int>("width") ?: 640 | |
| val height = call.argument<Int>("height") ?: 640 | |
| Log.i(TAG, "updateTextureSize to $width x $height") | |
| textureHandlers[textureId.toLong()]?.updateTextureSize(width, height) | |
| } | |
| "dispose" -> { | |
| val id = call.argument<Long>("id") ?: return@setMethodCallHandler | |
| textureHandlers[id]?.release() | |
| textureHandlers.remove(id) | |
| result.success(null) | |
| } | |
| "rotate"->{ | |
| val textureId = call.argument<Int>("textureId") ?: return@setMethodCallHandler | |
| textureHandlers[textureId.toLong()]?.run { | |
| val rotateX = call.argument<Double>("angleX") ?: 0.0 | |
| val rotateY = call.argument<Double>("angleY") ?: 0.0 | |
| rotate(rotateX.toFloat(), rotateY.toFloat()) | |
| } | |
| } | |
| else -> | |
| result.notImplemented() | |
| } | |
| } | |
| } | |
| override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { | |
| channel.setMethodCallHandler(null) | |
| textureHandlers.values.forEach { handler -> handler.release() } | |
| textureHandlers.clear() | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.chaosgoo.metasequoia | |
| import android.content.Context | |
| import android.opengl.GLSurfaceView | |
| import android.view.SurfaceHolder | |
| import android.view.View | |
| class LifecycleAwareGLSurfaceView(context: Context) : GLSurfaceView(context) { | |
| override fun onWindowVisibilityChanged(visibility: Int) { | |
| super.onWindowVisibilityChanged(visibility) | |
| if (visibility == View.VISIBLE) { | |
| onResume() | |
| } else { | |
| onPause() | |
| } | |
| } | |
| override fun surfaceDestroyed(holder: SurfaceHolder) { | |
| onPause() | |
| super.surfaceDestroyed(holder) | |
| } | |
| override fun surfaceCreated(holder: SurfaceHolder) { | |
| super.surfaceCreated(holder) | |
| onResume() | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.chaosgoo.metasequoia | |
| import com.chaosgoo.metasequoia.base.NativeAndroidViewPlugin | |
| import com.chaosgoo.metasequoia.glsurfaceview.NativeGLSurfaceViewPlugin | |
| import com.chaosgoo.metasequoia.texture.GLTexturePlugin | |
| import com.chaosgoo.metasequoia.texture.GLTexturePlugin2 | |
| import com.example.native_lib3mf.NativeAssetManager | |
| import io.flutter.embedding.android.FlutterActivity | |
| import io.flutter.embedding.engine.FlutterEngine | |
| class MainActivity : FlutterActivity() { | |
| override fun configureFlutterEngine(flutterEngine: FlutterEngine) { | |
| NativeAssetManager.initAssetManager(context.assets) | |
| super.configureFlutterEngine(flutterEngine) | |
| flutterEngine.plugins.add(GLTexturePlugin()) | |
| flutterEngine.plugins.add(GLTexturePlugin2()) | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'dart:io'; | |
| import 'package:file_picker/file_picker.dart'; | |
| import 'package:flutter/material.dart'; | |
| import 'package:flutter/services.dart'; | |
| class ModelFilePickerRoute extends StatefulWidget { | |
| const ModelFilePickerRoute({super.key}); | |
| @override | |
| State<ModelFilePickerRoute> createState() => _ModelFilePickerRouteState(); | |
| } | |
| class _ModelFilePickerRouteState extends State<ModelFilePickerRoute> { | |
| int? _textureId; | |
| String? modelPath; | |
| double _angleX = 0; | |
| double _angleY = 0; | |
| @override | |
| void initState() { | |
| super.initState(); | |
| _setupNativeTexture(); | |
| } | |
| Future<void> _setupNativeTexture() async { | |
| final id = await MethodChannel( | |
| "com.chaosgoo.metasequoia/gl_texture2", | |
| ).invokeMethod('createGLTexture', {'width': 512, 'height': 512}); | |
| setState(() { | |
| _textureId = id; | |
| }); | |
| } | |
| _updateNativeTexture(int width, int height) { | |
| if (_textureId != null) { | |
| MethodChannel("com.chaosgoo.metasequoia/gl_texture2").invokeMethod( | |
| 'updateTextureSize', | |
| {'textureId': _textureId, 'width': width, 'height': height}, | |
| ); | |
| } | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return Scaffold( | |
| appBar: AppBar(title: const Text('选择模型文件')), | |
| body: Column( | |
| mainAxisAlignment: MainAxisAlignment.center, | |
| crossAxisAlignment: CrossAxisAlignment.center, | |
| children: [ | |
| Center( | |
| child: FilledButton( | |
| onPressed: () async { | |
| FilePickerResult? result = await FilePicker.platform | |
| .pickFiles(); | |
| if (result != null) { | |
| File file = File(result.files.single.path!); | |
| String filePath = file.path; | |
| print("Selected file: $filePath"); | |
| if (_textureId != null) { | |
| MethodChannel( | |
| "com.chaosgoo.metasequoia/gl_texture2", | |
| ).invokeMethod('loadModel', { | |
| 'textureId': _textureId, | |
| 'modelPath': filePath, | |
| }); | |
| } | |
| setState(() { | |
| modelPath = filePath; | |
| }); | |
| } else { | |
| // User canceled the picker | |
| } | |
| }, | |
| child: const Text("选择模型文件"), | |
| ), | |
| ), | |
| Expanded( | |
| child: (_textureId == null || modelPath == null) | |
| ? Center(child: CircularProgressIndicator()) | |
| : LayoutBuilder( | |
| builder: (context, constraints) { | |
| final double pixelRatio = MediaQuery.of( | |
| context, | |
| ).devicePixelRatio; | |
| final double width = constraints.maxWidth * pixelRatio; | |
| final double height = constraints.maxHeight * pixelRatio; | |
| _updateNativeTexture(width.toInt(), height.toInt()); | |
| return GestureDetector( | |
| onPanUpdate: (details) { | |
| const sensitivity = 0.01; | |
| MethodChannel( | |
| "com.chaosgoo.metasequoia/gl_texture2", | |
| ).invokeMethod('rotate', { | |
| 'textureId': _textureId, | |
| 'angleX': details.delta.dx * sensitivity, | |
| 'angleY': details.delta.dy * sensitivity, | |
| }); | |
| }, | |
| child: Texture(textureId: _textureId!), | |
| ); | |
| }, | |
| ), | |
| ), | |
| ], | |
| ), | |
| ); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.chaosgoo.metasequoia.renderer | |
| import android.opengl.GLSurfaceView | |
| import com.example.native_lib3mf.NativeRenderBridge | |
| import javax.microedition.khronos.egl.EGLConfig | |
| import javax.microedition.khronos.opengles.GL10 | |
| class NativeDrawerRenderer() : GLSurfaceView.Renderer { | |
| override fun onDrawFrame(gl: GL10?) { | |
| NativeRenderBridge.onDrawFrame(gl) | |
| } | |
| override fun onSurfaceChanged( | |
| gl: GL10?, | |
| width: Int, | |
| height: Int | |
| ) { | |
| NativeRenderBridge.onSurfaceChange(gl, width, height) | |
| } | |
| override fun onSurfaceCreated( | |
| gl: GL10?, | |
| config: EGLConfig? | |
| ) { | |
| NativeRenderBridge.onSurfaceCreated(gl, config) | |
| } | |
| fun loadModel(modelPath: String) { | |
| NativeRenderBridge.loadModel(modelPath) | |
| } | |
| fun rotate(x: Float, y: Float) { | |
| NativeRenderBridge.rotateModel(x, y) | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.example.native_lib3mf; | |
| import androidx.annotation.Nullable; | |
| import javax.microedition.khronos.egl.EGLConfig; | |
| import javax.microedition.khronos.opengles.GL10; | |
| public class NativeRenderBridge { | |
| static { | |
| System.loadLibrary("native_lib3mf"); | |
| } | |
| public static native void loadModel(String path); | |
| public static native void rotateModel(float x, float y); | |
| public static native void onDrawFrame(GL10 gl); | |
| public static native void onSurfaceChange(@Nullable GL10 gl, int width, int height); | |
| public static native void onSurfaceCreated(@Nullable GL10 gl, @Nullable EGLConfig config); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment