feat: working implementation of multiple widgets on macos
This commit is contained in:
@@ -69,11 +69,15 @@ class DelegateInputHandler implements InputHandler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
factory DelegateInputHandler.flight(ThermionViewer viewer,
|
factory DelegateInputHandler.flight(ThermionViewer viewer,
|
||||||
{PickDelegate? pickDelegate, bool freeLook=false, double? clampY, ThermionEntity? entity}) =>
|
{PickDelegate? pickDelegate,
|
||||||
|
bool freeLook = false,
|
||||||
|
double? clampY,
|
||||||
|
ThermionEntity? entity}) =>
|
||||||
DelegateInputHandler(
|
DelegateInputHandler(
|
||||||
viewer: viewer,
|
viewer: viewer,
|
||||||
pickDelegate: pickDelegate,
|
pickDelegate: pickDelegate,
|
||||||
transformDelegate: FreeFlightInputHandlerDelegate(viewer, clampY:clampY, entity:entity),
|
transformDelegate: FreeFlightInputHandlerDelegate(viewer,
|
||||||
|
clampY: clampY, entity: entity),
|
||||||
actions: {
|
actions: {
|
||||||
InputType.MMB_HOLD_AND_MOVE: InputAction.ROTATE,
|
InputType.MMB_HOLD_AND_MOVE: InputAction.ROTATE,
|
||||||
InputType.SCROLLWHEEL: InputAction.TRANSLATE,
|
InputType.SCROLLWHEEL: InputAction.TRANSLATE,
|
||||||
@@ -82,8 +86,7 @@ class DelegateInputHandler implements InputHandler {
|
|||||||
InputType.KEYDOWN_W: InputAction.TRANSLATE,
|
InputType.KEYDOWN_W: InputAction.TRANSLATE,
|
||||||
InputType.KEYDOWN_S: InputAction.TRANSLATE,
|
InputType.KEYDOWN_S: InputAction.TRANSLATE,
|
||||||
InputType.KEYDOWN_D: InputAction.TRANSLATE,
|
InputType.KEYDOWN_D: InputAction.TRANSLATE,
|
||||||
if(freeLook)
|
if (freeLook) InputType.POINTER_MOVE: InputAction.ROTATE,
|
||||||
InputType.POINTER_MOVE: InputAction.ROTATE,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
bool _processing = false;
|
bool _processing = false;
|
||||||
@@ -160,15 +163,13 @@ class DelegateInputHandler implements InputHandler {
|
|||||||
}
|
}
|
||||||
if (isMiddle) {
|
if (isMiddle) {
|
||||||
_inputDeltas[InputType.MMB_HOLD_AND_MOVE] =
|
_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 {
|
} else {
|
||||||
_inputDeltas[InputType.LMB_HOLD_AND_MOVE] =
|
_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
|
@override
|
||||||
@@ -180,7 +181,8 @@ class DelegateInputHandler implements InputHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_inputDeltas[InputType.POINTER_MOVE] =
|
_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
|
@override
|
||||||
@@ -191,8 +193,8 @@ class DelegateInputHandler implements InputHandler {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
_inputDeltas[InputType.SCROLLWHEEL] =
|
_inputDeltas[InputType.SCROLLWHEEL] =
|
||||||
(_inputDeltas[InputType.SCROLLWHEEL] ?? Vector3.zero())
|
(_inputDeltas[InputType.SCROLLWHEEL] ?? Vector3.zero()) +
|
||||||
+ Vector3(0,0, scrollDelta > 0 ? 1 : -1);
|
Vector3(0, 0, scrollDelta > 0 ? 1 : -1);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_logger.warning("Error during scroll accumulation: $e");
|
_logger.warning("Error during scroll accumulation: $e");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,4 +39,18 @@ class FFIView extends View {
|
|||||||
final engine = Viewer_getEngine(viewer);
|
final engine = Viewer_getEngine(viewer);
|
||||||
return FFICamera(View_getCamera(view), engine);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ external void Viewer_destroySwapChain(
|
|||||||
@ffi.Native<
|
@ffi.Native<
|
||||||
ffi.Bool Function(
|
ffi.Bool Function(
|
||||||
ffi.Pointer<TViewer>,
|
ffi.Pointer<TViewer>,
|
||||||
ffi.Pointer<TView>,
|
|
||||||
ffi.Pointer<TSwapChain>,
|
ffi.Pointer<TSwapChain>,
|
||||||
ffi.Uint64,
|
ffi.Uint64,
|
||||||
ffi.Pointer<ffi.Void>,
|
ffi.Pointer<ffi.Void>,
|
||||||
@@ -76,7 +75,6 @@ external void Viewer_destroySwapChain(
|
|||||||
ffi.Pointer<ffi.Void>)>(isLeaf: true)
|
ffi.Pointer<ffi.Void>)>(isLeaf: true)
|
||||||
external bool Viewer_render(
|
external bool Viewer_render(
|
||||||
ffi.Pointer<TViewer> viewer,
|
ffi.Pointer<TViewer> viewer,
|
||||||
ffi.Pointer<TView> view,
|
|
||||||
ffi.Pointer<TSwapChain> swapChain,
|
ffi.Pointer<TSwapChain> swapChain,
|
||||||
int frameTimeInNanos,
|
int frameTimeInNanos,
|
||||||
ffi.Pointer<ffi.Void> pixelBuffer,
|
ffi.Pointer<ffi.Void> pixelBuffer,
|
||||||
@@ -146,6 +144,15 @@ external ffi.Pointer<TSwapChain> Viewer_getSwapChainAt(
|
|||||||
int index,
|
int index,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ffi.Native<
|
||||||
|
ffi.Void Function(
|
||||||
|
ffi.Pointer<TViewer>, ffi.Pointer<TView>, ffi.Bool)>(isLeaf: true)
|
||||||
|
external void Viewer_markViewRenderable(
|
||||||
|
ffi.Pointer<TViewer> viewer,
|
||||||
|
ffi.Pointer<TView> view,
|
||||||
|
bool renderable,
|
||||||
|
);
|
||||||
|
|
||||||
@ffi.Native<ffi.Pointer<TEngine> Function(ffi.Pointer<TViewer>)>(isLeaf: true)
|
@ffi.Native<ffi.Pointer<TEngine> Function(ffi.Pointer<TViewer>)>(isLeaf: true)
|
||||||
external ffi.Pointer<TEngine> Viewer_getEngine(
|
external ffi.Pointer<TEngine> Viewer_getEngine(
|
||||||
ffi.Pointer<TViewer> viewer,
|
ffi.Pointer<TViewer> viewer,
|
||||||
@@ -1451,6 +1458,15 @@ external void Viewer_captureRenderTargetRenderThread(
|
|||||||
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>> onComplete,
|
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>> onComplete,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ffi.Native<
|
||||||
|
ffi.Void Function(ffi.Pointer<TViewer>, ffi.Pointer<TSwapChain>,
|
||||||
|
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>>)>(isLeaf: true)
|
||||||
|
external void Viewer_requestFrameRenderThread(
|
||||||
|
ffi.Pointer<TViewer> viewer,
|
||||||
|
ffi.Pointer<TSwapChain> tSwapChain,
|
||||||
|
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>> onComplete,
|
||||||
|
);
|
||||||
|
|
||||||
@ffi.Native<ffi.Void Function(ffi.Pointer<TViewer>)>(isLeaf: true)
|
@ffi.Native<ffi.Void Function(ffi.Pointer<TViewer>)>(isLeaf: true)
|
||||||
external void destroy_filament_viewer_render_thread(
|
external void destroy_filament_viewer_render_thread(
|
||||||
ffi.Pointer<TViewer> viewer,
|
ffi.Pointer<TViewer> viewer,
|
||||||
@@ -1471,19 +1487,6 @@ external void set_rendering_render_thread(
|
|||||||
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>> onComplete,
|
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>> onComplete,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ffi.Native<
|
|
||||||
ffi.Void Function(
|
|
||||||
ffi.Pointer<TViewer>,
|
|
||||||
ffi.Pointer<TView>,
|
|
||||||
ffi.Pointer<TSwapChain>,
|
|
||||||
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>>)>(isLeaf: true)
|
|
||||||
external void Viewer_requestFrameRenderThread(
|
|
||||||
ffi.Pointer<TViewer> viewer,
|
|
||||||
ffi.Pointer<TView> view,
|
|
||||||
ffi.Pointer<TSwapChain> tSwapChain,
|
|
||||||
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>> onComplete,
|
|
||||||
);
|
|
||||||
|
|
||||||
@ffi.Native<ffi.Void Function(ffi.Pointer<TViewer>, ffi.Float)>(isLeaf: true)
|
@ffi.Native<ffi.Void Function(ffi.Pointer<TViewer>, ffi.Float)>(isLeaf: true)
|
||||||
external void set_frame_interval_render_thread(
|
external void set_frame_interval_render_thread(
|
||||||
ffi.Pointer<TViewer> viewer,
|
ffi.Pointer<TViewer> viewer,
|
||||||
@@ -1888,14 +1891,25 @@ external void View_setBloom(
|
|||||||
);
|
);
|
||||||
|
|
||||||
@ffi.Native<
|
@ffi.Native<
|
||||||
ffi.Void Function(
|
ffi.Void Function(ffi.Pointer<TView>, ffi.Pointer<TEngine>,
|
||||||
ffi.Pointer<TView>, ffi.Pointer<TEngine>, ffi.Int32)>(isLeaf: true)
|
ffi.UnsignedInt)>(symbol: "View_setToneMapping", isLeaf: true)
|
||||||
external void View_setToneMapping(
|
external void _View_setToneMapping(
|
||||||
ffi.Pointer<TView> tView,
|
ffi.Pointer<TView> tView,
|
||||||
ffi.Pointer<TEngine> tEngine,
|
ffi.Pointer<TEngine> tEngine,
|
||||||
int toneMapping,
|
int toneMapping,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
void View_setToneMapping(
|
||||||
|
ffi.Pointer<TView> tView,
|
||||||
|
ffi.Pointer<TEngine> tEngine,
|
||||||
|
ToneMapping toneMapping,
|
||||||
|
) =>
|
||||||
|
_View_setToneMapping(
|
||||||
|
tView,
|
||||||
|
tEngine,
|
||||||
|
toneMapping.value,
|
||||||
|
);
|
||||||
|
|
||||||
@ffi.Native<
|
@ffi.Native<
|
||||||
ffi.Void Function(
|
ffi.Void Function(
|
||||||
ffi.Pointer<TView>, ffi.Bool, ffi.Bool, ffi.Bool)>(isLeaf: true)
|
ffi.Pointer<TView>, ffi.Bool, ffi.Bool, ffi.Bool)>(isLeaf: true)
|
||||||
@@ -2224,10 +2238,20 @@ final class TViewport extends ffi.Struct {
|
|||||||
external int height;
|
external int height;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class ToneMapping {
|
enum ToneMapping {
|
||||||
static const int ACES = 0;
|
ACES(0),
|
||||||
static const int FILMIC = 1;
|
FILMIC(1),
|
||||||
static const int LINEAR = 2;
|
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
|
typedef GizmoPickCallback
|
||||||
|
|||||||
@@ -2040,10 +2040,8 @@ class ThermionViewerFFI extends ThermionViewer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
final swapChain = Viewer_getSwapChainAt(_viewer!, 0);
|
final swapChain = Viewer_getSwapChainAt(_viewer!, 0);
|
||||||
final view = Viewer_getViewAt(_viewer!, 0);
|
|
||||||
|
|
||||||
Viewer_requestFrameRenderThread(
|
Viewer_requestFrameRenderThread(_viewer!, swapChain, callback.nativeFunction);
|
||||||
_viewer!, view, swapChain, callback.nativeFunction);
|
|
||||||
|
|
||||||
await completer.future;
|
await completer.future;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
library shared_types;
|
library shared_types;
|
||||||
|
|
||||||
export 'swap_chain.dart';
|
export 'swap_chain.dart';
|
||||||
|
export 'view.dart';
|
||||||
export 'render_target.dart';
|
export 'render_target.dart';
|
||||||
export 'camera.dart';
|
export 'camera.dart';
|
||||||
export 'material.dart';
|
export 'material.dart';
|
||||||
|
|||||||
@@ -15,4 +15,7 @@ abstract class View {
|
|||||||
Future setRenderTarget(covariant RenderTarget? renderTarget);
|
Future setRenderTarget(covariant RenderTarget? renderTarget);
|
||||||
Future setCamera(covariant Camera camera);
|
Future setCamera(covariant Camera camera);
|
||||||
Camera getCamera();
|
Camera getCamera();
|
||||||
|
Future setPostProcessing(bool enabled);
|
||||||
|
Future setAntiAliasing(bool msaa, bool fxaa, bool taa);
|
||||||
|
Future setRenderable(bool renderable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ extern "C"
|
|||||||
typedef struct TView TView;
|
typedef struct TView TView;
|
||||||
typedef struct TGizmo TGizmo;
|
typedef struct TGizmo TGizmo;
|
||||||
typedef struct TScene TScene;
|
typedef struct TScene TScene;
|
||||||
|
|
||||||
struct TMaterialKey {
|
struct TMaterialKey {
|
||||||
bool doubleSided = 1;
|
bool doubleSided = 1;
|
||||||
bool unlit = 1;
|
bool unlit = 1;
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ namespace thermion
|
|||||||
|
|
||||||
bool render(
|
bool render(
|
||||||
uint64_t frameTimeInNanos,
|
uint64_t frameTimeInNanos,
|
||||||
View* view,
|
|
||||||
SwapChain* swapChain,
|
SwapChain* swapChain,
|
||||||
void *pixelBuffer,
|
void *pixelBuffer,
|
||||||
void (*callback)(void *buf, size_t size, void *data),
|
void (*callback)(void *buf, size_t size, void *data),
|
||||||
@@ -92,6 +91,22 @@ namespace thermion
|
|||||||
|
|
||||||
Renderer *getRenderer();
|
Renderer *getRenderer();
|
||||||
|
|
||||||
|
std::vector<View*> _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 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 setBackgroundImage(const char *resourcePath, bool fillHeight, uint32_t width, uint32_t height);
|
||||||
void clearBackgroundImage();
|
void clearBackgroundImage();
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ extern "C"
|
|||||||
EMSCRIPTEN_KEEPALIVE void Viewer_destroySwapChain(TViewer *viewer, TSwapChain* swapChain);
|
EMSCRIPTEN_KEEPALIVE void Viewer_destroySwapChain(TViewer *viewer, TSwapChain* swapChain);
|
||||||
EMSCRIPTEN_KEEPALIVE bool Viewer_render(
|
EMSCRIPTEN_KEEPALIVE bool Viewer_render(
|
||||||
TViewer *viewer,
|
TViewer *viewer,
|
||||||
TView *view,
|
|
||||||
TSwapChain *swapChain,
|
TSwapChain *swapChain,
|
||||||
uint64_t frameTimeInNanos,
|
uint64_t frameTimeInNanos,
|
||||||
void *pixelBuffer,
|
void *pixelBuffer,
|
||||||
@@ -85,13 +84,13 @@ extern "C"
|
|||||||
EMSCRIPTEN_KEEPALIVE TView* Viewer_createView(TViewer *viewer);
|
EMSCRIPTEN_KEEPALIVE TView* Viewer_createView(TViewer *viewer);
|
||||||
EMSCRIPTEN_KEEPALIVE TView* Viewer_getViewAt(TViewer *viewer, int index);
|
EMSCRIPTEN_KEEPALIVE TView* Viewer_getViewAt(TViewer *viewer, int index);
|
||||||
EMSCRIPTEN_KEEPALIVE void Viewer_setMainCamera(TViewer *tViewer, TView *tView);
|
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
|
// Engine
|
||||||
EMSCRIPTEN_KEEPALIVE TEngine *Viewer_getEngine(TViewer* viewer);
|
EMSCRIPTEN_KEEPALIVE TEngine *Viewer_getEngine(TViewer* viewer);
|
||||||
EMSCRIPTEN_KEEPALIVE TCamera *Engine_getCameraComponent(TEngine* tEngine, EntityId entityId);
|
EMSCRIPTEN_KEEPALIVE TCamera *Engine_getCameraComponent(TEngine* tEngine, EntityId entityId);
|
||||||
EMSCRIPTEN_KEEPALIVE void Engine_setTransform(TEngine* tEngine, EntityId entity, double4x4 transform);
|
EMSCRIPTEN_KEEPALIVE void Engine_setTransform(TEngine* tEngine, EntityId entity, double4x4 transform);
|
||||||
|
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE void clear_background_image(TViewer *viewer);
|
EMSCRIPTEN_KEEPALIVE void clear_background_image(TViewer *viewer);
|
||||||
EMSCRIPTEN_KEEPALIVE void set_background_image(TViewer *viewer, const char *path, bool fillHeight);
|
EMSCRIPTEN_KEEPALIVE void set_background_image(TViewer *viewer, const char *path, bool fillHeight);
|
||||||
|
|||||||
@@ -30,12 +30,13 @@ extern "C"
|
|||||||
EMSCRIPTEN_KEEPALIVE void Viewer_renderRenderThread(TViewer *viewer, TView* view, TSwapChain* swapChain);
|
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_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_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 void destroy_filament_viewer_render_thread(TViewer *viewer);
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE FilamentRenderCallback make_render_callback_fn_pointer(FilamentRenderCallback);
|
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 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_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 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);
|
EMSCRIPTEN_KEEPALIVE void clear_background_image_render_thread(TViewer *viewer);
|
||||||
|
|||||||
@@ -166,6 +166,8 @@ namespace thermion
|
|||||||
|
|
||||||
createView();
|
createView();
|
||||||
|
|
||||||
|
setRenderable(_views[0], true);
|
||||||
|
|
||||||
const float aperture = _mainCamera->getAperture();
|
const float aperture = _mainCamera->getAperture();
|
||||||
const float shutterSpeed = _mainCamera->getShutterSpeed();
|
const float shutterSpeed = _mainCamera->getShutterSpeed();
|
||||||
const float sens = _mainCamera->getSensitivity();
|
const float sens = _mainCamera->getSensitivity();
|
||||||
@@ -687,6 +689,7 @@ namespace thermion
|
|||||||
|
|
||||||
RenderTarget *FilamentViewer::createRenderTarget(intptr_t texture, uint32_t width, uint32_t height)
|
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)
|
// Create filament textures and render targets (note the color buffer has the import call)
|
||||||
auto rtColor = filament::Texture::Builder()
|
auto rtColor = filament::Texture::Builder()
|
||||||
.width(width)
|
.width(width)
|
||||||
@@ -707,6 +710,7 @@ namespace thermion
|
|||||||
.texture(RenderTarget::AttachmentPoint::COLOR, rtColor)
|
.texture(RenderTarget::AttachmentPoint::COLOR, rtColor)
|
||||||
.texture(RenderTarget::AttachmentPoint::DEPTH, rtDepth)
|
.texture(RenderTarget::AttachmentPoint::DEPTH, rtDepth)
|
||||||
.build(*_engine);
|
.build(*_engine);
|
||||||
|
_renderTargets.push_back(rt);
|
||||||
return rt;
|
return rt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -800,6 +804,9 @@ namespace thermion
|
|||||||
_sceneManager->destroyAll();
|
_sceneManager->destroyAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief
|
||||||
|
/// @param asset
|
||||||
|
///
|
||||||
void FilamentViewer::removeEntity(EntityId asset)
|
void FilamentViewer::removeEntity(EntityId asset)
|
||||||
{
|
{
|
||||||
_renderMutex.lock();
|
_renderMutex.lock();
|
||||||
@@ -1015,14 +1022,13 @@ namespace thermion
|
|||||||
|
|
||||||
bool FilamentViewer::render(
|
bool FilamentViewer::render(
|
||||||
uint64_t frameTimeInNanos,
|
uint64_t frameTimeInNanos,
|
||||||
View *view,
|
|
||||||
SwapChain *swapChain,
|
SwapChain *swapChain,
|
||||||
void *pixelBuffer,
|
void *pixelBuffer,
|
||||||
void (*callback)(void *buf, size_t size, void *data),
|
void (*callback)(void *buf, size_t size, void *data),
|
||||||
void *data)
|
void *data)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!view || !swapChain)
|
if (!swapChain)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1050,15 +1056,11 @@ namespace thermion
|
|||||||
if (!beginFrame)
|
if (!beginFrame)
|
||||||
{
|
{
|
||||||
_skippedFrames++;
|
_skippedFrames++;
|
||||||
}
|
} else {
|
||||||
|
for(auto *view : _renderable) {
|
||||||
if (beginFrame)
|
_renderer->render(view);
|
||||||
{
|
}
|
||||||
|
|
||||||
_renderer->render(view);
|
|
||||||
|
|
||||||
_frameCount++;
|
_frameCount++;
|
||||||
|
|
||||||
_renderer->endFrame();
|
_renderer->endFrame();
|
||||||
}
|
}
|
||||||
#ifdef __EMSCRIPTEN__
|
#ifdef __EMSCRIPTEN__
|
||||||
|
|||||||
@@ -341,7 +341,6 @@ extern "C"
|
|||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE bool Viewer_render(
|
EMSCRIPTEN_KEEPALIVE bool Viewer_render(
|
||||||
TViewer *tViewer,
|
TViewer *tViewer,
|
||||||
TView *tView,
|
|
||||||
TSwapChain *tSwapChain,
|
TSwapChain *tSwapChain,
|
||||||
uint64_t frameTimeInNanos,
|
uint64_t frameTimeInNanos,
|
||||||
void *pixelBuffer,
|
void *pixelBuffer,
|
||||||
@@ -355,8 +354,14 @@ extern "C"
|
|||||||
swapChain = viewer->getSwapChainAt(0);
|
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<FilamentViewer *>(tViewer);
|
||||||
|
|
||||||
auto *view = reinterpret_cast<View*>(tView);
|
auto *view = reinterpret_cast<View*>(tView);
|
||||||
return viewer->render(frameTimeInNanos, view, swapChain, pixelBuffer, callback, data);
|
viewer->setRenderable(view, renderable);
|
||||||
}
|
}
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE void Viewer_capture(
|
EMSCRIPTEN_KEEPALIVE void Viewer_capture(
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ public:
|
|||||||
~RenderLoop()
|
~RenderLoop()
|
||||||
{
|
{
|
||||||
_stop = true;
|
_stop = true;
|
||||||
target = nullptr;
|
swapChain = nullptr;
|
||||||
_cv.notify_one();
|
_cv.notify_one();
|
||||||
#ifdef __EMSCRIPTEN__
|
#ifdef __EMSCRIPTEN__
|
||||||
pthread_join(t, NULL);
|
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;
|
std::unique_lock<std::mutex> lock(_mutex);
|
||||||
this->view = tView;
|
this->swapChain = tSwapChain;
|
||||||
this->_requestFrameRenderCallback = callback;
|
this->_requestFrameRenderCallback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
void iter()
|
void iter()
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(_mutex);
|
std::unique_lock<std::mutex> lock(_mutex);
|
||||||
if (target)
|
if (swapChain)
|
||||||
{
|
{
|
||||||
doRender(view, target);
|
doRender(swapChain);
|
||||||
this->_requestFrameRenderCallback();
|
this->_requestFrameRenderCallback();
|
||||||
target = nullptr;
|
swapChain = nullptr;
|
||||||
|
|
||||||
// Calculate and print FPS
|
// Calculate and print FPS
|
||||||
auto currentTime = std::chrono::high_resolution_clock::now();
|
auto currentTime = std::chrono::high_resolution_clock::now();
|
||||||
@@ -126,8 +126,6 @@ public:
|
|||||||
_cv.wait_for(lock, std::chrono::microseconds(1000), [this]
|
_cv.wait_for(lock, std::chrono::microseconds(1000), [this]
|
||||||
{ return !_tasks.empty() || _stop; });
|
{ return !_tasks.empty() || _stop; });
|
||||||
|
|
||||||
if (_stop)
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void createViewer(void *const context,
|
void createViewer(void *const context,
|
||||||
@@ -170,14 +168,14 @@ public:
|
|||||||
{
|
{
|
||||||
std::packaged_task<void()> lambda([=]() mutable
|
std::packaged_task<void()> lambda([=]() mutable
|
||||||
{
|
{
|
||||||
target = nullptr;
|
swapChain = nullptr;
|
||||||
_viewer = nullptr;
|
_viewer = nullptr;
|
||||||
destroy_filament_viewer(reinterpret_cast<TViewer*>(viewer)); });
|
destroy_filament_viewer(reinterpret_cast<TViewer*>(viewer)); });
|
||||||
auto fut = add_task(lambda);
|
auto fut = add_task(lambda);
|
||||||
fut.wait();
|
fut.wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool doRender(TView* tView, TSwapChain *tSwapChain)
|
bool doRender(TSwapChain *tSwapChain)
|
||||||
{
|
{
|
||||||
#ifdef __EMSCRIPTEN__
|
#ifdef __EMSCRIPTEN__
|
||||||
if (emscripten_is_webgl_context_lost(_context) == EM_TRUE)
|
if (emscripten_is_webgl_context_lost(_context) == EM_TRUE)
|
||||||
@@ -188,7 +186,7 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
auto rendered = Viewer_render(_viewer, tView, tSwapChain, 0, nullptr, nullptr, nullptr);
|
bool rendered = Viewer_render(_viewer, tSwapChain, 0, nullptr, nullptr, nullptr);
|
||||||
if (_renderCallback)
|
if (_renderCallback)
|
||||||
{
|
{
|
||||||
_renderCallback(_renderCallbackOwner);
|
_renderCallback(_renderCallbackOwner);
|
||||||
@@ -218,8 +216,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TSwapChain *target;
|
TSwapChain *swapChain;
|
||||||
TView *view;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void(*_requestFrameRenderCallback)() = nullptr;
|
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)
|
if (!_rl)
|
||||||
{
|
{
|
||||||
@@ -315,7 +312,7 @@ extern "C"
|
|||||||
}
|
}
|
||||||
else
|
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)
|
EMSCRIPTEN_KEEPALIVE void Viewer_renderRenderThread(TViewer *viewer, TView *tView, TSwapChain *tSwapChain)
|
||||||
{
|
{
|
||||||
std::packaged_task<void()> lambda([=]() mutable
|
std::packaged_task<void()> lambda([=]() mutable
|
||||||
{ _rl->doRender(tView, tSwapChain); });
|
{
|
||||||
|
_rl->doRender(tSwapChain);
|
||||||
|
});
|
||||||
auto fut = _rl->add_task(lambda);
|
auto fut = _rl->add_task(lambda);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
typedef ResizeCallback = void Function(Size newSize);
|
typedef ResizeCallback = void Function(Size oldSize, Size newSize);
|
||||||
|
|
||||||
class ResizeObserver extends SingleChildRenderObjectWidget {
|
class ResizeObserver extends SingleChildRenderObjectWidget {
|
||||||
final ResizeCallback onResized;
|
final ResizeCallback onResized;
|
||||||
@@ -34,7 +34,7 @@ class _RenderResizeObserver extends RenderProxyBox {
|
|||||||
void performLayout() async {
|
void performLayout() async {
|
||||||
super.performLayout();
|
super.performLayout();
|
||||||
if (size.width != _oldSize.width || size.height != _oldSize.height) {
|
if (size.width != _oldSize.width || size.height != _oldSize.height) {
|
||||||
onLayoutChangedCallback(size);
|
onLayoutChangedCallback(_oldSize, size);
|
||||||
_oldSize = Size(size.width, size.height);
|
_oldSize = Size(size.width, size.height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,7 +124,6 @@ class _ThermionListenerWidgetState extends State<ThermionListenerWidget> {
|
|||||||
return widget.child ?? Container();
|
return widget.child ?? Container();
|
||||||
}
|
}
|
||||||
return Stack(children: [
|
return Stack(children: [
|
||||||
if (widget.child != null) Positioned.fill(child: widget.child!),
|
|
||||||
if (isDesktop) Positioned.fill(child: _desktop(pixelRatio)),
|
if (isDesktop) Positioned.fill(child: _desktop(pixelRatio)),
|
||||||
if (!isDesktop) Positioned.fill(child: _mobile(pixelRatio))
|
if (!isDesktop) Positioned.fill(child: _mobile(pixelRatio))
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -26,14 +26,21 @@ class _ThermionTextureWidgetState extends State<ThermionTextureWidget> {
|
|||||||
ThermionFlutterTexture? _texture;
|
ThermionFlutterTexture? _texture;
|
||||||
RenderTarget? _renderTarget;
|
RenderTarget? _renderTarget;
|
||||||
|
|
||||||
|
static final _views = <t.View>[];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
_views.remove(widget.view);
|
||||||
_texture?.destroy();
|
_texture?.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
if (_views.contains(widget.view)) {
|
||||||
|
throw Exception("View already embedded in a widget");
|
||||||
|
}
|
||||||
|
_views.add(widget.view);
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||||
await widget.viewer.initialized;
|
await widget.viewer.initialized;
|
||||||
|
|
||||||
@@ -51,9 +58,10 @@ class _ThermionTextureWidgetState extends State<ThermionTextureWidget> {
|
|||||||
|
|
||||||
await widget.view.setRenderTarget(_renderTarget!);
|
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();
|
var camera = await widget.view.getCamera();
|
||||||
await camera.setLensProjection(aspect: width / height);
|
await camera.setLensProjection(
|
||||||
|
aspect: _texture!.width / _texture!.height);
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
@@ -73,18 +81,29 @@ class _ThermionTextureWidgetState extends State<ThermionTextureWidget> {
|
|||||||
await _renderTarget!.destroy();
|
await _renderTarget!.destroy();
|
||||||
texture.destroy();
|
texture.destroy();
|
||||||
}
|
}
|
||||||
|
_views.clear();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
_callbackId = _numCallbacks;
|
||||||
|
_numCallbacks++;
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _rendering = false;
|
bool _rendering = false;
|
||||||
|
|
||||||
|
static int _numCallbacks = 0;
|
||||||
|
static int _primaryCallback = 0;
|
||||||
|
late int _callbackId;
|
||||||
|
int lastRender = 0;
|
||||||
|
|
||||||
void _requestFrame() {
|
void _requestFrame() {
|
||||||
WidgetsBinding.instance.scheduleFrameCallback((d) async {
|
WidgetsBinding.instance.scheduleFrameCallback((d) async {
|
||||||
if (!_rendering) {
|
if (widget.viewer.rendering && !_rendering) {
|
||||||
_rendering = true;
|
_rendering = true;
|
||||||
await widget.viewer.requestFrame();
|
if (_callbackId == _primaryCallback) {
|
||||||
|
await widget.viewer.requestFrame();
|
||||||
|
lastRender = d.inMilliseconds;
|
||||||
|
}
|
||||||
await _texture?.markFrameAvailable();
|
await _texture?.markFrameAvailable();
|
||||||
_rendering = false;
|
_rendering = false;
|
||||||
}
|
}
|
||||||
@@ -92,29 +111,37 @@ class _ThermionTextureWidgetState extends State<ThermionTextureWidget> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _resizing = false;
|
final _resizing = <Future>[];
|
||||||
|
|
||||||
Timer? _resizeTimer;
|
Timer? _resizeTimer;
|
||||||
|
|
||||||
Future _resize(Size newSize) async {
|
Future _resize(Size oldSize, Size newSize) async {
|
||||||
|
await Future.wait(_resizing);
|
||||||
|
|
||||||
_resizeTimer?.cancel();
|
_resizeTimer?.cancel();
|
||||||
|
|
||||||
_resizeTimer = Timer(const Duration(milliseconds: 10), () async {
|
_resizeTimer = Timer(const Duration(milliseconds: 100), () async {
|
||||||
if (_resizing || !mounted) {
|
await Future.wait(_resizing);
|
||||||
return;
|
|
||||||
}
|
|
||||||
_resizeTimer!.cancel();
|
|
||||||
_resizing = true;
|
|
||||||
|
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (newSize.width == _texture?.width &&
|
||||||
|
newSize.height == _texture?.height) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final completer = Completer();
|
||||||
|
|
||||||
|
_resizing.add(completer.future);
|
||||||
|
|
||||||
newSize *= MediaQuery.of(context).devicePixelRatio;
|
newSize *= MediaQuery.of(context).devicePixelRatio;
|
||||||
|
|
||||||
var newWidth = newSize.width.ceil();
|
var newWidth = newSize.width.ceil();
|
||||||
var newHeight = newSize.height.ceil();
|
var newHeight = newSize.height.ceil();
|
||||||
|
|
||||||
|
var lastTextureId = _texture?.hardwareId;
|
||||||
|
|
||||||
await _texture?.resize(
|
await _texture?.resize(
|
||||||
newWidth,
|
newWidth,
|
||||||
newHeight,
|
newHeight,
|
||||||
@@ -122,12 +149,23 @@ class _ThermionTextureWidgetState extends State<ThermionTextureWidget> {
|
|||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
|
|
||||||
await widget.view.updateViewport(newWidth, newHeight);
|
if (_texture?.hardwareId != lastTextureId) {
|
||||||
var camera = await widget.view.getCamera();
|
await _renderTarget?.destroy();
|
||||||
await camera.setLensProjection(aspect: newWidth / newHeight);
|
_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(() {});
|
setState(() {});
|
||||||
_resizing = false;
|
completer.complete();
|
||||||
|
_resizing.remove(completer.future);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,30 +175,17 @@ class _ThermionTextureWidgetState extends State<ThermionTextureWidget> {
|
|||||||
return widget.initial ?? Container(color: Colors.red);
|
return widget.initial ?? Container(color: Colors.red);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Stack(children: [
|
return ResizeObserver(
|
||||||
Positioned.fill(
|
onResized: _resize,
|
||||||
child: ResizeObserver(
|
child: Stack(children: [
|
||||||
onResized: _resize,
|
Positioned.fill(
|
||||||
child: Stack(children: [
|
child: Texture(
|
||||||
Positioned.fill(
|
key: ObjectKey("flutter_texture_${_texture!.flutterId}"),
|
||||||
child: Texture(
|
textureId: _texture!.flutterId,
|
||||||
key: ObjectKey("flutter_texture_${_texture!.flutterId}"),
|
filterQuality: FilterQuality.none,
|
||||||
textureId: _texture!.flutterId,
|
freeze: false,
|
||||||
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")),
|
|
||||||
)
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ public class SwiftThermionFlutterPlugin: NSObject, FlutterPlugin {
|
|||||||
|
|
||||||
var registrar : FlutterPluginRegistrar
|
var registrar : FlutterPluginRegistrar
|
||||||
var registry: FlutterTextureRegistry
|
var registry: FlutterTextureRegistry
|
||||||
var texture: ThermionFlutterTexture?
|
var textures: [Int64: ThermionFlutterTexture] = [:]
|
||||||
|
|
||||||
var createdAt = Date()
|
var createdAt = Date()
|
||||||
|
|
||||||
var destroying = false
|
var destroying = false
|
||||||
@@ -72,6 +72,13 @@ public class SwiftThermionFlutterPlugin: NSObject, FlutterPlugin {
|
|||||||
self.registry = textureRegistry;
|
self.registry = textureRegistry;
|
||||||
self.registrar = registrar
|
self.registrar = registrar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var markTextureFrameAvailable : @convention(c) (UnsafeMutableRawPointer?) -> () = { instancePtr in
|
||||||
|
let instance:SwiftThermionFlutterPlugin = Unmanaged<SwiftThermionFlutterPlugin>.fromOpaque(instancePtr!).takeUnretainedValue()
|
||||||
|
for (_, texture) in instance.textures {
|
||||||
|
instance.registry.textureFrameAvailable(texture.flutterTextureId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||||
let methodName = call.method;
|
let methodName = call.method;
|
||||||
@@ -86,33 +93,39 @@ public class SwiftThermionFlutterPlugin: NSObject, FlutterPlugin {
|
|||||||
registry.textureFrameAvailable(flutterTextureId)
|
registry.textureFrameAvailable(flutterTextureId)
|
||||||
result(nil)
|
result(nil)
|
||||||
case "getRenderCallback":
|
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":
|
case "getDriverPlatform":
|
||||||
result(nil)
|
result(nil)
|
||||||
case "getSharedContext":
|
case "getSharedContext":
|
||||||
result(nil)
|
result(nil)
|
||||||
case "createTexture":
|
case "createTexture":
|
||||||
if(destroying) {
|
|
||||||
result(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let args = call.arguments as! [Any]
|
let args = call.arguments as! [Any]
|
||||||
let width = args[0] as! Int64
|
let width = args[0] as! Int64
|
||||||
let height = args[1] 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)
|
result(nil)
|
||||||
} else {
|
} 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":
|
case "destroyTexture":
|
||||||
self.destroying = true
|
let args = call.arguments as! [Any]
|
||||||
self.texture?.destroy()
|
let flutterTextureId = args[0] as! Int64
|
||||||
self.texture = nil
|
|
||||||
result(true)
|
if let texture = textures[flutterTextureId] {
|
||||||
self.destroying = false
|
texture.destroy()
|
||||||
|
textures.removeValue(forKey: flutterTextureId)
|
||||||
|
result(true)
|
||||||
|
} else {
|
||||||
|
result(false)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
result(FlutterMethodNotImplemented)
|
result(FlutterMethodNotImplemented)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public class ThermionFlutterTexture : NSObject, FlutterTexture {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func onTextureUnregistered(_ texture:FlutterTexture) {
|
public func onTextureUnregistered(_ texture:FlutterTexture) {
|
||||||
print("Texture unregistered")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func destroy() {
|
public func destroy() {
|
||||||
|
|||||||
@@ -13,47 +13,36 @@ import 'package:logging/logging.dart';
|
|||||||
///
|
///
|
||||||
class ThermionFlutterMacOS extends ThermionFlutterMethodChannelInterface {
|
class ThermionFlutterMacOS extends ThermionFlutterMethodChannelInterface {
|
||||||
final _channel = const MethodChannel("dev.thermion.flutter/event");
|
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() {
|
static void registerWith() {
|
||||||
ThermionFlutterPlatform.instance = ThermionFlutterMacOS._();
|
instance ??= ThermionFlutterMacOS._();
|
||||||
|
ThermionFlutterPlatform.instance = instance!;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<ThermionViewer> 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
|
// On desktop platforms, textures are always created
|
||||||
Future<ThermionFlutterTexture?> createTexture(int width, int height) async {
|
Future<ThermionFlutterTexture?> createTexture(int width, int height) async {
|
||||||
if (_swapChain == null) {
|
var texture = MacOSMethodChannelFlutterTexture(_channel);
|
||||||
// this is the headless swap chain
|
await texture.resize(width, height, 0, 0);
|
||||||
// since we will be using render targets, the actual dimensions don't matter
|
return texture;
|
||||||
_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
|
// 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 {
|
class MacOSMethodChannelFlutterTexture extends MethodChannelFlutterTexture {
|
||||||
MacOSMethodChannelFlutterTexture(super.channel, super.flutterId,
|
final _logger = Logger("MacOSMethodChannelFlutterTexture");
|
||||||
super.hardwareId, super.width, super.height);
|
|
||||||
|
int flutterId = -1;
|
||||||
|
int hardwareId = -1;
|
||||||
|
int width = -1;
|
||||||
|
int height = -1;
|
||||||
|
|
||||||
|
static final Map<String, List<TextureCacheEntry>> _textureCache = {};
|
||||||
|
|
||||||
|
MacOSMethodChannelFlutterTexture(super.channel);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future resize(int width, int height, int left, int top) async {
|
Future<void> resize(
|
||||||
if (width > this.width || height > this.height || left != 0 || top != 0) {
|
int newWidth, int newHeight, int newLeft, int newTop) async {
|
||||||
throw Exception();
|
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 = <String, List<TextureCacheEntry>>{};
|
||||||
|
|
||||||
|
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<void> _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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ abstract class ThermionFlutterMethodChannelInterface
|
|||||||
final _logger = Logger("ThermionFlutterMethodChannelInterface");
|
final _logger = Logger("ThermionFlutterMethodChannelInterface");
|
||||||
|
|
||||||
ThermionViewerFFI? viewer;
|
ThermionViewerFFI? viewer;
|
||||||
|
SwapChain? _swapChain;
|
||||||
|
|
||||||
Future<ThermionViewer> createViewer({ThermionFlutterOptions? options}) async {
|
Future<ThermionViewer> createViewer({ThermionFlutterOptions? options}) async {
|
||||||
if (viewer != null) {
|
if (viewer != null) {
|
||||||
@@ -31,9 +32,13 @@ abstract class ThermionFlutterMethodChannelInterface
|
|||||||
if (resourceLoader == nullptr) {
|
if (resourceLoader == nullptr) {
|
||||||
throw Exception("Failed to get resource loader");
|
throw Exception("Failed to get resource loader");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// var renderCallbackResult = await _channel.invokeMethod("getRenderCallback");
|
||||||
var renderCallback = nullptr;
|
var renderCallback = nullptr;
|
||||||
|
// Pointer<NativeFunction<Void Function(Pointer<Void>)>>.fromAddress(
|
||||||
|
// renderCallbackResult[0]);
|
||||||
var renderCallbackOwner = nullptr;
|
var renderCallbackOwner = nullptr;
|
||||||
|
// Pointer<Void>.fromAddress(renderCallbackResult[1]);
|
||||||
|
|
||||||
var driverPlatform = await _channel.invokeMethod("getDriverPlatform");
|
var driverPlatform = await _channel.invokeMethod("getDriverPlatform");
|
||||||
var driverPtr = driverPlatform == null
|
var driverPtr = driverPlatform == null
|
||||||
@@ -54,33 +59,33 @@ abstract class ThermionFlutterMethodChannelInterface
|
|||||||
sharedContext: sharedContextPtr,
|
sharedContext: sharedContextPtr,
|
||||||
uberArchivePath: options?.uberarchivePath);
|
uberArchivePath: options?.uberarchivePath);
|
||||||
await viewer!.initialized;
|
await viewer!.initialized;
|
||||||
|
|
||||||
return viewer!;
|
return viewer!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class MethodChannelFlutterTexture extends ThermionFlutterTexture {
|
abstract class MethodChannelFlutterTexture extends ThermionFlutterTexture {
|
||||||
final MethodChannel _channel;
|
final MethodChannel channel;
|
||||||
|
|
||||||
MethodChannelFlutterTexture(
|
MethodChannelFlutterTexture(this.channel);
|
||||||
this._channel, this.flutterId, this.hardwareId, this.width, this.height);
|
|
||||||
|
|
||||||
Future destroy() async {
|
Future destroy() async {
|
||||||
await _channel.invokeMethod("destroyTexture", hardwareId);
|
await channel.invokeMethod("destroyTexture", hardwareId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final int flutterId;
|
int get flutterId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final int hardwareId;
|
int get hardwareId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final int height;
|
int get height;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final int width;
|
int get width;
|
||||||
|
|
||||||
Future markFrameAvailable() async {
|
Future markFrameAvailable() async {
|
||||||
await _channel.invokeMethod("markTextureFrameAvailable", this.flutterId);
|
await channel.invokeMethod("markTextureFrameAvailable", this.flutterId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,6 @@ abstract class ThermionFlutterPlatform extends PlatformInterface {
|
|||||||
///
|
///
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
Future resizeWindow(
|
Future resizeWindow(
|
||||||
int width, int height, int offsetTop, int offsetRight);
|
int width, int height, int offsetTop, int offsetRight);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user