feat!: big refactor to support multiple swapchains

This commit is contained in:
Nick Fisher
2024-09-27 18:39:20 +08:00
parent 399d447eec
commit a6d2f2ecf9
24 changed files with 752 additions and 626 deletions

View File

@@ -53,11 +53,9 @@ class FreeFlightInputHandlerDelegate implements InputHandlerDelegate {
_queuedZoomDelta += delta.z;
break;
case InputAction.PICK:
// Assuming PICK is used for zoom in this context
_queuedZoomDelta += delta.z;
break;
case InputAction.NONE:
// Do nothing
break;
}
}
@@ -81,14 +79,17 @@ class FreeFlightInputHandlerDelegate implements InputHandlerDelegate {
}
final activeCamera = await viewer.getActiveCamera();
Matrix4 currentViewMatrix = activeCamera.getViewMatrix();
Matrix4 currentViewMatrix = await activeCamera.getViewMatrix();
Matrix4 currentTransform = await viewer.getLocalTransform(await entity);
Vector3 currentPosition = currentTransform.getTranslation();
Quaternion currentRotation =
Quaternion.fromRotation(currentTransform.getRotation());
// Calculate relative transform
Matrix4 relativeTransform = Matrix4.identity();
Vector3 relativeTranslation = Vector3.zero();
Quaternion relativeRotation = Quaternion.identity();
// Apply rotation
if (_queuedRotationDelta.length2 > 0.0) {
double deltaX =
@@ -99,9 +100,7 @@ class FreeFlightInputHandlerDelegate implements InputHandlerDelegate {
Quaternion yawRotation = Quaternion.axisAngle(_up, -deltaX);
Quaternion pitchRotation = Quaternion.axisAngle(_right, -deltaY);
currentRotation = currentRotation * pitchRotation * yawRotation;
currentRotation.normalize();
relativeRotation = pitchRotation * yawRotation;
_queuedRotationDelta = Vector2.zero();
}
@@ -113,16 +112,14 @@ class FreeFlightInputHandlerDelegate implements InputHandlerDelegate {
double deltaX = _queuedPanDelta.x * panSensitivity * viewer.pixelRatio;
double deltaY = _queuedPanDelta.y * panSensitivity * viewer.pixelRatio;
Vector3 panOffset = right * deltaX + up * deltaY;
currentPosition += panOffset;
relativeTranslation += right * deltaX + up * deltaY;
_queuedPanDelta = Vector2.zero();
}
// Apply zoom
if (_queuedZoomDelta != 0.0) {
Vector3 forward = _forward.clone()..applyQuaternion(currentRotation);
currentPosition += forward * -_queuedZoomDelta * zoomSensitivity;
relativeTranslation += forward * -_queuedZoomDelta * zoomSensitivity;
_queuedZoomDelta = 0.0;
}
@@ -132,21 +129,37 @@ class FreeFlightInputHandlerDelegate implements InputHandlerDelegate {
Vector3 right = _right.clone()..applyQuaternion(currentRotation);
Vector3 up = _up.clone()..applyQuaternion(currentRotation);
Vector3 moveOffset = right * _queuedMoveDelta.x +
relativeTranslation += right * _queuedMoveDelta.x +
up * _queuedMoveDelta.y +
forward * _queuedMoveDelta.z;
currentPosition += moveOffset;
_queuedMoveDelta = Vector3.zero();
}
// Constrain position
currentPosition = _constrainPosition(currentPosition);
// If the managed entity is not the active camera, we need to apply the rotation from the current camera model matrix
// to the entity's translation
if (await entity != activeCamera.getEntity()) {
Matrix4 modelMatrix = await activeCamera.getModelMatrix();
relativeTranslation = modelMatrix.getRotation() * relativeTranslation;
}
// Compose relative transform
relativeTransform = Matrix4.compose(
relativeTranslation, relativeRotation, Vector3(1, 1, 1));
// Apply relative transform to current transform
Matrix4 newTransform = currentTransform * relativeTransform;
// Extract new position and constrain it
Vector3 newPosition = newTransform.getTranslation();
newPosition = _constrainPosition(newPosition);
// Recompose final transform with constrained position
Matrix4 finalTransform = Matrix4.compose(newPosition,
Quaternion.fromRotation(newTransform.getRotation()), Vector3(1, 1, 1));
// Update camera
Matrix4 newModelMatrix =
Matrix4.compose(currentPosition, currentRotation, Vector3(1, 1, 1));
await viewer.setTransform(await entity, newModelMatrix);
await viewer.setTransform(await entity, finalTransform);
_executing = false;
}

View File

@@ -29,13 +29,16 @@ class OverTheShoulderCameraDelegate implements InputHandlerDelegate {
final cameraUp = Vector3(0, 1, 0);
var cameraLookAt = Vector3(0, 0.5, 3);
final void Function(Matrix4 transform)? onUpdate;
OverTheShoulderCameraDelegate(this.viewer, this.player, this.camera,
{this.rotationSensitivity = 0.001,
this.movementSensitivity = 0.1,
this.zoomSensitivity = 0.1,
this.panSensitivity = 0.1,
this.clampY,
ThermionEntity? entity}) {}
ThermionEntity? entity,
this.onUpdate}) {}
@override
Future<void> queue(InputAction action, Vector3? delta) async {
@@ -97,7 +100,9 @@ class OverTheShoulderCameraDelegate implements InputHandlerDelegate {
double deltaY =
_queuedRotationDelta.y * rotationSensitivity * viewer.pixelRatio;
cameraLookAt = Matrix4.rotationY(-deltaX) * Matrix4.rotationX(-deltaY) * cameraLookAt;
cameraLookAt = Matrix4.rotationY(-deltaX) *
Matrix4.rotationX(-deltaY) *
cameraLookAt;
_queuedRotationDelta = Vector2.zero();
}
@@ -109,8 +114,7 @@ class OverTheShoulderCameraDelegate implements InputHandlerDelegate {
await viewer.queueTransformUpdates(
[camera.getEntity(), player], [newCameraTransform, newPlayerTransform]);
onUpdate?.call(newPlayerTransform);
_executing = false;
}
}

View File

@@ -26,7 +26,7 @@ Future<void> withVoidCallback(
nativeCallable.close();
}
Future<int> withPointerCallback<T extends NativeType>(
Future<Pointer<T>> withPointerCallback<T extends NativeType>(
Function(Pointer<NativeFunction<Void Function(Pointer<T>)>>)
func) async {
final completer = Completer<Pointer<T>>();
@@ -39,7 +39,7 @@ Future<int> withPointerCallback<T extends NativeType>(
func.call(nativeCallable.nativeFunction);
var ptr = await completer.future;
nativeCallable.close();
return ptr.address;
return ptr;
}
Future<bool> withBoolCallback(

View File

@@ -27,6 +27,101 @@ external ffi.Pointer<TSceneManager> Viewer_getSceneManager(
ffi.Pointer<TViewer> viewer,
);
@ffi.Native<
ffi.Pointer<TRenderTarget> Function(
ffi.Pointer<TViewer>, ffi.IntPtr, ffi.Uint32, ffi.Uint32)>(isLeaf: true)
external ffi.Pointer<TRenderTarget> Viewer_createRenderTarget(
ffi.Pointer<TViewer> viewer,
int texture,
int width,
int height,
);
@ffi.Native<
ffi.Void Function(
ffi.Pointer<TViewer>, ffi.Pointer<TRenderTarget>)>(isLeaf: true)
external void Viewer_destroyRenderTarget(
ffi.Pointer<TViewer> viewer,
ffi.Pointer<TRenderTarget> tRenderTarget,
);
@ffi.Native<
ffi.Void Function(
ffi.Pointer<TViewer>, ffi.Pointer<TRenderTarget>)>(isLeaf: true)
external void Viewer_setRenderTarget(
ffi.Pointer<TViewer> viewer,
ffi.Pointer<TRenderTarget> tRenderTarget,
);
@ffi.Native<
ffi.Pointer<TSwapChain> Function(ffi.Pointer<TViewer>,
ffi.Pointer<ffi.Void>, ffi.Uint32, ffi.Uint32)>(isLeaf: true)
external ffi.Pointer<TSwapChain> Viewer_createSwapChain(
ffi.Pointer<TViewer> viewer,
ffi.Pointer<ffi.Void> window,
int width,
int height,
);
@ffi.Native<ffi.Void Function(ffi.Pointer<TViewer>, ffi.Pointer<TSwapChain>)>(
isLeaf: true)
external void Viewer_destroySwapChain(
ffi.Pointer<TViewer> viewer,
ffi.Pointer<TSwapChain> swapChain,
);
@ffi.Native<
ffi.Bool Function(
ffi.Pointer<TViewer>,
ffi.Pointer<TSwapChain>,
ffi.Uint64,
ffi.Pointer<ffi.Void>,
ffi.Pointer<
ffi.NativeFunction<
ffi.Void Function(ffi.Pointer<ffi.Void> buf, ffi.Size size,
ffi.Pointer<ffi.Void> data)>>,
ffi.Pointer<ffi.Void>)>(isLeaf: true)
external bool Viewer_render(
ffi.Pointer<TViewer> viewer,
ffi.Pointer<TSwapChain> swapChain,
int frameTimeInNanos,
ffi.Pointer<ffi.Void> pixelBuffer,
ffi.Pointer<
ffi.NativeFunction<
ffi.Void Function(ffi.Pointer<ffi.Void> buf, ffi.Size size,
ffi.Pointer<ffi.Void> data)>>
callback,
ffi.Pointer<ffi.Void> data,
);
@ffi.Native<
ffi.Void Function(
ffi.Pointer<TViewer>,
ffi.Pointer<TSwapChain>,
ffi.Pointer<ffi.Uint8>,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>>)>(isLeaf: true)
external void Viewer_capture(
ffi.Pointer<TViewer> viewer,
ffi.Pointer<TSwapChain> swapChain,
ffi.Pointer<ffi.Uint8> pixelBuffer,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>> callback,
);
@ffi.Native<
ffi.Void Function(
ffi.Pointer<TViewer>,
ffi.Pointer<TSwapChain>,
ffi.Pointer<TRenderTarget>,
ffi.Pointer<ffi.Uint8>,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>>)>(isLeaf: true)
external void Viewer_captureRenderTarget(
ffi.Pointer<TViewer> viewer,
ffi.Pointer<TSwapChain> swapChain,
ffi.Pointer<TRenderTarget> renderTarget,
ffi.Pointer<ffi.Uint8> pixelBuffer,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>> callback,
);
@ffi.Native<ffi.Pointer<TEngine> Function(ffi.Pointer<TViewer>)>(isLeaf: true)
external ffi.Pointer<TEngine> Viewer_getEngine(
ffi.Pointer<TViewer> viewer,
@@ -47,16 +142,6 @@ external void Engine_setTransform(
double4x4 transform,
);
@ffi.Native<
ffi.Void Function(
ffi.Pointer<TViewer>, ffi.IntPtr, ffi.Uint32, ffi.Uint32)>(isLeaf: true)
external void create_render_target(
ffi.Pointer<TViewer> viewer,
int texture,
int width,
int height,
);
@ffi.Native<ffi.Void Function(ffi.Pointer<TViewer>)>(isLeaf: true)
external void clear_background_image(
ffi.Pointer<TViewer> viewer,
@@ -300,52 +385,6 @@ external void set_view_frustum_culling(
bool enabled,
);
@ffi.Native<
ffi.Bool Function(
ffi.Pointer<TViewer>,
ffi.Uint64,
ffi.Pointer<ffi.Void>,
ffi.Pointer<
ffi.NativeFunction<
ffi.Void Function(ffi.Pointer<ffi.Void> buf, ffi.Size size,
ffi.Pointer<ffi.Void> data)>>,
ffi.Pointer<ffi.Void>)>(isLeaf: true)
external bool render(
ffi.Pointer<TViewer> viewer,
int frameTimeInNanos,
ffi.Pointer<ffi.Void> pixelBuffer,
ffi.Pointer<
ffi.NativeFunction<
ffi.Void Function(ffi.Pointer<ffi.Void> buf, ffi.Size size,
ffi.Pointer<ffi.Void> data)>>
callback,
ffi.Pointer<ffi.Void> data,
);
@ffi.Native<
ffi.Void Function(ffi.Pointer<TViewer>, ffi.Pointer<ffi.Uint8>,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>>)>(isLeaf: true)
external void capture(
ffi.Pointer<TViewer> viewer,
ffi.Pointer<ffi.Uint8> pixelBuffer,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>> callback,
);
@ffi.Native<
ffi.Void Function(ffi.Pointer<TViewer>, ffi.Pointer<ffi.Void>, ffi.Uint32,
ffi.Uint32)>(isLeaf: true)
external void create_swap_chain(
ffi.Pointer<TViewer> viewer,
ffi.Pointer<ffi.Void> window,
int width,
int height,
);
@ffi.Native<ffi.Void Function(ffi.Pointer<TViewer>)>(isLeaf: true)
external void destroy_swap_chain(
ffi.Pointer<TViewer> viewer,
);
@ffi.Native<ffi.Void Function(ffi.Pointer<TViewer>, ffi.Float)>(isLeaf: true)
external void set_frame_interval(
ffi.Pointer<TViewer> viewer,
@@ -1111,19 +1150,6 @@ external ffi.Pointer<ffi.Char> get_entity_name_at(
bool renderableOnly,
);
@ffi.Native<ffi.Void Function(ffi.Pointer<TViewer>, ffi.Bool)>(isLeaf: true)
external void set_recording(
ffi.Pointer<TViewer> viewer,
bool recording,
);
@ffi.Native<ffi.Void Function(ffi.Pointer<TViewer>, ffi.Pointer<ffi.Char>)>(
isLeaf: true)
external void set_recording_output_directory(
ffi.Pointer<TViewer> viewer,
ffi.Pointer<ffi.Char> outputDirectory,
);
@ffi.Native<ffi.Void Function()>(isLeaf: true)
external void ios_dummy();
@@ -1484,36 +1510,65 @@ external void create_filament_viewer_render_thread(
);
@ffi.Native<
ffi.Void Function(
ffi.Pointer<TViewer>,
ffi.Pointer<ffi.Void>,
ffi.Uint32,
ffi.Uint32,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>>)>(isLeaf: true)
external void create_swap_chain_render_thread(
ffi.Void Function(
ffi.Pointer<TViewer>,
ffi.Pointer<ffi.Void>,
ffi.Uint32,
ffi.Uint32,
ffi.Pointer<
ffi
.NativeFunction<ffi.Void Function(ffi.Pointer<TSwapChain>)>>)>(
isLeaf: true)
external void Viewer_createSwapChainRenderThread(
ffi.Pointer<TViewer> viewer,
ffi.Pointer<ffi.Void> surface,
int width,
int height,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<TSwapChain>)>>
onComplete,
);
@ffi.Native<
ffi.Void Function(ffi.Pointer<TViewer>, ffi.Pointer<TSwapChain>,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>>)>(isLeaf: true)
external void Viewer_destroySwapChainRenderThread(
ffi.Pointer<TViewer> viewer,
ffi.Pointer<TSwapChain> swapChain,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>> onComplete,
);
@ffi.Native<ffi.Void Function(ffi.Pointer<TViewer>, ffi.Pointer<TSwapChain>)>(
isLeaf: true)
external void Viewer_renderRenderThread(
ffi.Pointer<TViewer> viewer,
ffi.Pointer<TSwapChain> swapChain,
);
@ffi.Native<
ffi.Void Function(
ffi.Pointer<TViewer>,
ffi.Pointer<TSwapChain>,
ffi.Pointer<ffi.Uint8>,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>>)>(isLeaf: true)
external void Viewer_captureRenderThread(
ffi.Pointer<TViewer> viewer,
ffi.Pointer<TSwapChain> swapChain,
ffi.Pointer<ffi.Uint8> out,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>> onComplete,
);
@ffi.Native<
ffi.Void Function(ffi.Pointer<TViewer>,
ffi.Void Function(
ffi.Pointer<TViewer>,
ffi.Pointer<TSwapChain>,
ffi.Pointer<TRenderTarget>,
ffi.Pointer<ffi.Uint8>,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>>)>(isLeaf: true)
external void destroy_swap_chain_render_thread(
external void Viewer_captureRenderTargetRenderThread(
ffi.Pointer<TViewer> viewer,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>> onComplete,
);
@ffi.Native<
ffi.Void Function(ffi.Pointer<TViewer>, ffi.IntPtr, ffi.Uint32, ffi.Uint32,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>>)>(isLeaf: true)
external void create_render_target_render_thread(
ffi.Pointer<TViewer> viewer,
int nativeTextureId,
int width,
int height,
ffi.Pointer<TSwapChain> swapChain,
ffi.Pointer<TRenderTarget> renderTarget,
ffi.Pointer<ffi.Uint8> out,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>> onComplete,
);
@@ -1522,20 +1577,6 @@ external void destroy_filament_viewer_render_thread(
ffi.Pointer<TViewer> viewer,
);
@ffi.Native<ffi.Void Function(ffi.Pointer<TViewer>)>(isLeaf: true)
external void render_render_thread(
ffi.Pointer<TViewer> viewer,
);
@ffi.Native<
ffi.Void Function(ffi.Pointer<TViewer>, ffi.Pointer<ffi.Uint8>,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>>)>(isLeaf: true)
external void capture_render_thread(
ffi.Pointer<TViewer> viewer,
ffi.Pointer<ffi.Uint8> out,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>> onComplete,
);
@ffi.Native<FilamentRenderCallback Function(FilamentRenderCallback)>(
isLeaf: true)
external FilamentRenderCallback make_render_callback_fn_pointer(
@@ -1927,6 +1968,10 @@ final class TViewer extends ffi.Opaque {}
final class TSceneManager extends ffi.Opaque {}
final class TRenderTarget extends ffi.Opaque {}
final class TSwapChain extends ffi.Opaque {}
final class TMaterialKey extends ffi.Struct {
@ffi.Bool()
external bool doubleSided;

View File

@@ -3,6 +3,7 @@ import 'dart:ffi';
import 'dart:math';
import 'dart:typed_data';
import 'package:animation_tools_dart/animation_tools_dart.dart';
import 'package:thermion_dart/src/viewer/src/shared_types/swap_chain.dart';
import 'package:vector_math/vector_math_64.dart';
import 'package:vector_math/vector_math_64.dart' as v64;
import '../../../../entities/gizmo.dart';
@@ -96,10 +97,21 @@ class ThermionViewerFFI extends ThermionViewer {
_initialize();
}
Future createRenderTarget(
double width, double height, int textureHandle) async {
await withVoidCallback((callback) => create_render_target_render_thread(
_viewer!, textureHandle, width.toInt(), height.toInt(), callback));
///
///
///
Future<RenderTarget> createRenderTarget(
int width, int height, int textureHandle) async {
final renderTarget =
Viewer_createRenderTarget(_viewer!, textureHandle, width, height);
return FFIRenderTarget(renderTarget, _viewer!);
}
///
///
///
Future setRenderTarget(FFIRenderTarget renderTarget) async {
Viewer_setRenderTarget(_viewer!, renderTarget.renderTarget);
}
Future updateViewportAndCameraProjection(double width, double height) async {
@@ -129,18 +141,13 @@ class ThermionViewerFFI extends ThermionViewer {
}
}
Future createSwapChain(double width, double height,
Future<SwapChain> createSwapChain(int width, int height,
{Pointer<Void>? surface}) async {
await withVoidCallback((callback) {
create_swap_chain_render_thread(_viewer!, surface ?? nullptr,
var swapChain = await withPointerCallback<TSwapChain>((callback) {
return Viewer_createSwapChainRenderThread(_viewer!, surface ?? nullptr,
width.toInt(), height.toInt(), callback);
});
}
Future destroySwapChain() async {
await withVoidCallback((callback) {
destroy_swap_chain_render_thread(_viewer!, callback);
});
return FFISwapChain(swapChain, _viewer!);
}
Gizmo? _gizmo;
@@ -150,7 +157,7 @@ class ThermionViewerFFI extends ThermionViewer {
final uberarchivePtr =
uberArchivePath?.toNativeUtf8(allocator: allocator).cast<Char>() ??
nullptr;
var viewer = await withPointerCallback(
_viewer = await withPointerCallback(
(Pointer<NativeFunction<Void Function(Pointer<TViewer>)>> callback) {
create_filament_viewer_render_thread(
_sharedContext,
@@ -161,7 +168,7 @@ class ThermionViewerFFI extends ThermionViewer {
_renderCallbackOwner,
callback);
});
_viewer = Pointer.fromAddress(viewer);
allocator.free(uberarchivePtr);
if (_viewer!.address == 0) {
throw Exception("Failed to create viewer. Check logs for details");
@@ -202,25 +209,30 @@ class ThermionViewerFFI extends ThermionViewer {
///
///
@override
Future render() async {
render_render_thread(_viewer!);
Future render(FFISwapChain swapChain) async {
Viewer_renderRenderThread(_viewer!, swapChain.swapChain);
}
///
///
///
@override
Future<Uint8List> capture() async {
Future<Uint8List> capture(FFISwapChain swapChain,
{FFIRenderTarget? renderTarget}) async {
final length = this.viewportDimensions.$1.toInt() *
this.viewportDimensions.$2.toInt() *
4;
final out = allocator<Uint8>(length);
final out = Uint8List(length);
await withVoidCallback((cb) {
capture_render_thread(_viewer!, out, cb);
if (renderTarget != null) {
Viewer_captureRenderTargetRenderThread(
_viewer!, swapChain.swapChain, renderTarget.renderTarget, out.address, cb);
} else {
Viewer_captureRenderThread(
_viewer!, swapChain.swapChain, out.address, cb);
}
});
final data = Uint8List.fromList(out.asTypedList(length));
allocator.free(out);
return data;
return out;
}
///
@@ -246,9 +258,7 @@ class ThermionViewerFFI extends ThermionViewer {
await setRendering(false);
await clearEntities();
await clearLights();
print("DESTROYING");
destroy_filament_viewer_render_thread(_viewer!);
print("DESTROYED");
_sceneManager = null;
_viewer = null;
await _pickResultController.close();
@@ -259,7 +269,6 @@ class ThermionViewerFFI extends ThermionViewer {
await callback.call();
}
_onDispose.clear();
print("DONE");
}
///
@@ -409,24 +418,24 @@ class ThermionViewerFFI extends ThermionViewer {
@override
Future<ThermionEntity> addDirectLight(DirectLight directLight) async {
var entity = add_light(
_viewer!,
directLight.type.index,
directLight.color,
directLight.intensity,
directLight.position.x,
directLight.position.y,
directLight.position.z,
directLight.direction.x,
directLight.direction.y,
directLight.direction.z,
directLight.falloffRadius,
directLight.spotLightConeInner,
directLight.spotLightConeOuter,
directLight.sunAngularRadius,
directLight.sunHaloSize,
directLight.sunHaloFallof,
directLight.castShadows,
);
_viewer!,
directLight.type.index,
directLight.color,
directLight.intensity,
directLight.position.x,
directLight.position.y,
directLight.position.z,
directLight.direction.x,
directLight.direction.y,
directLight.direction.z,
directLight.falloffRadius,
directLight.spotLightConeInner,
directLight.spotLightConeOuter,
directLight.sunAngularRadius,
directLight.sunHaloSize,
directLight.sunHaloFallof,
directLight.castShadows,
);
if (entity == _FILAMENT_ASSET_ERROR) {
throw Exception("Failed to add light to scene");
}
@@ -1747,24 +1756,6 @@ class ThermionViewerFFI extends ThermionViewer {
return names;
}
///
///
///
@override
Future setRecording(bool recording) async {
set_recording(_viewer!, recording);
}
///
///
///
@override
Future setRecordingOutputDirectory(String outputDir) async {
var pathPtr = outputDir.toNativeUtf8(allocator: allocator);
set_recording_output_directory(_viewer!, pathPtr.cast<Char>());
allocator.free(pathPtr);
}
final _collisions = <ThermionEntity, NativeCallable>{};
///
@@ -2254,3 +2245,28 @@ class ThermionFFIMaterialInstance extends MaterialInstance {
_pointer, name.toNativeUtf8().cast<Char>(), x, y);
}
}
class FFIRenderTarget extends RenderTarget {
final Pointer<TRenderTarget> renderTarget;
final Pointer<TViewer> viewer;
FFIRenderTarget(this.renderTarget, this.viewer);
@override
Future destroy() async {
Viewer_destroyRenderTarget(viewer, renderTarget);
}
}
class FFISwapChain extends SwapChain {
final Pointer<TSwapChain> swapChain;
final Pointer<TViewer> viewer;
FFISwapChain(this.swapChain, this.viewer);
Future destroy() async {
await withVoidCallback((callback) {
Viewer_destroySwapChainRenderThread(viewer, swapChain, callback);
});
}
}

View File

@@ -0,0 +1,3 @@
abstract class RenderTarget {
Future destroy();
}

View File

@@ -1,5 +1,7 @@
library shared_types;
export 'swap_chain.dart';
export 'render_target.dart';
export 'camera.dart';
export 'material.dart';
export 'texture.dart';

View File

@@ -0,0 +1,3 @@
abstract class SwapChain {
Future destroy();
}

View File

@@ -10,6 +10,8 @@ import 'package:vector_math/vector_math_64.dart';
import 'dart:async';
import 'package:animation_tools_dart/animation_tools_dart.dart';
import 'shared_types/swap_chain.dart';
const double kNear = 0.05;
const double kFar = 1000.0;
const double kFocalLength = 28.0;
@@ -60,7 +62,7 @@ abstract class ThermionViewer {
///
/// Render a single frame immediately.
///
Future render();
Future render(covariant SwapChain swapChain);
///
/// Requests a single frame to be rendered. This is only intended to be used internally.
@@ -70,7 +72,23 @@ abstract class ThermionViewer {
///
/// Render a single frame and copy the pixel buffer to [out].
///
Future<Uint8List> capture();
Future<Uint8List> capture(covariant SwapChain swapChain, { covariant RenderTarget? renderTarget });
///
///
///
Future<SwapChain> createSwapChain(int width, int height);
///
///
///
Future<RenderTarget> createRenderTarget(int width, int height, int textureHandle);
///
///
///
Future setRenderTarget(covariant RenderTarget renderTarget);
///
/// Sets the framerate for continuous rendering when [setRendering] is enabled.
@@ -368,7 +386,8 @@ abstract class ThermionViewer {
/// Sets multiple transforms (relative to parent) simultaneously for [entity].
/// Uses mutex to ensure that transform updates aren't split across frames.
///
Future queueTransformUpdates(List<ThermionEntity> entities, List<Matrix4> transforms);
Future queueTransformUpdates(
List<ThermionEntity> entities, List<Matrix4> transforms);
///
/// Updates the bone matrices for [entity] (which must be the ThermionEntity
@@ -757,17 +776,6 @@ abstract class ThermionViewer {
Future<List<String>> getChildEntityNames(ThermionEntity entity,
{bool renderableOnly = true});
///
/// If [recording] is set to true, each frame the framebuffer/texture will be written to /tmp/output_*.png.
/// This will impact performance; handle with care.
///
Future setRecording(bool recording);
///
/// Sets the output directory where recorded PNGs will be placed.
///
Future setRecordingOutputDirectory(String outputDirectory);
///
/// An [entity] will only be animatable after an animation component is attached.
/// Any calls to [playAnimation]/[setBoneAnimation]/[setMorphAnimation] will have no visual effect until [addAnimationComponent] has been called on the instance.

View File

@@ -1,6 +1,7 @@
import 'dart:math';
import 'dart:typed_data';
import 'package:thermion_dart/src/viewer/src/shared_types/swap_chain.dart';
import 'package:thermion_dart/thermion_dart.dart';
import 'package:vector_math/vector_math_64.dart';
import 'dart:async';
@@ -723,12 +724,6 @@ class ThermionViewerStub extends ThermionViewer {
throw UnimplementedError();
}
@override
Future<Uint8List> capture() {
// TODO: implement capture
throw UnimplementedError();
}
@override
Future<Aabb2> getBoundingBox(ThermionEntity entity) {
// TODO: implement getBoundingBox
@@ -1020,5 +1015,23 @@ class ThermionViewerStub extends ThermionViewer {
throw UnimplementedError();
}
@override
Future<SwapChain> createSwapChain(int width, int height) {
// TODO: implement createSwapChain
throw UnimplementedError();
}
@override
Future<RenderTarget> createRenderTarget(int width, int height, int textureHandle) {
// TODO: implement createRenderTarget
throw UnimplementedError();
}
@override
Future setRenderTarget(covariant RenderTarget renderTarget) {
// TODO: implement setRenderTarget
throw UnimplementedError();
}
}

View File

@@ -15,6 +15,8 @@ extern "C"
typedef struct TEntityManager TEntityManager;
typedef struct TViewer TViewer;
typedef struct TSceneManager TSceneManager;
typedef struct TRenderTarget TRenderTarget;
typedef struct TSwapChain TSwapChain;
struct TMaterialKey {
bool doubleSided = 1;

View File

@@ -77,6 +77,7 @@ namespace thermion_filament
void updateViewport(uint32_t width, uint32_t height);
bool render(
uint64_t frameTimeInNanos,
SwapChain* swapChain,
void *pixelBuffer,
void (*callback)(void *buf, size_t size, void *data),
void *data);
@@ -90,10 +91,12 @@ namespace thermion_filament
float getCameraFov(bool horizontal);
void setCameraFov(double fovDegrees, bool horizontal);
void createSwapChain(const void *surface, uint32_t width, uint32_t height);
void destroySwapChain();
SwapChain* createSwapChain(const void *surface, uint32_t width, uint32_t height);
void destroySwapChain(SwapChain* swapChain);
void createRenderTarget(intptr_t textureId, uint32_t width, uint32_t height);
RenderTarget* createRenderTarget(intptr_t textureId, uint32_t width, uint32_t height);
void destroyRenderTarget(RenderTarget* renderTarget);
void setRenderTarget(RenderTarget* renderTarget);
Renderer *getRenderer();
@@ -139,9 +142,8 @@ namespace thermion_filament
void clearLights();
void setPostProcessing(bool enabled);
void setRecording(bool recording);
void setRecordingOutputDirectory(const char *path);
void capture(uint8_t *out, bool useFence, void (*onComplete)());
void capture(uint8_t *out, bool useFence, SwapChain* swapChain, void (*onComplete)());
void capture(uint8_t *out, bool useFence, SwapChain* swapChain, RenderTarget* renderTarget, void (*onComplete)());
void setAntiAliasing(bool msaaEnabled, bool fxaaEnabled, bool taaEnabled);
void setDepthOfField();
@@ -164,10 +166,11 @@ namespace thermion_filament
Engine *_engine = nullptr;
thermion_filament::ThreadPool *_tp = nullptr;
Renderer *_renderer = nullptr;
SwapChain *_swapChain = nullptr;
SceneManager *_sceneManager = nullptr;
std::vector<RenderTarget*> _renderTargets;
std::vector<SwapChain*> _swapChains;
std::mutex mtx; // mutex to ensure thread safety when removing assets
std::mutex _renderMutex; // mutex to ensure thread safety when removing assets
std::vector<utils::Entity> _lights;
Texture *_skyboxTexture = nullptr;
@@ -208,12 +211,10 @@ namespace thermion_filament
void savePng(void *data, size_t size, int frameNumber);
void createBackgroundImage();
time_point_t _recordingStartTime = std::chrono::high_resolution_clock::now();
time_point_t _fpsCounterStartTime = std::chrono::high_resolution_clock::now();
bool _recording = false;
std::string _recordingOutputDirectory = std::string("/tmp");
std::mutex _recordingMutex;
std::mutex _imageMutex;
double _cumulativeAnimationUpdateTime = 0;
int _frameCount = 0;

View File

@@ -57,13 +57,37 @@ extern "C"
EMSCRIPTEN_KEEPALIVE TViewer *create_filament_viewer(const void *const context, const void *const loader, void *const platform, const char *uberArchivePath);
EMSCRIPTEN_KEEPALIVE void destroy_filament_viewer(TViewer *viewer);
EMSCRIPTEN_KEEPALIVE TSceneManager *Viewer_getSceneManager(TViewer *viewer);
EMSCRIPTEN_KEEPALIVE TRenderTarget* Viewer_createRenderTarget(TViewer *viewer, intptr_t texture, uint32_t width, uint32_t height);
EMSCRIPTEN_KEEPALIVE void Viewer_destroyRenderTarget(TViewer *viewer, TRenderTarget* tRenderTarget);
EMSCRIPTEN_KEEPALIVE void Viewer_setRenderTarget(TViewer *viewer, TRenderTarget* tRenderTarget);
EMSCRIPTEN_KEEPALIVE TSwapChain *Viewer_createSwapChain(TViewer *viewer, const void *const window, uint32_t width, uint32_t height);
EMSCRIPTEN_KEEPALIVE void Viewer_destroySwapChain(TViewer *viewer, TSwapChain* swapChain);
EMSCRIPTEN_KEEPALIVE bool Viewer_render(
TViewer *viewer,
TSwapChain *swapChain,
uint64_t frameTimeInNanos,
void *pixelBuffer,
void (*callback)(void *buf, size_t size, void *data),
void *data);
EMSCRIPTEN_KEEPALIVE void Viewer_capture(
TViewer *viewer,
TSwapChain *swapChain,
uint8_t *pixelBuffer,
void (*callback)(void));
EMSCRIPTEN_KEEPALIVE void Viewer_captureRenderTarget(
TViewer *viewer,
TSwapChain *swapChain,
TRenderTarget *renderTarget,
uint8_t *pixelBuffer,
void (*callback)(void));
// Engine
EMSCRIPTEN_KEEPALIVE TEngine *Viewer_getEngine(TViewer* viewer);
EMSCRIPTEN_KEEPALIVE TCamera *Engine_getCameraComponent(TEngine* tEngine, EntityId entityId);
EMSCRIPTEN_KEEPALIVE void Engine_setTransform(TEngine* tEngine, EntityId entity, double4x4 transform);
EMSCRIPTEN_KEEPALIVE void create_render_target(TViewer *viewer, intptr_t texture, uint32_t width, uint32_t height);
EMSCRIPTEN_KEEPALIVE void clear_background_image(TViewer *viewer);
EMSCRIPTEN_KEEPALIVE void set_background_image(TViewer *viewer, const char *path, bool fillHeight);
EMSCRIPTEN_KEEPALIVE void set_background_image_position(TViewer *viewer, float x, float y, bool clamp);
@@ -108,18 +132,8 @@ extern "C"
EMSCRIPTEN_KEEPALIVE EntityId get_main_camera(TViewer *viewer);
EMSCRIPTEN_KEEPALIVE bool set_camera(TViewer *viewer, EntityId entity, const char *nodeName);
EMSCRIPTEN_KEEPALIVE void set_view_frustum_culling(TViewer *viewer, bool enabled);
EMSCRIPTEN_KEEPALIVE bool render(
TViewer *viewer,
uint64_t frameTimeInNanos,
void *pixelBuffer,
void (*callback)(void *buf, size_t size, void *data),
void *data);
EMSCRIPTEN_KEEPALIVE void capture(
TViewer *viewer,
uint8_t *pixelBuffer,
void (*callback)(void));
EMSCRIPTEN_KEEPALIVE void create_swap_chain(TViewer *viewer, const void *const window, uint32_t width, uint32_t height);
EMSCRIPTEN_KEEPALIVE void destroy_swap_chain(TViewer *viewer);
EMSCRIPTEN_KEEPALIVE void set_frame_interval(TViewer *viewer, float interval);
EMSCRIPTEN_KEEPALIVE void update_viewport(TViewer *viewer, uint32_t width, uint32_t height);
EMSCRIPTEN_KEEPALIVE void scroll_begin(TViewer *viewer);
@@ -271,8 +285,7 @@ extern "C"
EMSCRIPTEN_KEEPALIVE int get_entity_count(TSceneManager *sceneManager, const EntityId target, bool renderableOnly);
EMSCRIPTEN_KEEPALIVE void get_entities(TSceneManager *sceneManager, const EntityId target, bool renderableOnly, EntityId *out);
EMSCRIPTEN_KEEPALIVE const char *get_entity_name_at(TSceneManager *sceneManager, const EntityId target, int index, bool renderableOnly);
EMSCRIPTEN_KEEPALIVE void set_recording(TViewer *viewer, bool recording);
EMSCRIPTEN_KEEPALIVE void set_recording_output_directory(TViewer *viewer, const char *outputDirectory);
EMSCRIPTEN_KEEPALIVE void ios_dummy();
EMSCRIPTEN_KEEPALIVE void thermion_flutter_free(void *ptr);
EMSCRIPTEN_KEEPALIVE void add_collision_component(TSceneManager *sceneManager, EntityId entityId, void (*callback)(const EntityId entityId1, const EntityId entityId2), bool affectsCollidingTransform);

View File

@@ -25,12 +25,14 @@ extern "C"
void (*renderCallback)(void *const renderCallbackOwner),
void *const renderCallbackOwner,
void (*callback)(TViewer *viewer));
EMSCRIPTEN_KEEPALIVE void create_swap_chain_render_thread(TViewer *viewer, void *const surface, uint32_t width, uint32_t height, void (*onComplete)());
EMSCRIPTEN_KEEPALIVE void destroy_swap_chain_render_thread(TViewer *viewer, void (*onComplete)());
EMSCRIPTEN_KEEPALIVE void create_render_target_render_thread(TViewer *viewer, intptr_t nativeTextureId, uint32_t width, uint32_t height, void (*onComplete)());
EMSCRIPTEN_KEEPALIVE void Viewer_createSwapChainRenderThread(TViewer *viewer, void *const surface, uint32_t width, uint32_t height, void (*onComplete)(TSwapChain*));
EMSCRIPTEN_KEEPALIVE void Viewer_destroySwapChainRenderThread(TViewer *viewer, TSwapChain* swapChain, void (*onComplete)());
EMSCRIPTEN_KEEPALIVE void Viewer_renderRenderThread(TViewer *viewer, TSwapChain* swapChain);
EMSCRIPTEN_KEEPALIVE void Viewer_captureRenderThread(TViewer *viewer, TSwapChain* swapChain, uint8_t* out, void (*onComplete)());
EMSCRIPTEN_KEEPALIVE void Viewer_captureRenderTargetRenderThread(TViewer *viewer, TSwapChain* swapChain, TRenderTarget* renderTarget, uint8_t* out, void (*onComplete)());
EMSCRIPTEN_KEEPALIVE void destroy_filament_viewer_render_thread(TViewer *viewer);
EMSCRIPTEN_KEEPALIVE void render_render_thread(TViewer *viewer);
EMSCRIPTEN_KEEPALIVE void capture_render_thread(TViewer *viewer, uint8_t* out, void (*onComplete)());
EMSCRIPTEN_KEEPALIVE FilamentRenderCallback make_render_callback_fn_pointer(FilamentRenderCallback);
EMSCRIPTEN_KEEPALIVE void set_rendering_render_thread(TViewer *viewer, bool rendering, void(*onComplete)());
EMSCRIPTEN_KEEPALIVE void request_frame_render_thread(TViewer *viewer, void(*onComplete)());

View File

@@ -739,7 +739,12 @@ namespace thermion_filament
FilamentViewer::~FilamentViewer()
{
clearLights();
destroySwapChain();
for(auto swapChain : _swapChains) {
_engine->destroy(swapChain);
}
_swapChains.clear();
if (!_imageEntity.isNull())
{
_engine->destroy(_imageEntity);
@@ -760,25 +765,29 @@ namespace thermion_filament
Renderer *FilamentViewer::getRenderer() { return _renderer; }
void FilamentViewer::createSwapChain(const void *window, uint32_t width, uint32_t height)
SwapChain* FilamentViewer::createSwapChain(const void *window, uint32_t width, uint32_t height)
{
std::lock_guard lock(_renderMutex);
SwapChain* swapChain;
#if TARGET_OS_IPHONE
_swapChain = _engine->createSwapChain((void *)window, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER);
swapChain = _engine->createSwapChain((void *)window, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER);
#else
if (window)
{
_swapChain = _engine->createSwapChain((void *)window, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE);
swapChain = _engine->createSwapChain((void *)window, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE);
Log("Created window swapchain.");
}
else
{
Log("Created headless swapchain.");
_swapChain = _engine->createSwapChain(width, height, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE | filament::SwapChain::CONFIG_HAS_STENCIL_BUFFER);
swapChain = _engine->createSwapChain(width, height, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE | filament::SwapChain::CONFIG_HAS_STENCIL_BUFFER);
}
#endif
_swapChains.push_back(swapChain);
return swapChain;
}
void FilamentViewer::createRenderTarget(intptr_t texture, uint32_t width, uint32_t height)
RenderTarget* FilamentViewer::createRenderTarget(intptr_t texture, uint32_t width, uint32_t height)
{
// Create filament textures and render targets (note the color buffer has the import call)
auto rtColor = filament::Texture::Builder()
@@ -796,40 +805,44 @@ namespace thermion_filament
.usage(filament::Texture::Usage::DEPTH_ATTACHMENT | filament::Texture::Usage::SAMPLEABLE)
.format(filament::Texture::InternalFormat::DEPTH32F)
.build(*_engine);
_rt = filament::RenderTarget::Builder()
auto rt = filament::RenderTarget::Builder()
.texture(RenderTarget::AttachmentPoint::COLOR, rtColor)
.texture(RenderTarget::AttachmentPoint::DEPTH, rtDepth)
.build(*_engine);
_view->setRenderTarget(_rt);
Log("Created render target for texture id %ld (%u x %u)", (long)texture, width, height);
return rt;
}
void FilamentViewer::destroySwapChain()
void FilamentViewer::destroyRenderTarget(RenderTarget* renderTarget) {
std::lock_guard lock(_renderMutex);
auto rtDepth = renderTarget->getTexture(RenderTarget::AttachmentPoint::DEPTH);
if(rtDepth) {
_engine->destroy(rtDepth);
}
auto rtColor = renderTarget->getTexture(RenderTarget::AttachmentPoint::COLOR);
if(rtColor) {
_engine->destroy(rtColor);
}
_engine->destroy(renderTarget);
auto it = std::find(_renderTargets.begin(), _renderTargets.end(), renderTarget);
if(it != _renderTargets.end()) {
_renderTargets.erase(it);
}
}
void FilamentViewer::setRenderTarget(RenderTarget *renderTarget) {
std::lock_guard lock(_renderMutex);
_view->setRenderTarget(renderTarget);
}
void FilamentViewer::destroySwapChain(SwapChain *swapChain)
{
if (_rt)
{
_view->setRenderTarget(nullptr);
auto rtDepth = _rt->getTexture(RenderTarget::AttachmentPoint::DEPTH);
if(rtDepth) {
_engine->destroy(rtDepth);
Log("destroyed depth");
}
auto rtColor = _rt->getTexture(RenderTarget::AttachmentPoint::COLOR);
if(rtColor) {
_engine->destroy(rtColor);
Log("destroyed color");
}
_engine->destroy(_rt);
_rt = nullptr;
}
if (_swapChain)
{
_engine->destroy(_swapChain);
_swapChain = nullptr;
Log("Swapchain destroyed.");
std::lock_guard lock(_renderMutex);
auto it = std::find(_swapChains.begin(), _swapChains.end(), swapChain);
if(it != _swapChains.end()) {
_swapChains.erase(it);
}
_engine->destroy(swapChain);
Log("Swapchain destroyed.");
#ifdef __EMSCRIPTEN__
_engine->execute();
#else
@@ -845,10 +858,10 @@ namespace thermion_filament
void FilamentViewer::removeEntity(EntityId asset)
{
mtx.lock();
_renderMutex.lock();
// todo - what if we are using a camera from this asset?
_sceneManager->remove(asset);
mtx.unlock();
_renderMutex.unlock();
}
///
@@ -987,7 +1000,6 @@ namespace thermion_filament
void FilamentViewer::removeSkybox()
{
Log("Removing skybox");
_scene->setSkybox(nullptr);
if (_skybox)
{
@@ -1114,12 +1126,13 @@ namespace thermion_filament
bool FilamentViewer::render(
uint64_t frameTimeInNanos,
SwapChain* swapChain,
void *pixelBuffer,
void (*callback)(void *buf, size_t size, void *data),
void *data)
{
if (!_view || !_swapChain)
if (!_view || !swapChain)
{
return false;
}
@@ -1152,7 +1165,7 @@ namespace thermion_filament
}
// Render the scene, unless the renderer wants to skip the frame.
bool beginFrame = _renderer->beginFrame(_swapChain, frameTimeInNanos);
bool beginFrame = _renderer->beginFrame(swapChain, frameTimeInNanos);
if (!beginFrame)
{
_skippedFrames++;
@@ -1165,33 +1178,6 @@ namespace thermion_filament
_frameCount++;
if (_recording)
{
Viewport const &vp = _view->getViewport();
size_t pixelBufferSize = vp.width * vp.height * 4;
auto *pixelBuffer = new uint8_t[pixelBufferSize];
auto callback = [](void *buf, size_t size, void *data)
{
auto frameCallbackData = (FrameCallbackData *)data;
auto viewer = (FilamentViewer *)frameCallbackData->viewer;
viewer->savePng(buf, size, frameCallbackData->frameNumber);
delete frameCallbackData;
};
auto now = std::chrono::high_resolution_clock::now();
auto elapsed = float(std::chrono::duration_cast<std::chrono::milliseconds>(now - _recordingStartTime).count());
auto frameNumber = uint32_t(floor(elapsed / _frameInterval));
auto userData = new FrameCallbackData{this, frameNumber};
auto pbd = Texture::PixelBufferDescriptor(
pixelBuffer, pixelBufferSize,
Texture::Format::RGBA,
Texture::Type::UBYTE, nullptr, callback, userData);
_renderer->readPixels(_rt, 0, 0, vp.width, vp.height, std::move(pbd));
}
_renderer->endFrame();
}
#ifdef __EMSCRIPTEN__
@@ -1206,9 +1192,14 @@ namespace thermion_filament
}
};
void FilamentViewer::capture(uint8_t *out, bool useFence, void (*onComplete)())
void FilamentViewer::capture(uint8_t *out, bool useFence, SwapChain* swapChain, void (*onComplete)())
{
if(!swapChain) {
Log("NO SWAPCHAIN");
return;
}
Viewport const &vp = _view->getViewport();
size_t pixelBufferSize = vp.width * vp.height * 4;
auto *pixelBuffer = new uint8_t[pixelBufferSize];
@@ -1240,18 +1231,9 @@ namespace thermion_filament
pixelBuffer, pixelBufferSize,
Texture::Format::RGBA,
Texture::Type::UBYTE, dispatcher, callback, userData);
_renderer->beginFrame(_swapChain, 0);
_renderer->render(_view);
if (_rt)
{
_renderer->readPixels(_rt, 0, 0, vp.width, vp.height, std::move(pbd));
}
else
{
_renderer->readPixels(0, 0, vp.width, vp.height, std::move(pbd));
}
_renderer->beginFrame(swapChain, 0);
_renderer->render(_view);
_renderer->readPixels(0, 0, vp.width, vp.height, std::move(pbd));
_renderer->endFrame();
#ifdef __EMSCRIPTEN__
@@ -1263,74 +1245,57 @@ namespace thermion_filament
}
}
void FilamentViewer::savePng(void *buf, size_t size, int frameNumber)
void FilamentViewer::capture(uint8_t *out, bool useFence, SwapChain* swapChain, RenderTarget* renderTarget, void (*onComplete)())
{
// std::lock_guard lock(_recordingMutex);
// if (!_recording)
// {
// delete[] static_cast<uint8_t *>(buf);
// return;
// }
if(!renderTarget) {
Log("NO SWAPCHAIN");
return;
}
Viewport const &vp = _view->getViewport();
size_t pixelBufferSize = vp.width * vp.height * 4;
auto *pixelBuffer = new uint8_t[pixelBufferSize];
auto callback = [](void *buf, size_t size, void *data)
{
auto frameCallbackData = (std::vector<void *> *)data;
uint8_t *out = (uint8_t *)(frameCallbackData->at(0));
void *callbackPtr = frameCallbackData->at(1);
std::packaged_task<void()> lambda([=]() mutable
{
int digits = 6;
std::ostringstream stringStream;
stringStream << _recordingOutputDirectory << "/output_";
stringStream << std::setfill('0') << std::setw(digits);
stringStream << std::to_string(frameNumber);
stringStream << ".png";
memcpy(out, buf, size);
delete frameCallbackData;
if(callbackPtr) {
void (*callback)(void) = (void (*)(void))callbackPtr;
callback();
}
};
std::string filename = stringStream.str();
// Create a fence
Fence* fence = nullptr;
if(useFence) {
fence = _engine->createFence();
}
std::ofstream wf(filename, std::ios::out | std::ios::binary);
auto userData = new std::vector<void *>{out, (void *)onComplete};
LinearImage image(toLinearWithAlpha<uint8_t>(vp.width, vp.height, vp.width * 4,
static_cast<uint8_t *>(buf)));
auto dispatcher = new CaptureCallbackHandler();
auto result = image::ImageEncoder::encode(
wf, image::ImageEncoder::Format::PNG, image, std::string(""), std::string(""));
auto pbd = Texture::PixelBufferDescriptor(
pixelBuffer, pixelBufferSize,
Texture::Format::RGBA,
Texture::Type::UBYTE, dispatcher, callback, userData);
_renderer->beginFrame(swapChain, 0);
_renderer->render(_view);
_renderer->readPixels(renderTarget, 0, 0, vp.width, vp.height, std::move(pbd));
_renderer->endFrame();
delete[] static_cast<uint8_t *>(buf);
if (!result)
{
Log("Failed to encode");
}
wf.close();
if (!wf.good())
{
Log("Write failed!");
} });
_tp->add_task(lambda);
#ifdef __EMSCRIPTEN__
_engine->execute();
emscripten_webgl_commit_frame();
#endif
if(fence) {
Fence::waitAndDestroy(fence);
}
void FilamentViewer::setRecordingOutputDirectory(const char *path)
{
_recordingOutputDirectory = std::string(path);
auto outputDirAsPath = std::filesystem::path(path);
if (!std::filesystem::is_directory(outputDirAsPath))
{
std::filesystem::create_directories(outputDirAsPath);
}
}
void FilamentViewer::setRecording(bool recording)
{
// std::lock_guard lock(_recordingMutex);
if (recording)
{
_tp = new thermion_filament::ThreadPool(16);
_recordingStartTime = std::chrono::high_resolution_clock::now();
}
else
{
delete _tp;
}
this->_recording = recording;
}
Camera* FilamentViewer::getCamera(EntityId entity) {
@@ -1381,7 +1346,7 @@ namespace thermion_filament
void FilamentViewer::grabBegin(float x, float y, bool pan)
{
if (!_view || !_mainCamera || !_swapChain)
if (!_view || !_mainCamera)
{
Log("View not ready, ignoring grab");
return;
@@ -1395,7 +1360,7 @@ namespace thermion_filament
void FilamentViewer::grabUpdate(float x, float y)
{
if (!_view || !_swapChain)
if (!_view )
{
Log("View not ready, ignoring grab");
return;
@@ -1413,7 +1378,7 @@ namespace thermion_filament
void FilamentViewer::grabEnd()
{
if (!_view || !_mainCamera || !_swapChain)
if (!_view || !_mainCamera )
{
Log("View not ready, ignoring grab");
return;

View File

@@ -56,9 +56,23 @@ extern "C"
return reinterpret_cast<TEngine*>(engine);
}
EMSCRIPTEN_KEEPALIVE void create_render_target(TViewer *viewer, intptr_t texture, uint32_t width, uint32_t height)
EMSCRIPTEN_KEEPALIVE TRenderTarget* Viewer_createRenderTarget(TViewer *tViewer, intptr_t texture, uint32_t width, uint32_t height)
{
((FilamentViewer *)viewer)->createRenderTarget(texture, width, height);
auto viewer = reinterpret_cast<FilamentViewer *>(tViewer);
auto renderTarget = viewer->createRenderTarget(texture, width, height);
return reinterpret_cast<TRenderTarget*>(renderTarget);
}
EMSCRIPTEN_KEEPALIVE void Viewer_destroyRenderTarget(TViewer *tViewer, TRenderTarget* tRenderTarget) {
auto viewer = reinterpret_cast<FilamentViewer *>(tViewer);
auto renderTarget = reinterpret_cast<RenderTarget*>(tRenderTarget);
viewer->destroyRenderTarget(renderTarget);
}
EMSCRIPTEN_KEEPALIVE void Viewer_setRenderTarget(TViewer *tViewer, TRenderTarget* tRenderTarget) {
auto viewer = reinterpret_cast<FilamentViewer *>(tViewer);
auto renderTarget = reinterpret_cast<RenderTarget*>(tRenderTarget);
viewer->setRenderTarget(renderTarget);
}
EMSCRIPTEN_KEEPALIVE void destroy_filament_viewer(TViewer *viewer)
@@ -349,18 +363,22 @@ extern "C"
cam->setModelMatrix(mat);
}
EMSCRIPTEN_KEEPALIVE bool render(
TViewer *viewer,
EMSCRIPTEN_KEEPALIVE bool Viewer_render(
TViewer *tViewer,
TSwapChain *tSwapChain,
uint64_t frameTimeInNanos,
void *pixelBuffer,
void (*callback)(void *buf, size_t size, void *data),
void *data)
{
return ((FilamentViewer *)viewer)->render(frameTimeInNanos, pixelBuffer, callback, data);
auto swapChain = reinterpret_cast<SwapChain*>(tSwapChain);
auto viewer = reinterpret_cast<FilamentViewer*>(tViewer);
return viewer->render(frameTimeInNanos, swapChain, pixelBuffer, callback, data);
}
EMSCRIPTEN_KEEPALIVE void capture(
TViewer *viewer,
EMSCRIPTEN_KEEPALIVE void Viewer_capture(
TViewer *tViewer,
TSwapChain* tSwapChain,
uint8_t *pixelBuffer,
void (*callback)(void))
{
@@ -369,7 +387,27 @@ extern "C"
#else
bool useFence = false;
#endif
((FilamentViewer *)viewer)->capture(pixelBuffer, useFence, callback);
auto swapChain = reinterpret_cast<SwapChain*>(tSwapChain);
auto viewer = reinterpret_cast<FilamentViewer*>(tViewer);
viewer->capture(pixelBuffer, useFence, swapChain, callback);
};
EMSCRIPTEN_KEEPALIVE void Viewer_captureRenderTarget(
TViewer *tViewer,
TSwapChain* tSwapChain,
TRenderTarget *tRenderTarget,
uint8_t *pixelBuffer,
void (*callback)(void))
{
#ifdef __EMSCRIPTEN__
bool useFence = true;
#else
bool useFence = false;
#endif
auto swapChain = reinterpret_cast<SwapChain*>(tSwapChain);
auto renderTarget = reinterpret_cast<RenderTarget*>(tRenderTarget);
auto viewer = reinterpret_cast<FilamentViewer*>(tViewer);
viewer->capture(pixelBuffer, useFence, swapChain, renderTarget, callback);
};
EMSCRIPTEN_KEEPALIVE void set_frame_interval(
@@ -379,14 +417,17 @@ extern "C"
((FilamentViewer *)viewer)->setFrameInterval(frameInterval);
}
EMSCRIPTEN_KEEPALIVE void destroy_swap_chain(TViewer *viewer)
EMSCRIPTEN_KEEPALIVE void Viewer_destroySwapChain(TViewer *tViewer, TSwapChain* tSwapChain)
{
((FilamentViewer *)viewer)->destroySwapChain();
auto viewer = reinterpret_cast<FilamentViewer*>(tViewer);
auto swapChain = reinterpret_cast<SwapChain*>(tSwapChain);
viewer->destroySwapChain(swapChain);
}
EMSCRIPTEN_KEEPALIVE void create_swap_chain(TViewer *viewer, const void *const window, uint32_t width, uint32_t height)
{
((FilamentViewer *)viewer)->createSwapChain(window, width, height);
EMSCRIPTEN_KEEPALIVE TSwapChain *Viewer_createSwapChain(TViewer *tViewer, const void *const window, uint32_t width, uint32_t height) {
auto viewer = reinterpret_cast<FilamentViewer*>(tViewer);
auto swapChain = viewer->createSwapChain(window, width, height);
return reinterpret_cast<TSwapChain*>(swapChain);
}
EMSCRIPTEN_KEEPALIVE void update_viewport(TViewer *viewer, uint32_t width, uint32_t height)
@@ -837,16 +878,6 @@ extern "C"
return ((SceneManager *)sceneManager)->getEntityNameAt(target, index, renderableOnly);
}
EMSCRIPTEN_KEEPALIVE void set_recording(TViewer *viewer, bool recording)
{
((FilamentViewer *)viewer)->setRecording(recording);
}
EMSCRIPTEN_KEEPALIVE void set_recording_output_directory(TViewer *viewer, const char *outputDirectory)
{
((FilamentViewer *)viewer)->setRecordingOutputDirectory(outputDirectory);
}
EMSCRIPTEN_KEEPALIVE void ios_dummy()
{
Log("Dummy called");

View File

@@ -49,8 +49,8 @@ public:
~RenderLoop()
{
_render = false;
_stop = true;
target = nullptr;
_cv.notify_one();
#ifdef __EMSCRIPTEN__
pthread_join(t, NULL);
@@ -82,20 +82,20 @@ public:
}
}
void requestFrame(void (*callback)())
void requestFrame(SwapChain* swapChain, void (*callback)())
{
this->_render = true;
this->target = swapChain;
this->_requestFrameRenderCallback = callback;
}
void iter()
{
std::unique_lock<std::mutex> lock(_mutex);
if (_render)
if (target)
{
doRender();
doRender(target);
this->_requestFrameRenderCallback();
_render = false;
target = nullptr;
// Calculate and print FPS
auto currentTime = std::chrono::high_resolution_clock::now();
@@ -123,7 +123,7 @@ public:
}
_cv.wait_for(lock, std::chrono::microseconds(1000), [this]
{ return !_tasks.empty() || _stop || _render; });
{ return !_tasks.empty() || _stop; });
if (_stop)
return;
@@ -169,14 +169,14 @@ public:
{
std::packaged_task<void()> lambda([=]() mutable
{
_render = false;
target = nullptr;
_viewer = nullptr;
destroy_filament_viewer(reinterpret_cast<TViewer*>(viewer)); });
auto fut = add_task(lambda);
fut.wait();
}
bool doRender()
bool doRender(SwapChain *swapChain)
{
#ifdef __EMSCRIPTEN__
if (emscripten_is_webgl_context_lost(_context) == EM_TRUE)
@@ -187,7 +187,8 @@ public:
return;
}
#endif
auto rendered = render(_viewer, 0, nullptr, nullptr, nullptr);
TSwapChain *tSwapChain = reinterpret_cast<TSwapChain*>(swapChain);
auto rendered = Viewer_render(_viewer, tSwapChain, 0, nullptr, nullptr, nullptr);
if (_renderCallback)
{
_renderCallback(_renderCallbackOwner);
@@ -217,7 +218,7 @@ public:
}
public:
std::atomic_bool _render = false;
SwapChain* target;
private:
void(*_requestFrameRenderCallback)() = nullptr;
@@ -270,16 +271,31 @@ extern "C"
_rl = nullptr;
}
EMSCRIPTEN_KEEPALIVE void create_swap_chain_render_thread(TViewer *viewer,
EMSCRIPTEN_KEEPALIVE void Viewer_createSwapChainRenderThread(TViewer *viewer,
void *const surface,
uint32_t width,
uint32_t height,
void (*onComplete)())
void (*onComplete)(TSwapChain*))
{
std::packaged_task<void()> lambda(
[=]() mutable
{
create_swap_chain(viewer, surface, width, height);
auto *swapChain = Viewer_createSwapChain(viewer, surface, width, height);
#ifdef __EMSCRIPTEN__
MAIN_THREAD_EM_ASM({ moduleArg.dartFilamentResolveCallback($0); }, onComplete);
#else
onComplete(swapChain);
#endif
});
auto fut = _rl->add_task(lambda);
}
EMSCRIPTEN_KEEPALIVE void Viewer_destroySwapChainRenderThread(TViewer *viewer, TSwapChain *swapChain, void (*onComplete)())
{
std::packaged_task<void()> lambda(
[=]() mutable
{
Viewer_destroySwapChain(viewer, swapChain);
#ifdef __EMSCRIPTEN__
MAIN_THREAD_EM_ASM({ moduleArg.dartFilamentResolveCallback($0); }, onComplete);
#else
@@ -289,40 +305,8 @@ extern "C"
auto fut = _rl->add_task(lambda);
}
EMSCRIPTEN_KEEPALIVE void destroy_swap_chain_render_thread(TViewer *viewer, void (*onComplete)())
{
std::packaged_task<void()> lambda(
[=]() mutable
{
destroy_swap_chain(viewer);
#ifdef __EMSCRIPTEN__
MAIN_THREAD_EM_ASM({ moduleArg.dartFilamentResolveCallback($0); }, onComplete);
#else
onComplete();
#endif
});
auto fut = _rl->add_task(lambda);
}
EMSCRIPTEN_KEEPALIVE void create_render_target_render_thread(TViewer *viewer,
intptr_t nativeTextureId,
uint32_t width,
uint32_t height,
void (*onComplete)())
{
std::packaged_task<void()> lambda([=]() mutable
{
create_render_target(viewer, nativeTextureId, width, height);
#ifdef __EMSCRIPTEN__
MAIN_THREAD_EM_ASM({ moduleArg.dartFilamentResolveCallback($0); }, onComplete);
#else
onComplete();
#endif
});
auto fut = _rl->add_task(lambda);
}
EMSCRIPTEN_KEEPALIVE void request_frame_render_thread(TViewer *viewer, void(*onComplete)())
EMSCRIPTEN_KEEPALIVE void Viewer_requestFrameRenderThread(TViewer *viewer, TSwapChain* tSwapChain, void(*onComplete)())
{
if (!_rl)
{
@@ -330,7 +314,7 @@ extern "C"
}
else
{
_rl->requestFrame(onComplete);
_rl->requestFrame(reinterpret_cast<SwapChain*>(tSwapChain), onComplete);
}
}
@@ -343,17 +327,24 @@ extern "C"
auto fut = _rl->add_task(lambda);
}
EMSCRIPTEN_KEEPALIVE void render_render_thread(TViewer *viewer)
EMSCRIPTEN_KEEPALIVE void Viewer_renderRenderThread(TViewer *viewer, TSwapChain *tSwapChain)
{
std::packaged_task<void()> lambda([=]() mutable
{ _rl->doRender(); });
{ _rl->doRender(reinterpret_cast<SwapChain*>(tSwapChain)); });
auto fut = _rl->add_task(lambda);
}
EMSCRIPTEN_KEEPALIVE void capture_render_thread(TViewer *viewer, uint8_t *pixelBuffer, void (*onComplete)())
EMSCRIPTEN_KEEPALIVE void Viewer_captureRenderThread(TViewer *viewer, TSwapChain *tSwapChain, uint8_t *pixelBuffer, void (*onComplete)())
{
std::packaged_task<void()> lambda([=]() mutable
{ capture(viewer, pixelBuffer, onComplete); });
{ Viewer_capture(viewer, tSwapChain, pixelBuffer, onComplete); });
auto fut = _rl->add_task(lambda);
}
EMSCRIPTEN_KEEPALIVE void Viewer_captureRenderTargetRenderThread(TViewer *viewer, TSwapChain *tSwapChain, TRenderTarget* tRenderTarget, uint8_t *pixelBuffer, void (*onComplete)())
{
std::packaged_task<void()> lambda([=]() mutable
{ Viewer_captureRenderTarget(viewer, tSwapChain, tRenderTarget, pixelBuffer, onComplete); });
auto fut = _rl->add_task(lambda);
}

View File

@@ -8,7 +8,7 @@ void main() async {
group('camera', () {
test('getCameraModelMatrix, getCameraPosition, rotation', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
var matrix = await viewer.getCameraModelMatrix();
expect(matrix.trace(), 4);
@@ -26,7 +26,7 @@ void main() async {
});
test('getCameraViewMatrix', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
var modelMatrix = await viewer.getCameraModelMatrix();
var viewMatrix = await viewer.getCameraViewMatrix();
@@ -49,20 +49,20 @@ void main() async {
});
test('getCameraProjectionMatrix', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
var projectionMatrix = await viewer.getCameraProjectionMatrix();
print(projectionMatrix);
});
test('getCameraCullingProjectionMatrix', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
var matrix = await viewer.getCameraCullingProjectionMatrix();
print(matrix);
throw Exception("TODO");
});
test('getCameraFrustum', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
var frustum = await viewer.getCameraFrustum();
print(frustum.plane5.normal);
print(frustum.plane5.constant);
@@ -76,7 +76,7 @@ void main() async {
test('set custom projection/culling matrix', () async {
var viewer =
await createViewer(bg: kRed, cameraPosition: Vector3(0, 0, 4));
await testHelper.createViewer(bg: kRed, cameraPosition: Vector3(0, 0, 4));
var camera = await viewer.getMainCamera();
final cube = await viewer.createGeometry(GeometryHelper.cube());
@@ -98,7 +98,7 @@ void main() async {
test('setting transform on camera updates model matrix (no parent)',
() async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
var cameraEntity = await viewer.getMainCameraEntity();
var camera = await viewer.getMainCamera();
@@ -114,7 +114,7 @@ void main() async {
test('setting transform on camera updates model matrix (with parent)',
() async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
var cameraEntity = await viewer.getMainCameraEntity();
var camera = await viewer.getMainCamera();
@@ -140,7 +140,7 @@ void main() async {
});
test('create camera', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setCameraPosition(0, 0, 5);
await viewer.setBackgroundColor(1.0, 0.0, 1.0, 1.0);

View File

@@ -0,0 +1,56 @@
import 'dart:io';
import 'package:thermion_dart/thermion_dart.dart';
import 'package:test/test.dart';
import 'package:vector_math/vector_math_64.dart';
import 'helpers.dart';
void main() async {
final testHelper = TestHelper("gltf");
group("gltf", () {
test('load glb from file', () async {
var viewer = await testHelper.createViewer(bg: kRed, cameraPosition: Vector3(0, 1, 5));
var model = await viewer.loadGlb("file://${testHelper.testDir}/assets/cube.glb");
await viewer
.setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -0.5));
await testHelper.capture(viewer, "load_glb_from_file");
await viewer.dispose();
});
test('load glb from buffer', () async {
var viewer = await testHelper.createViewer();
var buffer = File("${testHelper.testDir}/cube.glb").readAsBytesSync();
var model = await viewer.loadGlbFromBuffer(buffer);
await viewer.transformToUnitCube(model);
await viewer.setBackgroundColor(0.0, 0.0, 1.0, 1.0);
await viewer.setCameraPosition(0, 1, 5);
await viewer
.setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -0.5));
await testHelper.capture(viewer, "load_glb_from_buffer");
});
test('load glb from buffer with priority', () async {
var viewer = await testHelper.createViewer();
await viewer.addDirectLight(DirectLight.sun());
await viewer.setBackgroundColor(1.0, 1.0, 1.0, 1.0);
await viewer.setCameraPosition(0, 3, 5);
await viewer
.setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -0.5));
var buffer = File("${testHelper.testDir}/cube.glb").readAsBytesSync();
var model1 = await viewer.loadGlbFromBuffer(buffer, priority: 7);
var model2 = await viewer.loadGlbFromBuffer(buffer, priority: 0);
for (final entity in await viewer.getChildEntities(model1, true)) {
await viewer.setMaterialPropertyFloat4(
entity, "baseColorFactor", 0, 0, 0, 1.0, 1.0);
}
for (final entity in await viewer.getChildEntities(model2, true)) {
await viewer.setMaterialPropertyFloat4(
entity, "baseColorFactor", 0, 0, 1.0, 0.0, 1.0);
}
await testHelper.capture(viewer, "load_glb_from_buffer_with_priority");
});
});
}

View File

@@ -8,6 +8,7 @@ import 'package:image/image.dart';
import 'package:thermion_dart/src/swift/swift_bindings.g.dart';
import 'package:thermion_dart/src/utils/dart_resources.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/thermion_dart.g.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/thermion_viewer_ffi.dart';
import 'package:thermion_dart/src/viewer/src/ffi/thermion_viewer_ffi.dart';
import 'package:thermion_dart/thermion_dart.dart';
import 'package:vector_math/vector_math_64.dart';
@@ -55,7 +56,6 @@ extension on Uri {
String get name => pathSegments.where((e) => e != '').last;
}
Future<Uint8List> savePixelBufferToBmp(
Uint8List pixelBuffer, int width, int height, String outputPath) async {
var data = await pixelBufferToBmp(pixelBuffer, width, height);
@@ -65,7 +65,8 @@ Future<Uint8List> savePixelBufferToBmp(
}
class TestHelper {
late SwapChain swapChain;
late RenderTarget renderTarget;
late Directory outDir;
late String testDir;
@@ -81,7 +82,7 @@ class TestHelper {
Future capture(ThermionViewer viewer, String outputFilename) async {
await Future.delayed(Duration(milliseconds: 10));
var outPath = p.join(outDir.path, "$outputFilename.bmp");
var pixelBuffer = await viewer.capture();
var pixelBuffer = await viewer.capture(swapChain); //, renderTarget: renderTarget);
await savePixelBufferToBmp(
pixelBuffer,
viewer.viewportDimensions.$1.toInt(),
@@ -89,6 +90,53 @@ class TestHelper {
outPath);
return pixelBuffer;
}
Future<ThermionViewer> createViewer(
{img.Color? bg,
Vector3? cameraPosition,
viewportDimensions = (width: 500, height: 500)}) async {
final packageUri = findPackageRoot('thermion_dart');
final lib = ThermionDartTexture1(DynamicLibrary.open(
'${packageUri.toFilePath()}/native/lib/macos/swift/libthermion_swift.dylib'));
final object = ThermionDartTexture.new1(lib);
object.initWithWidth_height_(
viewportDimensions.width, viewportDimensions.height);
final resourceLoader = calloc<ResourceLoaderWrapper>(1);
var loadToOut = NativeCallable<
Void Function(Pointer<Char>,
Pointer<ResourceBuffer>)>.listener(DartResourceLoader.loadResource);
resourceLoader.ref.loadToOut = loadToOut.nativeFunction;
var freeResource = NativeCallable<Void Function(ResourceBuffer)>.listener(
DartResourceLoader.freeResource);
resourceLoader.ref.freeResource = freeResource.nativeFunction;
var viewer = ThermionViewerFFI(resourceLoader: resourceLoader.cast<Void>());
await viewer.initialized;
swapChain = await viewer.createSwapChain(
viewportDimensions.width, viewportDimensions.height);
renderTarget = await viewer.createRenderTarget(
viewportDimensions.width,
viewportDimensions.height,
object.metalTextureAddress);
await viewer.setRenderTarget(renderTarget as FFIRenderTarget);
await viewer.updateViewportAndCameraProjection(
viewportDimensions.width.toDouble(),
viewportDimensions.height.toDouble());
if (bg != null) {
await viewer.setBackgroundColor(
bg.r.toDouble(), bg.g.toDouble(), bg.b.toDouble(), bg.a.toDouble());
}
if (cameraPosition != null) {
await viewer.setCameraPosition(
cameraPosition.x, cameraPosition.y, cameraPosition.z);
}
return viewer;
}
}
Future<Uint8List> pixelBufferToBmp(
@@ -203,50 +251,6 @@ int _linearToSRGB(double linearValue) {
}
}
Future<ThermionViewer> createViewer(
{img.Color? bg,
Vector3? cameraPosition,
viewportDimensions = (width: 500, height: 500)}) async {
final packageUri = findPackageRoot('thermion_dart');
final lib = ThermionDartTexture1(DynamicLibrary.open(
'${packageUri.toFilePath()}/native/lib/macos/swift/libthermion_swift.dylib'));
final object = ThermionDartTexture.new1(lib);
object.initWithWidth_height_(
viewportDimensions.width, viewportDimensions.height);
final resourceLoader = calloc<ResourceLoaderWrapper>(1);
var loadToOut = NativeCallable<
Void Function(Pointer<Char>,
Pointer<ResourceBuffer>)>.listener(DartResourceLoader.loadResource);
resourceLoader.ref.loadToOut = loadToOut.nativeFunction;
var freeResource = NativeCallable<Void Function(ResourceBuffer)>.listener(
DartResourceLoader.freeResource);
resourceLoader.ref.freeResource = freeResource.nativeFunction;
var viewer = ThermionViewerFFI(resourceLoader: resourceLoader.cast<Void>());
await viewer.initialized;
await viewer.createSwapChain(viewportDimensions.width.toDouble(),
viewportDimensions.height.toDouble());
await viewer.createRenderTarget(viewportDimensions.width.toDouble(),
viewportDimensions.height.toDouble(), object.metalTextureAddress);
await viewer.updateViewportAndCameraProjection(
viewportDimensions.width.toDouble(),
viewportDimensions.height.toDouble());
if (bg != null) {
await viewer.setBackgroundColor(
bg.r.toDouble(), bg.g.toDouble(), bg.b.toDouble(), bg.a.toDouble());
}
if (cameraPosition != null) {
await viewer.setCameraPosition(
cameraPosition.x, cameraPosition.y, cameraPosition.z);
}
return viewer;
}
Uint8List poissonBlend(List<Uint8List> textures, int width, int height) {
final int numTextures = textures.length;
final int size = width * height;

View File

@@ -15,24 +15,22 @@ void main() async {
group('background', () {
test('set background color to solid green', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setBackgroundColor(0.0, 1.0, 0.0, 1.0);
await testHelper.capture(viewer, "set_background_color_to_solid_green");
await viewer.dispose();
});
test('set background color to full transparency', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setBackgroundColor(0.0, 1.0, 0.0, 0.0);
await testHelper.capture(
viewer, "set_background_color_to_transparent_green");
await viewer.dispose();
});
test('set background image', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setBackgroundImage(
"file:///${testHelper.testDir}/assets/cube_texture_512x512.png");
await viewer.setPostProcessing(true);
@@ -42,57 +40,11 @@ void main() async {
});
});
group("gltf", () {
test('load glb from file', () async {
var viewer = await createViewer();
var model = await viewer.loadGlb("file://${testHelper.testDir}/cube.glb");
await viewer.transformToUnitCube(model);
await viewer.setBackgroundColor(0.0, 0.0, 1.0, 1.0);
await viewer.setCameraPosition(0, 1, 5);
await viewer
.setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -0.5));
await testHelper.capture(viewer, "load_glb_from_file");
});
test('load glb from buffer', () async {
var viewer = await createViewer();
var buffer = File("${testHelper.testDir}/cube.glb").readAsBytesSync();
var model = await viewer.loadGlbFromBuffer(buffer);
await viewer.transformToUnitCube(model);
await viewer.setBackgroundColor(0.0, 0.0, 1.0, 1.0);
await viewer.setCameraPosition(0, 1, 5);
await viewer
.setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -0.5));
await testHelper.capture(viewer, "load_glb_from_buffer");
});
test('load glb from buffer with priority', () async {
var viewer = await createViewer();
await viewer.addDirectLight(DirectLight.sun());
await viewer.setBackgroundColor(1.0, 1.0, 1.0, 1.0);
await viewer.setCameraPosition(0, 3, 5);
await viewer
.setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -0.5));
var buffer = File("${testHelper.testDir}/cube.glb").readAsBytesSync();
var model1 = await viewer.loadGlbFromBuffer(buffer, priority: 7);
var model2 = await viewer.loadGlbFromBuffer(buffer, priority: 0);
for (final entity in await viewer.getChildEntities(model1, true)) {
await viewer.setMaterialPropertyFloat4(
entity, "baseColorFactor", 0, 0, 0, 1.0, 1.0);
}
for (final entity in await viewer.getChildEntities(model2, true)) {
await viewer.setMaterialPropertyFloat4(
entity, "baseColorFactor", 0, 0, 1.0, 0.0, 1.0);
}
await testHelper.capture(viewer, "load_glb_from_buffer_with_priority");
});
});
group("scene update events", () {
test('add light fires SceneUpdateEvent', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
final success = Completer<bool>();
var light = DirectLight(
@@ -116,7 +68,7 @@ void main() async {
});
test('remove light fires SceneUpdateEvent', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
final success = Completer<bool>();
var light = await viewer.addDirectLight(DirectLight.point());
@@ -135,7 +87,7 @@ void main() async {
});
test('add geometry fires SceneUpdateEvent', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
final success = Completer<bool>();
var geometry = GeometryHelper.cube();
@@ -153,7 +105,7 @@ void main() async {
});
test('remove geometry fires SceneUpdateEvent', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
var geometry = await viewer.createGeometry(GeometryHelper.cube());
final success = Completer<bool>();
@@ -171,7 +123,7 @@ void main() async {
});
test('loadGlb fires SceneUpdateEvent', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
final success = Completer<bool>();
@@ -191,7 +143,7 @@ void main() async {
});
test('remove glb fires SceneUpdateEvent', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
final uri = "${testHelper.testDir}/cube.glb";
var entity = await viewer.loadGlb(uri, keepData: false);
@@ -211,7 +163,7 @@ void main() async {
group("custom geometry", () {
test('create cube (no uvs/normals)', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.addLight(LightType.SUN, 6500, 1000000, 0, 0, 0, 0, 0, -1);
await viewer.setCameraPosition(0, 2, 6);
await viewer
@@ -224,7 +176,7 @@ void main() async {
});
test('create cube (no normals)', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
var light = await viewer.addLight(
LightType.POINT, 6500, 10000000, 0, 2, 0, 0, 0, 0,
falloffRadius: 100.0);
@@ -238,7 +190,7 @@ void main() async {
});
test('create cube (with normals)', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
var light = await viewer.addLight(
LightType.POINT, 6500, 10000000, 0, 2, 0, 0, 0, 0,
@@ -254,7 +206,7 @@ void main() async {
test('create cube with custom ubershader material instance (color)',
() async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.addLight(LightType.SUN, 6500, 1000000, 0, 0, 0, 0, 0, -1);
await viewer.setCameraPosition(0, 2, 6);
await viewer
@@ -276,7 +228,7 @@ void main() async {
test('create cube with custom ubershader material instance (texture)',
() async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.addLight(LightType.SUN, 6500, 1000000, 0, 0, 0, 0, 0, -1);
await viewer.setCameraPosition(0, 2, 6);
await viewer
@@ -299,7 +251,7 @@ void main() async {
});
test('unlit material with color only', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setCameraPosition(0, 0, 6);
await viewer.setBackgroundColor(1.0, 0.0, 0.0, 1.0);
await viewer.setPostProcessing(true);
@@ -318,7 +270,7 @@ void main() async {
});
test('create cube with custom material instance (unlit)', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setCameraPosition(0, 2, 6);
await viewer
.setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 8));
@@ -367,7 +319,7 @@ void main() async {
});
test('create sphere (no normals)', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setBackgroundColor(0.0, 0.0, 1.0, 1.0);
await viewer.setCameraPosition(0, 0, 6);
await viewer
@@ -378,7 +330,7 @@ void main() async {
group("MaterialInstance", () {
test('disable depth write', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setBackgroundColor(1.0, 0.0, 0.0, 1.0);
await viewer.setCameraPosition(0, 0, 6);
await viewer.addDirectLight(
@@ -467,7 +419,7 @@ void main() async {
group("materials", () {
test('set float4 material property for custom geometry', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setCameraPosition(0, 0, 6);
await viewer.setBackgroundColor(0.0, 0.0, 1.0, 1.0);
@@ -482,7 +434,7 @@ void main() async {
await testHelper.capture(viewer, "set_material_float4_post");
});
test('set float material property for custom geometry', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setCameraPosition(0, 0, 6);
await viewer.setBackgroundColor(0.0, 0.0, 1.0, 1.0);
@@ -500,7 +452,7 @@ void main() async {
test('set float material property (roughness) for custom geometry',
() async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setCameraPosition(0, 0, 6);
await viewer.setBackgroundColor(0.0, 0.0, 1.0, 1.0);
@@ -522,7 +474,7 @@ void main() async {
group("transforms & parenting", () {
test('set multiple transforms simultaneously with setTransforms', () async {
var viewer =
await createViewer(bg: kRed, cameraPosition: Vector3(0, 0, 5));
await testHelper.createViewer(bg: kRed, cameraPosition: Vector3(0, 0, 5));
final cube1 = await viewer.createGeometry(GeometryHelper.cube());
final cube2 = await viewer.createGeometry(GeometryHelper.cube());
@@ -534,14 +486,14 @@ void main() async {
Matrix4.translation(Vector3(1, 0, 0))
]);
await viewer.render();
await viewer.render(testHelper.swapChain);
await testHelper.capture(viewer, "set_multiple_transforms");
});
test('getParent and getAncestor both return null when entity has no parent',
() async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
final cube = await viewer.createGeometry(GeometryHelper.cube());
@@ -552,7 +504,7 @@ void main() async {
test(
'getParent returns the parent entity after one has been set via setParent',
() async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
final cube1 = await viewer.createGeometry(GeometryHelper.cube());
@@ -566,7 +518,7 @@ void main() async {
});
test('getAncestor returns the ultimate parent entity', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
final grandparent = await viewer.createGeometry(GeometryHelper.cube());
final parent = await viewer.createGeometry(GeometryHelper.cube());
@@ -579,7 +531,7 @@ void main() async {
});
test('set position based on screenspace coord', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
print(await viewer.getCameraFov(true));
await viewer.createIbl(1.0, 1.0, 1.0, 1000);
await viewer.setCameraPosition(0, 0, 6);
@@ -591,7 +543,7 @@ void main() async {
await viewer.queuePositionUpdateFromViewportCoords(cube, 0, 0);
// we need an explicit render call here to process the transform queue
await viewer.render();
await viewer.render(testHelper.swapChain);
await testHelper.capture(viewer, "set_position_from_viewport_coords");
});
@@ -599,7 +551,7 @@ void main() async {
group("layers & overlays", () {
test('enable grid overlay', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setBackgroundColor(0, 0, 0, 1);
await viewer
.setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 8));
@@ -612,7 +564,7 @@ void main() async {
});
test('load glb from buffer with layer', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setBackgroundColor(1, 0, 1, 1);
await viewer.setCameraPosition(0, 2, 5);
@@ -629,7 +581,7 @@ void main() async {
});
test('change layer visibility at runtime', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setBackgroundColor(1, 0, 1, 1);
await viewer.setCameraPosition(0, 2, 5);
@@ -719,7 +671,7 @@ void main() async {
group("stencil", () {
test('set stencil highlight for glb', () async {
final viewer = await createViewer();
final viewer = await testHelper.createViewer();
var model = await viewer.loadGlb("${testHelper.testDir}/cube.glb", keepData: true);
await viewer.setPostProcessing(true);
@@ -736,7 +688,7 @@ void main() async {
});
test('set stencil highlight for geometry', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setPostProcessing(true);
await viewer.setBackgroundColor(0.0, 1.0, 0.0, 1.0);
await viewer.setCameraPosition(0, 2, 5);
@@ -754,7 +706,7 @@ void main() async {
});
test('set stencil highlight for gltf asset', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setPostProcessing(true);
await viewer.setBackgroundColor(0.0, 1.0, 0.0, 1.0);
await viewer.setCameraPosition(0, 1, 5);
@@ -774,7 +726,7 @@ void main() async {
});
test('set stencil highlight for multiple geometry ', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setPostProcessing(true);
await viewer.setBackgroundColor(0.0, 1.0, 0.0, 1.0);
await viewer.setCameraPosition(0, 1, 5);
@@ -797,7 +749,7 @@ void main() async {
});
test('set stencil highlight for multiple gltf assets ', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setPostProcessing(true);
await viewer.setBackgroundColor(0.0, 1.0, 0.0, 1.0);
await viewer.setCameraPosition(0, 1, 5);
@@ -824,7 +776,7 @@ void main() async {
group("texture", () {
test("create/apply/dispose texture", () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
var textureData =
File("${testHelper.testDir}/assets/cube_texture_512x512.png").readAsBytesSync();
@@ -863,7 +815,7 @@ void main() async {
group("render thread", () {
test("request frame on render thread", () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
viewer.requestFrame();
await Future.delayed(Duration(milliseconds: 20));
@@ -875,7 +827,7 @@ void main() async {
// test("unproject", () async {
// final dimensions = (width: 1280, height: 768);
// var viewer = await createViewer(viewportDimensions: dimensions);
// var viewer = await testHelper.createViewer(viewportDimensions: dimensions);
// await viewer.setPostProcessing(false);
// // await viewer.setToneMapping(ToneMapper.LINEAR);
// await viewer.setBackgroundColor(1.0, 1.0, 1.0, 1.0);

View File

@@ -15,7 +15,7 @@ void main() async {
group("texture tests", () {
test('apply texture to custom ubershader material instance', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.addLight(LightType.SUN, 6500, 1000000, 0, 0, 0, 0, 0, -1);
await viewer.setCameraPosition(0, 2, 6);
await viewer
@@ -38,7 +38,7 @@ void main() async {
});
test('unlit material with color only', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setCameraPosition(0, 0, 6);
await viewer.setBackgroundColor(1.0, 0.0, 0.0, 1.0);
await viewer.setPostProcessing(true);
@@ -57,7 +57,7 @@ void main() async {
});
test('create cube with custom material instance (unlit)', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setCameraPosition(0, 2, 6);
await viewer
.setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 8));
@@ -106,7 +106,7 @@ void main() async {
});
test('create sphere (no normals)', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setBackgroundColor(0.0, 0.0, 1.0, 1.0);
await viewer.setCameraPosition(0, 0, 6);
await viewer
@@ -117,7 +117,7 @@ void main() async {
group("MaterialInstance", () {
test('disable depth write', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setBackgroundColor(1.0, 0.0, 0.0, 1.0);
await viewer.setCameraPosition(0, 0, 6);
await viewer.addDirectLight(
@@ -149,7 +149,7 @@ void main() async {
});
test('set uv scaling (unlit)', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setBackgroundColor(1.0, 0.0, 0.0, 1.0);
await viewer.setCameraPosition(0, 0, 6);
await viewer.addDirectLight(
@@ -174,7 +174,7 @@ void main() async {
group("stencil", () {
test('set stencil highlight for glb', () async {
final viewer = await createViewer();
final viewer = await testHelper.createViewer();
var model = await viewer.loadGlb("${testHelper.testDir}/cube.glb", keepData: true);
await viewer.setPostProcessing(true);
@@ -191,7 +191,7 @@ void main() async {
});
test('set stencil highlight for geometry', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setPostProcessing(true);
await viewer.setBackgroundColor(0.0, 1.0, 0.0, 1.0);
await viewer.setCameraPosition(0, 2, 5);
@@ -209,7 +209,7 @@ void main() async {
});
test('set stencil highlight for gltf asset', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setPostProcessing(true);
await viewer.setBackgroundColor(0.0, 1.0, 0.0, 1.0);
await viewer.setCameraPosition(0, 1, 5);
@@ -229,7 +229,7 @@ void main() async {
});
test('set stencil highlight for multiple geometry ', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setPostProcessing(true);
await viewer.setBackgroundColor(0.0, 1.0, 0.0, 1.0);
await viewer.setCameraPosition(0, 1, 5);
@@ -252,7 +252,7 @@ void main() async {
});
test('set stencil highlight for multiple gltf assets ', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.setPostProcessing(true);
await viewer.setBackgroundColor(0.0, 1.0, 0.0, 1.0);
await viewer.setCameraPosition(0, 1, 5);
@@ -279,7 +279,7 @@ void main() async {
group("texture", () {
test("create/apply/dispose texture", () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
var textureData =
File("${testHelper.testDir}/assets/cube_texture_512x512.png").readAsBytesSync();
@@ -318,7 +318,7 @@ void main() async {
group("render thread", () {
test("request frame on render thread", () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
viewer.requestFrame();
await Future.delayed(Duration(milliseconds: 20));
@@ -330,7 +330,7 @@ void main() async {
// test("unproject", () async {
// final dimensions = (width: 1280, height: 768);
// var viewer = await createViewer(viewportDimensions: dimensions);
// var viewer = await testHelper.createViewer(viewportDimensions: dimensions);
// await viewer.setPostProcessing(false);
// // await viewer.setToneMapping(ToneMapper.LINEAR);
// await viewer.setBackgroundColor(1.0, 1.0, 1.0, 1.0);

View File

@@ -8,7 +8,7 @@ void main() async {
group("skybox", () {
test('load skybox', () async {
var viewer = await createViewer();
var viewer = await testHelper.createViewer();
await viewer.loadSkybox(
"file:///${testHelper.testDir}/assets/default_env_skybox.ktx");
await testHelper.capture(viewer, "load_skybox");

View File

@@ -20,6 +20,9 @@ class ThermionFlutterFFI extends ThermionFlutterPlatform {
ThermionFlutterFFI._() {}
RenderTarget? _renderTarget;
SwapChain? _swapChain;
static void registerWith() {
ThermionFlutterPlatform.instance = ThermionFlutterFFI._();
}
@@ -125,7 +128,7 @@ class ThermionFlutterFFI extends ThermionFlutterPlatform {
return _textures.first;
} else {
await _viewer!.setRendering(false);
await _viewer!.destroySwapChain();
await _swapChain?.destroy();
await destroyTexture(_textures.first);
_textures.clear();
}
@@ -152,27 +155,26 @@ class ThermionFlutterFFI extends ThermionFlutterPlatform {
final texture = ThermionFlutterTexture(flutterTextureId, hardwareTextureId,
physicalWidth, physicalHeight, surfaceAddress);
await _viewer?.createSwapChain(
physicalWidth.toDouble(), physicalHeight.toDouble(),
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
var renderTarget = await _viewer?.createRenderTarget(
physicalWidth.toDouble(),
physicalHeight.toDouble(),
_renderTarget = await _viewer?.createRenderTarget(
physicalWidth,
physicalHeight,
texture.hardwareTextureId!);
}
await _viewer?.updateViewportAndCameraProjection(
physicalWidth.toDouble(), physicalHeight.toDouble());
_viewer?.render();
_creatingTexture = false;
_textures.add(texture);
return texture;
}
@@ -217,7 +219,7 @@ class ThermionFlutterFFI extends ThermionFlutterPlatform {
_resizing = true;
bool wasRendering = _viewer!.rendering;
await _viewer!.setRendering(false);
await _viewer!.destroySwapChain();
await _swapChain?.destroy();
await destroyTexture(texture);
var result = await _channel
@@ -230,7 +232,7 @@ class ThermionFlutterFFI extends ThermionFlutterPlatform {
var newTexture =
ThermionFlutterTexture(result[0], result[1], width, height, result[2]);
await _viewer!.createSwapChain(width.toDouble(), height.toDouble(),
await _viewer!.createSwapChain(width, height,
surface: newTexture.surfaceAddress == null
? nullptr
: Pointer<Void>.fromAddress(newTexture.surfaceAddress!));