diff --git a/thermion_dart/lib/src/entities/abstract_gizmo.dart b/thermion_dart/lib/src/entities/abstract_gizmo.dart deleted file mode 100644 index cebe9d42..00000000 --- a/thermion_dart/lib/src/entities/abstract_gizmo.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:vector_math/vector_math_64.dart'; - -import '../../thermion_dart.dart'; - -abstract class AbstractGizmo { - - bool get isVisible; - bool get isHovered; - - Future translate(double transX, double transY); - - void reset(); - - Future attach(ThermionEntity entity); - - Future detach(); - - Stream get boundingBox; - - void checkHover(double x, double y); -} diff --git a/thermion_dart/lib/src/input/src/implementations/fixed_orbit_camera_rotation_delegate.dart b/thermion_dart/lib/src/input/src/implementations/fixed_orbit_camera_rotation_delegate.dart index 047dba30..4059a553 100644 --- a/thermion_dart/lib/src/input/src/implementations/fixed_orbit_camera_rotation_delegate.dart +++ b/thermion_dart/lib/src/input/src/implementations/fixed_orbit_camera_rotation_delegate.dart @@ -56,6 +56,9 @@ class FixedOrbitRotateInputHandlerDelegate implements InputHandlerDelegate { return; } + final view = await viewer.getViewAt(0); + final viewport = await view.getViewport(); + var viewMatrix = await viewer.getCameraViewMatrix(); var modelMatrix = await viewer.getCameraModelMatrix(); var projectionMatrix = await viewer.getCameraProjectionMatrix(); @@ -80,12 +83,8 @@ class FixedOrbitRotateInputHandlerDelegate implements InputHandlerDelegate { intersectionInClipSpace / intersectionInClipSpace.w; // Calculate new camera position based on rotation - final ndcX = 2 * - ((-_queuedRotationDelta.x * viewer.pixelRatio) / - viewer.viewportDimensions.$1); - final ndcY = 2 * - ((_queuedRotationDelta.y * viewer.pixelRatio) / - viewer.viewportDimensions.$2); + final ndcX = 2 * ((-_queuedRotationDelta.x) / viewport.width); + final ndcY = 2 * ((_queuedRotationDelta.y) / viewport.height); final ndc = Vector4(ndcX, ndcY, intersectionInNdcSpace.z, 1.0); var clipSpace = Vector4( diff --git a/thermion_dart/lib/src/input/src/implementations/free_flight_camera_delegate.dart b/thermion_dart/lib/src/input/src/implementations/free_flight_camera_delegate.dart index 5ceaf84f..eae5040c 100644 --- a/thermion_dart/lib/src/input/src/implementations/free_flight_camera_delegate.dart +++ b/thermion_dart/lib/src/input/src/implementations/free_flight_camera_delegate.dart @@ -93,9 +93,9 @@ class FreeFlightInputHandlerDelegate implements InputHandlerDelegate { // Apply rotation if (_queuedRotationDelta.length2 > 0.0) { double deltaX = - _queuedRotationDelta.x * rotationSensitivity * viewer.pixelRatio; + _queuedRotationDelta.x * rotationSensitivity; double deltaY = - _queuedRotationDelta.y * rotationSensitivity * viewer.pixelRatio; + _queuedRotationDelta.y * rotationSensitivity; Quaternion yawRotation = Quaternion.axisAngle(_up, -deltaX); Quaternion pitchRotation = Quaternion.axisAngle(_right, -deltaY); @@ -109,8 +109,8 @@ class FreeFlightInputHandlerDelegate implements InputHandlerDelegate { Vector3 right = _right.clone()..applyQuaternion(currentRotation); Vector3 up = _up.clone()..applyQuaternion(currentRotation); - double deltaX = _queuedPanDelta.x * panSensitivity * viewer.pixelRatio; - double deltaY = _queuedPanDelta.y * panSensitivity * viewer.pixelRatio; + double deltaX = _queuedPanDelta.x * panSensitivity; + double deltaY = _queuedPanDelta.y * panSensitivity; relativeTranslation += right * deltaX + up * deltaY; _queuedPanDelta = Vector2.zero(); diff --git a/thermion_dart/lib/src/input/src/implementations/third_person_camera_delegate.dart b/thermion_dart/lib/src/input/src/implementations/third_person_camera_delegate.dart index edbee074..16e794f3 100644 --- a/thermion_dart/lib/src/input/src/implementations/third_person_camera_delegate.dart +++ b/thermion_dart/lib/src/input/src/implementations/third_person_camera_delegate.dart @@ -96,9 +96,9 @@ class OverTheShoulderCameraDelegate implements InputHandlerDelegate { // camera is always looking at -Z, whereas models generally face towards +Z if (_queuedRotationDelta.length2 > 0.0) { double deltaX = - _queuedRotationDelta.x * rotationSensitivity * viewer.pixelRatio; + _queuedRotationDelta.x * rotationSensitivity; double deltaY = - _queuedRotationDelta.y * rotationSensitivity * viewer.pixelRatio; + _queuedRotationDelta.y * rotationSensitivity; cameraLookAt = Matrix4.rotationY(-deltaX) * Matrix4.rotationX(-deltaY) * diff --git a/thermion_dart/lib/src/entities/gizmo.dart b/thermion_dart/lib/src/utils/gizmo.dart similarity index 63% rename from thermion_dart/lib/src/entities/gizmo.dart rename to thermion_dart/lib/src/utils/gizmo.dart index aef7fd60..ad023f0c 100644 --- a/thermion_dart/lib/src/entities/gizmo.dart +++ b/thermion_dart/lib/src/utils/gizmo.dart @@ -1,19 +1,31 @@ import 'dart:async'; -import 'package:thermion_dart/src/entities/abstract_gizmo.dart'; +import 'package:thermion_dart/src/viewer/viewer.dart'; import 'package:vector_math/vector_math_64.dart'; -import '../viewer/viewer.dart'; -class Gizmo extends AbstractGizmo { +abstract class Gizmo { + bool get isVisible; + bool get isHovered; + + void reset(); + + Future attach(ThermionEntity entity); + Future detach(); + + Stream get boundingBox; + + void checkHover(int x, int y); +} + +abstract class BaseGizmo extends Gizmo { final ThermionEntity x; final ThermionEntity y; final ThermionEntity z; final ThermionEntity center; - final ThermionViewer _viewer; - ThermionEntity? _activeAxis; ThermionEntity? _activeEntity; + ThermionViewer viewer; bool _visible = false; bool get isVisible => _visible; @@ -26,10 +38,9 @@ class Gizmo extends AbstractGizmo { Stream get boundingBox => _boundingBoxController.stream; final _boundingBoxController = StreamController.broadcast(); - Gizmo(this.x, this.y, this.z, this.center, this._viewer, - {this.ignore = const {}}) { - _viewer.gizmoPickResult.listen(_onGizmoPickResult); - _viewer.pickResult.listen(_onPickResult); + BaseGizmo({required this.x, required this.y, required this.z, required this.center, required this.viewer, + this.ignore = const {}}) { + onPick(_onGizmoPickResult); } final _stopwatch = Stopwatch(); @@ -52,12 +63,10 @@ class Gizmo extends AbstractGizmo { final axis = Vector3(_activeAxis == x ? 1.0 : 0.0, _activeAxis == y ? 1.0 : 0.0, _activeAxis == z ? 1.0 : 0.0); - await _viewer.queueRelativePositionUpdateWorldAxis( + await viewer.queueRelativePositionUpdateWorldAxis( _activeEntity!, - _transX * _viewer.pixelRatio, - -_transY * - _viewer - .pixelRatio, // flip the sign because "up" in NDC Y axis is positive, but negative in Flutter + _transX, + -_transY, // flip the sign because "up" in NDC Y axis is positive, but negative in Flutter axis.x, axis.y, axis.z); @@ -70,10 +79,6 @@ class Gizmo extends AbstractGizmo { _activeAxis = null; } - void _onPickResult(FilamentPickResult result) async { - await attach(result.entity); - } - void _onGizmoPickResult(FilamentPickResult result) async { if (result.entity == x || result.entity == y || result.entity == z) { _activeAxis = result.entity; @@ -98,21 +103,25 @@ class Gizmo extends AbstractGizmo { _visible = true; if (_activeEntity != null) { - await _viewer.removeStencilHighlight(_activeEntity!); + await viewer.removeStencilHighlight(_activeEntity!); } _activeEntity = entity; - await _viewer.setGizmoVisibility(true); - await _viewer.setParent(center, entity, preserveScaling: false); - _boundingBoxController.sink.add(await _viewer.getViewportBoundingBox(x)); + await viewer.setParent(center, entity, preserveScaling: false); + _boundingBoxController.sink.add(await viewer.getViewportBoundingBox(x)); } Future detach() async { - await _viewer.setGizmoVisibility(false); + await setVisibility(false); } @override - void checkHover(double x, double y) { - _viewer.pickGizmo(x.toInt(), y.toInt()); + void checkHover(int x, int y) { + pick(x, y); } + + Future pick(int x, int y); + + Future setVisibility(bool visible); + void onPick(void Function(PickResult result) callback); } diff --git a/thermion_dart/lib/src/viewer/src/ffi/src/ffi_gizmo.dart b/thermion_dart/lib/src/viewer/src/ffi/src/ffi_gizmo.dart new file mode 100644 index 00000000..0ff2b8a6 --- /dev/null +++ b/thermion_dart/lib/src/viewer/src/ffi/src/ffi_gizmo.dart @@ -0,0 +1,50 @@ +import 'dart:async'; +import 'dart:ffi'; + +import 'package:thermion_dart/src/viewer/src/ffi/src/thermion_dart.g.dart'; + +import '../../../../utils/gizmo.dart'; +import '../../../viewer.dart'; + +class FFIGizmo extends BaseGizmo { + Pointer pointer; + + late NativeCallable _nativeCallback; + FFIGizmo( + this.pointer, ThermionViewer viewer) : super(x: 0, y: 0, z: 0, center: 0, viewer: viewer) { + _nativeCallback = + NativeCallable.listener(_onPickResult); + } + + /// + /// The result(s) of calling [pickGizmo] (see below). + /// + // Stream get onPick => _pickResultController.stream; + // final _pickResultController = StreamController.broadcast(); + + void Function(PickResult)? _callback; + + void onPick(void Function(PickResult) callback) { + _callback = callback; + } + + void _onPickResult(DartEntityId entityId, int x, int y, Pointer view) { + _callback?.call((entity: entityId, x: x, y: y)); + } + + /// + /// Used to test whether a Gizmo is at the given viewport coordinates. + /// Called by `FilamentGestureDetector` on a mouse/finger down event. You probably don't want to call this yourself. + /// This is asynchronous and will require 2-3 frames to complete - subscribe to the [gizmoPickResult] stream to receive the results of this method. + /// [x] and [y] must be in local logical coordinates (i.e. where 0,0 is at top-left of the ThermionWidget). + /// + @override + Future pick(int x, int y) async { + Gizmo_pick(pointer, x.toInt(), y, _nativeCallback.nativeFunction); + } + + @override + Future setVisibility(bool visible) async { + Gizmo_setVisibility(pointer, visible); + } +} diff --git a/thermion_dart/lib/src/viewer/src/ffi/src/ffi_view.dart b/thermion_dart/lib/src/viewer/src/ffi/src/ffi_view.dart new file mode 100644 index 00000000..81dca075 --- /dev/null +++ b/thermion_dart/lib/src/viewer/src/ffi/src/ffi_view.dart @@ -0,0 +1,42 @@ +import 'dart:ffi'; + +import 'package:thermion_dart/src/viewer/src/ffi/src/thermion_dart.g.dart'; +import 'package:thermion_dart/src/viewer/src/shared_types/camera.dart'; + +import '../../shared_types/view.dart'; +import '../thermion_viewer_ffi.dart'; +import 'thermion_viewer_ffi.dart'; + +class FFIView extends View { + final Pointer view; + final Pointer viewer; + + FFIView(this.view, this.viewer); + + @override + Future updateViewport(int width, int height) async { + View_updateViewport(view, width, height); + } + + @override + Future setRenderTarget(covariant FFIRenderTarget renderTarget) async { + View_setRenderTarget(view, renderTarget.renderTarget); + } + + @override + Future setCamera(FFICamera camera) async { + View_setCamera(view, camera.camera); + } + + @override + Future getViewport() async { + TViewport vp = View_getViewport(view); + return Viewport(vp.left, vp.bottom, vp.width, vp.height); + } + + @override + Camera getCamera() { + final engine = Viewer_getEngine(viewer); + return FFICamera(View_getCamera(view), engine); + } +} diff --git a/thermion_dart/lib/src/viewer/src/ffi/src/thermion_dart.g.dart b/thermion_dart/lib/src/viewer/src/ffi/src/thermion_dart.g.dart index 4aacfc3b..1bb847f0 100644 --- a/thermion_dart/lib/src/viewer/src/ffi/src/thermion_dart.g.dart +++ b/thermion_dart/lib/src/viewer/src/ffi/src/thermion_dart.g.dart @@ -139,6 +139,13 @@ external void Viewer_setMainCamera( ffi.Pointer tView, ); +@ffi.Native Function(ffi.Pointer, ffi.Int)>( + isLeaf: true) +external ffi.Pointer Viewer_getSwapChainAt( + ffi.Pointer tViewer, + int index, +); + @ffi.Native Function(ffi.Pointer)>(isLeaf: true) external ffi.Pointer Viewer_getEngine( ffi.Pointer viewer, @@ -662,9 +669,9 @@ external void SceneManager_setVisibilityLayer( int layer, ); -@ffi.Native Function(ffi.Pointer)>( +@ffi.Native Function(ffi.Pointer)>( isLeaf: true) -external ffi.Pointer SceneManager_getGizmo( +external ffi.Pointer SceneManager_getScene( ffi.Pointer tSceneManager, ); @@ -1176,14 +1183,6 @@ external void set_priority( int priority, ); -@ffi.Native< - ffi.Void Function( - ffi.Pointer, ffi.Pointer)>(isLeaf: true) -external void get_gizmo( - ffi.Pointer sceneManager, - ffi.Pointer out, -); - @ffi.Native< Aabb2 Function( ffi.Pointer, ffi.Pointer, EntityId)>(isLeaf: true) @@ -1212,13 +1211,6 @@ external void get_bounding_box_to_out( ffi.Pointer maxY, ); -@ffi.Native, ffi.Bool)>( - isLeaf: true) -external void set_gizmo_visibility( - ffi.Pointer sceneManager, - bool visible, -); - @ffi.Native< ffi.Void Function(ffi.Pointer, EntityId, ffi.Float, ffi.Float, ffi.Float)>(isLeaf: true) @@ -1480,10 +1472,15 @@ external void set_rendering_render_thread( ); @ffi.Native< - ffi.Void Function(ffi.Pointer, + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, ffi.Pointer>)>(isLeaf: true) -external void request_frame_render_thread( +external void Viewer_requestFrameRenderThread( ffi.Pointer viewer, + ffi.Pointer view, + ffi.Pointer tSwapChain, ffi.Pointer> onComplete, ); @@ -1892,7 +1889,7 @@ external void View_setBloom( @ffi.Native< ffi.Void Function( - ffi.Pointer, ffi.Pointer, ffi.Int)>(isLeaf: true) + ffi.Pointer, ffi.Pointer, ffi.Int32)>(isLeaf: true) external void View_setToneMapping( ffi.Pointer tView, ffi.Pointer tEngine, @@ -1924,30 +1921,39 @@ external void View_setCamera( ffi.Pointer tCamera, ); +@ffi.Native Function(ffi.Pointer)>(isLeaf: true) +external ffi.Pointer View_getScene( + ffi.Pointer tView, +); + @ffi.Native Function(ffi.Pointer)>(isLeaf: true) external ffi.Pointer View_getCamera( ffi.Pointer tView, ); @ffi.Native< - ffi.Void Function( - ffi.Pointer, - ffi.Pointer, - ffi.Int, - ffi.Int, - ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function( - EntityId entityId, ffi.Int x, ffi.Int y)>>)>(isLeaf: true) + ffi.Pointer Function(ffi.Pointer, ffi.Pointer, + ffi.Pointer)>(isLeaf: true) +external ffi.Pointer Gizmo_new( + ffi.Pointer tEngine, + ffi.Pointer tView, + ffi.Pointer tScene, +); + +@ffi.Native< + ffi.Void Function(ffi.Pointer, ffi.Uint32, ffi.Uint32, + GizmoPickCallback)>(isLeaf: true) external void Gizmo_pick( ffi.Pointer tGizmo, - ffi.Pointer tView, int x, int y, - ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function(EntityId entityId, ffi.Int x, ffi.Int y)>> - callback, + GizmoPickCallback callback, +); + +@ffi.Native, ffi.Bool)>(isLeaf: true) +external void Gizmo_setVisibility( + ffi.Pointer tGizmo, + bool visible, ); final class TCamera extends ffi.Opaque {} @@ -1970,6 +1976,8 @@ final class TView extends ffi.Opaque {} final class TGizmo extends ffi.Opaque {} +final class TScene extends ffi.Opaque {} + final class TMaterialKey extends ffi.Struct { @ffi.Bool() external bool doubleSided; @@ -2216,6 +2224,19 @@ final class TViewport extends ffi.Struct { external int height; } +abstract class ToneMapping { + static const int ACES = 0; + static const int FILMIC = 1; + static const int LINEAR = 2; +} + +typedef GizmoPickCallback + = ffi.Pointer>; +typedef GizmoPickCallbackFunction = ffi.Void Function( + EntityId entityId, ffi.Uint32 x, ffi.Uint32 y, ffi.Pointer view); +typedef DartGizmoPickCallbackFunction = void Function( + DartEntityId entityId, int x, int y, ffi.Pointer view); + const int __bool_true_false_are_defined = 1; const int true1 = 1; diff --git a/thermion_dart/lib/src/viewer/src/ffi/src/thermion_viewer_ffi.dart b/thermion_dart/lib/src/viewer/src/ffi/src/thermion_viewer_ffi.dart index c0988b62..2a54072c 100644 --- a/thermion_dart/lib/src/viewer/src/ffi/src/thermion_viewer_ffi.dart +++ b/thermion_dart/lib/src/viewer/src/ffi/src/thermion_viewer_ffi.dart @@ -1,22 +1,20 @@ import 'dart:async'; -import 'dart:ffi'; import 'dart:math'; import 'dart:typed_data'; import 'package:animation_tools_dart/animation_tools_dart.dart'; -import 'package:thermion_dart/src/viewer/src/shared_types/swap_chain.dart'; +import 'package:thermion_dart/src/viewer/src/ffi/src/ffi_gizmo.dart'; import 'package:vector_math/vector_math_64.dart'; import 'package:vector_math/vector_math_64.dart' as v64; -import '../../../../entities/gizmo.dart'; +import '../../../../utils/gizmo.dart'; import '../../../../utils/matrix.dart'; import '../../events.dart'; -import '../../shared_types/camera.dart'; import '../../shared_types/view.dart'; import '../../thermion_viewer_base.dart'; import 'package:logging/logging.dart'; import 'callbacks.dart'; import 'camera_ffi.dart'; -import 'thermion_dart.g.dart'; +import 'ffi_view.dart'; // ignore: constant_identifier_names const ThermionEntity _FILAMENT_ASSET_ERROR = 0; @@ -26,8 +24,6 @@ typedef RenderCallback = Pointer)>>; class ThermionViewerFFI extends ThermionViewer { final _logger = Logger("ThermionViewerFFI"); - double pixelRatio = 1.0; - Pointer? _sceneManager; Pointer? _viewer; @@ -87,13 +83,9 @@ class ThermionViewerFFI extends ThermionViewer { this._driver = driver ?? nullptr; this._sharedContext = sharedContext ?? nullptr; - _onPickResultCallable = - NativeCallable.listener( - _onPickResult); - - _onGizmoPickResultCallable = - NativeCallable.listener( - _onGizmoPickResult); + // _onPickResultCallable = + // NativeCallable.listener( + // _onPickResult); _initialize(); } @@ -131,7 +123,6 @@ class ThermionViewerFFI extends ThermionViewer { /// /// Future updateViewportAndCameraProjection(double width, double height) async { - viewportDimensions = (width * pixelRatio, height * pixelRatio); var mainView = FFIView(Viewer_getViewAt(_viewer!, 0), _viewer!); mainView.updateViewport(width.toInt(), height.toInt()); @@ -148,7 +139,7 @@ class ThermionViewerFFI extends ThermionViewer { far = kFar; } - var aspect = viewportDimensions.$1 / viewportDimensions.$2; + var aspect = width / height; var focalLength = await camera.getFocalLength(); if (focalLength.abs() < 0.1) { focalLength = kFocalLength; @@ -161,8 +152,8 @@ class ThermionViewerFFI extends ThermionViewer { Future createSwapChain(int width, int height, {Pointer? surface}) async { var swapChain = await withPointerCallback((callback) { - return Viewer_createSwapChainRenderThread(_viewer!, surface ?? nullptr, - width, height, callback); + return Viewer_createSwapChainRenderThread( + _viewer!, surface ?? nullptr, width, height, callback); }); return FFISwapChain(swapChain, _viewer!); } @@ -193,11 +184,6 @@ class ThermionViewerFFI extends ThermionViewer { _sceneManager = Viewer_getSceneManager(_viewer!); - final gizmoEntities = allocator(4); - get_gizmo(_sceneManager!, gizmoEntities); - _gizmo = Gizmo(gizmoEntities[0], gizmoEntities[1], gizmoEntities[2], - gizmoEntities[3], this); - allocator.free(gizmoEntities); this._initialized.complete(true); } @@ -224,8 +210,9 @@ class ThermionViewerFFI extends ThermionViewer { /// /// @override - Future render(FFISwapChain swapChain) async { + Future render({FFISwapChain? swapChain}) async { final view = (await getViewAt(0)) as FFIView; + swapChain ??= FFISwapChain(Viewer_getSwapChainAt(_viewer!, 0), _viewer!); Viewer_renderRenderThread(_viewer!, view.view, swapChain.swapChain); } @@ -233,20 +220,24 @@ class ThermionViewerFFI extends ThermionViewer { /// /// @override - Future capture(FFISwapChain swapChain, - {FFIView? view, FFIRenderTarget? renderTarget}) async { - final length = this.viewportDimensions.$1.toInt() * - this.viewportDimensions.$2.toInt() * - 4; - final out = Uint8List(length); + Future capture( + {FFIView? view, + FFISwapChain? swapChain, + FFIRenderTarget? renderTarget}) async { view ??= (await getViewAt(0)) as FFIView; + final vp = await view.getViewport(); + final length = vp.width * vp.height * 4; + final out = Uint8List(length); + + swapChain ??= FFISwapChain(Viewer_getSwapChainAt(_viewer!, 0), _viewer!); + await withVoidCallback((cb) { if (renderTarget != null) { Viewer_captureRenderTargetRenderThread(_viewer!, view!.view, - swapChain.swapChain, renderTarget.renderTarget, out.address, cb); + swapChain!.swapChain, renderTarget.renderTarget, out.address, cb); } else { Viewer_captureRenderThread( - _viewer!, view!.view, swapChain.swapChain, out.address, cb); + _viewer!, view!.view, swapChain!.swapChain, out.address, cb); } }); return out; @@ -1341,20 +1332,6 @@ class ThermionViewerFFI extends ThermionViewer { set_camera_model_matrix(mainCamera.camera, out); } - /// - /// - /// - @override - Future setCameraLensProjection( - {double near = kNear, - double far = kFar, - double? aspect, - double focalLength = kFocalLength}) async { - aspect ??= viewportDimensions.$1 / viewportDimensions.$2; - var mainCamera = get_camera(_viewer!, get_main_camera(_viewer!)); - Camera_setLensProjection(mainCamera, near, far, aspect, focalLength); - } - /// /// /// @@ -1492,50 +1469,31 @@ class ThermionViewerFFI extends ThermionViewer { return result.cast().toDartString(); } - void _onPickResult(ThermionEntity entityId, int x, int y) { - _pickResultController.add(( - entity: entityId, - x: (x / pixelRatio).toDouble(), - y: (viewportDimensions.$2 - y) / pixelRatio - )); - } + void _onPickResult( + ThermionEntity entityId, int x, int y, Pointer viewPtr) async { + final view = FFIView(viewPtr, _viewer!); + final viewport = await view.getViewport(); - void _onGizmoPickResult(ThermionEntity entityId, int x, int y) { - _gizmoPickResultController.add(( - entity: entityId, - x: (x / pixelRatio).toDouble(), - y: (viewportDimensions.$2 - y) / pixelRatio - )); + // _pickResultController.add(( + // entity: entityId, + // x: (x / pixelRatio).toDouble(), + // y: (viewport.height - y) / pixelRatio + // )); } late NativeCallable _onPickResultCallable; - late NativeCallable - _onGizmoPickResultCallable; /// /// /// @override void pick(int x, int y) async { - x = (x * pixelRatio).ceil(); - y = (viewportDimensions.$2 - (y * pixelRatio)).ceil(); - final view = (await getViewAt(0)) as FFIView; - filament_pick( - _viewer!, view.view, x, y, _onPickResultCallable.nativeFunction); - } - - /// - /// - /// - @override - void pickGizmo(int x, int y) async { - x = (x * pixelRatio).ceil(); - y = (viewportDimensions.$2 - (y * pixelRatio)).ceil(); - final view = (await getViewAt(0)) as FFIView; - final gizmo = SceneManager_getGizmo(_sceneManager!); - Gizmo_pick( - gizmo, view.view, x, y, _onGizmoPickResultCallable.nativeFunction); + // x = (x * pixelRatio).ceil(); + // y = (viewportDimensions.$2 - (y * pixelRatio)).ceil(); + // final view = (await getViewAt(0)) as FFIView; + // filament_pick( + // _viewer!, view.view, x, y, _onPickResultCallable.nativeFunction); } /// @@ -1866,13 +1824,6 @@ class ThermionViewerFFI extends ThermionViewer { SceneManager_setVisibilityLayer(_sceneManager!, entity, layer); } - /// - /// - /// - Future setGizmoVisibility(bool visible) async { - set_gizmo_visibility(_sceneManager!, visible); - } - /// /// /// @@ -2088,7 +2039,11 @@ class ThermionViewerFFI extends ThermionViewer { completer.complete(true); }); - request_frame_render_thread(_viewer!, callback.nativeFunction); + final swapChain = Viewer_getSwapChainAt(_viewer!, 0); + final view = Viewer_getViewAt(_viewer!, 0); + + Viewer_requestFrameRenderThread( + _viewer!, view, swapChain, callback.nativeFunction); await completer.future; } @@ -2164,6 +2119,14 @@ class ThermionViewerFFI extends ThermionViewer { } return FFIView(view, _viewer!); } + + @override + Future createGizmo(FFIView view) async { + var view = (await getViewAt(0)) as FFIView; + var scene = View_getScene(view.view); + final gizmo = Gizmo_new(Viewer_getEngine(_viewer!), view.view, scene); + return FFIGizmo(gizmo, this); + } } class ThermionFFITexture extends ThermionTexture { @@ -2218,32 +2181,3 @@ class FFISwapChain extends SwapChain { }); } } - -class FFIView extends View { - final Pointer view; - final Pointer viewer; - - FFIView(this.view, this.viewer); - - @override - Future updateViewport(int width, int height) async { - View_updateViewport(view, width, height); - } - - @override - Future setRenderTarget(covariant FFIRenderTarget renderTarget) async { - View_setRenderTarget(view, renderTarget.renderTarget); - } - - @override - Future setCamera(FFICamera camera) async { - View_setCamera(view, camera.camera); - } - - @override - Future getViewport() async { - TViewport vp = View_getViewport(view); - return Viewport(vp.left, vp.bottom, vp.width, vp.height); - } - -} diff --git a/thermion_dart/lib/src/viewer/src/shared_types/pick_result.dart b/thermion_dart/lib/src/viewer/src/shared_types/pick_result.dart index 1c935e18..64a01290 100644 --- a/thermion_dart/lib/src/viewer/src/shared_types/pick_result.dart +++ b/thermion_dart/lib/src/viewer/src/shared_types/pick_result.dart @@ -1,5 +1,5 @@ // "picking" means clicking/tapping on the viewport, and unprojecting the X/Y coordinate to determine whether any renderable entities were present at those coordinates. import '../../viewer.dart'; -typedef FilamentPickResult = ({ThermionEntity entity, double x, double y}); -typedef ThermionPickResult = FilamentPickResult; +typedef FilamentPickResult = ({ThermionEntity entity, int x, int y}); +typedef PickResult = FilamentPickResult; diff --git a/thermion_dart/lib/src/viewer/src/shared_types/view.dart b/thermion_dart/lib/src/viewer/src/shared_types/view.dart index 7496ec94..c627446b 100644 --- a/thermion_dart/lib/src/viewer/src/shared_types/view.dart +++ b/thermion_dart/lib/src/viewer/src/shared_types/view.dart @@ -12,7 +12,7 @@ class Viewport { abstract class View { Future getViewport(); Future updateViewport(int width, int height); - Future setRenderTarget(covariant RenderTarget renderTarget); + Future setRenderTarget(covariant RenderTarget? renderTarget); Future setCamera(covariant Camera camera); - + Camera getCamera(); } diff --git a/thermion_dart/lib/src/viewer/src/thermion_viewer_base.dart b/thermion_dart/lib/src/viewer/src/thermion_viewer_base.dart index 6a747dd6..ff252209 100644 --- a/thermion_dart/lib/src/viewer/src/thermion_viewer_base.dart +++ b/thermion_dart/lib/src/viewer/src/thermion_viewer_base.dart @@ -1,6 +1,5 @@ import 'package:thermion_dart/src/viewer/src/events.dart'; -import '../../entities/abstract_gizmo.dart'; -import 'shared_types/camera.dart'; +import '../../utils/gizmo.dart'; import 'shared_types/shared_types.dart'; export 'shared_types/shared_types.dart'; @@ -10,7 +9,6 @@ import 'package:vector_math/vector_math_64.dart'; import 'dart:async'; import 'package:animation_tools_dart/animation_tools_dart.dart'; -import 'shared_types/swap_chain.dart'; import 'shared_types/view.dart'; const double kNear = 0.05; @@ -23,16 +21,6 @@ abstract class ThermionViewer { /// Future get initialized; - /// - /// The current dimensions of the viewport (in physical pixels). - /// - var viewportDimensions = (0.0, 0.0); - - /// - /// The current ratio of logical to physical pixels. - /// - late double pixelRatio; - /// /// The result(s) of calling [pick] (see below). /// This may be a broadcast stream, so you should ensure you have subscribed to this stream before calling [pick]. @@ -40,11 +28,6 @@ abstract class ThermionViewer { /// Stream get pickResult; - /// - /// The result(s) of calling [pickGizmo] (see below). - /// - Stream get gizmoPickResult; - /// /// A Stream containing entities added/removed to/from to the scene. /// @@ -63,7 +46,7 @@ abstract class ThermionViewer { /// /// Render a single frame immediately. /// - Future render(covariant SwapChain swapChain); + Future render({covariant SwapChain? swapChain}); /// /// Requests a single frame to be rendered. This is only intended to be used internally. @@ -73,8 +56,8 @@ abstract class ThermionViewer { /// /// Render a single frame and copy the pixel buffer to [out]. /// - Future capture(covariant SwapChain swapChain, - {covariant View? view, covariant RenderTarget? renderTarget}); + Future capture({covariant SwapChain? swapChain, + covariant View? view, covariant RenderTarget? renderTarget}); /// /// @@ -96,7 +79,7 @@ abstract class ThermionViewer { /// /// Future createView(); - + /// /// /// @@ -400,7 +383,6 @@ abstract class ThermionViewer { /// Future clearEntities(); - /// /// Schedules the glTF animation at [index] in [entity] to start playing on the next frame. /// @@ -493,15 +475,6 @@ abstract class ThermionViewer { /// Future getCameraCullingFar(); - /// - /// - /// - Future setCameraLensProjection( - {double near = kNear, - double far = kFar, - double? aspect, - double focalLength = kFocalLength}); - /// /// Sets the focus distance for the camera. /// @@ -701,14 +674,6 @@ abstract class ThermionViewer { /// void pick(int x, int y); - /// - /// Used to test whether a Gizmo is at the given viewport coordinates. - /// Called by `FilamentGestureDetector` on a mouse/finger down event. You probably don't want to call this yourself. - /// This is asynchronous and will require 2-3 frames to complete - subscribe to the [gizmoPickResult] stream to receive the results of this method. - /// [x] and [y] must be in local logical coordinates (i.e. where 0,0 is at top-left of the ThermionWidget). - /// - void pickGizmo(int x, int y); - /// /// Retrieves the name assigned to the given ThermionEntity (usually corresponds to the glTF mesh name). /// @@ -793,9 +758,9 @@ abstract class ThermionViewer { Future setPriority(ThermionEntity entityId, int priority); /// - /// The gizmo for translating/rotating objects. Only one gizmo is present in the scene. + /// The gizmo for translating/rotating objects. Only one gizmo can be active for a given view. /// - AbstractGizmo? get gizmo; + Future createGizmo(covariant View view); /// /// Register a callback to be invoked when this viewer is disposed. @@ -819,11 +784,6 @@ abstract class ThermionViewer { /// Future setVisibilityLayer(ThermionEntity entity, int layer); - /// - /// Show/hide the translation gizmo. - /// - Future setGizmoVisibility(bool visible); - /// /// Renders an outline around [entity] with the given color. /// diff --git a/thermion_dart/lib/src/viewer/src/thermion_viewer_stub.dart b/thermion_dart/lib/src/viewer/src/thermion_viewer_stub.dart index 771379b4..73795553 100644 --- a/thermion_dart/lib/src/viewer/src/thermion_viewer_stub.dart +++ b/thermion_dart/lib/src/viewer/src/thermion_viewer_stub.dart @@ -1,14 +1,13 @@ import 'dart:math'; import 'dart:typed_data'; +import 'package:thermion_dart/src/utils/gizmo.dart'; import 'package:thermion_dart/src/viewer/src/shared_types/swap_chain.dart'; import 'package:thermion_dart/src/viewer/src/shared_types/view.dart'; import 'package:thermion_dart/thermion_dart.dart'; import 'package:vector_math/vector_math_64.dart'; import 'dart:async'; import 'package:animation_tools_dart/animation_tools_dart.dart'; - -import '../../entities/abstract_gizmo.dart'; import 'events.dart'; import 'shared_types/camera.dart'; @@ -242,10 +241,6 @@ class ThermionViewerStub extends ThermionViewer { throw UnimplementedError(); } - @override - // TODO: implement gizmo - AbstractGizmo? get gizmo => throw UnimplementedError(); - @override Future hide(ThermionEntity entity, String? meshName) { // TODO: implement hide @@ -1028,12 +1023,6 @@ class ThermionViewerStub extends ThermionViewer { // TODO: implement setRenderTarget throw UnimplementedError(); } - - @override - Future capture(covariant SwapChain swapChain, {covariant RenderTarget? renderTarget}) { - // TODO: implement capture - throw UnimplementedError(); - } @override Future createView() { @@ -1052,6 +1041,18 @@ class ThermionViewerStub extends ThermionViewer { // TODO: implement render throw UnimplementedError(); } + + @override + Future capture(covariant SwapChain swapChain, {covariant View? view, covariant RenderTarget? renderTarget}) { + // TODO: implement capture + throw UnimplementedError(); + } + + @override + Future createGizmo(covariant View view) { + // TODO: implement createGizmo + throw UnimplementedError(); + } } diff --git a/thermion_dart/lib/src/viewer/src/web_js/src/thermion_viewer_js.dart b/thermion_dart/lib/src/viewer/src/web_js/src/thermion_viewer_js.dart index c066711a..68295449 100644 --- a/thermion_dart/lib/src/viewer/src/web_js/src/thermion_viewer_js.dart +++ b/thermion_dart/lib/src/viewer/src/web_js/src/thermion_viewer_js.dart @@ -6,7 +6,7 @@ import 'dart:typed_data'; import 'package:animation_tools_dart/animation_tools_dart.dart'; import 'package:logging/logging.dart'; import 'package:vector_math/vector_math_64.dart'; -import '../../../../entities/abstract_gizmo.dart'; +import '../../shared_types/internal/gizmo.dart'; import '../../../viewer.dart'; import '../../events.dart'; import '../../shared_types/camera.dart'; diff --git a/thermion_dart/lib/src/viewer/src/web_wasm/src/camera.dart b/thermion_dart/lib/src/viewer/src/web_wasm/src/camera.dart index 4296f7e5..1b158caa 100644 --- a/thermion_dart/lib/src/viewer/src/web_wasm/src/camera.dart +++ b/thermion_dart/lib/src/viewer/src/web_wasm/src/camera.dart @@ -45,4 +45,28 @@ class ThermionWasmCamera extends Camera { // TODO: implement setModelMatrix throw UnimplementedError(); } + + @override + Future getCullingFar() { + // TODO: implement getCullingFar + throw UnimplementedError(); + } + + @override + Future getFocalLength() { + // TODO: implement getFocalLength + throw UnimplementedError(); + } + + @override + Future getNear() { + // TODO: implement getNear + throw UnimplementedError(); + } + + @override + Future getViewMatrix() { + // TODO: implement getViewMatrix + throw UnimplementedError(); + } } diff --git a/thermion_dart/lib/src/viewer/src/web_wasm/src/thermion_viewer_wasm.dart b/thermion_dart/lib/src/viewer/src/web_wasm/src/thermion_viewer_wasm.dart index 3a524515..88814f29 100644 --- a/thermion_dart/lib/src/viewer/src/web_wasm/src/thermion_viewer_wasm.dart +++ b/thermion_dart/lib/src/viewer/src/web_wasm/src/thermion_viewer_wasm.dart @@ -11,8 +11,8 @@ import 'package:animation_tools_dart/animation_tools_dart.dart'; import 'package:vector_math/vector_math_64.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; -import '../../../../entities/abstract_gizmo.dart'; -import '../../../../entities/gizmo.dart'; +import '../../shared_types/internal/gizmo.dart'; +import '../../shared_types/internal/gizmo.dart'; import '../../../viewer.dart'; import '../../events.dart'; import '../../shared_types/camera.dart'; diff --git a/thermion_dart/native/include/FilamentViewer.hpp b/thermion_dart/native/include/FilamentViewer.hpp index d998bffd..80647b51 100644 --- a/thermion_dart/native/include/FilamentViewer.hpp +++ b/thermion_dart/native/include/FilamentViewer.hpp @@ -132,6 +132,14 @@ namespace thermion return (SceneManager *const)_sceneManager; } + SwapChain* getSwapChainAt(int index) { + if(index < _swapChains.size()) { + return _swapChains[index]; + } + Log("Error: index %d is greater than available swapchains", index); + return nullptr; + } + void unprojectTexture(EntityId entity, uint8_t* input, uint32_t inputWidth, uint32_t inputHeight, uint8_t* out, uint32_t outWidth, uint32_t outHeight); private: diff --git a/thermion_dart/native/include/ThermionDartApi.h b/thermion_dart/native/include/ThermionDartApi.h index 45296c15..a0eb5869 100644 --- a/thermion_dart/native/include/ThermionDartApi.h +++ b/thermion_dart/native/include/ThermionDartApi.h @@ -85,6 +85,7 @@ extern "C" EMSCRIPTEN_KEEPALIVE TView* Viewer_createView(TViewer *viewer); EMSCRIPTEN_KEEPALIVE TView* Viewer_getViewAt(TViewer *viewer, int index); EMSCRIPTEN_KEEPALIVE void Viewer_setMainCamera(TViewer *tViewer, TView *tView); + EMSCRIPTEN_KEEPALIVE TSwapChain* Viewer_getSwapChainAt(TViewer *tViewer, int index); // Engine EMSCRIPTEN_KEEPALIVE TEngine *Viewer_getEngine(TViewer* viewer); diff --git a/thermion_dart/native/include/ThermionDartRenderThreadApi.h b/thermion_dart/native/include/ThermionDartRenderThreadApi.h index 6794ef41..5f2c15b5 100644 --- a/thermion_dart/native/include/ThermionDartRenderThreadApi.h +++ b/thermion_dart/native/include/ThermionDartRenderThreadApi.h @@ -35,7 +35,7 @@ extern "C" EMSCRIPTEN_KEEPALIVE FilamentRenderCallback make_render_callback_fn_pointer(FilamentRenderCallback); EMSCRIPTEN_KEEPALIVE void set_rendering_render_thread(TViewer *viewer, bool rendering, void(*onComplete)()); - EMSCRIPTEN_KEEPALIVE void request_frame_render_thread(TViewer *viewer, void(*onComplete)()); + EMSCRIPTEN_KEEPALIVE void Viewer_requestFrameRenderThread(TViewer *viewer, TView *view, TSwapChain *tSwapChain, void(*onComplete)()); EMSCRIPTEN_KEEPALIVE void set_frame_interval_render_thread(TViewer *viewer, float frameInterval); EMSCRIPTEN_KEEPALIVE void set_background_color_render_thread(TViewer *viewer, const float r, const float g, const float b, const float a); EMSCRIPTEN_KEEPALIVE void clear_background_image_render_thread(TViewer *viewer); diff --git a/thermion_dart/native/include/generated/ThermionTextureSwiftObjCAPI.h b/thermion_dart/native/include/generated/ThermionTextureSwiftObjCAPI.h new file mode 100644 index 00000000..d42397bb --- /dev/null +++ b/thermion_dart/native/include/generated/ThermionTextureSwiftObjCAPI.h @@ -0,0 +1,325 @@ +// Generated by Apple Swift version 5.10 (swiftlang-5.10.0.13 clang-1500.3.9.4) +#ifndef SWIFT_MODULE_SWIFT_H +#define SWIFT_MODULE_SWIFT_H +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgcc-compat" + +#if !defined(__has_include) +# define __has_include(x) 0 +#endif +#if !defined(__has_attribute) +# define __has_attribute(x) 0 +#endif +#if !defined(__has_feature) +# define __has_feature(x) 0 +#endif +#if !defined(__has_warning) +# define __has_warning(x) 0 +#endif + +#if __has_include() +# include +#endif + +#pragma clang diagnostic ignored "-Wauto-import" +#if defined(__OBJC__) +#include +#endif +#if defined(__cplusplus) +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif +#if defined(__cplusplus) +#if defined(__arm64e__) && __has_include() +# include +#else +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-macro-identifier" +# ifndef __ptrauth_swift_value_witness_function_pointer +# define __ptrauth_swift_value_witness_function_pointer(x) +# endif +# ifndef __ptrauth_swift_class_method_pointer +# define __ptrauth_swift_class_method_pointer(x) +# endif +#pragma clang diagnostic pop +#endif +#endif + +#if !defined(SWIFT_TYPEDEFS) +# define SWIFT_TYPEDEFS 1 +# if __has_include() +# include +# elif !defined(__cplusplus) +typedef uint_least16_t char16_t; +typedef uint_least32_t char32_t; +# endif +typedef float swift_float2 __attribute__((__ext_vector_type__(2))); +typedef float swift_float3 __attribute__((__ext_vector_type__(3))); +typedef float swift_float4 __attribute__((__ext_vector_type__(4))); +typedef double swift_double2 __attribute__((__ext_vector_type__(2))); +typedef double swift_double3 __attribute__((__ext_vector_type__(3))); +typedef double swift_double4 __attribute__((__ext_vector_type__(4))); +typedef int swift_int2 __attribute__((__ext_vector_type__(2))); +typedef int swift_int3 __attribute__((__ext_vector_type__(3))); +typedef int swift_int4 __attribute__((__ext_vector_type__(4))); +typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2))); +typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3))); +typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); +#endif + +#if !defined(SWIFT_PASTE) +# define SWIFT_PASTE_HELPER(x, y) x##y +# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y) +#endif +#if !defined(SWIFT_METATYPE) +# define SWIFT_METATYPE(X) Class +#endif +#if !defined(SWIFT_CLASS_PROPERTY) +# if __has_feature(objc_class_property) +# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__ +# else +# define SWIFT_CLASS_PROPERTY(...) +# endif +#endif +#if !defined(SWIFT_RUNTIME_NAME) +# if __has_attribute(objc_runtime_name) +# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) +# else +# define SWIFT_RUNTIME_NAME(X) +# endif +#endif +#if !defined(SWIFT_COMPILE_NAME) +# if __has_attribute(swift_name) +# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) +# else +# define SWIFT_COMPILE_NAME(X) +# endif +#endif +#if !defined(SWIFT_METHOD_FAMILY) +# if __has_attribute(objc_method_family) +# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) +# else +# define SWIFT_METHOD_FAMILY(X) +# endif +#endif +#if !defined(SWIFT_NOESCAPE) +# if __has_attribute(noescape) +# define SWIFT_NOESCAPE __attribute__((noescape)) +# else +# define SWIFT_NOESCAPE +# endif +#endif +#if !defined(SWIFT_RELEASES_ARGUMENT) +# if __has_attribute(ns_consumed) +# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed)) +# else +# define SWIFT_RELEASES_ARGUMENT +# endif +#endif +#if !defined(SWIFT_WARN_UNUSED_RESULT) +# if __has_attribute(warn_unused_result) +# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +# else +# define SWIFT_WARN_UNUSED_RESULT +# endif +#endif +#if !defined(SWIFT_NORETURN) +# if __has_attribute(noreturn) +# define SWIFT_NORETURN __attribute__((noreturn)) +# else +# define SWIFT_NORETURN +# endif +#endif +#if !defined(SWIFT_CLASS_EXTRA) +# define SWIFT_CLASS_EXTRA +#endif +#if !defined(SWIFT_PROTOCOL_EXTRA) +# define SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_ENUM_EXTRA) +# define SWIFT_ENUM_EXTRA +#endif +#if !defined(SWIFT_CLASS) +# if __has_attribute(objc_subclassing_restricted) +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# else +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# endif +#endif +#if !defined(SWIFT_RESILIENT_CLASS) +# if __has_attribute(objc_class_stub) +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub)) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME) +# else +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME) +# endif +#endif +#if !defined(SWIFT_PROTOCOL) +# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_EXTENSION) +# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__) +#endif +#if !defined(OBJC_DESIGNATED_INITIALIZER) +# if __has_attribute(objc_designated_initializer) +# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) +# else +# define OBJC_DESIGNATED_INITIALIZER +# endif +#endif +#if !defined(SWIFT_ENUM_ATTR) +# if __has_attribute(enum_extensibility) +# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility))) +# else +# define SWIFT_ENUM_ATTR(_extensibility) +# endif +#endif +#if !defined(SWIFT_ENUM) +# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# if __has_feature(generalized_swift_name) +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# else +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility) +# endif +#endif +#if !defined(SWIFT_UNAVAILABLE) +# define SWIFT_UNAVAILABLE __attribute__((unavailable)) +#endif +#if !defined(SWIFT_UNAVAILABLE_MSG) +# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg))) +#endif +#if !defined(SWIFT_AVAILABILITY) +# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__))) +#endif +#if !defined(SWIFT_WEAK_IMPORT) +# define SWIFT_WEAK_IMPORT __attribute__((weak_import)) +#endif +#if !defined(SWIFT_DEPRECATED) +# define SWIFT_DEPRECATED __attribute__((deprecated)) +#endif +#if !defined(SWIFT_DEPRECATED_MSG) +# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__))) +#endif +#if !defined(SWIFT_DEPRECATED_OBJC) +# if __has_feature(attribute_diagnose_if_objc) +# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning"))) +# else +# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg) +# endif +#endif +#if defined(__OBJC__) +#if !defined(IBSegueAction) +# define IBSegueAction +#endif +#endif +#if !defined(SWIFT_EXTERN) +# if defined(__cplusplus) +# define SWIFT_EXTERN extern "C" +# else +# define SWIFT_EXTERN extern +# endif +#endif +#if !defined(SWIFT_CALL) +# define SWIFT_CALL __attribute__((swiftcall)) +#endif +#if !defined(SWIFT_INDIRECT_RESULT) +# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result)) +#endif +#if !defined(SWIFT_CONTEXT) +# define SWIFT_CONTEXT __attribute__((swift_context)) +#endif +#if !defined(SWIFT_ERROR_RESULT) +# define SWIFT_ERROR_RESULT __attribute__((swift_error_result)) +#endif +#if defined(__cplusplus) +# define SWIFT_NOEXCEPT noexcept +#else +# define SWIFT_NOEXCEPT +#endif +#if !defined(SWIFT_C_INLINE_THUNK) +# if __has_attribute(always_inline) +# if __has_attribute(nodebug) +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) __attribute__((nodebug)) +# else +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) +# endif +# else +# define SWIFT_C_INLINE_THUNK inline +# endif +#endif +#if defined(_WIN32) +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport) +#endif +#else +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL +#endif +#endif +#if defined(__OBJC__) +#if __has_feature(objc_modules) +#if __has_warning("-Watimport-in-framework-header") +#pragma clang diagnostic ignored "-Watimport-in-framework-header" +#endif +@import CoreVideo; +@import ObjectiveC; +#endif + +#endif +#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" +#pragma clang diagnostic ignored "-Wduplicate-method-arg" +#if __has_warning("-Wpragma-clang-attribute") +# pragma clang diagnostic ignored "-Wpragma-clang-attribute" +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wnullability" +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" + +#if __has_attribute(external_source_symbol) +# pragma push_macro("any") +# undef any +# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="swift_module",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol)) +# pragma pop_macro("any") +#endif + +#if defined(__OBJC__) +@protocol MTLDevice; +@protocol MTLTexture; +@class NSData; + +SWIFT_CLASS("_TtC12swift_module20ThermionTextureSwift") +@interface ThermionTextureSwift : NSObject +@property (nonatomic) CVMetalTextureCacheRef _Nullable cvMetalTextureCache; +@property (nonatomic, strong) id _Nullable metalDevice; +@property (nonatomic) CVMetalTextureRef _Nullable cvMetalTexture; +@property (nonatomic, strong) id _Nullable metalTexture; +@property (nonatomic) NSInteger metalTextureAddress; +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +- (nonnull instancetype)initWithWidth:(int64_t)width height:(int64_t)height OBJC_DESIGNATED_INITIALIZER; +- (void)destroyTexture; +- (void)fillColor; +- (NSData * _Nullable)getTextureBytes SWIFT_WARN_UNUSED_RESULT; +@end + +#endif +#if __has_attribute(external_source_symbol) +# pragma clang attribute pop +#endif +#if defined(__cplusplus) +#endif +#pragma clang diagnostic pop +#endif diff --git a/thermion_dart/native/src/TView.cpp b/thermion_dart/native/src/TView.cpp index acd4f116..cf8ec654 100644 --- a/thermion_dart/native/src/TView.cpp +++ b/thermion_dart/native/src/TView.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "ThermionDartApi.h" #include "TView.h" @@ -150,6 +151,11 @@ using namespace filament; return reinterpret_cast(view->getScene()); } + EMSCRIPTEN_KEEPALIVE TCamera* View_getCamera(TView *tView) { + auto view = reinterpret_cast(tView); + return reinterpret_cast(&(view->getCamera())); + } + #ifdef __cplusplus } diff --git a/thermion_dart/native/src/ThermionDartApi.cpp b/thermion_dart/native/src/ThermionDartApi.cpp index 5ab4f09a..c51d7e3b 100644 --- a/thermion_dart/native/src/ThermionDartApi.cpp +++ b/thermion_dart/native/src/ThermionDartApi.cpp @@ -348,8 +348,13 @@ extern "C" void (*callback)(void *buf, size_t size, void *data), void *data) { - auto swapChain = reinterpret_cast(tSwapChain); auto viewer = reinterpret_cast(tViewer); + auto swapChain = reinterpret_cast(tSwapChain); + + if(!swapChain) { + swapChain = viewer->getSwapChainAt(0); + } + auto *view = reinterpret_cast(tView); return viewer->render(frameTimeInNanos, view, swapChain, pixelBuffer, callback, data); } @@ -413,6 +418,12 @@ extern "C" return reinterpret_cast(swapChain); } + EMSCRIPTEN_KEEPALIVE TSwapChain* Viewer_getSwapChainAt(TViewer *tViewer, int index) { + auto viewer = reinterpret_cast(tViewer); + auto swapChain = viewer->getSwapChainAt(index); + return reinterpret_cast(swapChain); + } + EMSCRIPTEN_KEEPALIVE TView *Viewer_createView(TViewer *tViewer) { auto viewer = reinterpret_cast(tViewer); diff --git a/thermion_dart/test/camera_tests.dart b/thermion_dart/test/camera_tests.dart index 42538537..c93e4fde 100644 --- a/thermion_dart/test/camera_tests.dart +++ b/thermion_dart/test/camera_tests.dart @@ -67,7 +67,9 @@ void main() async { print(frustum.plane5.normal); print(frustum.plane5.constant); - await viewer.setCameraLensProjection( + var camera = await viewer.getMainCamera(); + + await camera.setLensProjection( near: 10.0, far: 1000.0, aspect: 1.0, focalLength: 28.0); frustum = await viewer.getCameraFrustum(); print(frustum.plane5.normal); @@ -75,8 +77,8 @@ void main() async { }); test('set custom projection/culling matrix', () async { - var viewer = - await testHelper.createViewer(bg: kRed, cameraPosition: Vector3(0, 0, 4)); + var viewer = await testHelper.createViewer( + bg: kRed, cameraPosition: Vector3(0, 0, 4)); var camera = await viewer.getMainCamera(); final cube = await viewer.createGeometry(GeometryHelper.cube()); @@ -155,7 +157,7 @@ void main() async { expect(await viewer.getActiveCamera(), newCamera); await testHelper.capture(viewer, "create_camera_new_camera"); - + final mainCamera = await viewer.getMainCamera(); await viewer.setActiveCamera(mainCamera); expect(await viewer.getActiveCamera(), mainCamera); diff --git a/thermion_dart/test/gizmo_tests.dart b/thermion_dart/test/gizmo_tests.dart new file mode 100644 index 00000000..ad3ba1a3 --- /dev/null +++ b/thermion_dart/test/gizmo_tests.dart @@ -0,0 +1,17 @@ +import 'package:test/test.dart'; + +import 'helpers.dart'; + +void main() async { + final testHelper = TestHelper("gizmo"); + + group('gizmo', () { + test('add gizmo to scene', () async { + var viewer = await testHelper.createViewer(); + var view = await viewer.getViewAt(0); + var gizmo = await viewer.createGizmo(view); + await testHelper.capture(viewer, "gizmo_add_to_scene"); + await viewer.dispose(); + }); + }); +} diff --git a/thermion_dart/test/helpers.dart b/thermion_dart/test/helpers.dart index ef92397a..c1661106 100644 --- a/thermion_dart/test/helpers.dart +++ b/thermion_dart/test/helpers.dart @@ -77,6 +77,8 @@ class TestHelper { outDir = Directory("$testDir/output/${dir}"); // outDir.deleteSync(recursive: true); outDir.createSync(); + DynamicLibrary.open('${testDir}/libThermionTextureSwift.dylib'); + } Future capture(ThermionViewer viewer, String outputFilename, @@ -84,33 +86,28 @@ class TestHelper { await Future.delayed(Duration(milliseconds: 10)); var outPath = p.join(outDir.path, "$outputFilename.bmp"); var pixelBuffer = await viewer.capture( - view: view, swapChain ?? this.swapChain, renderTarget: renderTarget); + view: view, + swapChain: swapChain ?? this.swapChain, + renderTarget: renderTarget); view ??= await viewer.getViewAt(0); var vp = await view.getViewport(); - await savePixelBufferToBmp( - pixelBuffer, - vp.width, - vp.height, - outPath); + await savePixelBufferToBmp(pixelBuffer, vp.width, vp.height, outPath); return pixelBuffer; } - Future createTexture(int width, int height) async { + Future createTexture(int width, int height) async { final packageUri = findPackageRoot('thermion_dart'); - final lib = ThermionDartTexture1(DynamicLibrary.open( - '${packageUri.toFilePath()}/native/lib/macos/swift/libthermion_swift.dylib')); - final object = ThermionDartTexture.new1(lib); + var testDir = Directory("${packageUri.toFilePath()}/test").path; + + final object = ThermionTextureSwift.new1(); object.initWithWidth_height_(width, height); - return object.metalTextureAddress; + return object; } Future createViewer( {img.Color? bg, Vector3? cameraPosition, viewportDimensions = (width: 500, height: 500)}) async { - final texture = await createTexture( - viewportDimensions.width, viewportDimensions.height); - final resourceLoader = calloc(1); var loadToOut = NativeCallable< Void Function(Pointer, diff --git a/thermion_dart/test/integration_test.dart b/thermion_dart/test/integration_test.dart index d00292cc..c5cb71ec 100644 --- a/thermion_dart/test/integration_test.dart +++ b/thermion_dart/test/integration_test.dart @@ -647,16 +647,6 @@ void main() async { }); }); - group("render thread", () { - test("request frame on render thread", () async { - var viewer = await testHelper.createViewer(); - viewer.requestFrame(); - - await Future.delayed(Duration(milliseconds: 20)); - await viewer.dispose(); - }); - }); - // group("unproject", () { // test("unproject", () async { // final dimensions = (width: 1280, height: 768); diff --git a/thermion_dart/test/libThermionTextureSwift.dylib b/thermion_dart/test/libThermionTextureSwift.dylib new file mode 100644 index 00000000..5a91f788 Binary files /dev/null and b/thermion_dart/test/libThermionTextureSwift.dylib differ diff --git a/thermion_dart/test/render_thread.dart b/thermion_dart/test/render_thread.dart new file mode 100644 index 00000000..ea8579be --- /dev/null +++ b/thermion_dart/test/render_thread.dart @@ -0,0 +1,33 @@ +import 'dart:async'; +import 'dart:ffi'; +import 'package:test/test.dart'; + +import 'helpers.dart'; + +void main() async { + final testHelper = TestHelper("render_thread"); + group("render thread/capture", () { + test("request frame on render thread", () async { + var viewer = await testHelper.createViewer(); + await viewer.setBackgroundColor(1.0, 0.0, 0.0, 1.0); + + var texture = await testHelper.createTexture(500, 500); + var renderTarget = await viewer.createRenderTarget( + 500, 500, texture.metalTextureAddress); + + final view = await viewer.getViewAt(0); + await view.setRenderTarget(renderTarget); + + await viewer.render(); + + await Future.delayed(Duration(milliseconds: 1)); + + var data = texture.getTextureBytes()!; + var pixels = data.bytes.cast().asTypedList(data.length); + + savePixelBufferToBmp( + pixels, 500, 500, "${testHelper.testDir}/request_frame.bmp"); + await viewer.dispose(); + }); + }); +} diff --git a/thermion_dart/test/view_tests.dart b/thermion_dart/test/view_tests.dart index 40019693..794d9478 100644 --- a/thermion_dart/test/view_tests.dart +++ b/thermion_dart/test/view_tests.dart @@ -1,5 +1,3 @@ -import 'dart:math'; - import 'package:test/test.dart'; import 'package:thermion_dart/thermion_dart.dart'; import 'package:vector_math/vector_math_64.dart'; @@ -9,11 +7,17 @@ void main() async { final testHelper = TestHelper("view"); group('view tests', () { + test('get camera from view', () async { + var viewer = await testHelper.createViewer(); + var view = await viewer.getViewAt(0); + expect(await view.getCamera(), isNotNull); + }); + test('one swapchain, render view to render target', () async { var viewer = await testHelper.createViewer(); final texture = await testHelper.createTexture(500, 500); - final renderTarget = await viewer.createRenderTarget(500, 500, texture); + final renderTarget = await viewer.createRenderTarget(500, 500, texture.metalTextureAddress); viewer.setRenderTarget(renderTarget); await viewer.setBackgroundColor(1.0, 0, 0, 1); @@ -27,9 +31,10 @@ void main() async { "default_swapchain_default_view_render_target"); }); - test('create secondary view, same swapchain', () async { + test('create secondary view, default swapchain', () async { var viewer = await testHelper.createViewer(); await viewer.setBackgroundColor(1.0, 0, 0, 1); + final cube = await viewer.createGeometry(GeometryHelper.cube()); var mainCamera = await viewer.getMainCamera(); @@ -71,48 +76,28 @@ void main() async { var mainCamera = await viewer.getMainCamera(); mainCamera.setTransform(Matrix4.translation(Vector3(0, 0, 5))); - final swapChain = await viewer.createSwapChain(200, 400); + final swapChain = await viewer.createSwapChain(1, 1); await testHelper.capture( viewer, "create_swapchain_default_view_default_swapchain"); final view = await viewer.createView(); - final texture = await testHelper.createTexture(400, 400); - final renderTarget = await viewer.createRenderTarget(400, 400, texture); + final texture = await testHelper.createTexture(200, 400); + final renderTarget = await viewer.createRenderTarget(200, 400, texture.metalTextureAddress); await view.setRenderTarget(renderTarget); - await view.updateViewport(400, 400); + await view.updateViewport(200, 400); view.setCamera(mainCamera); - + mainCamera.setLensProjection(aspect: 0.5); + await testHelper.capture( viewer, view: view, swapChain: swapChain, renderTarget: renderTarget, - "create_swapchain_new_view_new_swapchain", + "create_swapchain_secondary_view_new_swapchain", ); - // var newCamera = await viewer.createCamera(); - // newCamera.setTransform(Matrix4.translation(Vector3(0.0, 0.0, 10.0))); - // newCamera.setLensProjection(); - // view.setCamera(newCamera); - - // await testHelper.capture( - // viewer, - // "created_view_with_new_camera", - // view: view, - // ); - - // await testHelper.capture( - // viewer, - // "default_view_main_camera_no_change", - // ); - - // // await view.updateViewport(200, 400); - // // await view.setRenderTarget(renderTarget); - // // await viewer.setBackgroundColor(1.0, 0.0, 0.0, 1.0); - // // await testHelper.capture(viewer, "create_view_with_render_target", - // // renderTarget: renderTarget); await viewer.dispose(); }); }); diff --git a/thermion_dart/test/viewport_gizmo.dart b/thermion_dart/test/viewport_gizmo.dart index 52f90e2d..99a7d90e 100644 --- a/thermion_dart/test/viewport_gizmo.dart +++ b/thermion_dart/test/viewport_gizmo.dart @@ -47,9 +47,9 @@ late String testDir; void main() async { final packageUri = findPackageRoot('thermion_dart'); testDir = Directory("${packageUri.toFilePath()}/test").path; - final lib = ThermionDartTexture1(DynamicLibrary.open( + final lib = ThermionTexture1(DynamicLibrary.open( '${packageUri.toFilePath()}/native/lib/macos/swift/libthermion_swift.dylib')); - final object = ThermionDartTexture.new1(lib); + final object = ThermionTexture.new1(lib); object.initWithWidth_height_(500, 500); final resourceLoader = calloc(1); diff --git a/thermion_dart/test/viewport_grid.dart b/thermion_dart/test/viewport_grid.dart index fb727013..89463ff0 100644 --- a/thermion_dart/test/viewport_grid.dart +++ b/thermion_dart/test/viewport_grid.dart @@ -43,9 +43,9 @@ late String testDir; void main() async { final packageUri = findPackageRoot('thermion_dart'); testDir = Directory("${packageUri.toFilePath()}/test").path; - final lib = ThermionDartTexture1(DynamicLibrary.open( + final lib = ThermionTexture1(DynamicLibrary.open( '${packageUri.toFilePath()}/native/lib/macos/swift/libthermion_swift.dylib')); - final object = ThermionDartTexture.new1(lib); + final object = ThermionTexture.new1(lib); object.initWithWidth_height_(500, 500); final resourceLoader = calloc(1); diff --git a/thermion_flutter/thermion_flutter/lib/src/thermion_flutter_plugin.dart b/thermion_flutter/thermion_flutter/lib/src/thermion_flutter_plugin.dart index b37a158b..7a1ba8d6 100644 --- a/thermion_flutter/thermion_flutter/lib/src/thermion_flutter_plugin.dart +++ b/thermion_flutter/thermion_flutter/lib/src/thermion_flutter_plugin.dart @@ -1,8 +1,6 @@ import 'dart:async'; import 'package:thermion_dart/thermion_dart.dart'; -import 'package:flutter/widgets.dart'; import 'package:thermion_flutter_platform_interface/thermion_flutter_platform_interface.dart'; -import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart'; /// /// Handles all platform-specific initialization to create a backing rendering @@ -11,118 +9,30 @@ import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dar /// Call [createViewerWithOptions] to create an instance of [ThermionViewer]. /// class ThermionFlutterPlugin { - ThermionFlutterPlugin._(); - static AppLifecycleListener? _appLifecycleListener; + ThermionFlutterPlugin._(); static bool _initializing = false; static ThermionViewer? _viewer; - static bool _wasRenderingOnInactive = false; - - static void _handleStateChange(AppLifecycleState state) async { - if (_viewer == null) { - return; - } - - await _viewer!.initialized; - switch (state) { - case AppLifecycleState.detached: - if (!_wasRenderingOnInactive) { - _wasRenderingOnInactive = _viewer!.rendering; - } - await _viewer!.setRendering(false); - break; - case AppLifecycleState.hidden: - if (!_wasRenderingOnInactive) { - _wasRenderingOnInactive = _viewer!.rendering; - } - await _viewer!.setRendering(false); - break; - case AppLifecycleState.inactive: - if (!_wasRenderingOnInactive) { - _wasRenderingOnInactive = _viewer!.rendering; - } - // on Windows in particular, restoring a window after minimizing stalls the renderer (and the whole application) for a considerable length of time. - // disabling rendering on minimize seems to fix the issue (so I wonder if there's some kind of command buffer that's filling up while the window is minimized). - await _viewer!.setRendering(false); - break; - case AppLifecycleState.paused: - if (!_wasRenderingOnInactive) { - _wasRenderingOnInactive = _viewer!.rendering; - } - await _viewer!.setRendering(false); - break; - case AppLifecycleState.resumed: - await _viewer!.setRendering(_wasRenderingOnInactive); - break; - } - } - - @Deprecated("Use createViewerWithOptions") - static Future createViewer({String? uberArchivePath}) async { + static Future createViewer( + {ThermionFlutterOptions options = + const ThermionFlutterOptions.empty()}) async { + if (_initializing) { throw Exception("Existing call to createViewer has not completed."); } _initializing = true; - _viewer = await ThermionFlutterPlatform.instance - .createViewer(uberarchivePath: uberArchivePath); - _appLifecycleListener = AppLifecycleListener( - onStateChange: _handleStateChange, - ); - _viewer!.onDispose(() async { - _viewer = null; - _appLifecycleListener?.dispose(); - _appLifecycleListener = null; - }); - _initializing = false; - return _viewer!; - } - - static Future createViewerWithOptions( - {ThermionFlutterOptions options = const ThermionFlutterOptions.empty()}) async { - if (_initializing) { - throw Exception("Existing call to createViewer has not completed."); - } - _initializing = true; _viewer = - await ThermionFlutterPlatform.instance.createViewerWithOptions(options); - _appLifecycleListener = AppLifecycleListener( - onStateChange: _handleStateChange, - ); + await ThermionFlutterPlatform.instance.createViewer(options: options); + _viewer!.onDispose(() async { _viewer = null; - _appLifecycleListener?.dispose(); - _appLifecycleListener = null; }); _initializing = false; return _viewer!; } - static Future createTexture( - double width, - double height, - double offsetLeft, - double offsetTop, - double pixelRatio) async { - return ThermionFlutterPlatform.instance - .createTexture(width, height, offsetLeft, offsetTop, pixelRatio); - } - - static Future destroyTexture(ThermionFlutterTexture texture) async { - return ThermionFlutterPlatform.instance.destroyTexture(texture); - } - - static Future resizeTexture( - ThermionFlutterTexture texture, - int width, - int height, - int offsetLeft, - int offsetTop, - double pixelRatio) async { - return ThermionFlutterPlatform.instance.resizeTexture( - texture, width, height, offsetLeft, offsetTop, pixelRatio); - } } diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/src/pixel_ratio_aware.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/pixel_ratio_aware.dart new file mode 100644 index 00000000..1d92e832 --- /dev/null +++ b/thermion_flutter/thermion_flutter/lib/src/widgets/src/pixel_ratio_aware.dart @@ -0,0 +1,12 @@ +import 'package:flutter/widgets.dart'; + +class PixelRatioAware extends StatelessWidget { + final Widget Function(BuildContext context, double pixelRatio) builder; + + const PixelRatioAware({Key? key, required this.builder}) : super(key: key); + + @override + Widget build(BuildContext context) { + return builder(context, MediaQuery.of(context).devicePixelRatio); + } +} \ No newline at end of file diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_listener_widget.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_listener_widget.dart index d0a50817..8cfd2f68 100644 --- a/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_listener_widget.dart +++ b/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_listener_widget.dart @@ -5,6 +5,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:thermion_dart/thermion_dart.dart'; +import 'package:thermion_flutter/src/widgets/src/pixel_ratio_aware.dart'; import 'package:vector_math/vector_math_64.dart'; extension OffsetExtension on Offset { @@ -79,54 +80,64 @@ class _ThermionListenerWidgetState extends State { HardwareKeyboard.instance.removeHandler(_handleKeyEvent); } - Widget _desktop() { + Widget _desktop(double pixelRatio) { return Listener( - onPointerHover: (event) => widget.gestureHandler - .onPointerHover(event.localPosition.toVector2(), event.delta.toVector2()), + onPointerHover: (event) => widget.gestureHandler.onPointerHover( + event.localPosition.toVector2() * pixelRatio, + event.delta.toVector2() * pixelRatio), onPointerSignal: (PointerSignalEvent pointerSignal) { if (pointerSignal is PointerScrollEvent) { widget.gestureHandler.onPointerScroll( - pointerSignal.localPosition.toVector2(), - pointerSignal.scrollDelta.dy); + pointerSignal.localPosition.toVector2() * pixelRatio, + pointerSignal.scrollDelta.dy * pixelRatio); } }, onPointerPanZoomStart: (pzs) { throw Exception("TODO - is this a pinch zoom on laptop trackpad?"); }, - onPointerDown: (d) => widget.gestureHandler - .onPointerDown(d.localPosition.toVector2(), d.buttons & kMiddleMouseButton != 0), - onPointerMove: (d) => widget.gestureHandler - .onPointerMove(d.localPosition.toVector2(), d.delta.toVector2(), d.buttons & kMiddleMouseButton != 0), - onPointerUp: (d) => widget.gestureHandler.onPointerUp(d.buttons & kMiddleMouseButton != 0), + onPointerDown: (d) => widget.gestureHandler.onPointerDown( + d.localPosition.toVector2() * pixelRatio, + d.buttons & kMiddleMouseButton != 0), + onPointerMove: (d) => widget.gestureHandler.onPointerMove( + d.localPosition.toVector2() * pixelRatio, + d.delta.toVector2() * pixelRatio, + d.buttons & kMiddleMouseButton != 0), + onPointerUp: (d) => widget.gestureHandler + .onPointerUp(d.buttons & kMiddleMouseButton != 0), child: widget.child, ); } - Widget _mobile() { - return _MobileListenerWidget(gestureHandler: widget.gestureHandler); + Widget _mobile(double pixelRatio) { + return _MobileListenerWidget( + gestureHandler: widget.gestureHandler, pixelRatio: pixelRatio); } @override Widget build(BuildContext context) { - return FutureBuilder( - future: widget.gestureHandler.initialized, - builder: (_, initialized) { - if (initialized.data != true) { - return widget.child ?? Container(); - } - return Stack(children: [ - if (widget.child != null) Positioned.fill(child: widget.child!), - if (isDesktop) Positioned.fill(child: _desktop()), - if (!isDesktop) Positioned.fill(child: _mobile()) - ]); - }); + return PixelRatioAware(builder: (ctx, pixelRatio) { + return FutureBuilder( + initialData: 1.0, + future: widget.gestureHandler.initialized, + builder: (_, initialized) { + if (initialized.data != true) { + return widget.child ?? Container(); + } + return Stack(children: [ + if (widget.child != null) Positioned.fill(child: widget.child!), + if (isDesktop) Positioned.fill(child: _desktop(pixelRatio)), + if (!isDesktop) Positioned.fill(child: _mobile(pixelRatio)) + ]); + }); + }); } } class _MobileListenerWidget extends StatefulWidget { final InputHandler gestureHandler; + final double pixelRatio; - const _MobileListenerWidget({Key? key, required this.gestureHandler}) + const _MobileListenerWidget({Key? key, required this.gestureHandler, required this.pixelRatio}) : super(key: key); @override @@ -146,7 +157,7 @@ class _MobileListenerWidgetState extends State<_MobileListenerWidget> { return GestureDetector( behavior: HitTestBehavior.translucent, onTapDown: (details) => widget.gestureHandler - .onPointerDown(details.localPosition.toVector2(), false), + .onPointerDown(details.localPosition.toVector2() * widget.pixelRatio, false), onDoubleTap: () { widget.gestureHandler.setActionForType(InputType.SCALE1, isPan ? InputAction.TRANSLATE : InputAction.ROTATE); diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_texture_widget.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_texture_widget.dart new file mode 100644 index 00000000..04909ec3 --- /dev/null +++ b/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_texture_widget.dart @@ -0,0 +1,270 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:thermion_dart/src/viewer/src/shared_types/view.dart' as t; +import 'package:thermion_flutter/src/widgets/src/resize_observer.dart'; +import 'package:thermion_flutter/thermion_flutter.dart'; +import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart'; +import 'package:vector_math/vector_math_64.dart' hide Colors; + +class ThermionTextureWidget extends StatefulWidget { + final ThermionViewer viewer; + + final t.View view; + + final Widget? initial; + + const ThermionTextureWidget( + {super.key, required this.viewer, required this.view, this.initial}); + + @override + State createState() { + return _ThermionTextureWidgetState(); + } +} + +class _ThermionTextureWidgetState extends State { + ThermionFlutterTexture? _texture; + RenderTarget? _renderTarget; + + @override + void dispose() { + super.dispose(); + _texture?.destroy(); + } + + @override + void initState() { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + await widget.viewer.initialized; + + var dpr = MediaQuery.of(context).devicePixelRatio; + + var size = ((context.findRenderObject()) as RenderBox).size; + var width = (size.width * dpr).ceil(); + var height = (size.height * dpr).ceil(); + + _texture = + await ThermionFlutterPlatform.instance.createTexture(width, height); + + _renderTarget = await widget.viewer.createRenderTarget( + _texture!.width, _texture!.height, _texture!.hardwareId); + + await widget.view.setRenderTarget(_renderTarget!); + + await widget.view.updateViewport(width, height); + var camera = await widget.view.getCamera(); + await camera.setLensProjection(aspect: width / height); + + if (mounted) { + setState(() {}); + } + + _requestFrame(); + + widget.viewer.onDispose(() async { + var texture = _texture; + if (mounted) { + setState(() {}); + } + if (texture != null) { + _renderTarget = await widget.viewer.createRenderTarget( + texture.width, texture.height, texture.flutterId); + await widget.view.setRenderTarget(null); + await _renderTarget!.destroy(); + texture.destroy(); + } + }); + }); + super.initState(); + } + + bool _rendering = false; + + void _requestFrame() { + WidgetsBinding.instance.scheduleFrameCallback((d) async { + if (!_rendering) { + _rendering = true; + await widget.viewer.requestFrame(); + await _texture?.markFrameAvailable(); + _rendering = false; + } + _requestFrame(); + }); + } + + bool _resizing = false; + Timer? _resizeTimer; + + Future _resize(Size newSize) async { + + _resizeTimer?.cancel(); + + _resizeTimer = Timer(const Duration(milliseconds: 10), () async { + if (_resizing || !mounted) { + return; + } + _resizeTimer!.cancel(); + _resizing = true; + + if (!mounted) { + return; + } + + newSize *= MediaQuery.of(context).devicePixelRatio; + + var newWidth = newSize.width.ceil(); + var newHeight = newSize.height.ceil(); + + await _texture?.resize( + newWidth, + newHeight, + 0, + 0, + ); + + await widget.view.updateViewport(newWidth, newHeight); + var camera = await widget.view.getCamera(); + await camera.setLensProjection(aspect: newWidth / newHeight); + + setState(() {}); + _resizing = false; + }); + } + + @override + Widget build(BuildContext context) { + if (_texture == null) { + return widget.initial ?? Container(color: Colors.red); + } + + return Stack(children: [ + Positioned.fill( + child: ResizeObserver( + onResized: _resize, + child: Stack(children: [ + Positioned.fill( + child: Texture( + key: ObjectKey("flutter_texture_${_texture!.flutterId}"), + textureId: _texture!.flutterId, + filterQuality: FilterQuality.none, + freeze: false, + )) + ]))), + Align( + alignment: Alignment.bottomLeft, + child: ElevatedButton( + onPressed: () async { + var img = + await widget.viewer.capture(renderTarget: _renderTarget!); + print(img); + }, + child: Text("CAPTURE")), + ) + ]); + } +} + + + +// class _ThermionWidgetState extends State { + +// ThermionFlutterTexture? _texture; + +// @override +// void initState() { +// WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { +// await widget.viewer.initialized; +// widget.viewer.onDispose(() async { +// _rendering = false; + +// if (_texture != null) { +// var texture = _texture; +// _texture = null; +// if (mounted) { +// setState(() {}); +// } +// await ThermionFlutterPlugin.destroyTexture(texture!); +// } +// }); +// var dpr = MediaQuery.of(context).devicePixelRatio; + +// var size = ((context.findRenderObject()) as RenderBox).size; +// _texture = await ThermionFlutterPlugin.createTexture( +// size.width, size.height, 0, 0, dpr); + +// if (mounted) { +// setState(() {}); +// } + +// _requestFrame(); +// }); +// super.initState(); +// } + +// bool _rendering = false; + +// void _requestFrame() { +// WidgetsBinding.instance.scheduleFrameCallback((d) async { +// if (!_rendering) { +// _rendering = true; +// await widget.viewer.requestFrame(); +// _rendering = false; +// } +// _requestFrame(); +// }); +// } + +// bool _resizing = false; +// Timer? _resizeTimer; + +// Future _resizeTexture(Size newSize) async { +// _resizeTimer?.cancel(); +// _resizeTimer = Timer(const Duration(milliseconds: 500), () async { +// if (_resizing || !mounted) { +// return; +// } +// _resizeTimer!.cancel(); +// _resizing = true; + +// if (!mounted) { +// return; +// } + +// var dpr = MediaQuery.of(context).devicePixelRatio; + +// _texture.resize(newSize.width.ceil(), newSize.height.ceil(), 0, 0, dpr); +// setState(() {}); +// _resizing = false; +// }); +// } + +// @override +// Widget build(BuildContext context) { +// if (_texture == null || _resizing) { +// return widget.initial ?? +// Container( +// color: +// kIsWeb ? const Color.fromARGB(0, 170, 129, 129) : Colors.red); +// } + +// var textureWidget = Texture( +// key: ObjectKey("texture_${_texture!.flutterId}"), +// textureId: _texture!.flutterId!, +// filterQuality: FilterQuality.none, +// freeze: false, +// ); + +// return ResizeObserver( +// onResized: _resizeTexture, +// child: Stack(children: [ +// Positioned.fill( +// child: Platform.isLinux || Platform.isWindows +// ? Transform( +// alignment: Alignment.center, +// transform: Matrix4.rotationX( +// pi), // TODO - this rotation is due to OpenGL texture coordinate working in a different space from Flutter, can we move this to the C++ side somewhere? +// child: textureWidget) +// : textureWidget) +// ])); +// } +// } diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget.dart index 6e909c7d..5c5c27e1 100644 --- a/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget.dart +++ b/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget.dart @@ -1,18 +1,27 @@ import 'dart:io'; -import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:thermion_flutter/src/widgets/src/thermion_texture_widget.dart'; import 'package:thermion_flutter/src/widgets/src/thermion_widget_web.dart'; -import 'package:thermion_flutter/src/widgets/src/transparent_filament_widget.dart'; -import 'dart:async'; - -import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart'; import 'package:thermion_flutter/thermion_flutter.dart'; import 'package:thermion_flutter_web/thermion_flutter_web_options.dart'; -import 'resize_observer.dart'; +import 'package:thermion_dart/src/viewer/src/shared_types/view.dart' as t; +import 'thermion_widget_windows.dart'; class ThermionWidget extends StatefulWidget { + /// + /// The viewer. + /// final ThermionViewer viewer; + + /// + /// The view. + /// + final t.View? view; + + /// + /// The options to use when creating this widget. + /// final ThermionFlutterOptions? options; /// @@ -22,130 +31,51 @@ class ThermionWidget extends StatefulWidget { final Widget? initial; const ThermionWidget( - {Key? key, this.initial, required this.viewer, this.options}) + {Key? key, this.initial, required this.viewer, this.view, this.options}) : super(key: key); - @override - _ThermionWidgetState createState() => _ThermionWidgetState(); + State createState() => _ThermionWidgetState(); } class _ThermionWidgetState extends State { - ThermionFlutterTexture? _texture; + t.View? view; @override void initState() { - WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { - await widget.viewer.initialized; - widget.viewer.onDispose(() async { - if (_texture != null) { - var texture = _texture; - _texture = null; - if (mounted) { - setState(() {}); - } - await ThermionFlutterPlugin.destroyTexture(texture!); - } - }); - var dpr = MediaQuery.of(context).devicePixelRatio; - - var size = ((context.findRenderObject()) as RenderBox).size; - _texture = await ThermionFlutterPlugin.createTexture( - size.width, size.height, 0, 0, dpr); - - if (mounted) { - setState(() {}); - } - - _requestFrame(); - }); super.initState(); + initialize(); } - -bool _rendering = false; - -void _requestFrame() { - WidgetsBinding.instance.scheduleFrameCallback((d) async { - if (!_rendering) { - _rendering = true; - await widget.viewer.requestFrame(); - _rendering = false; + Future initialize() async { + if (widget.view != null) { + view = widget.view; + } else { + view = await widget.viewer.getViewAt(0); } - _requestFrame(); - }); -} - - bool _resizing = false; - Timer? _resizeTimer; - - Future _resizeTexture(Size newSize) async { - _resizeTimer?.cancel(); - _resizeTimer = Timer(const Duration(milliseconds: 500), () async { - if (_resizing || !mounted) { - return; - } - _resizeTimer!.cancel(); - _resizing = true; - var oldTexture = _texture; - _texture = null; - if (!mounted) { - return; - } - - var dpr = MediaQuery.of(context).devicePixelRatio; - - _texture = await ThermionFlutterPlugin.resizeTexture( - oldTexture!, newSize.width.ceil(), newSize.height.ceil(), 0, 0, dpr); - setState(() {}); - _resizing = false; - }); + setState(() {}); } @override Widget build(BuildContext context) { + if (view == null) { + return widget.initial ?? Container(color: Colors.red); + } + + // Windows & Web don't support imported textures yet if (kIsWeb) { - if (_texture == null || _resizing) { - return widget.initial ?? Container(color: Colors.red); - } - return ResizeObserver( - onResized: _resizeTexture, - child: ThermionWidgetWeb( - options: widget.options as ThermionFlutterWebOptions?)); + return ThermionWidgetWeb( + viewer: widget.viewer, + options: widget.options as ThermionFlutterWebOptions); } - if (_texture?.usesBackingWindow == true) { - return ResizeObserver( - onResized: _resizeTexture, - child: Stack(children: [ - Positioned.fill(child: CustomPaint(painter: TransparencyPainter())) - ])); + if (Platform.isWindows) { + return ThermionWidgetWindows(viewer: widget.viewer); } - if (_texture == null || _resizing) { - return widget.initial ?? - Container( - color: - kIsWeb ? const Color.fromARGB(0, 170, 129, 129) : Colors.red); - } - - var textureWidget = Texture( - key: ObjectKey("texture_${_texture!.flutterTextureId}"), - textureId: _texture!.flutterTextureId!, - filterQuality: FilterQuality.none, - freeze: false, - ); - - return ResizeObserver( - onResized: _resizeTexture, - child: Stack(children: [ - Positioned.fill( - child: Platform.isLinux || Platform.isWindows - ? Transform( - alignment: Alignment.center, - transform: Matrix4.rotationX( - pi), // TODO - this rotation is due to OpenGL texture coordinate working in a different space from Flutter, can we move this to the C++ side somewhere? - child: textureWidget) - : textureWidget) - ])); + return ThermionTextureWidget( + key: ObjectKey(view!), + initial: widget.initial, + viewer: widget.viewer, + view: view!); } } diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget_web_impl.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget_web_impl.dart index 653d774a..29ea1013 100644 --- a/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget_web_impl.dart +++ b/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget_web_impl.dart @@ -2,18 +2,28 @@ import 'dart:js_util'; import 'dart:ui' as ui; import 'dart:ui_web' as ui_web; import 'package:logging/logging.dart'; +import 'package:thermion_flutter/thermion_flutter.dart'; import 'package:thermion_flutter_web/thermion_flutter_web_options.dart'; import 'package:web/web.dart'; import 'package:flutter/widgets.dart'; class ThermionWidgetWeb extends StatelessWidget { final ThermionFlutterWebOptions options; + final ThermionViewer viewer; const ThermionWidgetWeb( - {super.key, this.options = const ThermionFlutterWebOptions.empty()}); + {super.key, this.options = const ThermionFlutterWebOptions.empty(), required this.viewer}); @override Widget build(BuildContext context) { + if (_texture == null || _resizing) { + return widget.initial ?? Container(color: Colors.red); + } + return ResizeObserver( + onResized: _resizeTexture, + child: ThermionWidgetWeb( + options: widget.options as ThermionFlutterWebOptions?)); + if (options?.importCanvasAsWidget == true) { return _ImageCopyingWidget(); } diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget_web_stub.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget_web_stub.dart index 945719c7..6c1c109d 100644 --- a/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget_web_stub.dart +++ b/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget_web_stub.dart @@ -1,14 +1,16 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:thermion_flutter_ffi/thermion_flutter_ffi.dart'; +import 'package:thermion_flutter/thermion_flutter.dart'; import 'package:thermion_flutter_web/thermion_flutter_web_options.dart'; -class ThermionWidgetWeb extends StatefulWidget { +class ThermionWidgetWeb extends StatelessWidget { final ThermionFlutterWebOptions? options; + final ThermionViewer viewer; + + const ThermionWidgetWeb({super.key, required this.options, required this.viewer}); - const ThermionWidgetWeb({super.key, required this.options}); - @override - // ignore: no_logic_in_create_state - State createState() => throw Exception(); + Widget build(BuildContext context) { + throw Exception("STUB"); + } } diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget_windows.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget_windows.dart new file mode 100644 index 00000000..c5040b15 --- /dev/null +++ b/thermion_flutter/thermion_flutter/lib/src/widgets/src/thermion_widget_windows.dart @@ -0,0 +1,14 @@ +import 'package:flutter/widgets.dart'; +import 'package:thermion_flutter/thermion_flutter.dart'; + +class ThermionWidgetWindows extends StatelessWidget { + + final ThermionViewer viewer; + + const ThermionWidgetWindows({super.key, required this.viewer}); + @override + Widget build(BuildContext context) { + // TODO: implement build + throw UnimplementedError(); + } +} diff --git a/thermion_flutter/thermion_flutter/macos/Classes/SwiftThermionFlutterPlugin.swift b/thermion_flutter/thermion_flutter/macos/Classes/SwiftThermionFlutterPlugin.swift index ba07fcc0..c66f124d 100644 --- a/thermion_flutter/thermion_flutter/macos/Classes/SwiftThermionFlutterPlugin.swift +++ b/thermion_flutter/thermion_flutter/macos/Classes/SwiftThermionFlutterPlugin.swift @@ -55,14 +55,7 @@ public class SwiftThermionFlutterPlugin: NSObject, FlutterPlugin { let instance:SwiftThermionFlutterPlugin = Unmanaged.fromOpaque(resourcesPtr!).takeUnretainedValue() instance.resources.removeValue(forKey:UInt32(rbuf.id)) } - - var markTextureFrameAvailable : @convention(c) (UnsafeMutableRawPointer?) -> () = { instancePtr in - let instance:SwiftThermionFlutterPlugin = Unmanaged.fromOpaque(instancePtr!).takeUnretainedValue() - if(instance.texture != nil) { - instance.registry.textureFrameAvailable(instance.texture!.flutterTextureId) - } - } - + public static func register(with registrar: FlutterPluginRegistrar) { let _messenger = registrar.messenger; @@ -88,12 +81,12 @@ public class SwiftThermionFlutterPlugin: NSObject, FlutterPlugin { resourceLoaderWrapper = make_resource_loader(loadResource, freeResource, Unmanaged.passUnretained(self).toOpaque()) } result(Int64(Int(bitPattern: resourceLoaderWrapper!))) + case "markTextureFrameAvailable": + let flutterTextureId = call.arguments as! Int64 + registry.textureFrameAvailable(flutterTextureId) + result(nil) case "getRenderCallback": - if(renderCallbackHolder.isEmpty) { - renderCallbackHolder.append(unsafeBitCast(markTextureFrameAvailable, to:Int64.self)) - renderCallbackHolder.append(unsafeBitCast(Unmanaged.passUnretained(self), to:UInt64.self)) - } - result(renderCallbackHolder) + result(nil) case "getDriverPlatform": result(nil) case "getSharedContext": diff --git a/thermion_flutter/thermion_flutter/macos/Classes/ThermionFlutterTexture.swift b/thermion_flutter/thermion_flutter/macos/Classes/ThermionFlutterTexture.swift index fc8669ae..7fd94a43 100644 --- a/thermion_flutter/thermion_flutter/macos/Classes/ThermionFlutterTexture.swift +++ b/thermion_flutter/thermion_flutter/macos/Classes/ThermionFlutterTexture.swift @@ -6,11 +6,11 @@ public class ThermionFlutterTexture : NSObject, FlutterTexture { var flutterTextureId: Int64 = -1 var registry: FlutterTextureRegistry - var texture: ThermionTexture + var texture: ThermionTextureSwift init(registry:FlutterTextureRegistry, width:Int64, height:Int64) { self.registry = registry - self.texture = ThermionTexture(width:width, height: height) + self.texture = ThermionTextureSwift(width:width, height: height) super.init() self.flutterTextureId = registry.register(self) } diff --git a/thermion_flutter/thermion_flutter/test/cpp/test.cpp b/thermion_flutter/thermion_flutter/test/cpp/test.cpp index b046dff0..fa9f0d6e 100644 --- a/thermion_flutter/thermion_flutter/test/cpp/test.cpp +++ b/thermion_flutter/thermion_flutter/test/cpp/test.cpp @@ -14,7 +14,7 @@ #include "ResourceBuffer.hpp" using namespace filament; -using namespace thermion_filament; +using namespace thermion; using namespace std; int _i = 0; diff --git a/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_android.dart b/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_android.dart new file mode 100644 index 00000000..0f6f43b4 --- /dev/null +++ b/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_android.dart @@ -0,0 +1,130 @@ +import 'dart:async'; +import 'package:flutter/services.dart'; +import 'package:thermion_dart/thermion_dart.dart'; +import 'package:thermion_dart/src/viewer/src/ffi/thermion_viewer_ffi.dart'; +import 'package:thermion_flutter_ffi/thermion_flutter_method_channel_interface.dart'; +import 'package:thermion_flutter_platform_interface/thermion_flutter_platform_interface.dart'; +import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart'; +import 'package:logging/logging.dart'; + +/// +/// An implementation of [ThermionFlutterPlatform] that uses +/// Flutter platform channels to create a rendering context, +/// resource loaders, and surface/render target(s). +/// +class ThermionFlutterAndroid + extends ThermionFlutterMethodChannelInterface { + final _channel = const MethodChannel("dev.thermion.flutter/event"); + final _logger = Logger("ThermionFlutterFFI"); + + ThermionViewerFFI? _viewer; + + ThermionFlutterAndroid._() {} + + RenderTarget? _renderTarget; + SwapChain? _swapChain; + + static void registerWith() { + ThermionFlutterPlatform.instance = ThermionFlutterAndroid._(); + } + + final _textures = {}; + + bool _creatingTexture = false; + bool _destroyingTexture = false; + + bool _resizing = false; + + /// + /// Create a rendering surface. + /// + /// This is internal; unless you are [thermion_*] package developer, don't + /// call this yourself. + /// + /// The name here is slightly misleading because we only create + /// a texture render target on macOS and iOS; on Android, we render into + /// a native window derived from a Surface, and on Windows we render into + /// a HWND. + /// + /// Currently, this only supports a single "texture" (aka rendering surface) + /// at any given time. If a [ThermionWidget] is disposed, it will call + /// [destroyTexture]; if it is resized, it will call [resizeTexture]. + /// + /// In future, we probably want to be able to create multiple distinct + /// textures/render targets. This would make it possible to have multiple + /// Flutter Texture widgets, each with its own Filament View attached. + /// The current design doesn't accommodate this (for example, it seems we can + /// only create a single native window from a Surface at any one time). + /// + Future createTexture(int width, int height) async { + throw Exception("TODO"); + // note that when [ThermionWidget] is disposed, we don't destroy the + // texture; instead, we keep it around in case a subsequent call requests + // a texture of the same size. + + // if (_textures.length > 1) { + // throw Exception("Multiple textures not yet supported"); + // } else if (_textures.length == 1) { + // if (_textures.first.height == physicalHeight && + // _textures.first.width == physicalWidth) { + // return _textures.first; + // } else { + // await _viewer!.setRendering(false); + // await _swapChain?.destroy(); + // await destroyTexture(_textures.first); + // _textures.clear(); + // } + // } + + // _creatingTexture = true; + + // var result = await _channel.invokeMethod("createTexture", + // [physicalWidth, physicalHeight, offsetLeft, offsetLeft]); + + // if (result == null || (result[0] == -1)) { + // throw Exception("Failed to create texture"); + // } + // final flutterTextureId = result[0] as int?; + // final hardwareTextureId = result[1] as int?; + // final surfaceAddress = result[2] as int?; + + // _logger.info( + // "Created texture with flutter texture id ${flutterTextureId}, hardwareTextureId $hardwareTextureId and surfaceAddress $surfaceAddress"); + + // final texture = ThermionFlutterTexture(flutterTextureId, hardwareTextureId, + // physicalWidth, physicalHeight, surfaceAddress); + + // await _viewer?.createSwapChain(physicalWidth, physicalHeight, + // surface: texture.surfaceAddress == null + // ? nullptr + // : Pointer.fromAddress(texture.surfaceAddress!)); + + // if (texture.hardwareTextureId != null) { + // if (_renderTarget != null) { + // await _renderTarget!.destroy(); + // } + // // ignore: unused_local_variable + // _renderTarget = await _viewer?.createRenderTarget( + // physicalWidth, physicalHeight, texture.hardwareTextureId!); + // } + + // await _viewer?.updateViewportAndCameraProjection( + // physicalWidth.toDouble(), physicalHeight.toDouble()); + // _creatingTexture = false; + // _textures.add(texture); + // return texture; + } + + /// + /// Called by [ThermionWidget] to resize a texture. Don't call this yourself. + /// + @override + Future resizeWindow( + int width, + int height, + int offsetLeft, + int offsetTop, + ) async { + throw Exception("Not supported on iOS"); + } +} diff --git a/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_ffi.dart b/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_ffi.dart index 232789b4..98d22962 100644 --- a/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_ffi.dart +++ b/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_ffi.dart @@ -1,256 +1,4 @@ -import 'dart:async'; -import 'package:flutter/services.dart'; -import 'dart:ffi'; -import 'package:thermion_dart/thermion_dart.dart'; -import 'package:thermion_dart/src/viewer/src/ffi/thermion_viewer_ffi.dart'; -import 'package:thermion_flutter_platform_interface/thermion_flutter_platform_interface.dart'; -import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart'; -import 'package:logging/logging.dart'; - -/// -/// An implementation of [ThermionFlutterPlatform] that uses a Flutter platform -/// channel to create a rendering context, resource loaders, and -/// render target(s). -/// -class ThermionFlutterFFI extends ThermionFlutterPlatform { - final _channel = const MethodChannel("dev.thermion.flutter/event"); - final _logger = Logger("ThermionFlutterFFI"); - - ThermionViewerFFI? _viewer; - - ThermionFlutterFFI._() {} - - RenderTarget? _renderTarget; - SwapChain? _swapChain; - - static void registerWith() { - ThermionFlutterPlatform.instance = ThermionFlutterFFI._(); - } - - final _textures = {}; - - Future createViewerWithOptions( - ThermionFlutterOptions options) async { - return createViewer(uberarchivePath: options.uberarchivePath); - } - - Future createViewer({String? uberarchivePath}) async { - var resourceLoader = Pointer.fromAddress( - await _channel.invokeMethod("getResourceLoaderWrapper")); - - if (resourceLoader == nullptr) { - throw Exception("Failed to get resource loader"); - } - - var renderCallbackResult = await _channel.invokeMethod("getRenderCallback"); - var renderCallback = - Pointer)>>.fromAddress( - renderCallbackResult[0]); - var renderCallbackOwner = - Pointer.fromAddress(renderCallbackResult[1]); - - var driverPlatform = await _channel.invokeMethod("getDriverPlatform"); - var driverPtr = driverPlatform == null - ? nullptr - : Pointer.fromAddress(driverPlatform); - - var sharedContext = await _channel.invokeMethod("getSharedContext"); - - var sharedContextPtr = sharedContext == null - ? nullptr - : Pointer.fromAddress(sharedContext); - - _viewer = ThermionViewerFFI( - resourceLoader: resourceLoader, - renderCallback: renderCallback, - renderCallbackOwner: renderCallbackOwner, - driver: driverPtr, - sharedContext: sharedContextPtr, - uberArchivePath: uberarchivePath); - await _viewer!.initialized; - return _viewer!; - } - - bool _creatingTexture = false; - bool _destroyingTexture = false; - - Future _waitForTextureCreationToComplete() async { - var iter = 0; - - while (_creatingTexture || _destroyingTexture) { - await Future.delayed(Duration(milliseconds: 50)); - iter++; - if (iter > 10) { - throw Exception( - "Previous call to createTexture failed to complete within 500ms"); - } - } - } - - /// - /// Create a backing surface for rendering. - /// This is called by [ThermionWidget]; don't call this yourself. - /// - /// The name here is slightly misleading because we only create - /// a texture render target on macOS and iOS; on Android, we render into - /// a native window derived from a Surface, and on Windows we render into - /// a HWND. - /// - /// Currently, this only supports a single "texture" (aka rendering surface) - /// at any given time. If a [ThermionWidget] is disposed, it will call - /// [destroyTexture]; if it is resized, it will call [resizeTexture]. - /// - /// In future, we probably want to be able to create multiple distinct - /// textures/render targets. This would make it possible to have multiple - /// Flutter Texture widgets, each with its own Filament View attached. - /// The current design doesn't accommodate this (for example, it seems we can - /// only create a single native window from a Surface at any one time). - /// - Future createTexture(double width, double height, - double offsetLeft, double offsetRight, double pixelRatio) async { - final physicalWidth = (width * pixelRatio).ceil(); - final physicalHeight = (height * pixelRatio).ceil(); - // when a ThermionWidget is inserted, disposed then immediately reinserted - // into the widget hierarchy (e.g. rebuilding due to setState(() {}) being called in an ancestor widget) - // the first call to createTexture may not have completed before the second. - // add a loop here to wait (max 500ms) for the first call to complete - await _waitForTextureCreationToComplete(); - - // note that when [ThermionWidget] is disposed, we don't destroy the - // texture; instead, we keep it around in case a subsequent call requests - // a texture of the same size. - - if (_textures.length > 1) { - throw Exception("Multiple textures not yet supported"); - } else if (_textures.length == 1) { - if (_textures.first.height == physicalHeight && - _textures.first.width == physicalWidth) { - return _textures.first; - } else { - await _viewer!.setRendering(false); - await _swapChain?.destroy(); - await destroyTexture(_textures.first); - _textures.clear(); - } - } - - _creatingTexture = true; - - var result = await _channel.invokeMethod("createTexture", - [physicalWidth, physicalHeight, offsetLeft, offsetLeft]); - - if (result == null || (result[0] == -1)) { - throw Exception("Failed to create texture"); - } - final flutterTextureId = result[0] as int?; - final hardwareTextureId = result[1] as int?; - final surfaceAddress = result[2] as int?; - - _logger.info( - "Created texture with flutter texture id ${flutterTextureId}, hardwareTextureId $hardwareTextureId and surfaceAddress $surfaceAddress"); - - _viewer?.viewportDimensions = - (physicalWidth.toDouble(), physicalHeight.toDouble()); - - final texture = ThermionFlutterTexture(flutterTextureId, hardwareTextureId, - physicalWidth, physicalHeight, surfaceAddress); - - await _viewer?.createSwapChain(physicalWidth, physicalHeight, - surface: texture.surfaceAddress == null - ? nullptr - : Pointer.fromAddress(texture.surfaceAddress!)); - - if (texture.hardwareTextureId != null) { - if (_renderTarget != null) { - await _renderTarget!.destroy(); - } - // ignore: unused_local_variable - _renderTarget = await _viewer?.createRenderTarget( - physicalWidth, - physicalHeight, - texture.hardwareTextureId!); - } - - await _viewer?.updateViewportAndCameraProjection( - physicalWidth.toDouble(), physicalHeight.toDouble()); - _creatingTexture = false; - _textures.add(texture); - return texture; - } - - /// - /// Destroy a texture and clean up the texture cache (if applicable). - /// - Future destroyTexture(ThermionFlutterTexture texture) async { - if (_creatingTexture || _destroyingTexture) { - throw Exception( - "Cannot destroy texture while concurrent call to createTexture/destroyTexture has not completed"); - } - _destroyingTexture = true; - _textures.remove(texture); - await _channel.invokeMethod("destroyTexture", texture.flutterTextureId); - _destroyingTexture = false; - } - - bool _resizing = false; - - /// - /// Called by [ThermionWidget] to resize a texture. Don't call this yourself. - /// - @override - Future resizeTexture( - ThermionFlutterTexture texture, - int width, - int height, - int offsetLeft, - int offsetTop, - double pixelRatio) async { - if (_resizing) { - throw Exception("Resize underway"); - } - - width = (width * pixelRatio).ceil(); - height = (height * pixelRatio).ceil(); - - if ((width - _viewer!.viewportDimensions.$1).abs() < 0.001 || - (height - _viewer!.viewportDimensions.$2).abs() < 0.001) { - return texture; - } - _resizing = true; - bool wasRendering = _viewer!.rendering; - await _viewer!.setRendering(false); - await _swapChain?.destroy(); - await destroyTexture(texture); - - var result = await _channel - .invokeMethod("createTexture", [width, height, offsetLeft, offsetLeft]); - - if (result == null || result[0] == -1) { - throw Exception("Failed to create texture"); - } - _viewer!.viewportDimensions = (width.toDouble(), height.toDouble()); - var newTexture = - ThermionFlutterTexture(result[0], result[1], width, height, result[2]); - - await _viewer!.createSwapChain(width, height, - surface: newTexture.surfaceAddress == null - ? nullptr - : Pointer.fromAddress(newTexture.surfaceAddress!)); - - if (newTexture.hardwareTextureId != null) { - // ignore: unused_local_variable - var renderTarget = await _viewer!.createRenderTarget( - width, height, newTexture.hardwareTextureId!); - } - await _viewer! - .updateViewportAndCameraProjection(width.toDouble(), height.toDouble()); - - _viewer!.viewportDimensions = (width.toDouble(), height.toDouble()); - if (wasRendering) { - await _viewer!.setRendering(true); - } - _textures.add(newTexture); - _resizing = false; - return newTexture; - } -} +export 'thermion_flutter_android.dart'; +export 'thermion_flutter_macos.dart'; +export 'thermion_flutter_windows.dart'; +export 'thermion_flutter_ios.dart'; diff --git a/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_ios.dart b/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_ios.dart new file mode 100644 index 00000000..acacd036 --- /dev/null +++ b/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_ios.dart @@ -0,0 +1,131 @@ +import 'dart:async'; +import 'package:flutter/services.dart'; +import 'dart:ffi'; +import 'package:thermion_dart/thermion_dart.dart'; +import 'package:thermion_dart/src/viewer/src/ffi/thermion_viewer_ffi.dart'; +import 'package:thermion_flutter_ffi/thermion_flutter_method_channel_interface.dart'; +import 'package:thermion_flutter_platform_interface/thermion_flutter_platform_interface.dart'; +import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart'; +import 'package:logging/logging.dart'; + +/// +/// An implementation of [ThermionFlutterPlatform] that uses +/// Flutter platform channels to create a rendering context, +/// resource loaders, and surface/render target(s). +/// +class ThermionFlutterIOS + extends ThermionFlutterMethodChannelInterface { + final _channel = const MethodChannel("dev.thermion.flutter/event"); + final _logger = Logger("ThermionFlutterFFI"); + + ThermionViewerFFI? _viewer; + + ThermionFlutterIOS._() {} + + RenderTarget? _renderTarget; + SwapChain? _swapChain; + + static void registerWith() { + ThermionFlutterPlatform.instance = ThermionFlutterIOS._(); + } + + final _textures = {}; + + bool _creatingTexture = false; + bool _destroyingTexture = false; + + bool _resizing = false; + + /// + /// Create a rendering surface. + /// + /// This is internal; unless you are [thermion_*] package developer, don't + /// call this yourself. + /// + /// The name here is slightly misleading because we only create + /// a texture render target on macOS and iOS; on Android, we render into + /// a native window derived from a Surface, and on Windows we render into + /// a HWND. + /// + /// Currently, this only supports a single "texture" (aka rendering surface) + /// at any given time. If a [ThermionWidget] is disposed, it will call + /// [destroyTexture]; if it is resized, it will call [resizeTexture]. + /// + /// In future, we probably want to be able to create multiple distinct + /// textures/render targets. This would make it possible to have multiple + /// Flutter Texture widgets, each with its own Filament View attached. + /// The current design doesn't accommodate this (for example, it seems we can + /// only create a single native window from a Surface at any one time). + /// + Future createTexture(int width, int height) async { + throw Exception("TODO"); + // note that when [ThermionWidget] is disposed, we don't destroy the + // texture; instead, we keep it around in case a subsequent call requests + // a texture of the same size. + + // if (_textures.length > 1) { + // throw Exception("Multiple textures not yet supported"); + // } else if (_textures.length == 1) { + // if (_textures.first.height == physicalHeight && + // _textures.first.width == physicalWidth) { + // return _textures.first; + // } else { + // await _viewer!.setRendering(false); + // await _swapChain?.destroy(); + // await destroyTexture(_textures.first); + // _textures.clear(); + // } + // } + + // _creatingTexture = true; + + // var result = await _channel.invokeMethod("createTexture", + // [physicalWidth, physicalHeight, offsetLeft, offsetLeft]); + + // if (result == null || (result[0] == -1)) { + // throw Exception("Failed to create texture"); + // } + // final flutterTextureId = result[0] as int?; + // final hardwareTextureId = result[1] as int?; + // final surfaceAddress = result[2] as int?; + + // _logger.info( + // "Created texture with flutter texture id ${flutterTextureId}, hardwareTextureId $hardwareTextureId and surfaceAddress $surfaceAddress"); + + // final texture = ThermionFlutterTexture(flutterTextureId, hardwareTextureId, + // physicalWidth, physicalHeight, surfaceAddress); + + // await _viewer?.createSwapChain(physicalWidth, physicalHeight, + // surface: texture.surfaceAddress == null + // ? nullptr + // : Pointer.fromAddress(texture.surfaceAddress!)); + + // if (texture.hardwareTextureId != null) { + // if (_renderTarget != null) { + // await _renderTarget!.destroy(); + // } + // // ignore: unused_local_variable + // _renderTarget = await _viewer?.createRenderTarget( + // physicalWidth, physicalHeight, texture.hardwareTextureId!); + // } + + // await _viewer?.updateViewportAndCameraProjection( + // physicalWidth.toDouble(), physicalHeight.toDouble()); + // _creatingTexture = false; + // _textures.add(texture); + // return texture; + } + + /// + /// Called by [ThermionWidget] to resize a texture. Don't call this yourself. + /// + @override + Future resizeWindow( + int width, + int height, + int offsetLeft, + int offsetTop, + ) async { + throw Exception("Not supported on iOS"); + } +} diff --git a/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_macos.dart b/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_macos.dart new file mode 100644 index 00000000..50820605 --- /dev/null +++ b/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_macos.dart @@ -0,0 +1,77 @@ +import 'dart:async'; +import 'package:flutter/services.dart'; +import 'package:thermion_dart/thermion_dart.dart'; +import 'package:thermion_flutter_ffi/thermion_flutter_method_channel_interface.dart'; +import 'package:thermion_flutter_platform_interface/thermion_flutter_platform_interface.dart'; +import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart'; +import 'package:logging/logging.dart'; + +/// +/// An implementation of [ThermionFlutterPlatform] that uses +/// Flutter platform channels to create a rendering context, +/// resource loaders, and surface/render target(s). +/// +class ThermionFlutterMacOS extends ThermionFlutterMethodChannelInterface { + final _channel = const MethodChannel("dev.thermion.flutter/event"); + final _logger = Logger("ThermionFlutterFFI"); + + SwapChain? _swapChain; + + ThermionFlutterMacOS._() {} + + static void registerWith() { + ThermionFlutterPlatform.instance = ThermionFlutterMacOS._(); + } + + // On desktop platforms, textures are always created + Future createTexture(int width, int height) async { + if (_swapChain == null) { + // this is the headless swap chain + // since we will be using render targets, the actual dimensions don't matter + _swapChain = await viewer!.createSwapChain(width, height); + } + // Get screen width and height + int screenWidth = width; //1920; + int screenHeight = height; //1080; + + if (width > screenWidth || height > screenHeight) { + throw Exception("TODO - unsupported"); + } + + var result = await _channel + .invokeMethod("createTexture", [screenWidth, screenHeight, 0, 0]); + + if (result == null || (result[0] == -1)) { + throw Exception("Failed to create texture"); + } + final flutterTextureId = result[0] as int?; + final hardwareTextureId = result[1] as int?; + final surfaceAddress = result[2] as int?; + + + _logger.info( + "Created texture with flutter texture id ${flutterTextureId}, hardwareTextureId $hardwareTextureId and surfaceAddress $surfaceAddress"); + + return MacOSMethodChannelFlutterTexture(_channel, flutterTextureId!, + hardwareTextureId!, screenWidth, screenHeight); + } + + // On MacOS, we currently use textures/render targets, so there's no window to resize + @override + Future resizeWindow( + int width, int height, int offsetTop, int offsetRight) { + throw UnimplementedError(); + } +} + +class MacOSMethodChannelFlutterTexture extends MethodChannelFlutterTexture { + MacOSMethodChannelFlutterTexture(super.channel, super.flutterId, + super.hardwareId, super.width, super.height); + + @override + Future resize(int width, int height, int left, int top) async { + if (width > this.width || height > this.height || left != 0 || top != 0) { + throw Exception(); + } + } +} diff --git a/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_method_channel_interface.dart b/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_method_channel_interface.dart new file mode 100644 index 00000000..1c8249c9 --- /dev/null +++ b/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_method_channel_interface.dart @@ -0,0 +1,86 @@ +import 'dart:async'; +import 'package:flutter/services.dart'; +import 'dart:ffi'; +import 'package:thermion_dart/thermion_dart.dart'; +import 'package:thermion_dart/src/viewer/src/ffi/thermion_viewer_ffi.dart'; +import 'package:thermion_flutter_platform_interface/thermion_flutter_platform_interface.dart'; +import 'package:logging/logging.dart'; +import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart'; + +/// +/// An abstract implementation of [ThermionFlutterPlatform] that uses +/// Flutter platform channels to create a rendering context, +/// resource loaders, and surface/render target(s). +/// +abstract class ThermionFlutterMethodChannelInterface + extends ThermionFlutterPlatform { + final _channel = const MethodChannel("dev.thermion.flutter/event"); + final _logger = Logger("ThermionFlutterMethodChannelInterface"); + + ThermionViewerFFI? viewer; + + Future createViewer({ThermionFlutterOptions? options}) async { + if (viewer != null) { + throw Exception( + "Only one viewer can be created over the lifetime of an application"); + } + + var resourceLoader = Pointer.fromAddress( + await _channel.invokeMethod("getResourceLoaderWrapper")); + + if (resourceLoader == nullptr) { + throw Exception("Failed to get resource loader"); + } + + var renderCallback = nullptr; + var renderCallbackOwner = nullptr; + + var driverPlatform = await _channel.invokeMethod("getDriverPlatform"); + var driverPtr = driverPlatform == null + ? nullptr + : Pointer.fromAddress(driverPlatform); + + var sharedContext = await _channel.invokeMethod("getSharedContext"); + + var sharedContextPtr = sharedContext == null + ? nullptr + : Pointer.fromAddress(sharedContext); + + viewer = ThermionViewerFFI( + resourceLoader: resourceLoader, + renderCallback: renderCallback, + renderCallbackOwner: renderCallbackOwner, + driver: driverPtr, + sharedContext: sharedContextPtr, + uberArchivePath: options?.uberarchivePath); + await viewer!.initialized; + return viewer!; + } +} + +abstract class MethodChannelFlutterTexture extends ThermionFlutterTexture { + final MethodChannel _channel; + + MethodChannelFlutterTexture( + this._channel, this.flutterId, this.hardwareId, this.width, this.height); + + Future destroy() async { + await _channel.invokeMethod("destroyTexture", hardwareId); + } + + @override + final int flutterId; + + @override + final int hardwareId; + + @override + final int height; + + @override + final int width; + + Future markFrameAvailable() async { + await _channel.invokeMethod("markTextureFrameAvailable", this.flutterId); + } +} diff --git a/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_windows.dart b/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_windows.dart new file mode 100644 index 00000000..dd41948c --- /dev/null +++ b/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_windows.dart @@ -0,0 +1,97 @@ +import 'dart:async'; +import 'package:flutter/services.dart'; +import 'dart:ffi'; +import 'package:thermion_dart/thermion_dart.dart'; +import 'package:thermion_dart/src/viewer/src/ffi/thermion_viewer_ffi.dart'; +import 'package:thermion_flutter_ffi/thermion_flutter_method_channel_interface.dart'; +import 'package:thermion_flutter_platform_interface/thermion_flutter_platform_interface.dart'; +import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart'; +import 'package:logging/logging.dart'; + +/// +/// An implementation of [ThermionFlutterPlatform] that uses +/// Flutter platform channels to create a rendering context, +/// resource loaders, and surface/render target(s). +/// +class ThermionFlutterWindows + extends ThermionFlutterMethodChannelInterface { + final _channel = const MethodChannel("dev.thermion.flutter/event"); + + final _logger = Logger("ThermionFlutterWindows"); + + ThermionViewerFFI? _viewer; + + ThermionFlutterWindows._() {} + + SwapChain? _swapChain; + + static void registerWith() { + ThermionFlutterPlatform.instance = ThermionFlutterWindows._(); + } + + /// + /// Not supported on Windows. Throws an exception. + /// + Future createTexture(int width, int height) async { + throw Exception("Texture not supported on Windows"); + } + + bool _resizing = false; + + /// + /// Called by [ThermionWidget] to resize a texture. Don't call this yourself. + /// + @override + Future resizeWindow( + int width, int height, int offsetLeft, int offsetTop) async { + if (_resizing) { + throw Exception("Resize underway"); + } + + throw Exception("TODO"); + + // final view = await this._viewer!.getViewAt(0); + // final viewport = await view.getViewport(); + // final swapChain = await this._viewer.getSwapChainAt(0); + + // if (width == viewport.width && height - viewport.height == 0) { + // return; + // } + + // _resizing = true; + // bool wasRendering = _viewer!.rendering; + // await _viewer!.setRendering(false); + // await _swapChain?.destroy(); + + // var result = await _channel + // .invokeMethod("createTexture", [width, height, offsetLeft, offsetLeft]); + + // if (result == null || result[0] == -1) { + // throw Exception("Failed to create texture"); + // } + + // var newTexture = + // ThermionFlutterTexture(result[0], result[1], width, height, result[2]); + + // await _viewer!.createSwapChain(width, height, + // surface: newTexture.surfaceAddress == null + // ? nullptr + // : Pointer.fromAddress(newTexture.surfaceAddress!)); + + // if (newTexture.hardwareTextureId != null) { + // // ignore: unused_local_variable + // var renderTarget = await _viewer! + // .createRenderTarget(width, height, newTexture.hardwareTextureId!); + // } + + // await _viewer! + // .updateViewportAndCameraProjection(width.toDouble(), height.toDouble()); + + // if (wasRendering) { + // await _viewer!.setRendering(true); + // } + // _textures.add(newTexture); + // _resizing = false; + // return newTexture; + } +} diff --git a/thermion_flutter/thermion_flutter_ffi/pubspec.yaml b/thermion_flutter/thermion_flutter_ffi/pubspec.yaml index 1fa1f154..6d45d758 100644 --- a/thermion_flutter/thermion_flutter_ffi/pubspec.yaml +++ b/thermion_flutter/thermion_flutter_ffi/pubspec.yaml @@ -11,13 +11,13 @@ flutter: implements: thermion_flutter_platform_interface platforms: ios: - dartPluginClass: ThermionFlutterFFI + dartPluginClass: ThermionFlutterIOS android: - dartPluginClass: ThermionFlutterFFI + dartPluginClass: ThermionFlutterAndroid macos: - dartPluginClass: ThermionFlutterFFI + dartPluginClass: ThermionFlutterMacOS windows: - dartPluginClass: ThermionFlutterFFI + dartPluginClass: ThermionFlutterWindows dependencies: flutter: sdk: flutter diff --git a/thermion_flutter/thermion_flutter_platform_interface/lib/thermion_flutter_platform_interface.dart b/thermion_flutter/thermion_flutter_platform_interface/lib/thermion_flutter_platform_interface.dart index 2a861b84..70620993 100644 --- a/thermion_flutter/thermion_flutter_platform_interface/lib/thermion_flutter_platform_interface.dart +++ b/thermion_flutter/thermion_flutter_platform_interface/lib/thermion_flutter_platform_interface.dart @@ -9,7 +9,6 @@ class ThermionFlutterOptions { ThermionFlutterOptions({this.uberarchivePath}); const ThermionFlutterOptions.empty() : uberarchivePath = null; - } abstract class ThermionFlutterPlatform extends PlatformInterface { @@ -25,17 +24,23 @@ abstract class ThermionFlutterPlatform extends PlatformInterface { _instance = instance; } - Future createViewerWithOptions( - covariant ThermionFlutterOptions options); + /// + /// + /// + Future createViewer( + {covariant ThermionFlutterOptions? options}); - @deprecated - Future createViewer({String? uberarchivePath}); + /// + /// Create a rendering surface. + /// + /// This is internal; unless you are [thermion_*] package developer, don't + /// call this yourself. May not be supported on all platforms. + /// + Future createTexture(int width, int height); - Future createTexture(double width, double height, - double offsetLeft, double offsetTop, double pixelRatio); - - Future destroyTexture(ThermionFlutterTexture texture); - - Future resizeTexture(ThermionFlutterTexture texture, - int width, int height, int offsetTop, int offsetRight, double pixelRatio); + /// + /// + /// + Future resizeWindow( + int width, int height, int offsetTop, int offsetRight); } diff --git a/thermion_flutter/thermion_flutter_platform_interface/lib/thermion_flutter_texture.dart b/thermion_flutter/thermion_flutter_platform_interface/lib/thermion_flutter_texture.dart index c71723ee..25b62394 100644 --- a/thermion_flutter/thermion_flutter_platform_interface/lib/thermion_flutter_texture.dart +++ b/thermion_flutter/thermion_flutter_platform_interface/lib/thermion_flutter_texture.dart @@ -1,13 +1,30 @@ -class ThermionFlutterTexture { - final int width; - final int height; - final int? flutterTextureId; - final int? hardwareTextureId; - final int? surfaceAddress; - bool get usesBackingWindow => flutterTextureId == null; +// class ThermionFlutterTextureImpl { +// final int width; +// final int height; +// final int? flutterTextureId; +// final int? hardwareTextureId; +// final int? surfaceAddress; +// bool get usesBackingWindow => flutterTextureId == null; - ThermionFlutterTexture(this.flutterTextureId, this.hardwareTextureId, - this.width, this.height, this.surfaceAddress) { +// ThermionFlutterTexture(this.flutterTextureId, this.hardwareTextureId, +// this.width, this.height, this.surfaceAddress) { - } +// } +// } + +abstract class ThermionFlutterTexture { + int get width; + int get height; + + int get flutterId; + int get hardwareId; + + /// + /// Destroy a texture and clean up the texture cache (if applicable). + /// + Future destroy(); + + Future resize(int width, int height, int left, int top); + + Future markFrameAvailable(); }