From ef5b12af4fd7803cf35752e95922e015d5a8264a Mon Sep 17 00:00:00 2001 From: Nick Fisher Date: Fri, 27 Sep 2024 18:39:20 +0800 Subject: [PATCH] feat!: big refactor to support multiple swapchains --- .../free_flight_camera_delegate.dart | 51 ++-- .../third_person_camera_delegate.dart | 12 +- .../lib/src/viewer/src/ffi/src/callbacks.dart | 4 +- .../viewer/src/ffi/src/thermion_dart.g.dart | 251 +++++++++++------- .../src/ffi/src/thermion_viewer_ffi.dart | 140 +++++----- .../src/shared_types/render_target.dart | 3 + .../viewer/src/shared_types/shared_types.dart | 2 + .../viewer/src/shared_types/swap_chain.dart | 3 + .../src/viewer/src/thermion_viewer_base.dart | 36 ++- .../src/viewer/src/thermion_viewer_stub.dart | 25 +- .../native/include/APIBoundaryTypes.h | 2 + .../native/include/FilamentViewer.hpp | 25 +- .../native/include/ThermionDartApi.h | 43 +-- .../include/ThermionDartRenderThreadApi.h | 12 +- thermion_dart/native/src/FilamentViewer.cpp | 243 ++++++++--------- thermion_dart/native/src/ThermionDartApi.cpp | 77 ++++-- .../src/ThermionDartRenderThreadApi.cpp | 95 +++---- thermion_dart/test/camera_tests.dart | 18 +- thermion_dart/test/gltf_tests.dart | 56 ++++ thermion_dart/test/helpers.dart | 98 +++---- thermion_dart/test/integration_test.dart | 128 +++------ thermion_dart/test/material_tests.dart | 28 +- thermion_dart/test/skybox_tests.dart | 2 +- .../lib/thermion_flutter_ffi.dart | 24 +- 24 files changed, 752 insertions(+), 626 deletions(-) create mode 100644 thermion_dart/lib/src/viewer/src/shared_types/render_target.dart create mode 100644 thermion_dart/lib/src/viewer/src/shared_types/swap_chain.dart create mode 100644 thermion_dart/test/gltf_tests.dart diff --git a/thermion_dart/lib/src/input/src/implementations/free_flight_camera_delegate.dart b/thermion_dart/lib/src/input/src/implementations/free_flight_camera_delegate.dart index 04e35c66..5ceaf84f 100644 --- a/thermion_dart/lib/src/input/src/implementations/free_flight_camera_delegate.dart +++ b/thermion_dart/lib/src/input/src/implementations/free_flight_camera_delegate.dart @@ -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; } diff --git a/thermion_dart/lib/src/input/src/implementations/third_person_camera_delegate.dart b/thermion_dart/lib/src/input/src/implementations/third_person_camera_delegate.dart index 45745027..edbee074 100644 --- a/thermion_dart/lib/src/input/src/implementations/third_person_camera_delegate.dart +++ b/thermion_dart/lib/src/input/src/implementations/third_person_camera_delegate.dart @@ -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 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; } } - diff --git a/thermion_dart/lib/src/viewer/src/ffi/src/callbacks.dart b/thermion_dart/lib/src/viewer/src/ffi/src/callbacks.dart index e36a430b..41f65a6a 100644 --- a/thermion_dart/lib/src/viewer/src/ffi/src/callbacks.dart +++ b/thermion_dart/lib/src/viewer/src/ffi/src/callbacks.dart @@ -26,7 +26,7 @@ Future withVoidCallback( nativeCallable.close(); } -Future withPointerCallback( +Future> withPointerCallback( Function(Pointer)>>) func) async { final completer = Completer>(); @@ -39,7 +39,7 @@ Future withPointerCallback( func.call(nativeCallable.nativeFunction); var ptr = await completer.future; nativeCallable.close(); - return ptr.address; + return ptr; } Future withBoolCallback( diff --git a/thermion_dart/lib/src/viewer/src/ffi/src/thermion_dart.g.dart b/thermion_dart/lib/src/viewer/src/ffi/src/thermion_dart.g.dart index 18bfd942..7b19b3ca 100644 --- a/thermion_dart/lib/src/viewer/src/ffi/src/thermion_dart.g.dart +++ b/thermion_dart/lib/src/viewer/src/ffi/src/thermion_dart.g.dart @@ -27,6 +27,101 @@ external ffi.Pointer Viewer_getSceneManager( ffi.Pointer viewer, ); +@ffi.Native< + ffi.Pointer Function( + ffi.Pointer, ffi.IntPtr, ffi.Uint32, ffi.Uint32)>(isLeaf: true) +external ffi.Pointer Viewer_createRenderTarget( + ffi.Pointer viewer, + int texture, + int width, + int height, +); + +@ffi.Native< + ffi.Void Function( + ffi.Pointer, ffi.Pointer)>(isLeaf: true) +external void Viewer_destroyRenderTarget( + ffi.Pointer viewer, + ffi.Pointer tRenderTarget, +); + +@ffi.Native< + ffi.Void Function( + ffi.Pointer, ffi.Pointer)>(isLeaf: true) +external void Viewer_setRenderTarget( + ffi.Pointer viewer, + ffi.Pointer tRenderTarget, +); + +@ffi.Native< + ffi.Pointer Function(ffi.Pointer, + ffi.Pointer, ffi.Uint32, ffi.Uint32)>(isLeaf: true) +external ffi.Pointer Viewer_createSwapChain( + ffi.Pointer viewer, + ffi.Pointer window, + int width, + int height, +); + +@ffi.Native, ffi.Pointer)>( + isLeaf: true) +external void Viewer_destroySwapChain( + ffi.Pointer viewer, + ffi.Pointer swapChain, +); + +@ffi.Native< + ffi.Bool Function( + ffi.Pointer, + ffi.Pointer, + ffi.Uint64, + ffi.Pointer, + ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer buf, ffi.Size size, + ffi.Pointer data)>>, + ffi.Pointer)>(isLeaf: true) +external bool Viewer_render( + ffi.Pointer viewer, + ffi.Pointer swapChain, + int frameTimeInNanos, + ffi.Pointer pixelBuffer, + ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer buf, ffi.Size size, + ffi.Pointer data)>> + callback, + ffi.Pointer data, +); + +@ffi.Native< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer>)>(isLeaf: true) +external void Viewer_capture( + ffi.Pointer viewer, + ffi.Pointer swapChain, + ffi.Pointer pixelBuffer, + ffi.Pointer> callback, +); + +@ffi.Native< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer>)>(isLeaf: true) +external void Viewer_captureRenderTarget( + ffi.Pointer viewer, + ffi.Pointer swapChain, + ffi.Pointer renderTarget, + ffi.Pointer pixelBuffer, + ffi.Pointer> callback, +); + @ffi.Native Function(ffi.Pointer)>(isLeaf: true) external ffi.Pointer Viewer_getEngine( ffi.Pointer viewer, @@ -47,16 +142,6 @@ external void Engine_setTransform( double4x4 transform, ); -@ffi.Native< - ffi.Void Function( - ffi.Pointer, ffi.IntPtr, ffi.Uint32, ffi.Uint32)>(isLeaf: true) -external void create_render_target( - ffi.Pointer viewer, - int texture, - int width, - int height, -); - @ffi.Native)>(isLeaf: true) external void clear_background_image( ffi.Pointer viewer, @@ -300,52 +385,6 @@ external void set_view_frustum_culling( bool enabled, ); -@ffi.Native< - ffi.Bool Function( - ffi.Pointer, - ffi.Uint64, - ffi.Pointer, - ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function(ffi.Pointer buf, ffi.Size size, - ffi.Pointer data)>>, - ffi.Pointer)>(isLeaf: true) -external bool render( - ffi.Pointer viewer, - int frameTimeInNanos, - ffi.Pointer pixelBuffer, - ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function(ffi.Pointer buf, ffi.Size size, - ffi.Pointer data)>> - callback, - ffi.Pointer data, -); - -@ffi.Native< - ffi.Void Function(ffi.Pointer, ffi.Pointer, - ffi.Pointer>)>(isLeaf: true) -external void capture( - ffi.Pointer viewer, - ffi.Pointer pixelBuffer, - ffi.Pointer> callback, -); - -@ffi.Native< - ffi.Void Function(ffi.Pointer, ffi.Pointer, ffi.Uint32, - ffi.Uint32)>(isLeaf: true) -external void create_swap_chain( - ffi.Pointer viewer, - ffi.Pointer window, - int width, - int height, -); - -@ffi.Native)>(isLeaf: true) -external void destroy_swap_chain( - ffi.Pointer viewer, -); - @ffi.Native, ffi.Float)>(isLeaf: true) external void set_frame_interval( ffi.Pointer viewer, @@ -1111,19 +1150,6 @@ external ffi.Pointer get_entity_name_at( bool renderableOnly, ); -@ffi.Native, ffi.Bool)>(isLeaf: true) -external void set_recording( - ffi.Pointer viewer, - bool recording, -); - -@ffi.Native, ffi.Pointer)>( - isLeaf: true) -external void set_recording_output_directory( - ffi.Pointer viewer, - ffi.Pointer outputDirectory, -); - @ffi.Native(isLeaf: true) external void ios_dummy(); @@ -1484,36 +1510,65 @@ external void create_filament_viewer_render_thread( ); @ffi.Native< - ffi.Void Function( - ffi.Pointer, - ffi.Pointer, - ffi.Uint32, - ffi.Uint32, - ffi.Pointer>)>(isLeaf: true) -external void create_swap_chain_render_thread( + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Uint32, + ffi.Uint32, + ffi.Pointer< + ffi + .NativeFunction)>>)>( + isLeaf: true) +external void Viewer_createSwapChainRenderThread( ffi.Pointer viewer, ffi.Pointer surface, int width, int height, + ffi.Pointer)>> + onComplete, +); + +@ffi.Native< + ffi.Void Function(ffi.Pointer, ffi.Pointer, + ffi.Pointer>)>(isLeaf: true) +external void Viewer_destroySwapChainRenderThread( + ffi.Pointer viewer, + ffi.Pointer swapChain, + ffi.Pointer> onComplete, +); + +@ffi.Native, ffi.Pointer)>( + isLeaf: true) +external void Viewer_renderRenderThread( + ffi.Pointer viewer, + ffi.Pointer swapChain, +); + +@ffi.Native< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer>)>(isLeaf: true) +external void Viewer_captureRenderThread( + ffi.Pointer viewer, + ffi.Pointer swapChain, + ffi.Pointer out, ffi.Pointer> onComplete, ); @ffi.Native< - ffi.Void Function(ffi.Pointer, + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, ffi.Pointer>)>(isLeaf: true) -external void destroy_swap_chain_render_thread( +external void Viewer_captureRenderTargetRenderThread( ffi.Pointer viewer, - ffi.Pointer> onComplete, -); - -@ffi.Native< - ffi.Void Function(ffi.Pointer, ffi.IntPtr, ffi.Uint32, ffi.Uint32, - ffi.Pointer>)>(isLeaf: true) -external void create_render_target_render_thread( - ffi.Pointer viewer, - int nativeTextureId, - int width, - int height, + ffi.Pointer swapChain, + ffi.Pointer renderTarget, + ffi.Pointer out, ffi.Pointer> onComplete, ); @@ -1522,20 +1577,6 @@ external void destroy_filament_viewer_render_thread( ffi.Pointer viewer, ); -@ffi.Native)>(isLeaf: true) -external void render_render_thread( - ffi.Pointer viewer, -); - -@ffi.Native< - ffi.Void Function(ffi.Pointer, ffi.Pointer, - ffi.Pointer>)>(isLeaf: true) -external void capture_render_thread( - ffi.Pointer viewer, - ffi.Pointer out, - ffi.Pointer> onComplete, -); - @ffi.Native( 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; diff --git a/thermion_dart/lib/src/viewer/src/ffi/src/thermion_viewer_ffi.dart b/thermion_dart/lib/src/viewer/src/ffi/src/thermion_viewer_ffi.dart index 38021be7..6038e54d 100644 --- a/thermion_dart/lib/src/viewer/src/ffi/src/thermion_viewer_ffi.dart +++ b/thermion_dart/lib/src/viewer/src/ffi/src/thermion_viewer_ffi.dart @@ -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 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 createSwapChain(int width, int height, {Pointer? surface}) async { - await withVoidCallback((callback) { - create_swap_chain_render_thread(_viewer!, surface ?? nullptr, + var swapChain = await withPointerCallback((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() ?? nullptr; - var viewer = await withPointerCallback( + _viewer = await withPointerCallback( (Pointer)>> 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 capture() async { + Future capture(FFISwapChain swapChain, + {FFIRenderTarget? renderTarget}) async { final length = this.viewportDimensions.$1.toInt() * this.viewportDimensions.$2.toInt() * 4; - final out = allocator(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 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()); - allocator.free(pathPtr); - } - final _collisions = {}; /// @@ -2254,3 +2245,28 @@ class ThermionFFIMaterialInstance extends MaterialInstance { _pointer, name.toNativeUtf8().cast(), x, y); } } + +class FFIRenderTarget extends RenderTarget { + final Pointer renderTarget; + final Pointer viewer; + + FFIRenderTarget(this.renderTarget, this.viewer); + + @override + Future destroy() async { + Viewer_destroyRenderTarget(viewer, renderTarget); + } +} + +class FFISwapChain extends SwapChain { + final Pointer swapChain; + final Pointer viewer; + + FFISwapChain(this.swapChain, this.viewer); + + Future destroy() async { + await withVoidCallback((callback) { + Viewer_destroySwapChainRenderThread(viewer, swapChain, callback); + }); + } +} diff --git a/thermion_dart/lib/src/viewer/src/shared_types/render_target.dart b/thermion_dart/lib/src/viewer/src/shared_types/render_target.dart new file mode 100644 index 00000000..4b648e6a --- /dev/null +++ b/thermion_dart/lib/src/viewer/src/shared_types/render_target.dart @@ -0,0 +1,3 @@ +abstract class RenderTarget { + Future destroy(); +} diff --git a/thermion_dart/lib/src/viewer/src/shared_types/shared_types.dart b/thermion_dart/lib/src/viewer/src/shared_types/shared_types.dart index 55aee2c1..a9904e1e 100644 --- a/thermion_dart/lib/src/viewer/src/shared_types/shared_types.dart +++ b/thermion_dart/lib/src/viewer/src/shared_types/shared_types.dart @@ -1,5 +1,7 @@ library shared_types; +export 'swap_chain.dart'; +export 'render_target.dart'; export 'camera.dart'; export 'material.dart'; export 'texture.dart'; diff --git a/thermion_dart/lib/src/viewer/src/shared_types/swap_chain.dart b/thermion_dart/lib/src/viewer/src/shared_types/swap_chain.dart new file mode 100644 index 00000000..0c89746b --- /dev/null +++ b/thermion_dart/lib/src/viewer/src/shared_types/swap_chain.dart @@ -0,0 +1,3 @@ +abstract class SwapChain { + Future destroy(); +} diff --git a/thermion_dart/lib/src/viewer/src/thermion_viewer_base.dart b/thermion_dart/lib/src/viewer/src/thermion_viewer_base.dart index ad7a1aa1..b0fec8ba 100644 --- a/thermion_dart/lib/src/viewer/src/thermion_viewer_base.dart +++ b/thermion_dart/lib/src/viewer/src/thermion_viewer_base.dart @@ -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 capture(); + Future capture(covariant SwapChain swapChain, { covariant RenderTarget? renderTarget }); + + /// + /// + /// + Future createSwapChain(int width, int height); + + /// + /// + /// + Future 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 entities, List transforms); + Future queueTransformUpdates( + List entities, List transforms); /// /// Updates the bone matrices for [entity] (which must be the ThermionEntity @@ -757,17 +776,6 @@ abstract class ThermionViewer { Future> 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. diff --git a/thermion_dart/lib/src/viewer/src/thermion_viewer_stub.dart b/thermion_dart/lib/src/viewer/src/thermion_viewer_stub.dart index 4f2c9688..e0ce4fca 100644 --- a/thermion_dart/lib/src/viewer/src/thermion_viewer_stub.dart +++ b/thermion_dart/lib/src/viewer/src/thermion_viewer_stub.dart @@ -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 capture() { - // TODO: implement capture - throw UnimplementedError(); - } - @override Future getBoundingBox(ThermionEntity entity) { // TODO: implement getBoundingBox @@ -1020,5 +1015,23 @@ class ThermionViewerStub extends ThermionViewer { throw UnimplementedError(); } + @override + Future createSwapChain(int width, int height) { + // TODO: implement createSwapChain + throw UnimplementedError(); + } + + @override + Future createRenderTarget(int width, int height, int textureHandle) { + // TODO: implement createRenderTarget + throw UnimplementedError(); + } + + @override + Future setRenderTarget(covariant RenderTarget renderTarget) { + // TODO: implement setRenderTarget + throw UnimplementedError(); + } + } diff --git a/thermion_dart/native/include/APIBoundaryTypes.h b/thermion_dart/native/include/APIBoundaryTypes.h index 1a8b2195..f96c25b6 100644 --- a/thermion_dart/native/include/APIBoundaryTypes.h +++ b/thermion_dart/native/include/APIBoundaryTypes.h @@ -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; diff --git a/thermion_dart/native/include/FilamentViewer.hpp b/thermion_dart/native/include/FilamentViewer.hpp index 50c2d83f..dd98ae89 100644 --- a/thermion_dart/native/include/FilamentViewer.hpp +++ b/thermion_dart/native/include/FilamentViewer.hpp @@ -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 _renderTargets; + std::vector _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 _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; diff --git a/thermion_dart/native/include/ThermionDartApi.h b/thermion_dart/native/include/ThermionDartApi.h index 33218f60..0886fe40 100644 --- a/thermion_dart/native/include/ThermionDartApi.h +++ b/thermion_dart/native/include/ThermionDartApi.h @@ -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); diff --git a/thermion_dart/native/include/ThermionDartRenderThreadApi.h b/thermion_dart/native/include/ThermionDartRenderThreadApi.h index cccafd34..8b882f0f 100644 --- a/thermion_dart/native/include/ThermionDartRenderThreadApi.h +++ b/thermion_dart/native/include/ThermionDartRenderThreadApi.h @@ -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)()); diff --git a/thermion_dart/native/src/FilamentViewer.cpp b/thermion_dart/native/src/FilamentViewer.cpp index fb4d9a37..564427a6 100644 --- a/thermion_dart/native/src/FilamentViewer.cpp +++ b/thermion_dart/native/src/FilamentViewer.cpp @@ -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(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(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 *)data; + uint8_t *out = (uint8_t *)(frameCallbackData->at(0)); + void *callbackPtr = frameCallbackData->at(1); - std::packaged_task 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{out, (void *)onComplete}; - LinearImage image(toLinearWithAlpha(vp.width, vp.height, vp.width * 4, - static_cast(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(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; diff --git a/thermion_dart/native/src/ThermionDartApi.cpp b/thermion_dart/native/src/ThermionDartApi.cpp index 71100c2a..135921ac 100644 --- a/thermion_dart/native/src/ThermionDartApi.cpp +++ b/thermion_dart/native/src/ThermionDartApi.cpp @@ -56,9 +56,23 @@ extern "C" return reinterpret_cast(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(tViewer); + auto renderTarget = viewer->createRenderTarget(texture, width, height); + return reinterpret_cast(renderTarget); + } + + EMSCRIPTEN_KEEPALIVE void Viewer_destroyRenderTarget(TViewer *tViewer, TRenderTarget* tRenderTarget) { + auto viewer = reinterpret_cast(tViewer); + auto renderTarget = reinterpret_cast(tRenderTarget); + viewer->destroyRenderTarget(renderTarget); + } + + EMSCRIPTEN_KEEPALIVE void Viewer_setRenderTarget(TViewer *tViewer, TRenderTarget* tRenderTarget) { + auto viewer = reinterpret_cast(tViewer); + auto renderTarget = reinterpret_cast(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(tSwapChain); + auto viewer = reinterpret_cast(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(tSwapChain); + auto viewer = reinterpret_cast(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(tSwapChain); + auto renderTarget = reinterpret_cast(tRenderTarget); + auto viewer = reinterpret_cast(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(tViewer); + auto swapChain = reinterpret_cast(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(tViewer); + auto swapChain = viewer->createSwapChain(window, width, height); + return reinterpret_cast(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"); diff --git a/thermion_dart/native/src/ThermionDartRenderThreadApi.cpp b/thermion_dart/native/src/ThermionDartRenderThreadApi.cpp index 57e3636b..f9fcf49c 100644 --- a/thermion_dart/native/src/ThermionDartRenderThreadApi.cpp +++ b/thermion_dart/native/src/ThermionDartRenderThreadApi.cpp @@ -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 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 lambda([=]() mutable { - _render = false; + target = nullptr; _viewer = nullptr; destroy_filament_viewer(reinterpret_cast(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(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 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 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 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 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(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 lambda([=]() mutable - { _rl->doRender(); }); + { _rl->doRender(reinterpret_cast(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 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 lambda([=]() mutable + { Viewer_captureRenderTarget(viewer, tSwapChain, tRenderTarget, pixelBuffer, onComplete); }); auto fut = _rl->add_task(lambda); } diff --git a/thermion_dart/test/camera_tests.dart b/thermion_dart/test/camera_tests.dart index 1ed61534..42538537 100644 --- a/thermion_dart/test/camera_tests.dart +++ b/thermion_dart/test/camera_tests.dart @@ -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); diff --git a/thermion_dart/test/gltf_tests.dart b/thermion_dart/test/gltf_tests.dart new file mode 100644 index 00000000..627fe1ef --- /dev/null +++ b/thermion_dart/test/gltf_tests.dart @@ -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"); + }); + }); +} diff --git a/thermion_dart/test/helpers.dart b/thermion_dart/test/helpers.dart index e078a95f..a413e241 100644 --- a/thermion_dart/test/helpers.dart +++ b/thermion_dart/test/helpers.dart @@ -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 savePixelBufferToBmp( Uint8List pixelBuffer, int width, int height, String outputPath) async { var data = await pixelBufferToBmp(pixelBuffer, width, height); @@ -65,7 +65,8 @@ Future 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 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(1); + var loadToOut = NativeCallable< + Void Function(Pointer, + Pointer)>.listener(DartResourceLoader.loadResource); + + resourceLoader.ref.loadToOut = loadToOut.nativeFunction; + var freeResource = NativeCallable.listener( + DartResourceLoader.freeResource); + resourceLoader.ref.freeResource = freeResource.nativeFunction; + + var viewer = ThermionViewerFFI(resourceLoader: resourceLoader.cast()); + + 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 pixelBufferToBmp( @@ -203,50 +251,6 @@ int _linearToSRGB(double linearValue) { } } -Future 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(1); - var loadToOut = NativeCallable< - Void Function(Pointer, - Pointer)>.listener(DartResourceLoader.loadResource); - - resourceLoader.ref.loadToOut = loadToOut.nativeFunction; - var freeResource = NativeCallable.listener( - DartResourceLoader.freeResource); - resourceLoader.ref.freeResource = freeResource.nativeFunction; - - var viewer = ThermionViewerFFI(resourceLoader: resourceLoader.cast()); - - 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 textures, int width, int height) { final int numTextures = textures.length; final int size = width * height; diff --git a/thermion_dart/test/integration_test.dart b/thermion_dart/test/integration_test.dart index 7daa522b..fe6d89ab 100644 --- a/thermion_dart/test/integration_test.dart +++ b/thermion_dart/test/integration_test.dart @@ -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(); 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(); 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(); 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(); @@ -171,7 +123,7 @@ void main() async { }); test('loadGlb fires SceneUpdateEvent', () async { - var viewer = await createViewer(); + var viewer = await testHelper.createViewer(); final success = Completer(); @@ -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); diff --git a/thermion_dart/test/material_tests.dart b/thermion_dart/test/material_tests.dart index 2a50bc09..8df3dc54 100644 --- a/thermion_dart/test/material_tests.dart +++ b/thermion_dart/test/material_tests.dart @@ -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); diff --git a/thermion_dart/test/skybox_tests.dart b/thermion_dart/test/skybox_tests.dart index 723ac172..e1a87695 100644 --- a/thermion_dart/test/skybox_tests.dart +++ b/thermion_dart/test/skybox_tests.dart @@ -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"); diff --git a/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_ffi.dart b/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_ffi.dart index e30c6020..3c9d97c2 100644 --- a/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_ffi.dart +++ b/thermion_flutter/thermion_flutter_ffi/lib/thermion_flutter_ffi.dart @@ -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.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.fromAddress(newTexture.surfaceAddress!));