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