From fbd54a2a09d4d3bb98a0050d9595741a6aa23c29 Mon Sep 17 00:00:00 2001 From: Nick Fisher Date: Mon, 30 Sep 2024 13:45:57 +0800 Subject: [PATCH] feat: working implementation of multiple widgets on macos --- .../input/src/delegate_gesture_handler.dart | 28 +-- .../lib/src/viewer/src/ffi/src/ffi_view.dart | 14 ++ .../viewer/src/ffi/src/thermion_dart.g.dart | 68 ++++--- .../src/ffi/src/thermion_viewer_ffi.dart | 4 +- .../viewer/src/shared_types/shared_types.dart | 1 + .../lib/src/viewer/src/shared_types/view.dart | 3 + .../native/include/APIBoundaryTypes.h | 2 +- .../native/include/FilamentViewer.hpp | 17 +- .../native/include/ThermionDartApi.h | 5 +- .../include/ThermionDartRenderThreadApi.h | 3 +- thermion_dart/native/src/FilamentViewer.cpp | 22 +-- thermion_dart/native/src/ThermionDartApi.cpp | 9 +- .../src/ThermionDartRenderThreadApi.cpp | 33 ++-- .../lib/src/widgets/src/resize_observer.dart | 4 +- .../widgets/src/thermion_listener_widget.dart | 1 - .../widgets/src/thermion_texture_widget.dart | 109 ++++++----- .../Classes/SwiftThermionFlutterPlugin.swift | 43 +++-- .../Classes/ThermionFlutterTexture.swift | 2 +- .../lib/thermion_flutter_macos.dart | 170 ++++++++++++++---- ...mion_flutter_method_channel_interface.dart | 25 +-- .../thermion_flutter_platform_interface.dart | 2 +- 21 files changed, 382 insertions(+), 183 deletions(-) diff --git a/thermion_dart/lib/src/input/src/delegate_gesture_handler.dart b/thermion_dart/lib/src/input/src/delegate_gesture_handler.dart index 668d3ba2..0d599028 100644 --- a/thermion_dart/lib/src/input/src/delegate_gesture_handler.dart +++ b/thermion_dart/lib/src/input/src/delegate_gesture_handler.dart @@ -69,11 +69,15 @@ class DelegateInputHandler implements InputHandler { }); factory DelegateInputHandler.flight(ThermionViewer viewer, - {PickDelegate? pickDelegate, bool freeLook=false, double? clampY, ThermionEntity? entity}) => + {PickDelegate? pickDelegate, + bool freeLook = false, + double? clampY, + ThermionEntity? entity}) => DelegateInputHandler( viewer: viewer, pickDelegate: pickDelegate, - transformDelegate: FreeFlightInputHandlerDelegate(viewer, clampY:clampY, entity:entity), + transformDelegate: FreeFlightInputHandlerDelegate(viewer, + clampY: clampY, entity: entity), actions: { InputType.MMB_HOLD_AND_MOVE: InputAction.ROTATE, InputType.SCROLLWHEEL: InputAction.TRANSLATE, @@ -82,8 +86,7 @@ class DelegateInputHandler implements InputHandler { InputType.KEYDOWN_W: InputAction.TRANSLATE, InputType.KEYDOWN_S: InputAction.TRANSLATE, InputType.KEYDOWN_D: InputAction.TRANSLATE, - if(freeLook) - InputType.POINTER_MOVE: InputAction.ROTATE, + if (freeLook) InputType.POINTER_MOVE: InputAction.ROTATE, }); bool _processing = false; @@ -160,15 +163,13 @@ class DelegateInputHandler implements InputHandler { } if (isMiddle) { _inputDeltas[InputType.MMB_HOLD_AND_MOVE] = - (_inputDeltas[InputType.MMB_HOLD_AND_MOVE] ?? Vector3.zero()) + Vector3(delta.x, delta.y, 0.0); + (_inputDeltas[InputType.MMB_HOLD_AND_MOVE] ?? Vector3.zero()) + + Vector3(delta.x, delta.y, 0.0); } else { _inputDeltas[InputType.LMB_HOLD_AND_MOVE] = - (_inputDeltas[InputType.LMB_HOLD_AND_MOVE] ?? Vector3.zero()) + Vector3(delta.x, delta.y, 0.0); + (_inputDeltas[InputType.LMB_HOLD_AND_MOVE] ?? Vector3.zero()) + + Vector3(delta.x, delta.y, 0.0); } - // else { - // _inputDeltas[InputType.POINTER_MOVE] = - // (_inputDeltas[InputType.POINTER_MOVE] ?? Vector3.zero()) + delta; - // } } @override @@ -180,7 +181,8 @@ class DelegateInputHandler implements InputHandler { return; } _inputDeltas[InputType.POINTER_MOVE] = - (_inputDeltas[InputType.POINTER_MOVE] ?? Vector3.zero()) + Vector3(delta.x, delta.y, 0.0); + (_inputDeltas[InputType.POINTER_MOVE] ?? Vector3.zero()) + + Vector3(delta.x, delta.y, 0.0); } @override @@ -191,8 +193,8 @@ class DelegateInputHandler implements InputHandler { } try { _inputDeltas[InputType.SCROLLWHEEL] = - (_inputDeltas[InputType.SCROLLWHEEL] ?? Vector3.zero()) - + Vector3(0,0, scrollDelta > 0 ? 1 : -1); + (_inputDeltas[InputType.SCROLLWHEEL] ?? Vector3.zero()) + + Vector3(0, 0, scrollDelta > 0 ? 1 : -1); } catch (e) { _logger.warning("Error during scroll accumulation: $e"); } 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 index 81dca075..72e4a208 100644 --- a/thermion_dart/lib/src/viewer/src/ffi/src/ffi_view.dart +++ b/thermion_dart/lib/src/viewer/src/ffi/src/ffi_view.dart @@ -39,4 +39,18 @@ class FFIView extends View { final engine = Viewer_getEngine(viewer); return FFICamera(View_getCamera(view), engine); } + + @override + Future setAntiAliasing(bool msaa, bool fxaa, bool taa) async { + View_setAntiAliasing(view, msaa, fxaa, taa); + } + + @override + Future setPostProcessing(bool enabled) async { + View_setPostProcessing(view, enabled); + } + + Future setRenderable(bool renderable) async { + Viewer_markViewRenderable(viewer, view, renderable); + } } 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 1bb847f0..f8539a76 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 @@ -65,7 +65,6 @@ external void Viewer_destroySwapChain( @ffi.Native< ffi.Bool Function( ffi.Pointer, - ffi.Pointer, ffi.Pointer, ffi.Uint64, ffi.Pointer, @@ -76,7 +75,6 @@ external void Viewer_destroySwapChain( ffi.Pointer)>(isLeaf: true) external bool Viewer_render( ffi.Pointer viewer, - ffi.Pointer view, ffi.Pointer swapChain, int frameTimeInNanos, ffi.Pointer pixelBuffer, @@ -146,6 +144,15 @@ external ffi.Pointer Viewer_getSwapChainAt( int index, ); +@ffi.Native< + ffi.Void Function( + ffi.Pointer, ffi.Pointer, ffi.Bool)>(isLeaf: true) +external void Viewer_markViewRenderable( + ffi.Pointer viewer, + ffi.Pointer view, + bool renderable, +); + @ffi.Native Function(ffi.Pointer)>(isLeaf: true) external ffi.Pointer Viewer_getEngine( ffi.Pointer viewer, @@ -1451,6 +1458,15 @@ external void Viewer_captureRenderTargetRenderThread( ffi.Pointer> onComplete, ); +@ffi.Native< + ffi.Void Function(ffi.Pointer, ffi.Pointer, + ffi.Pointer>)>(isLeaf: true) +external void Viewer_requestFrameRenderThread( + ffi.Pointer viewer, + ffi.Pointer tSwapChain, + ffi.Pointer> onComplete, +); + @ffi.Native)>(isLeaf: true) external void destroy_filament_viewer_render_thread( ffi.Pointer viewer, @@ -1471,19 +1487,6 @@ external void set_rendering_render_thread( ffi.Pointer> onComplete, ); -@ffi.Native< - ffi.Void Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer, - ffi.Pointer>)>(isLeaf: true) -external void Viewer_requestFrameRenderThread( - ffi.Pointer viewer, - ffi.Pointer view, - ffi.Pointer tSwapChain, - ffi.Pointer> onComplete, -); - @ffi.Native, ffi.Float)>(isLeaf: true) external void set_frame_interval_render_thread( ffi.Pointer viewer, @@ -1888,14 +1891,25 @@ external void View_setBloom( ); @ffi.Native< - ffi.Void Function( - ffi.Pointer, ffi.Pointer, ffi.Int32)>(isLeaf: true) -external void View_setToneMapping( + ffi.Void Function(ffi.Pointer, ffi.Pointer, + ffi.UnsignedInt)>(symbol: "View_setToneMapping", isLeaf: true) +external void _View_setToneMapping( ffi.Pointer tView, ffi.Pointer tEngine, int toneMapping, ); +void View_setToneMapping( + ffi.Pointer tView, + ffi.Pointer tEngine, + ToneMapping toneMapping, +) => + _View_setToneMapping( + tView, + tEngine, + toneMapping.value, + ); + @ffi.Native< ffi.Void Function( ffi.Pointer, ffi.Bool, ffi.Bool, ffi.Bool)>(isLeaf: true) @@ -2224,10 +2238,20 @@ 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; +enum ToneMapping { + ACES(0), + FILMIC(1), + LINEAR(2); + + final int value; + const ToneMapping(this.value); + + static ToneMapping fromValue(int value) => switch (value) { + 0 => ACES, + 1 => FILMIC, + 2 => LINEAR, + _ => throw ArgumentError("Unknown value for ToneMapping: $value"), + }; } typedef GizmoPickCallback 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 2a54072c..050582df 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 @@ -2040,10 +2040,8 @@ class ThermionViewerFFI extends ThermionViewer { }); final swapChain = Viewer_getSwapChainAt(_viewer!, 0); - final view = Viewer_getViewAt(_viewer!, 0); - Viewer_requestFrameRenderThread( - _viewer!, view, swapChain, callback.nativeFunction); + Viewer_requestFrameRenderThread(_viewer!, swapChain, callback.nativeFunction); await completer.future; } diff --git a/thermion_dart/lib/src/viewer/src/shared_types/shared_types.dart b/thermion_dart/lib/src/viewer/src/shared_types/shared_types.dart index a9904e1e..80480a67 100644 --- a/thermion_dart/lib/src/viewer/src/shared_types/shared_types.dart +++ b/thermion_dart/lib/src/viewer/src/shared_types/shared_types.dart @@ -1,6 +1,7 @@ library shared_types; export 'swap_chain.dart'; +export 'view.dart'; export 'render_target.dart'; export 'camera.dart'; export 'material.dart'; 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 c627446b..07455acd 100644 --- a/thermion_dart/lib/src/viewer/src/shared_types/view.dart +++ b/thermion_dart/lib/src/viewer/src/shared_types/view.dart @@ -15,4 +15,7 @@ abstract class View { Future setRenderTarget(covariant RenderTarget? renderTarget); Future setCamera(covariant Camera camera); Camera getCamera(); + Future setPostProcessing(bool enabled); + Future setAntiAliasing(bool msaa, bool fxaa, bool taa); + Future setRenderable(bool renderable); } diff --git a/thermion_dart/native/include/APIBoundaryTypes.h b/thermion_dart/native/include/APIBoundaryTypes.h index 2eac56fe..fae05ee8 100644 --- a/thermion_dart/native/include/APIBoundaryTypes.h +++ b/thermion_dart/native/include/APIBoundaryTypes.h @@ -19,7 +19,7 @@ extern "C" typedef struct TView TView; typedef struct TGizmo TGizmo; typedef struct TScene TScene; - + struct TMaterialKey { bool doubleSided = 1; bool unlit = 1; diff --git a/thermion_dart/native/include/FilamentViewer.hpp b/thermion_dart/native/include/FilamentViewer.hpp index 80647b51..fb8a8d57 100644 --- a/thermion_dart/native/include/FilamentViewer.hpp +++ b/thermion_dart/native/include/FilamentViewer.hpp @@ -70,7 +70,6 @@ namespace thermion bool render( uint64_t frameTimeInNanos, - View* view, SwapChain* swapChain, void *pixelBuffer, void (*callback)(void *buf, size_t size, void *data), @@ -92,6 +91,22 @@ namespace thermion Renderer *getRenderer(); + std::vector _renderable; + void setRenderable(View* view, bool renderable) { + auto it = std::find(_renderable.begin(), _renderable.end(), view); + + if(renderable) { + if(it == _renderable.end()) { + _renderable.push_back(view); + } + } else { + if(it != _renderable.end()) { + _renderable.erase(it); + } + } + + } + void setBackgroundColor(const float r, const float g, const float b, const float a); void setBackgroundImage(const char *resourcePath, bool fillHeight, uint32_t width, uint32_t height); void clearBackgroundImage(); diff --git a/thermion_dart/native/include/ThermionDartApi.h b/thermion_dart/native/include/ThermionDartApi.h index a0eb5869..edc23b76 100644 --- a/thermion_dart/native/include/ThermionDartApi.h +++ b/thermion_dart/native/include/ThermionDartApi.h @@ -63,7 +63,6 @@ extern "C" EMSCRIPTEN_KEEPALIVE void Viewer_destroySwapChain(TViewer *viewer, TSwapChain* swapChain); EMSCRIPTEN_KEEPALIVE bool Viewer_render( TViewer *viewer, - TView *view, TSwapChain *swapChain, uint64_t frameTimeInNanos, void *pixelBuffer, @@ -85,13 +84,13 @@ 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); + EMSCRIPTEN_KEEPALIVE TSwapChain* Viewer_getSwapChainAt(TViewer *tViewer, int index); + EMSCRIPTEN_KEEPALIVE void Viewer_markViewRenderable(TViewer *viewer, TView* view, bool renderable); // Engine EMSCRIPTEN_KEEPALIVE TEngine *Viewer_getEngine(TViewer* viewer); EMSCRIPTEN_KEEPALIVE TCamera *Engine_getCameraComponent(TEngine* tEngine, EntityId entityId); EMSCRIPTEN_KEEPALIVE void Engine_setTransform(TEngine* tEngine, EntityId entity, double4x4 transform); - EMSCRIPTEN_KEEPALIVE void clear_background_image(TViewer *viewer); EMSCRIPTEN_KEEPALIVE void set_background_image(TViewer *viewer, const char *path, bool fillHeight); diff --git a/thermion_dart/native/include/ThermionDartRenderThreadApi.h b/thermion_dart/native/include/ThermionDartRenderThreadApi.h index 5f2c15b5..077c1119 100644 --- a/thermion_dart/native/include/ThermionDartRenderThreadApi.h +++ b/thermion_dart/native/include/ThermionDartRenderThreadApi.h @@ -30,12 +30,13 @@ extern "C" EMSCRIPTEN_KEEPALIVE void Viewer_renderRenderThread(TViewer *viewer, TView* view, TSwapChain* swapChain); EMSCRIPTEN_KEEPALIVE void Viewer_captureRenderThread(TViewer *viewer, TView* view, TSwapChain* swapChain, uint8_t* out, void (*onComplete)()); EMSCRIPTEN_KEEPALIVE void Viewer_captureRenderTargetRenderThread(TViewer *viewer, TView* view, TSwapChain* swapChain, TRenderTarget* renderTarget, uint8_t* out, void (*onComplete)()); + EMSCRIPTEN_KEEPALIVE void Viewer_requestFrameRenderThread(TViewer *viewer, TSwapChain* tSwapChain, void(*onComplete)()); EMSCRIPTEN_KEEPALIVE void destroy_filament_viewer_render_thread(TViewer *viewer); 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 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/src/FilamentViewer.cpp b/thermion_dart/native/src/FilamentViewer.cpp index bba4145a..6daa969b 100644 --- a/thermion_dart/native/src/FilamentViewer.cpp +++ b/thermion_dart/native/src/FilamentViewer.cpp @@ -166,6 +166,8 @@ namespace thermion createView(); + setRenderable(_views[0], true); + const float aperture = _mainCamera->getAperture(); const float shutterSpeed = _mainCamera->getShutterSpeed(); const float sens = _mainCamera->getSensitivity(); @@ -687,6 +689,7 @@ namespace thermion RenderTarget *FilamentViewer::createRenderTarget(intptr_t texture, uint32_t width, uint32_t height) { + Log("Creating render target with size %d x %d", width, height); // Create filament textures and render targets (note the color buffer has the import call) auto rtColor = filament::Texture::Builder() .width(width) @@ -707,6 +710,7 @@ namespace thermion .texture(RenderTarget::AttachmentPoint::COLOR, rtColor) .texture(RenderTarget::AttachmentPoint::DEPTH, rtDepth) .build(*_engine); + _renderTargets.push_back(rt); return rt; } @@ -800,6 +804,9 @@ namespace thermion _sceneManager->destroyAll(); } + /// @brief + /// @param asset + /// void FilamentViewer::removeEntity(EntityId asset) { _renderMutex.lock(); @@ -1015,14 +1022,13 @@ namespace thermion bool FilamentViewer::render( uint64_t frameTimeInNanos, - View *view, SwapChain *swapChain, void *pixelBuffer, void (*callback)(void *buf, size_t size, void *data), void *data) { - if (!view || !swapChain) + if (!swapChain) { return false; } @@ -1050,15 +1056,11 @@ namespace thermion if (!beginFrame) { _skippedFrames++; - } - - if (beginFrame) - { - - _renderer->render(view); - + } else { + for(auto *view : _renderable) { + _renderer->render(view); + } _frameCount++; - _renderer->endFrame(); } #ifdef __EMSCRIPTEN__ diff --git a/thermion_dart/native/src/ThermionDartApi.cpp b/thermion_dart/native/src/ThermionDartApi.cpp index c51d7e3b..f7b31461 100644 --- a/thermion_dart/native/src/ThermionDartApi.cpp +++ b/thermion_dart/native/src/ThermionDartApi.cpp @@ -341,7 +341,6 @@ extern "C" EMSCRIPTEN_KEEPALIVE bool Viewer_render( TViewer *tViewer, - TView *tView, TSwapChain *tSwapChain, uint64_t frameTimeInNanos, void *pixelBuffer, @@ -355,8 +354,14 @@ extern "C" swapChain = viewer->getSwapChainAt(0); } + return viewer->render(frameTimeInNanos, swapChain, pixelBuffer, callback, data); + } + + EMSCRIPTEN_KEEPALIVE void Viewer_markViewRenderable(TViewer *tViewer, TView* tView, bool renderable) { + auto viewer = reinterpret_cast(tViewer); + auto *view = reinterpret_cast(tView); - return viewer->render(frameTimeInNanos, view, swapChain, pixelBuffer, callback, data); + viewer->setRenderable(view, renderable); } EMSCRIPTEN_KEEPALIVE void Viewer_capture( diff --git a/thermion_dart/native/src/ThermionDartRenderThreadApi.cpp b/thermion_dart/native/src/ThermionDartRenderThreadApi.cpp index 03c4272f..72744dd5 100644 --- a/thermion_dart/native/src/ThermionDartRenderThreadApi.cpp +++ b/thermion_dart/native/src/ThermionDartRenderThreadApi.cpp @@ -50,7 +50,7 @@ public: ~RenderLoop() { _stop = true; - target = nullptr; + swapChain = nullptr; _cv.notify_one(); #ifdef __EMSCRIPTEN__ pthread_join(t, NULL); @@ -82,21 +82,21 @@ public: } } - void requestFrame(TView* tView, TSwapChain* tSwapChain, void (*callback)()) + void requestFrame(TSwapChain* tSwapChain, void (*callback)()) { - this->target = tSwapChain; - this->view = tView; + std::unique_lock lock(_mutex); + this->swapChain = tSwapChain; this->_requestFrameRenderCallback = callback; } void iter() { std::unique_lock lock(_mutex); - if (target) + if (swapChain) { - doRender(view, target); + doRender(swapChain); this->_requestFrameRenderCallback(); - target = nullptr; + swapChain = nullptr; // Calculate and print FPS auto currentTime = std::chrono::high_resolution_clock::now(); @@ -126,8 +126,6 @@ public: _cv.wait_for(lock, std::chrono::microseconds(1000), [this] { return !_tasks.empty() || _stop; }); - if (_stop) - return; } void createViewer(void *const context, @@ -170,14 +168,14 @@ public: { std::packaged_task lambda([=]() mutable { - target = nullptr; + swapChain = nullptr; _viewer = nullptr; destroy_filament_viewer(reinterpret_cast(viewer)); }); auto fut = add_task(lambda); fut.wait(); } - bool doRender(TView* tView, TSwapChain *tSwapChain) + bool doRender(TSwapChain *tSwapChain) { #ifdef __EMSCRIPTEN__ if (emscripten_is_webgl_context_lost(_context) == EM_TRUE) @@ -188,7 +186,7 @@ public: return; } #endif - auto rendered = Viewer_render(_viewer, tView, tSwapChain, 0, nullptr, nullptr, nullptr); + bool rendered = Viewer_render(_viewer, tSwapChain, 0, nullptr, nullptr, nullptr); if (_renderCallback) { _renderCallback(_renderCallbackOwner); @@ -218,8 +216,7 @@ public: } public: - TSwapChain *target; - TView *view; + TSwapChain *swapChain; private: void(*_requestFrameRenderCallback)() = nullptr; @@ -307,7 +304,7 @@ extern "C" } - EMSCRIPTEN_KEEPALIVE void Viewer_requestFrameRenderThread(TViewer *viewer, TView* view, TSwapChain* tSwapChain, void(*onComplete)()) + EMSCRIPTEN_KEEPALIVE void Viewer_requestFrameRenderThread(TViewer *viewer, TSwapChain* tSwapChain, void(*onComplete)()) { if (!_rl) { @@ -315,7 +312,7 @@ extern "C" } else { - _rl->requestFrame(view, tSwapChain, onComplete); + _rl->requestFrame(tSwapChain, onComplete); } } @@ -331,7 +328,9 @@ extern "C" EMSCRIPTEN_KEEPALIVE void Viewer_renderRenderThread(TViewer *viewer, TView *tView, TSwapChain *tSwapChain) { std::packaged_task lambda([=]() mutable - { _rl->doRender(tView, tSwapChain); }); + { + _rl->doRender(tSwapChain); + }); auto fut = _rl->add_task(lambda); } diff --git a/thermion_flutter/thermion_flutter/lib/src/widgets/src/resize_observer.dart b/thermion_flutter/thermion_flutter/lib/src/widgets/src/resize_observer.dart index 218f84f1..7ed5bb62 100644 --- a/thermion_flutter/thermion_flutter/lib/src/widgets/src/resize_observer.dart +++ b/thermion_flutter/thermion_flutter/lib/src/widgets/src/resize_observer.dart @@ -1,7 +1,7 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; -typedef ResizeCallback = void Function(Size newSize); +typedef ResizeCallback = void Function(Size oldSize, Size newSize); class ResizeObserver extends SingleChildRenderObjectWidget { final ResizeCallback onResized; @@ -34,7 +34,7 @@ class _RenderResizeObserver extends RenderProxyBox { void performLayout() async { super.performLayout(); if (size.width != _oldSize.width || size.height != _oldSize.height) { - onLayoutChangedCallback(size); + onLayoutChangedCallback(_oldSize, size); _oldSize = Size(size.width, size.height); } } 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 8cfd2f68..262da5fd 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 @@ -124,7 +124,6 @@ class _ThermionListenerWidgetState extends State { 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)) ]); 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 index 04909ec3..9bb3e761 100644 --- 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 @@ -26,14 +26,21 @@ class _ThermionTextureWidgetState extends State { ThermionFlutterTexture? _texture; RenderTarget? _renderTarget; + static final _views = []; + @override void dispose() { super.dispose(); + _views.remove(widget.view); _texture?.destroy(); } @override void initState() { + if (_views.contains(widget.view)) { + throw Exception("View already embedded in a widget"); + } + _views.add(widget.view); WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { await widget.viewer.initialized; @@ -51,9 +58,10 @@ class _ThermionTextureWidgetState extends State { await widget.view.setRenderTarget(_renderTarget!); - await widget.view.updateViewport(width, height); + await widget.view.updateViewport(_texture!.width, _texture!.height); var camera = await widget.view.getCamera(); - await camera.setLensProjection(aspect: width / height); + await camera.setLensProjection( + aspect: _texture!.width / _texture!.height); if (mounted) { setState(() {}); @@ -73,18 +81,29 @@ class _ThermionTextureWidgetState extends State { await _renderTarget!.destroy(); texture.destroy(); } + _views.clear(); }); }); + _callbackId = _numCallbacks; + _numCallbacks++; super.initState(); } bool _rendering = false; + static int _numCallbacks = 0; + static int _primaryCallback = 0; + late int _callbackId; + int lastRender = 0; + void _requestFrame() { WidgetsBinding.instance.scheduleFrameCallback((d) async { - if (!_rendering) { + if (widget.viewer.rendering && !_rendering) { _rendering = true; - await widget.viewer.requestFrame(); + if (_callbackId == _primaryCallback) { + await widget.viewer.requestFrame(); + lastRender = d.inMilliseconds; + } await _texture?.markFrameAvailable(); _rendering = false; } @@ -92,29 +111,37 @@ class _ThermionTextureWidgetState extends State { }); } - bool _resizing = false; + final _resizing = []; + Timer? _resizeTimer; - Future _resize(Size newSize) async { - + Future _resize(Size oldSize, Size newSize) async { + await Future.wait(_resizing); + _resizeTimer?.cancel(); - _resizeTimer = Timer(const Duration(milliseconds: 10), () async { - if (_resizing || !mounted) { - return; - } - _resizeTimer!.cancel(); - _resizing = true; - + _resizeTimer = Timer(const Duration(milliseconds: 100), () async { + await Future.wait(_resizing); if (!mounted) { return; } + if (newSize.width == _texture?.width && + newSize.height == _texture?.height) { + return; + } + + final completer = Completer(); + + _resizing.add(completer.future); + newSize *= MediaQuery.of(context).devicePixelRatio; var newWidth = newSize.width.ceil(); var newHeight = newSize.height.ceil(); + var lastTextureId = _texture?.hardwareId; + await _texture?.resize( newWidth, newHeight, @@ -122,12 +149,23 @@ class _ThermionTextureWidgetState extends State { 0, ); - await widget.view.updateViewport(newWidth, newHeight); - var camera = await widget.view.getCamera(); - await camera.setLensProjection(aspect: newWidth / newHeight); + if (_texture?.hardwareId != lastTextureId) { + await _renderTarget?.destroy(); + _renderTarget = await widget.viewer.createRenderTarget( + _texture!.width, _texture!.height, _texture!.hardwareId); + await widget.view.setRenderTarget(_renderTarget!); + } + await widget.view.updateViewport(_texture!.width, _texture!.height); + var camera = await widget.view.getCamera(); + await camera.setLensProjection( + aspect: _texture!.width.toDouble() / _texture!.height.toDouble()); + if (!mounted) { + return; + } setState(() {}); - _resizing = false; + completer.complete(); + _resizing.remove(completer.future); }); } @@ -137,30 +175,17 @@ class _ThermionTextureWidgetState extends State { 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")), - ) - ]); + return ResizeObserver( + onResized: _resize, + child: Stack(children: [ + Positioned.fill( + child: Texture( + key: ObjectKey("flutter_texture_${_texture!.flutterId}"), + textureId: _texture!.flutterId, + filterQuality: FilterQuality.none, + freeze: false, + )) + ])); } } diff --git a/thermion_flutter/thermion_flutter/macos/Classes/SwiftThermionFlutterPlugin.swift b/thermion_flutter/thermion_flutter/macos/Classes/SwiftThermionFlutterPlugin.swift index c66f124d..e1c6e0c8 100644 --- a/thermion_flutter/thermion_flutter/macos/Classes/SwiftThermionFlutterPlugin.swift +++ b/thermion_flutter/thermion_flutter/macos/Classes/SwiftThermionFlutterPlugin.swift @@ -5,8 +5,8 @@ public class SwiftThermionFlutterPlugin: NSObject, FlutterPlugin { var registrar : FlutterPluginRegistrar var registry: FlutterTextureRegistry - var texture: ThermionFlutterTexture? - + var textures: [Int64: ThermionFlutterTexture] = [:] + var createdAt = Date() var destroying = false @@ -72,6 +72,13 @@ public class SwiftThermionFlutterPlugin: NSObject, FlutterPlugin { self.registry = textureRegistry; self.registrar = registrar } + + var markTextureFrameAvailable : @convention(c) (UnsafeMutableRawPointer?) -> () = { instancePtr in + let instance:SwiftThermionFlutterPlugin = Unmanaged.fromOpaque(instancePtr!).takeUnretainedValue() + for (_, texture) in instance.textures { + instance.registry.textureFrameAvailable(texture.flutterTextureId) + } + } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { let methodName = call.method; @@ -86,33 +93,39 @@ public class SwiftThermionFlutterPlugin: NSObject, FlutterPlugin { registry.textureFrameAvailable(flutterTextureId) result(nil) case "getRenderCallback": - result(nil) + if(renderCallbackHolder.isEmpty) { + renderCallbackHolder.append(unsafeBitCast(markTextureFrameAvailable, to:Int64.self)) + renderCallbackHolder.append(unsafeBitCast(Unmanaged.passUnretained(self), to:UInt64.self)) + } + result(renderCallbackHolder) case "getDriverPlatform": result(nil) case "getSharedContext": result(nil) case "createTexture": - if(destroying) { - result(nil) - return - } let args = call.arguments as! [Any] let width = args[0] as! Int64 let height = args[1] as! Int64 - self.texture = ThermionFlutterTexture(registry: registry, width: width, height: height) + let texture = ThermionFlutterTexture(registry: registry, width: width, height: height) - if(self.texture!.texture.metalTextureAddress == -1) { + if texture.texture.metalTextureAddress == -1 { result(nil) } else { - result([self.texture!.flutterTextureId as Any, self.texture!.texture.metalTextureAddress, nil]) + textures[texture.flutterTextureId] = texture + result([texture.flutterTextureId, texture.texture.metalTextureAddress, nil]) } case "destroyTexture": - self.destroying = true - self.texture?.destroy() - self.texture = nil - result(true) - self.destroying = false + let args = call.arguments as! [Any] + let flutterTextureId = args[0] as! Int64 + + if let texture = textures[flutterTextureId] { + texture.destroy() + textures.removeValue(forKey: flutterTextureId) + result(true) + } else { + result(false) + } default: result(FlutterMethodNotImplemented) } diff --git a/thermion_flutter/thermion_flutter/macos/Classes/ThermionFlutterTexture.swift b/thermion_flutter/thermion_flutter/macos/Classes/ThermionFlutterTexture.swift index 7fd94a43..5610ba3a 100644 --- a/thermion_flutter/thermion_flutter/macos/Classes/ThermionFlutterTexture.swift +++ b/thermion_flutter/thermion_flutter/macos/Classes/ThermionFlutterTexture.swift @@ -23,7 +23,7 @@ public class ThermionFlutterTexture : NSObject, FlutterTexture { } public func onTextureUnregistered(_ texture:FlutterTexture) { - print("Texture unregistered") + } public func destroy() { diff --git a/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_macos.dart b/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_macos.dart index 50820605..c66f7b8e 100644 --- a/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_macos.dart +++ b/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_macos.dart @@ -13,47 +13,36 @@ import 'package:logging/logging.dart'; /// class ThermionFlutterMacOS extends ThermionFlutterMethodChannelInterface { final _channel = const MethodChannel("dev.thermion.flutter/event"); - final _logger = Logger("ThermionFlutterFFI"); + final _logger = Logger("ThermionFlutterMacOS"); - SwapChain? _swapChain; + static SwapChain? _swapChain; - ThermionFlutterMacOS._() {} + ThermionFlutterMacOS._(); + + static ThermionFlutterMacOS? instance; static void registerWith() { - ThermionFlutterPlatform.instance = ThermionFlutterMacOS._(); + instance ??= ThermionFlutterMacOS._(); + ThermionFlutterPlatform.instance = instance!; + } + + @override + Future createViewer({ThermionFlutterOptions? options}) async { + var viewer = await super.createViewer(options: options); + if (_swapChain != null) { + throw Exception("Only a single swapchain can be created"); + } + // this is the headless swap chain + // since we will be using render targets, the actual dimensions don't matter + _swapChain = await viewer.createSwapChain(1, 1); + return viewer; } // 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); + var texture = MacOSMethodChannelFlutterTexture(_channel); + await texture.resize(width, height, 0, 0); + return texture; } // On MacOS, we currently use textures/render targets, so there's no window to resize @@ -64,14 +53,119 @@ class ThermionFlutterMacOS extends ThermionFlutterMethodChannelInterface { } } +class TextureCacheEntry { + final int flutterId; + final int hardwareId; + final DateTime creationTime; + DateTime? removalTime; + bool inUse; + + TextureCacheEntry(this.flutterId, this.hardwareId, {this.removalTime, this.inUse = true}) + : creationTime = DateTime.now(); +} + + class MacOSMethodChannelFlutterTexture extends MethodChannelFlutterTexture { - MacOSMethodChannelFlutterTexture(super.channel, super.flutterId, - super.hardwareId, super.width, super.height); + final _logger = Logger("MacOSMethodChannelFlutterTexture"); + + int flutterId = -1; + int hardwareId = -1; + int width = -1; + int height = -1; + + static final Map> _textureCache = {}; + + MacOSMethodChannelFlutterTexture(super.channel); @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(); + Future resize( + int newWidth, int newHeight, int newLeft, int newTop) async { + if (newWidth == this.width && + newHeight == this.height && + newLeft == 0 && + newTop == 0) { + return; + } + + this.width = newWidth; + this.height = newHeight; + + // Clean up old textures + await _cleanupOldTextures(); + + final cacheKey = '${width}x$height'; + final availableTextures = + _textureCache[cacheKey]?.where((entry) => !entry.inUse) ?? []; + if (availableTextures.isNotEmpty) { + final cachedTexture = availableTextures.first; + flutterId = cachedTexture.flutterId; + hardwareId = cachedTexture.hardwareId; + cachedTexture.inUse = true; + _logger.info( + "Using cached texture: flutter id $flutterId, hardware id $hardwareId"); + } else { + var result = + await channel.invokeMethod("createTexture", [width, height, 0, 0]); + if (result == null || (result[0] == -1)) { + throw Exception("Failed to create texture"); + } + flutterId = result[0] as int; + hardwareId = result[1] as int; + + final newEntry = TextureCacheEntry(flutterId, hardwareId, inUse: true); + _textureCache.putIfAbsent(cacheKey, () => []).add(newEntry); + _logger.info( + "Created new MacOS texture: flutter id $flutterId, hardware id $hardwareId"); + } + + // Mark old texture as not in use + if (this.width != -1 && this.height != -1) { + final oldCacheKey = '${this.width}x${this.height}'; + final oldEntry = _textureCache[oldCacheKey]?.firstWhere( + (entry) => entry.flutterId == this.flutterId, + orElse: () => TextureCacheEntry(-1, -1), + ); + if (oldEntry != null && oldEntry.flutterId != -1) { + oldEntry.inUse = false; + oldEntry.removalTime = DateTime.now(); + } + } + } + + Future _cleanupOldTextures() async { + final now = DateTime.now(); + final entriesToRemove = >{}; + + for (var entry in _textureCache.entries) { + final expiredTextures = entry.value.where((texture) { + return !texture.inUse && + texture.removalTime != null && + now.difference(texture.removalTime!).inSeconds > 5; + }).toList(); + + if (expiredTextures.isNotEmpty) { + entriesToRemove[entry.key] = expiredTextures; + } + } + + for (var entry in entriesToRemove.entries) { + for (var texture in entry.value) { + await _destroyTexture(texture.flutterId, texture.hardwareId); + _logger.info("Destroying texture: ${texture.flutterId}"); + _textureCache[entry.key]?.remove(texture); + } + if (_textureCache[entry.key]?.isEmpty ?? false) { + _textureCache.remove(entry.key); + } + } + } + + Future _destroyTexture(int flutterId, int hardwareId) async { + try { + await channel.invokeMethod("destroyTexture", [flutterId, hardwareId]); + _logger.info("Destroyed old texture: flutter id $flutterId, hardware id $hardwareId"); + } catch (e) { + _logger.severe("Failed to destroy texture: $e"); } } } 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 index 1c8249c9..c88990bc 100644 --- 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 @@ -18,6 +18,7 @@ abstract class ThermionFlutterMethodChannelInterface final _logger = Logger("ThermionFlutterMethodChannelInterface"); ThermionViewerFFI? viewer; + SwapChain? _swapChain; Future createViewer({ThermionFlutterOptions? options}) async { if (viewer != null) { @@ -31,9 +32,13 @@ abstract class ThermionFlutterMethodChannelInterface if (resourceLoader == nullptr) { throw Exception("Failed to get resource loader"); } - + + // var renderCallbackResult = await _channel.invokeMethod("getRenderCallback"); var renderCallback = nullptr; + // Pointer)>>.fromAddress( + // renderCallbackResult[0]); var renderCallbackOwner = nullptr; + // Pointer.fromAddress(renderCallbackResult[1]); var driverPlatform = await _channel.invokeMethod("getDriverPlatform"); var driverPtr = driverPlatform == null @@ -54,33 +59,33 @@ abstract class ThermionFlutterMethodChannelInterface sharedContext: sharedContextPtr, uberArchivePath: options?.uberarchivePath); await viewer!.initialized; + return viewer!; } } abstract class MethodChannelFlutterTexture extends ThermionFlutterTexture { - final MethodChannel _channel; + final MethodChannel channel; - MethodChannelFlutterTexture( - this._channel, this.flutterId, this.hardwareId, this.width, this.height); + MethodChannelFlutterTexture(this.channel); Future destroy() async { - await _channel.invokeMethod("destroyTexture", hardwareId); + await channel.invokeMethod("destroyTexture", hardwareId); } @override - final int flutterId; + int get flutterId; @override - final int hardwareId; + int get hardwareId; @override - final int height; + int get height; @override - final int width; + int get width; Future markFrameAvailable() async { - await _channel.invokeMethod("markTextureFrameAvailable", this.flutterId); + await channel.invokeMethod("markTextureFrameAvailable", this.flutterId); } } 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 70620993..43c06db5 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 @@ -41,6 +41,6 @@ abstract class ThermionFlutterPlatform extends PlatformInterface { /// /// /// - Future resizeWindow( +Future resizeWindow( int width, int height, int offsetTop, int offsetRight); }