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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final view = await viewer.getViewAt(0);
|
||||||
|
final viewport = await view.getViewport();
|
||||||
|
|
||||||
var viewMatrix = await viewer.getCameraViewMatrix();
|
var viewMatrix = await viewer.getCameraViewMatrix();
|
||||||
var modelMatrix = await viewer.getCameraModelMatrix();
|
var modelMatrix = await viewer.getCameraModelMatrix();
|
||||||
var projectionMatrix = await viewer.getCameraProjectionMatrix();
|
var projectionMatrix = await viewer.getCameraProjectionMatrix();
|
||||||
@@ -80,12 +83,8 @@ class FixedOrbitRotateInputHandlerDelegate implements InputHandlerDelegate {
|
|||||||
intersectionInClipSpace / intersectionInClipSpace.w;
|
intersectionInClipSpace / intersectionInClipSpace.w;
|
||||||
|
|
||||||
// Calculate new camera position based on rotation
|
// Calculate new camera position based on rotation
|
||||||
final ndcX = 2 *
|
final ndcX = 2 * ((-_queuedRotationDelta.x) / viewport.width);
|
||||||
((-_queuedRotationDelta.x * viewer.pixelRatio) /
|
final ndcY = 2 * ((_queuedRotationDelta.y) / viewport.height);
|
||||||
viewer.viewportDimensions.$1);
|
|
||||||
final ndcY = 2 *
|
|
||||||
((_queuedRotationDelta.y * viewer.pixelRatio) /
|
|
||||||
viewer.viewportDimensions.$2);
|
|
||||||
final ndc = Vector4(ndcX, ndcY, intersectionInNdcSpace.z, 1.0);
|
final ndc = Vector4(ndcX, ndcY, intersectionInNdcSpace.z, 1.0);
|
||||||
|
|
||||||
var clipSpace = Vector4(
|
var clipSpace = Vector4(
|
||||||
|
|||||||
@@ -93,9 +93,9 @@ class FreeFlightInputHandlerDelegate implements InputHandlerDelegate {
|
|||||||
// Apply rotation
|
// Apply rotation
|
||||||
if (_queuedRotationDelta.length2 > 0.0) {
|
if (_queuedRotationDelta.length2 > 0.0) {
|
||||||
double deltaX =
|
double deltaX =
|
||||||
_queuedRotationDelta.x * rotationSensitivity * viewer.pixelRatio;
|
_queuedRotationDelta.x * rotationSensitivity;
|
||||||
double deltaY =
|
double deltaY =
|
||||||
_queuedRotationDelta.y * rotationSensitivity * viewer.pixelRatio;
|
_queuedRotationDelta.y * rotationSensitivity;
|
||||||
|
|
||||||
Quaternion yawRotation = Quaternion.axisAngle(_up, -deltaX);
|
Quaternion yawRotation = Quaternion.axisAngle(_up, -deltaX);
|
||||||
Quaternion pitchRotation = Quaternion.axisAngle(_right, -deltaY);
|
Quaternion pitchRotation = Quaternion.axisAngle(_right, -deltaY);
|
||||||
@@ -109,8 +109,8 @@ class FreeFlightInputHandlerDelegate implements InputHandlerDelegate {
|
|||||||
Vector3 right = _right.clone()..applyQuaternion(currentRotation);
|
Vector3 right = _right.clone()..applyQuaternion(currentRotation);
|
||||||
Vector3 up = _up.clone()..applyQuaternion(currentRotation);
|
Vector3 up = _up.clone()..applyQuaternion(currentRotation);
|
||||||
|
|
||||||
double deltaX = _queuedPanDelta.x * panSensitivity * viewer.pixelRatio;
|
double deltaX = _queuedPanDelta.x * panSensitivity;
|
||||||
double deltaY = _queuedPanDelta.y * panSensitivity * viewer.pixelRatio;
|
double deltaY = _queuedPanDelta.y * panSensitivity;
|
||||||
|
|
||||||
relativeTranslation += right * deltaX + up * deltaY;
|
relativeTranslation += right * deltaX + up * deltaY;
|
||||||
_queuedPanDelta = Vector2.zero();
|
_queuedPanDelta = Vector2.zero();
|
||||||
|
|||||||
@@ -96,9 +96,9 @@ class OverTheShoulderCameraDelegate implements InputHandlerDelegate {
|
|||||||
// camera is always looking at -Z, whereas models generally face towards +Z
|
// camera is always looking at -Z, whereas models generally face towards +Z
|
||||||
if (_queuedRotationDelta.length2 > 0.0) {
|
if (_queuedRotationDelta.length2 > 0.0) {
|
||||||
double deltaX =
|
double deltaX =
|
||||||
_queuedRotationDelta.x * rotationSensitivity * viewer.pixelRatio;
|
_queuedRotationDelta.x * rotationSensitivity;
|
||||||
double deltaY =
|
double deltaY =
|
||||||
_queuedRotationDelta.y * rotationSensitivity * viewer.pixelRatio;
|
_queuedRotationDelta.y * rotationSensitivity;
|
||||||
|
|
||||||
cameraLookAt = Matrix4.rotationY(-deltaX) *
|
cameraLookAt = Matrix4.rotationY(-deltaX) *
|
||||||
Matrix4.rotationX(-deltaY) *
|
Matrix4.rotationX(-deltaY) *
|
||||||
|
|||||||
@@ -1,19 +1,31 @@
|
|||||||
import 'dart:async';
|
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 '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 x;
|
||||||
final ThermionEntity y;
|
final ThermionEntity y;
|
||||||
final ThermionEntity z;
|
final ThermionEntity z;
|
||||||
final ThermionEntity center;
|
final ThermionEntity center;
|
||||||
|
|
||||||
final ThermionViewer _viewer;
|
|
||||||
|
|
||||||
ThermionEntity? _activeAxis;
|
ThermionEntity? _activeAxis;
|
||||||
ThermionEntity? _activeEntity;
|
ThermionEntity? _activeEntity;
|
||||||
|
ThermionViewer viewer;
|
||||||
|
|
||||||
bool _visible = false;
|
bool _visible = false;
|
||||||
bool get isVisible => _visible;
|
bool get isVisible => _visible;
|
||||||
@@ -26,10 +38,9 @@ class Gizmo extends AbstractGizmo {
|
|||||||
Stream<Aabb2> get boundingBox => _boundingBoxController.stream;
|
Stream<Aabb2> get boundingBox => _boundingBoxController.stream;
|
||||||
final _boundingBoxController = StreamController<Aabb2>.broadcast();
|
final _boundingBoxController = StreamController<Aabb2>.broadcast();
|
||||||
|
|
||||||
Gizmo(this.x, this.y, this.z, this.center, this._viewer,
|
BaseGizmo({required this.x, required this.y, required this.z, required this.center, required this.viewer,
|
||||||
{this.ignore = const <ThermionEntity>{}}) {
|
this.ignore = const <ThermionEntity>{}}) {
|
||||||
_viewer.gizmoPickResult.listen(_onGizmoPickResult);
|
onPick(_onGizmoPickResult);
|
||||||
_viewer.pickResult.listen(_onPickResult);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final _stopwatch = Stopwatch();
|
final _stopwatch = Stopwatch();
|
||||||
@@ -52,12 +63,10 @@ class Gizmo extends AbstractGizmo {
|
|||||||
final axis = Vector3(_activeAxis == x ? 1.0 : 0.0,
|
final axis = Vector3(_activeAxis == x ? 1.0 : 0.0,
|
||||||
_activeAxis == y ? 1.0 : 0.0, _activeAxis == z ? 1.0 : 0.0);
|
_activeAxis == y ? 1.0 : 0.0, _activeAxis == z ? 1.0 : 0.0);
|
||||||
|
|
||||||
await _viewer.queueRelativePositionUpdateWorldAxis(
|
await viewer.queueRelativePositionUpdateWorldAxis(
|
||||||
_activeEntity!,
|
_activeEntity!,
|
||||||
_transX * _viewer.pixelRatio,
|
_transX,
|
||||||
-_transY *
|
-_transY, // flip the sign because "up" in NDC Y axis is positive, but negative in Flutter
|
||||||
_viewer
|
|
||||||
.pixelRatio, // flip the sign because "up" in NDC Y axis is positive, but negative in Flutter
|
|
||||||
axis.x,
|
axis.x,
|
||||||
axis.y,
|
axis.y,
|
||||||
axis.z);
|
axis.z);
|
||||||
@@ -70,10 +79,6 @@ class Gizmo extends AbstractGizmo {
|
|||||||
_activeAxis = null;
|
_activeAxis = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onPickResult(FilamentPickResult result) async {
|
|
||||||
await attach(result.entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onGizmoPickResult(FilamentPickResult result) async {
|
void _onGizmoPickResult(FilamentPickResult result) async {
|
||||||
if (result.entity == x || result.entity == y || result.entity == z) {
|
if (result.entity == x || result.entity == y || result.entity == z) {
|
||||||
_activeAxis = result.entity;
|
_activeAxis = result.entity;
|
||||||
@@ -98,21 +103,25 @@ class Gizmo extends AbstractGizmo {
|
|||||||
_visible = true;
|
_visible = true;
|
||||||
|
|
||||||
if (_activeEntity != null) {
|
if (_activeEntity != null) {
|
||||||
await _viewer.removeStencilHighlight(_activeEntity!);
|
await viewer.removeStencilHighlight(_activeEntity!);
|
||||||
}
|
}
|
||||||
_activeEntity = entity;
|
_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 {
|
Future detach() async {
|
||||||
await _viewer.setGizmoVisibility(false);
|
await setVisibility(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void checkHover(double x, double y) {
|
void checkHover(int x, int y) {
|
||||||
_viewer.pickGizmo(x.toInt(), y.toInt());
|
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.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)
|
@ffi.Native<ffi.Pointer<TEngine> Function(ffi.Pointer<TViewer>)>(isLeaf: true)
|
||||||
external ffi.Pointer<TEngine> Viewer_getEngine(
|
external ffi.Pointer<TEngine> Viewer_getEngine(
|
||||||
ffi.Pointer<TViewer> viewer,
|
ffi.Pointer<TViewer> viewer,
|
||||||
@@ -662,9 +669,9 @@ external void SceneManager_setVisibilityLayer(
|
|||||||
int layer,
|
int layer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ffi.Native<ffi.Pointer<TGizmo> Function(ffi.Pointer<TSceneManager>)>(
|
@ffi.Native<ffi.Pointer<TScene> Function(ffi.Pointer<TSceneManager>)>(
|
||||||
isLeaf: true)
|
isLeaf: true)
|
||||||
external ffi.Pointer<TGizmo> SceneManager_getGizmo(
|
external ffi.Pointer<TScene> SceneManager_getScene(
|
||||||
ffi.Pointer<TSceneManager> tSceneManager,
|
ffi.Pointer<TSceneManager> tSceneManager,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1176,14 +1183,6 @@ external void set_priority(
|
|||||||
int 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<
|
@ffi.Native<
|
||||||
Aabb2 Function(
|
Aabb2 Function(
|
||||||
ffi.Pointer<TSceneManager>, ffi.Pointer<TView>, EntityId)>(isLeaf: true)
|
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.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.Native<
|
||||||
ffi.Void Function(ffi.Pointer<TSceneManager>, EntityId, ffi.Float,
|
ffi.Void Function(ffi.Pointer<TSceneManager>, EntityId, ffi.Float,
|
||||||
ffi.Float, ffi.Float)>(isLeaf: true)
|
ffi.Float, ffi.Float)>(isLeaf: true)
|
||||||
@@ -1480,10 +1472,15 @@ external void set_rendering_render_thread(
|
|||||||
);
|
);
|
||||||
|
|
||||||
@ffi.Native<
|
@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)
|
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<TViewer> viewer,
|
||||||
|
ffi.Pointer<TView> view,
|
||||||
|
ffi.Pointer<TSwapChain> tSwapChain,
|
||||||
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>> onComplete,
|
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>> onComplete,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1892,7 +1889,7 @@ external void View_setBloom(
|
|||||||
|
|
||||||
@ffi.Native<
|
@ffi.Native<
|
||||||
ffi.Void Function(
|
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(
|
external void View_setToneMapping(
|
||||||
ffi.Pointer<TView> tView,
|
ffi.Pointer<TView> tView,
|
||||||
ffi.Pointer<TEngine> tEngine,
|
ffi.Pointer<TEngine> tEngine,
|
||||||
@@ -1924,30 +1921,39 @@ external void View_setCamera(
|
|||||||
ffi.Pointer<TCamera> tCamera,
|
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)
|
@ffi.Native<ffi.Pointer<TCamera> Function(ffi.Pointer<TView>)>(isLeaf: true)
|
||||||
external ffi.Pointer<TCamera> View_getCamera(
|
external ffi.Pointer<TCamera> View_getCamera(
|
||||||
ffi.Pointer<TView> tView,
|
ffi.Pointer<TView> tView,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ffi.Native<
|
@ffi.Native<
|
||||||
ffi.Void Function(
|
ffi.Pointer<TGizmo> Function(ffi.Pointer<TEngine>, ffi.Pointer<TView>,
|
||||||
ffi.Pointer<TGizmo>,
|
ffi.Pointer<TScene>)>(isLeaf: true)
|
||||||
ffi.Pointer<TView>,
|
external ffi.Pointer<TGizmo> Gizmo_new(
|
||||||
ffi.Int,
|
ffi.Pointer<TEngine> tEngine,
|
||||||
ffi.Int,
|
ffi.Pointer<TView> tView,
|
||||||
ffi.Pointer<
|
ffi.Pointer<TScene> tScene,
|
||||||
ffi.NativeFunction<
|
);
|
||||||
ffi.Void Function(
|
|
||||||
EntityId entityId, ffi.Int x, ffi.Int y)>>)>(isLeaf: true)
|
@ffi.Native<
|
||||||
|
ffi.Void Function(ffi.Pointer<TGizmo>, ffi.Uint32, ffi.Uint32,
|
||||||
|
GizmoPickCallback)>(isLeaf: true)
|
||||||
external void Gizmo_pick(
|
external void Gizmo_pick(
|
||||||
ffi.Pointer<TGizmo> tGizmo,
|
ffi.Pointer<TGizmo> tGizmo,
|
||||||
ffi.Pointer<TView> tView,
|
|
||||||
int x,
|
int x,
|
||||||
int y,
|
int y,
|
||||||
ffi.Pointer<
|
GizmoPickCallback callback,
|
||||||
ffi.NativeFunction<
|
);
|
||||||
ffi.Void Function(EntityId entityId, ffi.Int x, ffi.Int y)>>
|
|
||||||
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 {}
|
final class TCamera extends ffi.Opaque {}
|
||||||
@@ -1970,6 +1976,8 @@ final class TView extends ffi.Opaque {}
|
|||||||
|
|
||||||
final class TGizmo extends ffi.Opaque {}
|
final class TGizmo extends ffi.Opaque {}
|
||||||
|
|
||||||
|
final class TScene extends ffi.Opaque {}
|
||||||
|
|
||||||
final class TMaterialKey extends ffi.Struct {
|
final class TMaterialKey extends ffi.Struct {
|
||||||
@ffi.Bool()
|
@ffi.Bool()
|
||||||
external bool doubleSided;
|
external bool doubleSided;
|
||||||
@@ -2216,6 +2224,19 @@ final class TViewport extends ffi.Struct {
|
|||||||
external int height;
|
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 __bool_true_false_are_defined = 1;
|
||||||
|
|
||||||
const int true1 = 1;
|
const int true1 = 1;
|
||||||
|
|||||||
@@ -1,22 +1,20 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:ffi';
|
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:animation_tools_dart/animation_tools_dart.dart';
|
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';
|
||||||
import 'package:vector_math/vector_math_64.dart' as v64;
|
import 'package:vector_math/vector_math_64.dart' as v64;
|
||||||
import '../../../../entities/gizmo.dart';
|
import '../../../../utils/gizmo.dart';
|
||||||
import '../../../../utils/matrix.dart';
|
import '../../../../utils/matrix.dart';
|
||||||
import '../../events.dart';
|
import '../../events.dart';
|
||||||
import '../../shared_types/camera.dart';
|
|
||||||
import '../../shared_types/view.dart';
|
import '../../shared_types/view.dart';
|
||||||
import '../../thermion_viewer_base.dart';
|
import '../../thermion_viewer_base.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
import 'callbacks.dart';
|
import 'callbacks.dart';
|
||||||
import 'camera_ffi.dart';
|
import 'camera_ffi.dart';
|
||||||
import 'thermion_dart.g.dart';
|
import 'ffi_view.dart';
|
||||||
|
|
||||||
// ignore: constant_identifier_names
|
// ignore: constant_identifier_names
|
||||||
const ThermionEntity _FILAMENT_ASSET_ERROR = 0;
|
const ThermionEntity _FILAMENT_ASSET_ERROR = 0;
|
||||||
@@ -26,8 +24,6 @@ typedef RenderCallback = Pointer<NativeFunction<Void Function(Pointer<Void>)>>;
|
|||||||
class ThermionViewerFFI extends ThermionViewer {
|
class ThermionViewerFFI extends ThermionViewer {
|
||||||
final _logger = Logger("ThermionViewerFFI");
|
final _logger = Logger("ThermionViewerFFI");
|
||||||
|
|
||||||
double pixelRatio = 1.0;
|
|
||||||
|
|
||||||
Pointer<TSceneManager>? _sceneManager;
|
Pointer<TSceneManager>? _sceneManager;
|
||||||
|
|
||||||
Pointer<TViewer>? _viewer;
|
Pointer<TViewer>? _viewer;
|
||||||
@@ -87,13 +83,9 @@ class ThermionViewerFFI extends ThermionViewer {
|
|||||||
this._driver = driver ?? nullptr;
|
this._driver = driver ?? nullptr;
|
||||||
this._sharedContext = sharedContext ?? nullptr;
|
this._sharedContext = sharedContext ?? nullptr;
|
||||||
|
|
||||||
_onPickResultCallable =
|
// _onPickResultCallable =
|
||||||
NativeCallable<Void Function(EntityId entityId, Int x, Int y)>.listener(
|
// NativeCallable<Void Function(EntityId entityId, Int x, Int y)>.listener(
|
||||||
_onPickResult);
|
// _onPickResult);
|
||||||
|
|
||||||
_onGizmoPickResultCallable =
|
|
||||||
NativeCallable<Void Function(EntityId entityId, Int x, Int y)>.listener(
|
|
||||||
_onGizmoPickResult);
|
|
||||||
|
|
||||||
_initialize();
|
_initialize();
|
||||||
}
|
}
|
||||||
@@ -131,7 +123,6 @@ class ThermionViewerFFI extends ThermionViewer {
|
|||||||
///
|
///
|
||||||
///
|
///
|
||||||
Future updateViewportAndCameraProjection(double width, double height) async {
|
Future updateViewportAndCameraProjection(double width, double height) async {
|
||||||
viewportDimensions = (width * pixelRatio, height * pixelRatio);
|
|
||||||
var mainView = FFIView(Viewer_getViewAt(_viewer!, 0), _viewer!);
|
var mainView = FFIView(Viewer_getViewAt(_viewer!, 0), _viewer!);
|
||||||
mainView.updateViewport(width.toInt(), height.toInt());
|
mainView.updateViewport(width.toInt(), height.toInt());
|
||||||
|
|
||||||
@@ -148,7 +139,7 @@ class ThermionViewerFFI extends ThermionViewer {
|
|||||||
far = kFar;
|
far = kFar;
|
||||||
}
|
}
|
||||||
|
|
||||||
var aspect = viewportDimensions.$1 / viewportDimensions.$2;
|
var aspect = width / height;
|
||||||
var focalLength = await camera.getFocalLength();
|
var focalLength = await camera.getFocalLength();
|
||||||
if (focalLength.abs() < 0.1) {
|
if (focalLength.abs() < 0.1) {
|
||||||
focalLength = kFocalLength;
|
focalLength = kFocalLength;
|
||||||
@@ -161,8 +152,8 @@ class ThermionViewerFFI extends ThermionViewer {
|
|||||||
Future<SwapChain> createSwapChain(int width, int height,
|
Future<SwapChain> createSwapChain(int width, int height,
|
||||||
{Pointer<Void>? surface}) async {
|
{Pointer<Void>? surface}) async {
|
||||||
var swapChain = await withPointerCallback<TSwapChain>((callback) {
|
var swapChain = await withPointerCallback<TSwapChain>((callback) {
|
||||||
return Viewer_createSwapChainRenderThread(_viewer!, surface ?? nullptr,
|
return Viewer_createSwapChainRenderThread(
|
||||||
width, height, callback);
|
_viewer!, surface ?? nullptr, width, height, callback);
|
||||||
});
|
});
|
||||||
return FFISwapChain(swapChain, _viewer!);
|
return FFISwapChain(swapChain, _viewer!);
|
||||||
}
|
}
|
||||||
@@ -193,11 +184,6 @@ class ThermionViewerFFI extends ThermionViewer {
|
|||||||
|
|
||||||
_sceneManager = Viewer_getSceneManager(_viewer!);
|
_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);
|
this._initialized.complete(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,8 +210,9 @@ class ThermionViewerFFI extends ThermionViewer {
|
|||||||
///
|
///
|
||||||
///
|
///
|
||||||
@override
|
@override
|
||||||
Future render(FFISwapChain swapChain) async {
|
Future render({FFISwapChain? swapChain}) async {
|
||||||
final view = (await getViewAt(0)) as FFIView;
|
final view = (await getViewAt(0)) as FFIView;
|
||||||
|
swapChain ??= FFISwapChain(Viewer_getSwapChainAt(_viewer!, 0), _viewer!);
|
||||||
Viewer_renderRenderThread(_viewer!, view.view, swapChain.swapChain);
|
Viewer_renderRenderThread(_viewer!, view.view, swapChain.swapChain);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,20 +220,24 @@ class ThermionViewerFFI extends ThermionViewer {
|
|||||||
///
|
///
|
||||||
///
|
///
|
||||||
@override
|
@override
|
||||||
Future<Uint8List> capture(FFISwapChain swapChain,
|
Future<Uint8List> capture(
|
||||||
{FFIView? view, FFIRenderTarget? renderTarget}) async {
|
{FFIView? view,
|
||||||
final length = this.viewportDimensions.$1.toInt() *
|
FFISwapChain? swapChain,
|
||||||
this.viewportDimensions.$2.toInt() *
|
FFIRenderTarget? renderTarget}) async {
|
||||||
4;
|
|
||||||
final out = Uint8List(length);
|
|
||||||
view ??= (await getViewAt(0)) as FFIView;
|
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) {
|
await withVoidCallback((cb) {
|
||||||
if (renderTarget != null) {
|
if (renderTarget != null) {
|
||||||
Viewer_captureRenderTargetRenderThread(_viewer!, view!.view,
|
Viewer_captureRenderTargetRenderThread(_viewer!, view!.view,
|
||||||
swapChain.swapChain, renderTarget.renderTarget, out.address, cb);
|
swapChain!.swapChain, renderTarget.renderTarget, out.address, cb);
|
||||||
} else {
|
} else {
|
||||||
Viewer_captureRenderThread(
|
Viewer_captureRenderThread(
|
||||||
_viewer!, view!.view, swapChain.swapChain, out.address, cb);
|
_viewer!, view!.view, swapChain!.swapChain, out.address, cb);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return out;
|
return out;
|
||||||
@@ -1341,20 +1332,6 @@ class ThermionViewerFFI extends ThermionViewer {
|
|||||||
set_camera_model_matrix(mainCamera.camera, out);
|
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();
|
return result.cast<Utf8>().toDartString();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onPickResult(ThermionEntity entityId, int x, int y) {
|
void _onPickResult(
|
||||||
_pickResultController.add((
|
ThermionEntity entityId, int x, int y, Pointer<TView> viewPtr) async {
|
||||||
entity: entityId,
|
final view = FFIView(viewPtr, _viewer!);
|
||||||
x: (x / pixelRatio).toDouble(),
|
final viewport = await view.getViewport();
|
||||||
y: (viewportDimensions.$2 - y) / pixelRatio
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onGizmoPickResult(ThermionEntity entityId, int x, int y) {
|
// _pickResultController.add((
|
||||||
_gizmoPickResultController.add((
|
// entity: entityId,
|
||||||
entity: entityId,
|
// x: (x / pixelRatio).toDouble(),
|
||||||
x: (x / pixelRatio).toDouble(),
|
// y: (viewport.height - y) / pixelRatio
|
||||||
y: (viewportDimensions.$2 - y) / pixelRatio
|
// ));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
late NativeCallable<Void Function(EntityId entityId, Int x, Int y)>
|
late NativeCallable<Void Function(EntityId entityId, Int x, Int y)>
|
||||||
_onPickResultCallable;
|
_onPickResultCallable;
|
||||||
late NativeCallable<Void Function(EntityId entityId, Int x, Int y)>
|
|
||||||
_onGizmoPickResultCallable;
|
|
||||||
|
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
@override
|
@override
|
||||||
void pick(int x, int y) async {
|
void pick(int x, int y) async {
|
||||||
x = (x * pixelRatio).ceil();
|
// x = (x * pixelRatio).ceil();
|
||||||
y = (viewportDimensions.$2 - (y * pixelRatio)).ceil();
|
// y = (viewportDimensions.$2 - (y * pixelRatio)).ceil();
|
||||||
final view = (await getViewAt(0)) as FFIView;
|
// final view = (await getViewAt(0)) as FFIView;
|
||||||
filament_pick(
|
// filament_pick(
|
||||||
_viewer!, view.view, x, y, _onPickResultCallable.nativeFunction);
|
// _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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@@ -1866,13 +1824,6 @@ class ThermionViewerFFI extends ThermionViewer {
|
|||||||
SceneManager_setVisibilityLayer(_sceneManager!, entity, layer);
|
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);
|
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;
|
await completer.future;
|
||||||
}
|
}
|
||||||
@@ -2164,6 +2119,14 @@ class ThermionViewerFFI extends ThermionViewer {
|
|||||||
}
|
}
|
||||||
return FFIView(view, _viewer!);
|
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 {
|
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.
|
// "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';
|
import '../../viewer.dart';
|
||||||
|
|
||||||
typedef FilamentPickResult = ({ThermionEntity entity, double x, double y});
|
typedef FilamentPickResult = ({ThermionEntity entity, int x, int y});
|
||||||
typedef ThermionPickResult = FilamentPickResult;
|
typedef PickResult = FilamentPickResult;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class Viewport {
|
|||||||
abstract class View {
|
abstract class View {
|
||||||
Future<Viewport> getViewport();
|
Future<Viewport> getViewport();
|
||||||
Future updateViewport(int width, int height);
|
Future updateViewport(int width, int height);
|
||||||
Future setRenderTarget(covariant RenderTarget renderTarget);
|
Future setRenderTarget(covariant RenderTarget? renderTarget);
|
||||||
Future setCamera(covariant Camera camera);
|
Future setCamera(covariant Camera camera);
|
||||||
|
Camera getCamera();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:thermion_dart/src/viewer/src/events.dart';
|
import 'package:thermion_dart/src/viewer/src/events.dart';
|
||||||
import '../../entities/abstract_gizmo.dart';
|
import '../../utils/gizmo.dart';
|
||||||
import 'shared_types/camera.dart';
|
|
||||||
import 'shared_types/shared_types.dart';
|
import 'shared_types/shared_types.dart';
|
||||||
export '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 'dart:async';
|
||||||
import 'package:animation_tools_dart/animation_tools_dart.dart';
|
import 'package:animation_tools_dart/animation_tools_dart.dart';
|
||||||
|
|
||||||
import 'shared_types/swap_chain.dart';
|
|
||||||
import 'shared_types/view.dart';
|
import 'shared_types/view.dart';
|
||||||
|
|
||||||
const double kNear = 0.05;
|
const double kNear = 0.05;
|
||||||
@@ -23,16 +21,6 @@ abstract class ThermionViewer {
|
|||||||
///
|
///
|
||||||
Future<bool> get initialized;
|
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).
|
/// 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].
|
/// 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;
|
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.
|
/// A Stream containing entities added/removed to/from to the scene.
|
||||||
///
|
///
|
||||||
@@ -63,7 +46,7 @@ abstract class ThermionViewer {
|
|||||||
///
|
///
|
||||||
/// Render a single frame immediately.
|
/// 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.
|
/// 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].
|
/// Render a single frame and copy the pixel buffer to [out].
|
||||||
///
|
///
|
||||||
Future<Uint8List> capture(covariant SwapChain swapChain,
|
Future<Uint8List> capture({covariant SwapChain? swapChain,
|
||||||
{covariant View? view, covariant RenderTarget? renderTarget});
|
covariant View? view, covariant RenderTarget? renderTarget});
|
||||||
|
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
@@ -96,7 +79,7 @@ abstract class ThermionViewer {
|
|||||||
///
|
///
|
||||||
///
|
///
|
||||||
Future<View> createView();
|
Future<View> createView();
|
||||||
|
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
@@ -400,7 +383,6 @@ abstract class ThermionViewer {
|
|||||||
///
|
///
|
||||||
Future clearEntities();
|
Future clearEntities();
|
||||||
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Schedules the glTF animation at [index] in [entity] to start playing on the next frame.
|
/// 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<double> getCameraCullingFar();
|
||||||
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
Future setCameraLensProjection(
|
|
||||||
{double near = kNear,
|
|
||||||
double far = kFar,
|
|
||||||
double? aspect,
|
|
||||||
double focalLength = kFocalLength});
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Sets the focus distance for the camera.
|
/// Sets the focus distance for the camera.
|
||||||
///
|
///
|
||||||
@@ -701,14 +674,6 @@ abstract class ThermionViewer {
|
|||||||
///
|
///
|
||||||
void pick(int x, int y);
|
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).
|
/// 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);
|
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.
|
/// Register a callback to be invoked when this viewer is disposed.
|
||||||
@@ -819,11 +784,6 @@ abstract class ThermionViewer {
|
|||||||
///
|
///
|
||||||
Future setVisibilityLayer(ThermionEntity entity, int layer);
|
Future setVisibilityLayer(ThermionEntity entity, int layer);
|
||||||
|
|
||||||
///
|
|
||||||
/// Show/hide the translation gizmo.
|
|
||||||
///
|
|
||||||
Future setGizmoVisibility(bool visible);
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Renders an outline around [entity] with the given color.
|
/// Renders an outline around [entity] with the given color.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:typed_data';
|
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/swap_chain.dart';
|
||||||
import 'package:thermion_dart/src/viewer/src/shared_types/view.dart';
|
import 'package:thermion_dart/src/viewer/src/shared_types/view.dart';
|
||||||
import 'package:thermion_dart/thermion_dart.dart';
|
import 'package:thermion_dart/thermion_dart.dart';
|
||||||
import 'package:vector_math/vector_math_64.dart';
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:animation_tools_dart/animation_tools_dart.dart';
|
import 'package:animation_tools_dart/animation_tools_dart.dart';
|
||||||
|
|
||||||
import '../../entities/abstract_gizmo.dart';
|
|
||||||
import 'events.dart';
|
import 'events.dart';
|
||||||
import 'shared_types/camera.dart';
|
import 'shared_types/camera.dart';
|
||||||
|
|
||||||
@@ -242,10 +241,6 @@ class ThermionViewerStub extends ThermionViewer {
|
|||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
// TODO: implement gizmo
|
|
||||||
AbstractGizmo? get gizmo => throw UnimplementedError();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future hide(ThermionEntity entity, String? meshName) {
|
Future hide(ThermionEntity entity, String? meshName) {
|
||||||
// TODO: implement hide
|
// TODO: implement hide
|
||||||
@@ -1028,12 +1023,6 @@ class ThermionViewerStub extends ThermionViewer {
|
|||||||
// TODO: implement setRenderTarget
|
// TODO: implement setRenderTarget
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Uint8List> capture(covariant SwapChain swapChain, {covariant RenderTarget? renderTarget}) {
|
|
||||||
// TODO: implement capture
|
|
||||||
throw UnimplementedError();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<View> createView() {
|
Future<View> createView() {
|
||||||
@@ -1052,6 +1041,18 @@ class ThermionViewerStub extends ThermionViewer {
|
|||||||
// TODO: implement render
|
// TODO: implement render
|
||||||
throw UnimplementedError();
|
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:animation_tools_dart/animation_tools_dart.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:vector_math/vector_math_64.dart';
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
import '../../../../entities/abstract_gizmo.dart';
|
import '../../shared_types/internal/gizmo.dart';
|
||||||
import '../../../viewer.dart';
|
import '../../../viewer.dart';
|
||||||
import '../../events.dart';
|
import '../../events.dart';
|
||||||
import '../../shared_types/camera.dart';
|
import '../../shared_types/camera.dart';
|
||||||
|
|||||||
@@ -45,4 +45,28 @@ class ThermionWasmCamera extends Camera {
|
|||||||
// TODO: implement setModelMatrix
|
// TODO: implement setModelMatrix
|
||||||
throw UnimplementedError();
|
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:vector_math/vector_math_64.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import '../../../../entities/abstract_gizmo.dart';
|
import '../../shared_types/internal/gizmo.dart';
|
||||||
import '../../../../entities/gizmo.dart';
|
import '../../shared_types/internal/gizmo.dart';
|
||||||
import '../../../viewer.dart';
|
import '../../../viewer.dart';
|
||||||
import '../../events.dart';
|
import '../../events.dart';
|
||||||
import '../../shared_types/camera.dart';
|
import '../../shared_types/camera.dart';
|
||||||
|
|||||||
@@ -132,6 +132,14 @@ namespace thermion
|
|||||||
return (SceneManager *const)_sceneManager;
|
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);
|
void unprojectTexture(EntityId entity, uint8_t* input, uint32_t inputWidth, uint32_t inputHeight, uint8_t* out, uint32_t outWidth, uint32_t outHeight);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ extern "C"
|
|||||||
EMSCRIPTEN_KEEPALIVE TView* Viewer_createView(TViewer *viewer);
|
EMSCRIPTEN_KEEPALIVE TView* Viewer_createView(TViewer *viewer);
|
||||||
EMSCRIPTEN_KEEPALIVE TView* Viewer_getViewAt(TViewer *viewer, int index);
|
EMSCRIPTEN_KEEPALIVE TView* Viewer_getViewAt(TViewer *viewer, int index);
|
||||||
EMSCRIPTEN_KEEPALIVE void Viewer_setMainCamera(TViewer *tViewer, TView *tView);
|
EMSCRIPTEN_KEEPALIVE void Viewer_setMainCamera(TViewer *tViewer, TView *tView);
|
||||||
|
EMSCRIPTEN_KEEPALIVE TSwapChain* Viewer_getSwapChainAt(TViewer *tViewer, int index);
|
||||||
|
|
||||||
// Engine
|
// Engine
|
||||||
EMSCRIPTEN_KEEPALIVE TEngine *Viewer_getEngine(TViewer* viewer);
|
EMSCRIPTEN_KEEPALIVE TEngine *Viewer_getEngine(TViewer* viewer);
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ extern "C"
|
|||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE FilamentRenderCallback make_render_callback_fn_pointer(FilamentRenderCallback);
|
EMSCRIPTEN_KEEPALIVE FilamentRenderCallback make_render_callback_fn_pointer(FilamentRenderCallback);
|
||||||
EMSCRIPTEN_KEEPALIVE void set_rendering_render_thread(TViewer *viewer, bool rendering, void(*onComplete)());
|
EMSCRIPTEN_KEEPALIVE void set_rendering_render_thread(TViewer *viewer, bool rendering, void(*onComplete)());
|
||||||
EMSCRIPTEN_KEEPALIVE void 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_frame_interval_render_thread(TViewer *viewer, float frameInterval);
|
||||||
EMSCRIPTEN_KEEPALIVE void set_background_color_render_thread(TViewer *viewer, const float r, const float g, const float b, const float a);
|
EMSCRIPTEN_KEEPALIVE void set_background_color_render_thread(TViewer *viewer, const float r, const float g, const float b, const float a);
|
||||||
EMSCRIPTEN_KEEPALIVE void clear_background_image_render_thread(TViewer *viewer);
|
EMSCRIPTEN_KEEPALIVE void clear_background_image_render_thread(TViewer *viewer);
|
||||||
|
|||||||
@@ -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/Engine.h>
|
||||||
#include <filament/ToneMapper.h>
|
#include <filament/ToneMapper.h>
|
||||||
#include <filament/ColorGrading.h>
|
#include <filament/ColorGrading.h>
|
||||||
|
#include <filament/Camera.h>
|
||||||
|
|
||||||
#include "ThermionDartApi.h"
|
#include "ThermionDartApi.h"
|
||||||
#include "TView.h"
|
#include "TView.h"
|
||||||
@@ -150,6 +151,11 @@ using namespace filament;
|
|||||||
return reinterpret_cast<TScene*>(view->getScene());
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -348,8 +348,13 @@ extern "C"
|
|||||||
void (*callback)(void *buf, size_t size, void *data),
|
void (*callback)(void *buf, size_t size, void *data),
|
||||||
void *data)
|
void *data)
|
||||||
{
|
{
|
||||||
auto swapChain = reinterpret_cast<SwapChain *>(tSwapChain);
|
|
||||||
auto viewer = reinterpret_cast<FilamentViewer *>(tViewer);
|
auto viewer = reinterpret_cast<FilamentViewer *>(tViewer);
|
||||||
|
auto swapChain = reinterpret_cast<SwapChain *>(tSwapChain);
|
||||||
|
|
||||||
|
if(!swapChain) {
|
||||||
|
swapChain = viewer->getSwapChainAt(0);
|
||||||
|
}
|
||||||
|
|
||||||
auto *view = reinterpret_cast<View*>(tView);
|
auto *view = reinterpret_cast<View*>(tView);
|
||||||
return viewer->render(frameTimeInNanos, view, swapChain, pixelBuffer, callback, data);
|
return viewer->render(frameTimeInNanos, view, swapChain, pixelBuffer, callback, data);
|
||||||
}
|
}
|
||||||
@@ -413,6 +418,12 @@ extern "C"
|
|||||||
return reinterpret_cast<TSwapChain *>(swapChain);
|
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)
|
EMSCRIPTEN_KEEPALIVE TView *Viewer_createView(TViewer *tViewer)
|
||||||
{
|
{
|
||||||
auto viewer = reinterpret_cast<FilamentViewer *>(tViewer);
|
auto viewer = reinterpret_cast<FilamentViewer *>(tViewer);
|
||||||
|
|||||||
@@ -67,7 +67,9 @@ void main() async {
|
|||||||
print(frustum.plane5.normal);
|
print(frustum.plane5.normal);
|
||||||
print(frustum.plane5.constant);
|
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);
|
near: 10.0, far: 1000.0, aspect: 1.0, focalLength: 28.0);
|
||||||
frustum = await viewer.getCameraFrustum();
|
frustum = await viewer.getCameraFrustum();
|
||||||
print(frustum.plane5.normal);
|
print(frustum.plane5.normal);
|
||||||
@@ -75,8 +77,8 @@ void main() async {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('set custom projection/culling matrix', () async {
|
test('set custom projection/culling matrix', () async {
|
||||||
var viewer =
|
var viewer = await testHelper.createViewer(
|
||||||
await testHelper.createViewer(bg: kRed, cameraPosition: Vector3(0, 0, 4));
|
bg: kRed, cameraPosition: Vector3(0, 0, 4));
|
||||||
var camera = await viewer.getMainCamera();
|
var camera = await viewer.getMainCamera();
|
||||||
final cube = await viewer.createGeometry(GeometryHelper.cube());
|
final cube = await viewer.createGeometry(GeometryHelper.cube());
|
||||||
|
|
||||||
@@ -155,7 +157,7 @@ void main() async {
|
|||||||
expect(await viewer.getActiveCamera(), newCamera);
|
expect(await viewer.getActiveCamera(), newCamera);
|
||||||
|
|
||||||
await testHelper.capture(viewer, "create_camera_new_camera");
|
await testHelper.capture(viewer, "create_camera_new_camera");
|
||||||
|
|
||||||
final mainCamera = await viewer.getMainCamera();
|
final mainCamera = await viewer.getMainCamera();
|
||||||
await viewer.setActiveCamera(mainCamera);
|
await viewer.setActiveCamera(mainCamera);
|
||||||
expect(await viewer.getActiveCamera(), 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 = Directory("$testDir/output/${dir}");
|
||||||
// outDir.deleteSync(recursive: true);
|
// outDir.deleteSync(recursive: true);
|
||||||
outDir.createSync();
|
outDir.createSync();
|
||||||
|
DynamicLibrary.open('${testDir}/libThermionTextureSwift.dylib');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future capture(ThermionViewer viewer, String outputFilename,
|
Future capture(ThermionViewer viewer, String outputFilename,
|
||||||
@@ -84,33 +86,28 @@ class TestHelper {
|
|||||||
await Future.delayed(Duration(milliseconds: 10));
|
await Future.delayed(Duration(milliseconds: 10));
|
||||||
var outPath = p.join(outDir.path, "$outputFilename.bmp");
|
var outPath = p.join(outDir.path, "$outputFilename.bmp");
|
||||||
var pixelBuffer = await viewer.capture(
|
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);
|
view ??= await viewer.getViewAt(0);
|
||||||
var vp = await view.getViewport();
|
var vp = await view.getViewport();
|
||||||
await savePixelBufferToBmp(
|
await savePixelBufferToBmp(pixelBuffer, vp.width, vp.height, outPath);
|
||||||
pixelBuffer,
|
|
||||||
vp.width,
|
|
||||||
vp.height,
|
|
||||||
outPath);
|
|
||||||
return pixelBuffer;
|
return pixelBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> createTexture(int width, int height) async {
|
Future<ThermionTextureSwift> createTexture(int width, int height) async {
|
||||||
final packageUri = findPackageRoot('thermion_dart');
|
final packageUri = findPackageRoot('thermion_dart');
|
||||||
final lib = ThermionDartTexture1(DynamicLibrary.open(
|
var testDir = Directory("${packageUri.toFilePath()}/test").path;
|
||||||
'${packageUri.toFilePath()}/native/lib/macos/swift/libthermion_swift.dylib'));
|
|
||||||
final object = ThermionDartTexture.new1(lib);
|
final object = ThermionTextureSwift.new1();
|
||||||
object.initWithWidth_height_(width, height);
|
object.initWithWidth_height_(width, height);
|
||||||
return object.metalTextureAddress;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ThermionViewer> createViewer(
|
Future<ThermionViewer> createViewer(
|
||||||
{img.Color? bg,
|
{img.Color? bg,
|
||||||
Vector3? cameraPosition,
|
Vector3? cameraPosition,
|
||||||
viewportDimensions = (width: 500, height: 500)}) async {
|
viewportDimensions = (width: 500, height: 500)}) async {
|
||||||
final texture = await createTexture(
|
|
||||||
viewportDimensions.width, viewportDimensions.height);
|
|
||||||
|
|
||||||
final resourceLoader = calloc<ResourceLoaderWrapper>(1);
|
final resourceLoader = calloc<ResourceLoaderWrapper>(1);
|
||||||
var loadToOut = NativeCallable<
|
var loadToOut = NativeCallable<
|
||||||
Void Function(Pointer<Char>,
|
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", () {
|
// group("unproject", () {
|
||||||
// test("unproject", () async {
|
// test("unproject", () async {
|
||||||
// final dimensions = (width: 1280, height: 768);
|
// 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:test/test.dart';
|
||||||
import 'package:thermion_dart/thermion_dart.dart';
|
import 'package:thermion_dart/thermion_dart.dart';
|
||||||
import 'package:vector_math/vector_math_64.dart';
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
@@ -9,11 +7,17 @@ void main() async {
|
|||||||
final testHelper = TestHelper("view");
|
final testHelper = TestHelper("view");
|
||||||
|
|
||||||
group('view tests', () {
|
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 {
|
test('one swapchain, render view to render target', () async {
|
||||||
var viewer = await testHelper.createViewer();
|
var viewer = await testHelper.createViewer();
|
||||||
|
|
||||||
final texture = await testHelper.createTexture(500, 500);
|
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);
|
viewer.setRenderTarget(renderTarget);
|
||||||
|
|
||||||
await viewer.setBackgroundColor(1.0, 0, 0, 1);
|
await viewer.setBackgroundColor(1.0, 0, 0, 1);
|
||||||
@@ -27,9 +31,10 @@ void main() async {
|
|||||||
"default_swapchain_default_view_render_target");
|
"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();
|
var viewer = await testHelper.createViewer();
|
||||||
await viewer.setBackgroundColor(1.0, 0, 0, 1);
|
await viewer.setBackgroundColor(1.0, 0, 0, 1);
|
||||||
|
|
||||||
final cube = await viewer.createGeometry(GeometryHelper.cube());
|
final cube = await viewer.createGeometry(GeometryHelper.cube());
|
||||||
|
|
||||||
var mainCamera = await viewer.getMainCamera();
|
var mainCamera = await viewer.getMainCamera();
|
||||||
@@ -71,48 +76,28 @@ void main() async {
|
|||||||
|
|
||||||
var mainCamera = await viewer.getMainCamera();
|
var mainCamera = await viewer.getMainCamera();
|
||||||
mainCamera.setTransform(Matrix4.translation(Vector3(0, 0, 5)));
|
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(
|
await testHelper.capture(
|
||||||
viewer, "create_swapchain_default_view_default_swapchain");
|
viewer, "create_swapchain_default_view_default_swapchain");
|
||||||
|
|
||||||
final view = await viewer.createView();
|
final view = await viewer.createView();
|
||||||
|
|
||||||
final texture = await testHelper.createTexture(400, 400);
|
final texture = await testHelper.createTexture(200, 400);
|
||||||
final renderTarget = await viewer.createRenderTarget(400, 400, texture);
|
final renderTarget = await viewer.createRenderTarget(200, 400, texture.metalTextureAddress);
|
||||||
await view.setRenderTarget(renderTarget);
|
await view.setRenderTarget(renderTarget);
|
||||||
|
|
||||||
await view.updateViewport(400, 400);
|
await view.updateViewport(200, 400);
|
||||||
view.setCamera(mainCamera);
|
view.setCamera(mainCamera);
|
||||||
|
mainCamera.setLensProjection(aspect: 0.5);
|
||||||
|
|
||||||
await testHelper.capture(
|
await testHelper.capture(
|
||||||
viewer,
|
viewer,
|
||||||
view: view,
|
view: view,
|
||||||
swapChain: swapChain,
|
swapChain: swapChain,
|
||||||
renderTarget: renderTarget,
|
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();
|
await viewer.dispose();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -47,9 +47,9 @@ late String testDir;
|
|||||||
void main() async {
|
void main() async {
|
||||||
final packageUri = findPackageRoot('thermion_dart');
|
final packageUri = findPackageRoot('thermion_dart');
|
||||||
testDir = Directory("${packageUri.toFilePath()}/test").path;
|
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'));
|
'${packageUri.toFilePath()}/native/lib/macos/swift/libthermion_swift.dylib'));
|
||||||
final object = ThermionDartTexture.new1(lib);
|
final object = ThermionTexture.new1(lib);
|
||||||
object.initWithWidth_height_(500, 500);
|
object.initWithWidth_height_(500, 500);
|
||||||
|
|
||||||
final resourceLoader = calloc<ResourceLoaderWrapper>(1);
|
final resourceLoader = calloc<ResourceLoaderWrapper>(1);
|
||||||
|
|||||||
@@ -43,9 +43,9 @@ late String testDir;
|
|||||||
void main() async {
|
void main() async {
|
||||||
final packageUri = findPackageRoot('thermion_dart');
|
final packageUri = findPackageRoot('thermion_dart');
|
||||||
testDir = Directory("${packageUri.toFilePath()}/test").path;
|
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'));
|
'${packageUri.toFilePath()}/native/lib/macos/swift/libthermion_swift.dylib'));
|
||||||
final object = ThermionDartTexture.new1(lib);
|
final object = ThermionTexture.new1(lib);
|
||||||
object.initWithWidth_height_(500, 500);
|
object.initWithWidth_height_(500, 500);
|
||||||
|
|
||||||
final resourceLoader = calloc<ResourceLoaderWrapper>(1);
|
final resourceLoader = calloc<ResourceLoaderWrapper>(1);
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:thermion_dart/thermion_dart.dart';
|
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_platform_interface.dart';
|
||||||
import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart';
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Handles all platform-specific initialization to create a backing rendering
|
/// 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].
|
/// Call [createViewerWithOptions] to create an instance of [ThermionViewer].
|
||||||
///
|
///
|
||||||
class ThermionFlutterPlugin {
|
class ThermionFlutterPlugin {
|
||||||
ThermionFlutterPlugin._();
|
|
||||||
|
|
||||||
static AppLifecycleListener? _appLifecycleListener;
|
ThermionFlutterPlugin._();
|
||||||
|
|
||||||
static bool _initializing = false;
|
static bool _initializing = false;
|
||||||
|
|
||||||
static ThermionViewer? _viewer;
|
static ThermionViewer? _viewer;
|
||||||
|
|
||||||
static bool _wasRenderingOnInactive = false;
|
static Future<ThermionViewer> createViewer(
|
||||||
|
{ThermionFlutterOptions options =
|
||||||
static void _handleStateChange(AppLifecycleState state) async {
|
const ThermionFlutterOptions.empty()}) 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 {
|
|
||||||
if (_initializing) {
|
if (_initializing) {
|
||||||
throw Exception("Existing call to createViewer has not completed.");
|
throw Exception("Existing call to createViewer has not completed.");
|
||||||
}
|
}
|
||||||
_initializing = true;
|
_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 =
|
_viewer =
|
||||||
await ThermionFlutterPlatform.instance.createViewerWithOptions(options);
|
await ThermionFlutterPlatform.instance.createViewer(options: options);
|
||||||
_appLifecycleListener = AppLifecycleListener(
|
|
||||||
onStateChange: _handleStateChange,
|
|
||||||
);
|
|
||||||
_viewer!.onDispose(() async {
|
_viewer!.onDispose(() async {
|
||||||
_viewer = null;
|
_viewer = null;
|
||||||
_appLifecycleListener?.dispose();
|
|
||||||
_appLifecycleListener = null;
|
|
||||||
});
|
});
|
||||||
_initializing = false;
|
_initializing = false;
|
||||||
return _viewer!;
|
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/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:thermion_dart/thermion_dart.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';
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
extension OffsetExtension on Offset {
|
extension OffsetExtension on Offset {
|
||||||
@@ -79,54 +80,64 @@ class _ThermionListenerWidgetState extends State<ThermionListenerWidget> {
|
|||||||
HardwareKeyboard.instance.removeHandler(_handleKeyEvent);
|
HardwareKeyboard.instance.removeHandler(_handleKeyEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _desktop() {
|
Widget _desktop(double pixelRatio) {
|
||||||
return Listener(
|
return Listener(
|
||||||
onPointerHover: (event) => widget.gestureHandler
|
onPointerHover: (event) => widget.gestureHandler.onPointerHover(
|
||||||
.onPointerHover(event.localPosition.toVector2(), event.delta.toVector2()),
|
event.localPosition.toVector2() * pixelRatio,
|
||||||
|
event.delta.toVector2() * pixelRatio),
|
||||||
onPointerSignal: (PointerSignalEvent pointerSignal) {
|
onPointerSignal: (PointerSignalEvent pointerSignal) {
|
||||||
if (pointerSignal is PointerScrollEvent) {
|
if (pointerSignal is PointerScrollEvent) {
|
||||||
widget.gestureHandler.onPointerScroll(
|
widget.gestureHandler.onPointerScroll(
|
||||||
pointerSignal.localPosition.toVector2(),
|
pointerSignal.localPosition.toVector2() * pixelRatio,
|
||||||
pointerSignal.scrollDelta.dy);
|
pointerSignal.scrollDelta.dy * pixelRatio);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onPointerPanZoomStart: (pzs) {
|
onPointerPanZoomStart: (pzs) {
|
||||||
throw Exception("TODO - is this a pinch zoom on laptop trackpad?");
|
throw Exception("TODO - is this a pinch zoom on laptop trackpad?");
|
||||||
},
|
},
|
||||||
onPointerDown: (d) => widget.gestureHandler
|
onPointerDown: (d) => widget.gestureHandler.onPointerDown(
|
||||||
.onPointerDown(d.localPosition.toVector2(), d.buttons & kMiddleMouseButton != 0),
|
d.localPosition.toVector2() * pixelRatio,
|
||||||
onPointerMove: (d) => widget.gestureHandler
|
d.buttons & kMiddleMouseButton != 0),
|
||||||
.onPointerMove(d.localPosition.toVector2(), d.delta.toVector2(), d.buttons & kMiddleMouseButton != 0),
|
onPointerMove: (d) => widget.gestureHandler.onPointerMove(
|
||||||
onPointerUp: (d) => widget.gestureHandler.onPointerUp(d.buttons & kMiddleMouseButton != 0),
|
d.localPosition.toVector2() * pixelRatio,
|
||||||
|
d.delta.toVector2() * pixelRatio,
|
||||||
|
d.buttons & kMiddleMouseButton != 0),
|
||||||
|
onPointerUp: (d) => widget.gestureHandler
|
||||||
|
.onPointerUp(d.buttons & kMiddleMouseButton != 0),
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _mobile() {
|
Widget _mobile(double pixelRatio) {
|
||||||
return _MobileListenerWidget(gestureHandler: widget.gestureHandler);
|
return _MobileListenerWidget(
|
||||||
|
gestureHandler: widget.gestureHandler, pixelRatio: pixelRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return FutureBuilder(
|
return PixelRatioAware(builder: (ctx, pixelRatio) {
|
||||||
future: widget.gestureHandler.initialized,
|
return FutureBuilder(
|
||||||
builder: (_, initialized) {
|
initialData: 1.0,
|
||||||
if (initialized.data != true) {
|
future: widget.gestureHandler.initialized,
|
||||||
return widget.child ?? Container();
|
builder: (_, initialized) {
|
||||||
}
|
if (initialized.data != true) {
|
||||||
return Stack(children: [
|
return widget.child ?? Container();
|
||||||
if (widget.child != null) Positioned.fill(child: widget.child!),
|
}
|
||||||
if (isDesktop) Positioned.fill(child: _desktop()),
|
return Stack(children: [
|
||||||
if (!isDesktop) Positioned.fill(child: _mobile())
|
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 {
|
class _MobileListenerWidget extends StatefulWidget {
|
||||||
final InputHandler gestureHandler;
|
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);
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -146,7 +157,7 @@ class _MobileListenerWidgetState extends State<_MobileListenerWidget> {
|
|||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
onTapDown: (details) => widget.gestureHandler
|
onTapDown: (details) => widget.gestureHandler
|
||||||
.onPointerDown(details.localPosition.toVector2(), false),
|
.onPointerDown(details.localPosition.toVector2() * widget.pixelRatio, false),
|
||||||
onDoubleTap: () {
|
onDoubleTap: () {
|
||||||
widget.gestureHandler.setActionForType(InputType.SCALE1,
|
widget.gestureHandler.setActionForType(InputType.SCALE1,
|
||||||
isPan ? InputAction.TRANSLATE : InputAction.ROTATE);
|
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:io';
|
||||||
import 'dart:math';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.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/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/thermion_flutter.dart';
|
||||||
import 'package:thermion_flutter_web/thermion_flutter_web_options.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 {
|
class ThermionWidget extends StatefulWidget {
|
||||||
|
///
|
||||||
|
/// The viewer.
|
||||||
|
///
|
||||||
final ThermionViewer viewer;
|
final ThermionViewer viewer;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// The view.
|
||||||
|
///
|
||||||
|
final t.View? view;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// The options to use when creating this widget.
|
||||||
|
///
|
||||||
final ThermionFlutterOptions? options;
|
final ThermionFlutterOptions? options;
|
||||||
|
|
||||||
///
|
///
|
||||||
@@ -22,130 +31,51 @@ class ThermionWidget extends StatefulWidget {
|
|||||||
final Widget? initial;
|
final Widget? initial;
|
||||||
|
|
||||||
const ThermionWidget(
|
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);
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
State<ThermionWidget> createState() => _ThermionWidgetState();
|
||||||
_ThermionWidgetState createState() => _ThermionWidgetState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ThermionWidgetState extends State<ThermionWidget> {
|
class _ThermionWidgetState extends State<ThermionWidget> {
|
||||||
ThermionFlutterTexture? _texture;
|
t.View? view;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
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();
|
super.initState();
|
||||||
|
initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future initialize() async {
|
||||||
bool _rendering = false;
|
if (widget.view != null) {
|
||||||
|
view = widget.view;
|
||||||
void _requestFrame() {
|
} else {
|
||||||
WidgetsBinding.instance.scheduleFrameCallback((d) async {
|
view = await widget.viewer.getViewAt(0);
|
||||||
if (!_rendering) {
|
|
||||||
_rendering = true;
|
|
||||||
await widget.viewer.requestFrame();
|
|
||||||
_rendering = false;
|
|
||||||
}
|
}
|
||||||
_requestFrame();
|
setState(() {});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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 (kIsWeb) {
|
||||||
if (_texture == null || _resizing) {
|
return ThermionWidgetWeb(
|
||||||
return widget.initial ?? Container(color: Colors.red);
|
viewer: widget.viewer,
|
||||||
}
|
options: widget.options as ThermionFlutterWebOptions);
|
||||||
return ResizeObserver(
|
|
||||||
onResized: _resizeTexture,
|
|
||||||
child: ThermionWidgetWeb(
|
|
||||||
options: widget.options as ThermionFlutterWebOptions?));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_texture?.usesBackingWindow == true) {
|
if (Platform.isWindows) {
|
||||||
return ResizeObserver(
|
return ThermionWidgetWindows(viewer: widget.viewer);
|
||||||
onResized: _resizeTexture,
|
|
||||||
child: Stack(children: [
|
|
||||||
Positioned.fill(child: CustomPaint(painter: TransparencyPainter()))
|
|
||||||
]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_texture == null || _resizing) {
|
return ThermionTextureWidget(
|
||||||
return widget.initial ??
|
key: ObjectKey(view!),
|
||||||
Container(
|
initial: widget.initial,
|
||||||
color:
|
viewer: widget.viewer,
|
||||||
kIsWeb ? const Color.fromARGB(0, 170, 129, 129) : Colors.red);
|
view: view!);
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
]));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,28 @@ import 'dart:js_util';
|
|||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
import 'dart:ui_web' as ui_web;
|
import 'dart:ui_web' as ui_web;
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:thermion_flutter/thermion_flutter.dart';
|
||||||
import 'package:thermion_flutter_web/thermion_flutter_web_options.dart';
|
import 'package:thermion_flutter_web/thermion_flutter_web_options.dart';
|
||||||
import 'package:web/web.dart';
|
import 'package:web/web.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
class ThermionWidgetWeb extends StatelessWidget {
|
class ThermionWidgetWeb extends StatelessWidget {
|
||||||
final ThermionFlutterWebOptions options;
|
final ThermionFlutterWebOptions options;
|
||||||
|
final ThermionViewer viewer;
|
||||||
|
|
||||||
const ThermionWidgetWeb(
|
const ThermionWidgetWeb(
|
||||||
{super.key, this.options = const ThermionFlutterWebOptions.empty()});
|
{super.key, this.options = const ThermionFlutterWebOptions.empty(), required this.viewer});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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) {
|
if (options?.importCanvasAsWidget == true) {
|
||||||
return _ImageCopyingWidget();
|
return _ImageCopyingWidget();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/widgets.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';
|
import 'package:thermion_flutter_web/thermion_flutter_web_options.dart';
|
||||||
|
|
||||||
class ThermionWidgetWeb extends StatefulWidget {
|
class ThermionWidgetWeb extends StatelessWidget {
|
||||||
final ThermionFlutterWebOptions? options;
|
final ThermionFlutterWebOptions? options;
|
||||||
|
final ThermionViewer viewer;
|
||||||
|
|
||||||
|
const ThermionWidgetWeb({super.key, required this.options, required this.viewer});
|
||||||
|
|
||||||
const ThermionWidgetWeb({super.key, required this.options});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: no_logic_in_create_state
|
Widget build(BuildContext context) {
|
||||||
State<StatefulWidget> createState() => throw Exception();
|
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()
|
let instance:SwiftThermionFlutterPlugin = Unmanaged<SwiftThermionFlutterPlugin>.fromOpaque(resourcesPtr!).takeUnretainedValue()
|
||||||
instance.resources.removeValue(forKey:UInt32(rbuf.id))
|
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) {
|
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||||
let _messenger = registrar.messenger;
|
let _messenger = registrar.messenger;
|
||||||
@@ -88,12 +81,12 @@ public class SwiftThermionFlutterPlugin: NSObject, FlutterPlugin {
|
|||||||
resourceLoaderWrapper = make_resource_loader(loadResource, freeResource, Unmanaged.passUnretained(self).toOpaque())
|
resourceLoaderWrapper = make_resource_loader(loadResource, freeResource, Unmanaged.passUnretained(self).toOpaque())
|
||||||
}
|
}
|
||||||
result(Int64(Int(bitPattern: resourceLoaderWrapper!)))
|
result(Int64(Int(bitPattern: resourceLoaderWrapper!)))
|
||||||
|
case "markTextureFrameAvailable":
|
||||||
|
let flutterTextureId = call.arguments as! Int64
|
||||||
|
registry.textureFrameAvailable(flutterTextureId)
|
||||||
|
result(nil)
|
||||||
case "getRenderCallback":
|
case "getRenderCallback":
|
||||||
if(renderCallbackHolder.isEmpty) {
|
result(nil)
|
||||||
renderCallbackHolder.append(unsafeBitCast(markTextureFrameAvailable, to:Int64.self))
|
|
||||||
renderCallbackHolder.append(unsafeBitCast(Unmanaged.passUnretained(self), to:UInt64.self))
|
|
||||||
}
|
|
||||||
result(renderCallbackHolder)
|
|
||||||
case "getDriverPlatform":
|
case "getDriverPlatform":
|
||||||
result(nil)
|
result(nil)
|
||||||
case "getSharedContext":
|
case "getSharedContext":
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ public class ThermionFlutterTexture : NSObject, FlutterTexture {
|
|||||||
|
|
||||||
var flutterTextureId: Int64 = -1
|
var flutterTextureId: Int64 = -1
|
||||||
var registry: FlutterTextureRegistry
|
var registry: FlutterTextureRegistry
|
||||||
var texture: ThermionTexture
|
var texture: ThermionTextureSwift
|
||||||
|
|
||||||
init(registry:FlutterTextureRegistry, width:Int64, height:Int64) {
|
init(registry:FlutterTextureRegistry, width:Int64, height:Int64) {
|
||||||
self.registry = registry
|
self.registry = registry
|
||||||
self.texture = ThermionTexture(width:width, height: height)
|
self.texture = ThermionTextureSwift(width:width, height: height)
|
||||||
super.init()
|
super.init()
|
||||||
self.flutterTextureId = registry.register(self)
|
self.flutterTextureId = registry.register(self)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
#include "ResourceBuffer.hpp"
|
#include "ResourceBuffer.hpp"
|
||||||
|
|
||||||
using namespace filament;
|
using namespace filament;
|
||||||
using namespace thermion_filament;
|
using namespace thermion;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
int _i = 0;
|
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';
|
export 'thermion_flutter_android.dart';
|
||||||
import 'package:flutter/services.dart';
|
export 'thermion_flutter_macos.dart';
|
||||||
import 'dart:ffi';
|
export 'thermion_flutter_windows.dart';
|
||||||
import 'package:thermion_dart/thermion_dart.dart';
|
export 'thermion_flutter_ios.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
|
implements: thermion_flutter_platform_interface
|
||||||
platforms:
|
platforms:
|
||||||
ios:
|
ios:
|
||||||
dartPluginClass: ThermionFlutterFFI
|
dartPluginClass: ThermionFlutterIOS
|
||||||
android:
|
android:
|
||||||
dartPluginClass: ThermionFlutterFFI
|
dartPluginClass: ThermionFlutterAndroid
|
||||||
macos:
|
macos:
|
||||||
dartPluginClass: ThermionFlutterFFI
|
dartPluginClass: ThermionFlutterMacOS
|
||||||
windows:
|
windows:
|
||||||
dartPluginClass: ThermionFlutterFFI
|
dartPluginClass: ThermionFlutterWindows
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ class ThermionFlutterOptions {
|
|||||||
|
|
||||||
ThermionFlutterOptions({this.uberarchivePath});
|
ThermionFlutterOptions({this.uberarchivePath});
|
||||||
const ThermionFlutterOptions.empty() : uberarchivePath = null;
|
const ThermionFlutterOptions.empty() : uberarchivePath = null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class ThermionFlutterPlatform extends PlatformInterface {
|
abstract class ThermionFlutterPlatform extends PlatformInterface {
|
||||||
@@ -25,17 +24,23 @@ abstract class ThermionFlutterPlatform extends PlatformInterface {
|
|||||||
_instance = instance;
|
_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 resizeWindow(
|
||||||
|
int width, int height, int offsetTop, int offsetRight);
|
||||||
Future<ThermionFlutterTexture?> resizeTexture(ThermionFlutterTexture texture,
|
|
||||||
int width, int height, int offsetTop, int offsetRight, double pixelRatio);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,30 @@
|
|||||||
class ThermionFlutterTexture {
|
// class ThermionFlutterTextureImpl {
|
||||||
final int width;
|
// final int width;
|
||||||
final int height;
|
// final int height;
|
||||||
final int? flutterTextureId;
|
// final int? flutterTextureId;
|
||||||
final int? hardwareTextureId;
|
// final int? hardwareTextureId;
|
||||||
final int? surfaceAddress;
|
// final int? surfaceAddress;
|
||||||
bool get usesBackingWindow => flutterTextureId == null;
|
// bool get usesBackingWindow => flutterTextureId == null;
|
||||||
|
|
||||||
ThermionFlutterTexture(this.flutterTextureId, this.hardwareTextureId,
|
// ThermionFlutterTexture(this.flutterTextureId, this.hardwareTextureId,
|
||||||
this.width, this.height, this.surfaceAddress) {
|
// 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