From a2a26555e20e483ae36ddd3f9a09ef4137f0eb9d Mon Sep 17 00:00:00 2001 From: Nick Fisher Date: Sat, 28 Sep 2024 11:19:06 +0800 Subject: [PATCH] feat: more work on multiple views/swapchains --- .../src/viewer/src/ffi/src/camera_ffi.dart | 6 +- .../viewer/src/ffi/src/thermion_dart.g.dart | 35 +++- .../src/ffi/src/thermion_viewer_ffi.dart | 56 +++--- .../lib/src/viewer/src/shared_types/view.dart | 12 ++ .../src/viewer/src/thermion_viewer_base.dart | 2 +- .../native/include/FilamentViewer.hpp | 7 - thermion_dart/native/include/SceneManager.hpp | 1 + thermion_dart/native/include/TView.h | 75 +++----- thermion_dart/native/src/FilamentViewer.cpp | 20 +- thermion_dart/native/src/SceneManager.cpp | 9 +- thermion_dart/native/src/TView.cpp | 62 +++--- thermion_dart/test/geometry_tests.dart | 181 ++++++++++++++++++ thermion_dart/test/helpers.dart | 22 +-- thermion_dart/test/integration_test.dart | 168 +--------------- thermion_dart/test/view_tests.dart | 112 ++++++++++- 15 files changed, 458 insertions(+), 310 deletions(-) create mode 100644 thermion_dart/test/geometry_tests.dart diff --git a/thermion_dart/lib/src/viewer/src/ffi/src/camera_ffi.dart b/thermion_dart/lib/src/viewer/src/ffi/src/camera_ffi.dart index f741aa1e..c2756d79 100644 --- a/thermion_dart/lib/src/viewer/src/ffi/src/camera_ffi.dart +++ b/thermion_dart/lib/src/viewer/src/ffi/src/camera_ffi.dart @@ -6,12 +6,12 @@ import '../../../../utils/matrix.dart'; import '../../thermion_viewer_base.dart'; import 'thermion_dart.g.dart'; -class ThermionFFICamera extends Camera { +class FFICamera extends Camera { final Pointer camera; final Pointer engine; late ThermionEntity _entity; - ThermionFFICamera(this.camera, this.engine) { + FFICamera(this.camera, this.engine) { _entity = Camera_getEntity(camera); } @@ -54,7 +54,7 @@ class ThermionFFICamera extends Camera { @override bool operator ==(Object other) => identical(this, other) || - other is ThermionFFICamera && + other is FFICamera && runtimeType == other.runtimeType && camera == other.camera; 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 dcb1bb21..4aacfc3b 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 @@ -1832,6 +1832,19 @@ external void unproject_texture_render_thread( ffi.Pointer> callback, ); +@ffi.Native)>(isLeaf: true) +external TViewport View_getViewport( + ffi.Pointer view, +); + +@ffi.Native, ffi.Uint32, ffi.Uint32)>( + isLeaf: true) +external void View_updateViewport( + ffi.Pointer view, + int width, + int height, +); + @ffi.Native, ffi.Pointer)>( isLeaf: true) external void View_setRenderTarget( @@ -1845,14 +1858,6 @@ external void View_setFrustumCullingEnabled( bool enabled, ); -@ffi.Native, ffi.Uint32, ffi.Uint32)>( - isLeaf: true) -external void View_updateViewport( - ffi.Pointer tView, - int width, - int height, -); - @ffi.Native, ffi.Bool)>(isLeaf: true) external void View_setPostProcessing( ffi.Pointer tView, @@ -2197,6 +2202,20 @@ typedef FilamentRenderCallbackFunction = ffi.Void Function( typedef DartFilamentRenderCallbackFunction = void Function( ffi.Pointer owner); +final class TViewport extends ffi.Struct { + @ffi.Int32() + external int left; + + @ffi.Int32() + external int bottom; + + @ffi.Uint32() + external int width; + + @ffi.Uint32() + external int height; +} + const int __bool_true_false_are_defined = 1; const int true1 = 1; 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 f93da411..c0988b62 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 @@ -162,7 +162,7 @@ class ThermionViewerFFI extends ThermionViewer { {Pointer? surface}) async { var swapChain = await withPointerCallback((callback) { return Viewer_createSwapChainRenderThread(_viewer!, surface ?? nullptr, - width.toInt(), height.toInt(), callback); + width, height, callback); }); return FFISwapChain(swapChain, _viewer!); } @@ -234,19 +234,19 @@ class ThermionViewerFFI extends ThermionViewer { /// @override Future capture(FFISwapChain swapChain, - {FFIRenderTarget? renderTarget}) async { + {FFIView? view, FFIRenderTarget? renderTarget}) async { final length = this.viewportDimensions.$1.toInt() * this.viewportDimensions.$2.toInt() * 4; final out = Uint8List(length); - final view = (await getViewAt(0)) as FFIView; + view ??= (await getViewAt(0)) as FFIView; await withVoidCallback((cb) { if (renderTarget != null) { - Viewer_captureRenderTargetRenderThread(_viewer!, view.view, + Viewer_captureRenderTargetRenderThread(_viewer!, view!.view, swapChain.swapChain, renderTarget.renderTarget, out.address, cb); } else { Viewer_captureRenderThread( - _viewer!, view.view, swapChain.swapChain, out.address, cb); + _viewer!, view!.view, swapChain.swapChain, out.address, cb); } }); return out; @@ -1131,7 +1131,7 @@ class ThermionViewerFFI extends ThermionViewer { Future getCameraComponent(ThermionEntity cameraEntity) async { var engine = Viewer_getEngine(_viewer!); var camera = Engine_getCameraComponent(engine, cameraEntity); - return ThermionFFICamera(camera, engine); + return FFICamera(camera, engine); } /// @@ -1224,7 +1224,7 @@ class ThermionViewerFFI extends ThermionViewer { /// /// Future getCameraFov(bool horizontal) async { - var mainCamera = await getMainCamera() as ThermionFFICamera; + var mainCamera = await getMainCamera() as FFICamera; return get_camera_fov(mainCamera.camera, horizontal); } @@ -1253,7 +1253,7 @@ class ThermionViewerFFI extends ThermionViewer { } Future getCameraNear() async { - var mainCamera = await getMainCamera() as ThermionFFICamera; + var mainCamera = await getMainCamera() as FFICamera; return get_camera_near(mainCamera.camera); } @@ -1262,7 +1262,7 @@ class ThermionViewerFFI extends ThermionViewer { /// @override Future getCameraCullingFar() async { - var mainCamera = await getMainCamera() as ThermionFFICamera; + var mainCamera = await getMainCamera() as FFICamera; return get_camera_culling_far(mainCamera.camera); } @@ -1271,7 +1271,7 @@ class ThermionViewerFFI extends ThermionViewer { /// @override Future setCameraFocusDistance(double focusDistance) async { - var mainCamera = await getMainCamera() as ThermionFFICamera; + var mainCamera = await getMainCamera() as FFICamera; set_camera_focus_distance(mainCamera.camera, focusDistance); } @@ -1307,7 +1307,7 @@ class ThermionViewerFFI extends ThermionViewer { @override Future setCameraExposure( double aperture, double shutterSpeed, double sensitivity) async { - var mainCamera = await getMainCamera() as ThermionFFICamera; + var mainCamera = await getMainCamera() as FFICamera; set_camera_exposure(mainCamera.camera, aperture, shutterSpeed, sensitivity); } @@ -1336,7 +1336,7 @@ class ThermionViewerFFI extends ThermionViewer { /// @override Future setCameraModelMatrix4(Matrix4 modelMatrix) async { - var mainCamera = await getMainCamera() as ThermionFFICamera; + var mainCamera = await getMainCamera() as FFICamera; final out = matrix4ToDouble4x4(modelMatrix); set_camera_model_matrix(mainCamera.camera, out); } @@ -1546,7 +1546,7 @@ class ThermionViewerFFI extends ThermionViewer { if (_viewer == null) { throw Exception("No viewer available"); } - var mainCamera = await getMainCamera() as ThermionFFICamera; + var mainCamera = await getMainCamera() as FFICamera; var matrixStruct = get_camera_view_matrix(mainCamera.camera); return double4x4ToMatrix4(matrixStruct); } @@ -1559,7 +1559,7 @@ class ThermionViewerFFI extends ThermionViewer { if (_viewer == null) { throw Exception("No viewer available"); } - var mainCamera = await getMainCamera() as ThermionFFICamera; + var mainCamera = await getMainCamera() as FFICamera; var matrixStruct = get_camera_model_matrix(mainCamera.camera); return double4x4ToMatrix4(matrixStruct); } @@ -1572,7 +1572,7 @@ class ThermionViewerFFI extends ThermionViewer { if (_viewer == null) { throw Exception("No viewer available"); } - var mainCamera = await getMainCamera() as ThermionFFICamera; + var mainCamera = await getMainCamera() as FFICamera; var matrixStruct = get_camera_projection_matrix(mainCamera.camera); return double4x4ToMatrix4(matrixStruct); } @@ -1585,7 +1585,7 @@ class ThermionViewerFFI extends ThermionViewer { if (_viewer == null) { throw Exception("No viewer available"); } - var mainCamera = await getMainCamera() as ThermionFFICamera; + var mainCamera = await getMainCamera() as FFICamera; var matrixStruct = get_camera_culling_projection_matrix(mainCamera.camera); return double4x4ToMatrix4(matrixStruct); } @@ -1624,7 +1624,7 @@ class ThermionViewerFFI extends ThermionViewer { if (_viewer == null) { throw Exception("No viewer available"); } - var mainCamera = await getMainCamera() as ThermionFFICamera; + var mainCamera = await getMainCamera() as FFICamera; var arrayPtr = get_camera_frustum(mainCamera.camera); var doubleList = arrayPtr.asTypedList(24); @@ -2096,18 +2096,18 @@ class ThermionViewerFFI extends ThermionViewer { Future createCamera() async { var cameraPtr = SceneManager_createCamera(_sceneManager!); var engine = Viewer_getEngine(_viewer!); - var camera = ThermionFFICamera(cameraPtr, engine); + var camera = FFICamera(cameraPtr, engine); return camera; } - Future destroyCamera(ThermionFFICamera camera) async { + Future destroyCamera(FFICamera camera) async { SceneManager_destroyCamera(_sceneManager!, camera.camera); } /// /// /// - Future setActiveCamera(ThermionFFICamera camera) async { + Future setActiveCamera(FFICamera camera) async { final view = (await getViewAt(0)) as FFIView; View_setCamera(view.view, camera.camera); } @@ -2118,7 +2118,7 @@ class ThermionViewerFFI extends ThermionViewer { Future getActiveCamera() async { final view = (await getViewAt(0)) as FFIView; final ptr = View_getCamera(view.view); - return ThermionFFICamera(ptr, Viewer_getEngine(_viewer!)); + return FFICamera(ptr, Viewer_getEngine(_viewer!)); } final _hooks = []; @@ -2153,7 +2153,7 @@ class ThermionViewerFFI extends ThermionViewer { if (camera == nullptr) { throw Exception("No camera at index $index"); } - return ThermionFFICamera(camera, Viewer_getEngine(_viewer!)); + return FFICamera(camera, Viewer_getEngine(_viewer!)); } @override @@ -2234,4 +2234,16 @@ class FFIView extends View { Future setRenderTarget(covariant FFIRenderTarget renderTarget) async { View_setRenderTarget(view, renderTarget.renderTarget); } + + @override + Future setCamera(FFICamera camera) async { + View_setCamera(view, camera.camera); + } + + @override + Future getViewport() async { + TViewport vp = View_getViewport(view); + return Viewport(vp.left, vp.bottom, vp.width, vp.height); + } + } diff --git a/thermion_dart/lib/src/viewer/src/shared_types/view.dart b/thermion_dart/lib/src/viewer/src/shared_types/view.dart index 478a7710..7496ec94 100644 --- a/thermion_dart/lib/src/viewer/src/shared_types/view.dart +++ b/thermion_dart/lib/src/viewer/src/shared_types/view.dart @@ -1,6 +1,18 @@ import 'package:thermion_dart/thermion_dart.dart'; +class Viewport { + final int left; + final int bottom; + final int width; + final int height; + + Viewport(this.left, this.bottom, this.width, this.height); +} + abstract class View { + Future getViewport(); Future updateViewport(int width, int height); Future setRenderTarget(covariant RenderTarget renderTarget); + Future setCamera(covariant Camera camera); + } 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 c19fc395..6a747dd6 100644 --- a/thermion_dart/lib/src/viewer/src/thermion_viewer_base.dart +++ b/thermion_dart/lib/src/viewer/src/thermion_viewer_base.dart @@ -74,7 +74,7 @@ abstract class ThermionViewer { /// Render a single frame and copy the pixel buffer to [out]. /// Future capture(covariant SwapChain swapChain, - {covariant RenderTarget? renderTarget}); + {covariant View? view, covariant RenderTarget? renderTarget}); /// /// diff --git a/thermion_dart/native/include/FilamentViewer.hpp b/thermion_dart/native/include/FilamentViewer.hpp index f7ec467d..23ef388d 100644 --- a/thermion_dart/native/include/FilamentViewer.hpp +++ b/thermion_dart/native/include/FilamentViewer.hpp @@ -45,13 +45,6 @@ namespace thermion_filament using namespace gltfio; using namespace camutils; - enum ToneMapping - { - ACES, - FILMIC, - LINEAR - }; - class FilamentViewer { diff --git a/thermion_dart/native/include/SceneManager.hpp b/thermion_dart/native/include/SceneManager.hpp index a29c2ed1..1dce4c3a 100644 --- a/thermion_dart/native/include/SceneManager.hpp +++ b/thermion_dart/native/include/SceneManager.hpp @@ -321,6 +321,7 @@ namespace thermion_filament gltfio::TextureProvider *_ktxDecoder = nullptr; std::mutex _mutex; std::mutex _stencilMutex; + std::vector _materialInstances; utils::NameComponentManager *_ncm; diff --git a/thermion_dart/native/include/TView.h b/thermion_dart/native/include/TView.h index aa287122..17c9a823 100644 --- a/thermion_dart/native/include/TView.h +++ b/thermion_dart/native/include/TView.h @@ -1,60 +1,30 @@ -#ifndef _FLUTTER_FILAMENT_API_H -#define _FLUTTER_FILAMENT_API_H - -#ifdef _WIN32 -#ifdef IS_DLL -#define EMSCRIPTEN_KEEPALIVE __declspec(dllimport) -#else -#define EMSCRIPTEN_KEEPALIVE __declspec(dllexport) -#endif -#else -#ifndef EMSCRIPTEN_KEEPALIVE -#define EMSCRIPTEN_KEEPALIVE __attribute__((visibility("default"))) -#endif -#endif - -// we copy the LLVM here rather than including, -// because on Windows it's difficult to pin the exact location which confuses dart ffigen - -#ifndef __STDBOOL_H -#define __STDBOOL_H - -#define __bool_true_false_are_defined 1 - -#if defined(__STDC_VERSION__) && __STDC_VERSION__ > 201710L -/* FIXME: We should be issuing a deprecation warning here, but cannot yet due - * to system headers which include this header file unconditionally. - */ -#elif !defined(__cplusplus) -#define bool _Bool -#define true 1 -#define false 0 -#elif defined(__GNUC__) && !defined(__STRICT_ANSI__) -/* Define _Bool as a GNU extension. */ -#define _Bool bool -#if defined(__cplusplus) && __cplusplus < 201103L -/* For C++98, define bool, false, true as a GNU extension. */ -#define bool bool -#define false false -#define true true -#endif -#endif - -#endif /* __STDBOOL_H */ - -#if defined(__APPLE__) || defined(__EMSCRIPTEN__) -#include -#endif - -#include "APIBoundaryTypes.h" -#include "ResourceBuffer.hpp" +#pragma once #ifdef __cplusplus +namespace thermion { extern "C" { #endif +#include "ThermionDartApi.h" + +struct TViewport { + int32_t left; + int32_t bottom; + uint32_t width; + uint32_t height; +}; +typedef struct TViewport TViewport; + +enum ToneMapping +{ + ACES, + FILMIC, + LINEAR +}; + // View +EMSCRIPTEN_KEEPALIVE TViewport View_getViewport(TView *view); EMSCRIPTEN_KEEPALIVE void View_updateViewport(TView *view, uint32_t width, uint32_t height); EMSCRIPTEN_KEEPALIVE void View_setRenderTarget(TView *view, TRenderTarget *renderTarget); EMSCRIPTEN_KEEPALIVE void View_setFrustumCullingEnabled(TView *view, bool enabled); @@ -66,14 +36,13 @@ EMSCRIPTEN_KEEPALIVE void View_setShadowsEnabled(TView* tView, bool enabled); EMSCRIPTEN_KEEPALIVE void View_setShadowType(TView* tView, int shadowType); EMSCRIPTEN_KEEPALIVE void View_setSoftShadowOptions(TView* tView, float penumbraScale, float penumbraRatioScale); EMSCRIPTEN_KEEPALIVE void View_setBloom(TView* tView, float strength); -EMSCRIPTEN_KEEPALIVE void View_setToneMapping(TView* tView, TEngine* tEngine, int toneMapping); +EMSCRIPTEN_KEEPALIVE void View_setToneMapping(TView* tView, TEngine* tEngine, ToneMapping toneMapping); EMSCRIPTEN_KEEPALIVE void View_setAntiAliasing(TView *tView, bool msaa, bool fxaa, bool taa); EMSCRIPTEN_KEEPALIVE void View_setLayerEnabled(TView *tView, int layer, bool visible); EMSCRIPTEN_KEEPALIVE void View_setCamera(TView *tView, TCamera *tCamera); EMSCRIPTEN_KEEPALIVE TCamera* View_getCamera(TView *tView); - #ifdef __cplusplus } -#endif +} #endif diff --git a/thermion_dart/native/src/FilamentViewer.cpp b/thermion_dart/native/src/FilamentViewer.cpp index 510eb025..65e2ef5d 100644 --- a/thermion_dart/native/src/FilamentViewer.cpp +++ b/thermion_dart/native/src/FilamentViewer.cpp @@ -677,7 +677,7 @@ namespace thermion_filament } else { - Log("Created headless swapchain."); + Log("Created headless swapchain %dx%d.", width, height); swapChain = _engine->createSwapChain(width, height, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE | filament::SwapChain::CONFIG_HAS_STENCIL_BUFFER); } #endif @@ -1135,12 +1135,24 @@ namespace thermion_filament void FilamentViewer::capture(View *view, uint8_t *out, bool useFence, SwapChain *swapChain, RenderTarget *renderTarget, void (*onComplete)()) { - if (!renderTarget) - { - Log("NO SWAPCHAIN"); + if (!(renderTarget || swapChain)) { + Log("NO RENDER TARGET OR SWAPCHAIN"); return; } + if(swapChain && !_engine->isValid(swapChain)) { + Log("SWAPCHAIN PROVIDED BUT NOT VALID"); + return; + } + + int i =0 ; + for(auto sc : _swapChains) { + if(sc == swapChain) { + Log("Using swapchain at index %d", i); + } + i++; + } + Viewport const &vp = view->getViewport(); size_t pixelBufferSize = vp.width * vp.height * 4; auto *pixelBuffer = new uint8_t[pixelBufferSize]; diff --git a/thermion_dart/native/src/SceneManager.cpp b/thermion_dart/native/src/SceneManager.cpp index b5cf75ad..bd50a89a 100644 --- a/thermion_dart/native/src/SceneManager.cpp +++ b/thermion_dart/native/src/SceneManager.cpp @@ -528,13 +528,18 @@ namespace thermion_filament asset.second->getLightEntityCount()); _assetLoader->destroyAsset(asset.second); } - for(auto* texture : _textures) { + for(auto *texture : _textures) { _engine->destroy(texture); } + for(auto *materialInstance : _materialInstances) { + _engine->destroy(materialInstance); + } + // TODO - free geometry? _textures.clear(); _assets.clear(); + _materialInstances.clear(); } FilamentInstance *SceneManager::getInstanceByEntityId(EntityId entityId) @@ -2453,6 +2458,7 @@ EntityId SceneManager::createGeometry( } materialInstance->setParameter("baseColorFactor", RgbaType::sRGB, filament::math::float4{1.0f, 0.0f, 1.0f, 1.0f}); materialInstance->setParameter("baseColorIndex", 0); + _materialInstances.push_back(materialInstance); return materialInstance; } @@ -2460,6 +2466,7 @@ EntityId SceneManager::createGeometry( UvMap uvmap; auto instance = _unlitMaterialProvider->createMaterialInstance(nullptr, &uvmap); instance->setParameter("uvScale", filament::math::float2 { 1.0f, 1.0f }); + _materialInstances.push_back(instance); return instance; } diff --git a/thermion_dart/native/src/TView.cpp b/thermion_dart/native/src/TView.cpp index 826d2c91..eb200403 100644 --- a/thermion_dart/native/src/TView.cpp +++ b/thermion_dart/native/src/TView.cpp @@ -1,27 +1,33 @@ -#ifdef _WIN32 -#pragma comment(lib, "Shlwapi.lib") -#pragma comment(lib, "opengl32.lib") -#endif - -#include "ResourceBuffer.hpp" -#include "FilamentViewer.hpp" -#include "filament/LightManager.h" -#include "Log.hpp" -#include "ThreadPool.hpp" - -#include -#include - -#ifdef __EMSCRIPTEN__ -#include -#endif - -using namespace thermion_filament; - -extern "C" -{ +#include +#include +#include +#include +#include #include "ThermionDartApi.h" +#include "TView.h" +#include "Log.hpp" + + +#ifdef __cplusplus +namespace thermion { +extern "C" +{ +using namespace filament; + +#endif + + EMSCRIPTEN_KEEPALIVE TViewport View_getViewport(TView *tView) + { + auto view = reinterpret_cast(tView); + auto & vp = view->getViewport(); + TViewport tvp; + tvp.left = vp.left; + tvp.bottom = vp.bottom; + tvp.width = vp.width; + tvp.height = vp.height; + return tvp; + } EMSCRIPTEN_KEEPALIVE void View_updateViewport(TView *tView, uint32_t width, uint32_t height) { @@ -54,10 +60,10 @@ extern "C" view->setShadowingEnabled(enabled); } - EMSCRIPTEN_KEEPALIVE void View_setShadowType(TView *tView, ShadowType shadowType) + EMSCRIPTEN_KEEPALIVE void View_setShadowType(TView *tView, int shadowType) { auto view = reinterpret_cast(tView); - view->setShadowType(shadowType); + view->setShadowType((ShadowType)shadowType); } EMSCRIPTEN_KEEPALIVE void View_setSoftShadowOptions(TView *tView, float penumbraScale, float penumbraRatioScale) @@ -80,7 +86,7 @@ extern "C" #endif } - EMSCRIPTEN_KEEPALIVE void View_setToneMapping(TView *tView, TEngine *tEngine, int toneMapping) + EMSCRIPTEN_KEEPALIVE void View_setToneMapping(TView *tView, TEngine *tEngine, ToneMapping toneMapping) { auto view = reinterpret_cast(tView); auto engine = reinterpret_cast(tEngine); @@ -139,4 +145,8 @@ extern "C" view->setCamera(camera); } -} \ No newline at end of file + +#ifdef __cplusplus +} +} +#endif diff --git a/thermion_dart/test/geometry_tests.dart b/thermion_dart/test/geometry_tests.dart new file mode 100644 index 00000000..cc922116 --- /dev/null +++ b/thermion_dart/test/geometry_tests.dart @@ -0,0 +1,181 @@ +import 'dart:io'; +import 'dart:math'; +import 'package:thermion_dart/src/viewer/src/ffi/src/thermion_viewer_ffi.dart'; +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("geometry"); + group("custom geometry", () { + test('create cube (no uvs/normals)', () async { + 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 + .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 8)); + await viewer.setBackgroundColor(1.0, 1.0, 1.0, 1.0); + await viewer + .createGeometry(GeometryHelper.cube(normals: false, uvs: false)); + + await testHelper.capture(viewer, "geometry_cube_no_uv_no_normal"); + }); + + test('create cube (no normals)', () async { + var viewer = await testHelper.createViewer(); + var light = await viewer.addLight( + LightType.POINT, 6500, 10000000, 0, 2, 0, 0, 0, 0, + falloffRadius: 100.0); + await viewer.setCameraPosition(0, 2, 6); + await viewer + .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 8)); + await viewer.setBackgroundColor(1.0, 0.0, 1.0, 1.0); + await viewer + .createGeometry(GeometryHelper.cube(normals: false, uvs: false)); + await testHelper.capture(viewer, "geometry_cube_no_normals"); + }); + + test('create cube (with normals)', () async { + var viewer = await testHelper.createViewer(); + + var light = await viewer.addLight( + LightType.POINT, 6500, 10000000, 0, 2, 0, 0, 0, 0, + falloffRadius: 100.0); + await viewer.setCameraPosition(0, 2, 6); + await viewer + .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 8)); + await viewer.setBackgroundColor(1.0, 1.0, 1.0, 1.0); + await viewer + .createGeometry(GeometryHelper.cube(normals: true, uvs: false)); + await testHelper.capture(viewer, "geometry_cube_with_normals"); + }); + + test('create cube with custom ubershader material instance (color)', + () async { + 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 + .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 8)); + await viewer.setBackgroundColor(1.0, 0.0, 1.0, 1.0); + + var materialInstance = + await viewer.createUbershaderMaterialInstance(unlit: true); + final cube = await viewer.createGeometry( + GeometryHelper.cube(uvs: false, normals: true), + materialInstance: materialInstance); + await viewer.setMaterialPropertyFloat4( + cube, "baseColorFactor", 0, 0.0, 1.0, 0.0, 0.0); + await testHelper.capture( + viewer, "geometry_cube_with_custom_material_ubershader"); + await viewer.removeEntity(cube); + await viewer.destroyMaterialInstance(materialInstance); + }); + + test('create cube with custom ubershader material instance (texture)', + () async { + 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 + .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 8)); + await viewer.setBackgroundColor(1.0, 0.0, 0.0, 1.0); + + var materialInstance = await viewer.createUbershaderMaterialInstance(); + final cube = await viewer.createGeometry( + GeometryHelper.cube(uvs: true, normals: true), + materialInstance: materialInstance); + var textureData = + File("${testHelper.testDir}/assets/cube_texture_512x512.png") + .readAsBytesSync(); + var texture = await viewer.createTexture(textureData); + await viewer.applyTexture(texture as ThermionFFITexture, cube); + await testHelper.capture( + viewer, "geometry_cube_with_custom_material_ubershader_texture"); + await viewer.removeEntity(cube); + await viewer.destroyMaterialInstance(materialInstance); + await viewer.destroyTexture(texture); + }); + + test('unlit material with color only', () async { + 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); + await viewer.setToneMapping(ToneMapper.LINEAR); + + var materialInstance = await viewer.createUnlitMaterialInstance(); + var cube = await viewer.createGeometry(GeometryHelper.cube(), + materialInstance: materialInstance); + + await viewer.setMaterialPropertyFloat4( + cube, "baseColorFactor", 0, 0.0, 1.0, 0.0, 1.0); + + await testHelper.capture(viewer, "unlit_material_base_color"); + + await viewer.dispose(); + }); + + test('create cube with custom material instance (unlit)', () async { + var viewer = await testHelper.createViewer(); + await viewer.setCameraPosition(0, 2, 6); + await viewer + .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 8)); + await viewer.setBackgroundColor(1.0, 0.0, 0.0, 1.0); + await viewer.setPostProcessing(true); + await viewer.setToneMapping(ToneMapper.LINEAR); + + var materialInstance = await viewer.createUnlitMaterialInstance(); + var cube = await viewer.createGeometry(GeometryHelper.cube(), + materialInstance: materialInstance); + + var textureData = + File("${testHelper.testDir}/assets/cube_texture_512x512.png") + .readAsBytesSync(); + var texture = await viewer.createTexture(textureData); + await viewer.applyTexture(texture, cube); + await testHelper.capture( + viewer, "geometry_cube_with_custom_material_unlit_texture_only"); + await viewer.removeEntity(cube); + + cube = await viewer.createGeometry(GeometryHelper.cube(), + materialInstance: materialInstance); + // reusing same material instance, so set baseColorIndex to -1 to disable the texture + await viewer.setMaterialPropertyInt(cube, "baseColorIndex", 0, -1); + await viewer.setMaterialPropertyFloat4( + cube, "baseColorFactor", 0, 0.0, 1.0, 0.0, 1.0); + await testHelper.capture( + viewer, "geometry_cube_with_custom_material_unlit_color_only"); + await viewer.removeEntity(cube); + + cube = await viewer.createGeometry(GeometryHelper.cube(), + materialInstance: materialInstance); + // now set baseColorIndex to 0 to enable the texture and the base color + await viewer.setMaterialPropertyInt(cube, "baseColorIndex", 0, 0); + await viewer.setMaterialPropertyFloat4( + cube, "baseColorFactor", 0, 0.0, 1.0, 0.0, 0.5); + await viewer.applyTexture(texture, cube); + + await testHelper.capture( + viewer, "geometry_cube_with_custom_material_unlit_color_and_texture"); + + await viewer.removeEntity(cube); + + await viewer.destroyTexture(texture); + await viewer.destroyMaterialInstance(materialInstance); + await viewer.dispose(); + }); + + test('create sphere (no normals)', () async { + var viewer = await testHelper.createViewer(); + await viewer.setBackgroundColor(0.0, 0.0, 1.0, 1.0); + await viewer.setCameraPosition(0, 0, 6); + await viewer + .createGeometry(GeometryHelper.sphere(normals: false, uvs: false)); + await testHelper.capture(viewer, "geometry_sphere_no_normals"); + }); + }); +} diff --git a/thermion_dart/test/helpers.dart b/thermion_dart/test/helpers.dart index 6ea6e697..ef92397a 100644 --- a/thermion_dart/test/helpers.dart +++ b/thermion_dart/test/helpers.dart @@ -10,6 +10,7 @@ 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/src/viewer/src/shared_types/view.dart'; import 'package:thermion_dart/thermion_dart.dart'; import 'package:vector_math/vector_math_64.dart'; import 'package:path/path.dart' as p; @@ -66,7 +67,6 @@ Future savePixelBufferToBmp( class TestHelper { late SwapChain swapChain; - late RenderTarget renderTarget; late Directory outDir; late String testDir; @@ -79,15 +79,18 @@ class TestHelper { outDir.createSync(); } - Future capture(ThermionViewer viewer, String outputFilename, { SwapChain? swapChain, RenderTarget? renderTarget}) async { + Future capture(ThermionViewer viewer, String outputFilename, + {View? view, SwapChain? swapChain, RenderTarget? renderTarget}) async { await Future.delayed(Duration(milliseconds: 10)); var outPath = p.join(outDir.path, "$outputFilename.bmp"); - var pixelBuffer = - await viewer.capture(swapChain ?? this.swapChain, renderTarget: renderTarget); + var pixelBuffer = await viewer.capture( + view: view, swapChain ?? this.swapChain, renderTarget: renderTarget); + view ??= await viewer.getViewAt(0); + var vp = await view.getViewport(); await savePixelBufferToBmp( pixelBuffer, - viewer.viewportDimensions.$1.toInt(), - viewer.viewportDimensions.$2.toInt(), + vp.width, + vp.height, outPath); return pixelBuffer; } @@ -105,8 +108,8 @@ class TestHelper { {img.Color? bg, Vector3? cameraPosition, viewportDimensions = (width: 500, height: 500)}) async { - final texture = - await createTexture(viewportDimensions.width, viewportDimensions.height); + final texture = await createTexture( + viewportDimensions.width, viewportDimensions.height); final resourceLoader = calloc(1); var loadToOut = NativeCallable< @@ -123,10 +126,7 @@ class TestHelper { await viewer.initialized; swapChain = await viewer.createSwapChain( viewportDimensions.width, viewportDimensions.height); - renderTarget = await viewer.createRenderTarget(viewportDimensions.width, - viewportDimensions.height, texture); - // await viewer.setRenderTarget(renderTarget as FFIRenderTarget); await viewer.updateViewportAndCameraProjection( viewportDimensions.width.toDouble(), viewportDimensions.height.toDouble()); diff --git a/thermion_dart/test/integration_test.dart b/thermion_dart/test/integration_test.dart index fe6d89ab..d00292cc 100644 --- a/thermion_dart/test/integration_test.dart +++ b/thermion_dart/test/integration_test.dart @@ -161,173 +161,7 @@ void main() async { }); }); - group("custom geometry", () { - test('create cube (no uvs/normals)', () async { - 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 - .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 8)); - await viewer.setBackgroundColor(1.0, 1.0, 1.0, 1.0); - await viewer - .createGeometry(GeometryHelper.cube(normals: false, uvs: false)); - - await testHelper.capture(viewer, "geometry_cube_no_uv_no_normal"); - }); - - test('create cube (no normals)', () async { - var viewer = await testHelper.createViewer(); - var light = await viewer.addLight( - LightType.POINT, 6500, 10000000, 0, 2, 0, 0, 0, 0, - falloffRadius: 100.0); - await viewer.setCameraPosition(0, 2, 6); - await viewer - .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 8)); - await viewer.setBackgroundColor(1.0, 0.0, 1.0, 1.0); - await viewer - .createGeometry(GeometryHelper.cube(normals: false, uvs: false)); - await testHelper.capture(viewer, "geometry_cube_no_normals"); - }); - - test('create cube (with normals)', () async { - var viewer = await testHelper.createViewer(); - - var light = await viewer.addLight( - LightType.POINT, 6500, 10000000, 0, 2, 0, 0, 0, 0, - falloffRadius: 100.0); - await viewer.setCameraPosition(0, 2, 6); - await viewer - .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 8)); - await viewer.setBackgroundColor(1.0, 1.0, 1.0, 1.0); - await viewer - .createGeometry(GeometryHelper.cube(normals: true, uvs: false)); - await testHelper.capture(viewer, "geometry_cube_with_normals"); - }); - - test('create cube with custom ubershader material instance (color)', - () async { - 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 - .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 8)); - await viewer.setBackgroundColor(1.0, 0.0, 1.0, 1.0); - - var materialInstance = - await viewer.createUbershaderMaterialInstance(unlit: true); - final cube = await viewer.createGeometry( - GeometryHelper.cube(uvs: false, normals: true), - materialInstance: materialInstance); - await viewer.setMaterialPropertyFloat4( - cube, "baseColorFactor", 0, 0.0, 1.0, 0.0, 0.0); - await testHelper.capture( - viewer, "geometry_cube_with_custom_material_ubershader"); - await viewer.removeEntity(cube); - await viewer.destroyMaterialInstance(materialInstance); - }); - - test('create cube with custom ubershader material instance (texture)', - () async { - 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 - .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 8)); - await viewer.setBackgroundColor(1.0, 0.0, 0.0, 1.0); - - var materialInstance = await viewer.createUbershaderMaterialInstance(); - final cube = await viewer.createGeometry( - GeometryHelper.cube(uvs: true, normals: true), - materialInstance: materialInstance); - var textureData = - File("${testHelper.testDir}/assets/cube_texture_512x512.png").readAsBytesSync(); - var texture = await viewer.createTexture(textureData); - await viewer.applyTexture(texture as ThermionFFITexture, cube); - await testHelper.capture( - viewer, "geometry_cube_with_custom_material_ubershader_texture"); - await viewer.removeEntity(cube); - await viewer.destroyMaterialInstance(materialInstance); - await viewer.destroyTexture(texture); - }); - - test('unlit material with color only', () async { - 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); - await viewer.setToneMapping(ToneMapper.LINEAR); - - var materialInstance = await viewer.createUnlitMaterialInstance(); - var cube = await viewer.createGeometry(GeometryHelper.cube(), - materialInstance: materialInstance); - - await viewer.setMaterialPropertyFloat4( - cube, "baseColorFactor", 0, 0.0, 1.0, 0.0, 1.0); - - await testHelper.capture(viewer, "unlit_material_base_color"); - - await viewer.dispose(); - }); - - test('create cube with custom material instance (unlit)', () async { - var viewer = await testHelper.createViewer(); - await viewer.setCameraPosition(0, 2, 6); - await viewer - .setCameraRotation(Quaternion.axisAngle(Vector3(1, 0, 0), -pi / 8)); - await viewer.setBackgroundColor(1.0, 0.0, 0.0, 1.0); - await viewer.setPostProcessing(true); - await viewer.setToneMapping(ToneMapper.LINEAR); - - var materialInstance = await viewer.createUnlitMaterialInstance(); - var cube = await viewer.createGeometry(GeometryHelper.cube(), - materialInstance: materialInstance); - - var textureData = - File("${testHelper.testDir}/assets/cube_texture_512x512.png").readAsBytesSync(); - var texture = await viewer.createTexture(textureData); - await viewer.applyTexture(texture, cube); - await testHelper.capture( - viewer, "geometry_cube_with_custom_material_unlit_texture_only"); - await viewer.removeEntity(cube); - - cube = await viewer.createGeometry(GeometryHelper.cube(), - materialInstance: materialInstance); - // reusing same material instance, so set baseColorIndex to -1 to disable the texture - await viewer.setMaterialPropertyInt(cube, "baseColorIndex", 0, -1); - await viewer.setMaterialPropertyFloat4( - cube, "baseColorFactor", 0, 0.0, 1.0, 0.0, 1.0); - await testHelper.capture( - viewer, "geometry_cube_with_custom_material_unlit_color_only"); - await viewer.removeEntity(cube); - - cube = await viewer.createGeometry(GeometryHelper.cube(), - materialInstance: materialInstance); - // now set baseColorIndex to 0 to enable the texture and the base color - await viewer.setMaterialPropertyInt(cube, "baseColorIndex", 0, 0); - await viewer.setMaterialPropertyFloat4( - cube, "baseColorFactor", 0, 0.0, 1.0, 0.0, 0.5); - await viewer.applyTexture(texture, cube); - - await testHelper.capture( - viewer, "geometry_cube_with_custom_material_unlit_color_and_texture"); - - await viewer.removeEntity(cube); - - await viewer.destroyTexture(texture); - await viewer.destroyMaterialInstance(materialInstance); - await viewer.dispose(); - }); - - test('create sphere (no normals)', () async { - var viewer = await testHelper.createViewer(); - await viewer.setBackgroundColor(0.0, 0.0, 1.0, 1.0); - await viewer.setCameraPosition(0, 0, 6); - await viewer - .createGeometry(GeometryHelper.sphere(normals: false, uvs: false)); - await testHelper.capture(viewer, "geometry_sphere_no_normals"); - }); - }); - + group("MaterialInstance", () { test('disable depth write', () async { var viewer = await testHelper.createViewer(); diff --git a/thermion_dart/test/view_tests.dart b/thermion_dart/test/view_tests.dart index 5252e196..40019693 100644 --- a/thermion_dart/test/view_tests.dart +++ b/thermion_dart/test/view_tests.dart @@ -1,20 +1,118 @@ +import 'dart:math'; + import 'package:test/test.dart'; +import 'package:thermion_dart/thermion_dart.dart'; +import 'package:vector_math/vector_math_64.dart'; import 'helpers.dart'; void main() async { final testHelper = TestHelper("view"); group('view tests', () { - test('create view', () async { + test('one swapchain, render view to render target', () async { var viewer = await testHelper.createViewer(); - final renderTarget = await viewer.createRenderTarget( - 200, 400, await testHelper.createTexture(200, 400)); + + final texture = await testHelper.createTexture(500, 500); + final renderTarget = await viewer.createRenderTarget(500, 500, texture); + viewer.setRenderTarget(renderTarget); + + await viewer.setBackgroundColor(1.0, 0, 0, 1); + final cube = await viewer.createGeometry(GeometryHelper.cube()); + + var mainCamera = await viewer.getMainCamera(); + mainCamera.setTransform(Matrix4.translation(Vector3(0, 0, 5))); + await testHelper.capture( + viewer, + renderTarget: renderTarget, + "default_swapchain_default_view_render_target"); + }); + + test('create secondary view, same swapchain', () async { + var viewer = await testHelper.createViewer(); + await viewer.setBackgroundColor(1.0, 0, 0, 1); + final cube = await viewer.createGeometry(GeometryHelper.cube()); + + var mainCamera = await viewer.getMainCamera(); + mainCamera.setTransform(Matrix4.translation(Vector3(0, 0, 5))); + await testHelper.capture(viewer, "default_swapchain_default_view"); + final view = await viewer.createView(); - - await view.updateViewport(200, 400); + view.updateViewport(500, 500); + view.setCamera(mainCamera); + await testHelper.capture( + viewer, + "default_swapchain_new_view_with_main_camera", + view: view, + ); + + var newCamera = await viewer.createCamera(); + newCamera.setTransform(Matrix4.translation(Vector3(0.0, 0.0, 10.0))); + newCamera.setLensProjection(); + view.setCamera(newCamera); + + await testHelper.capture( + viewer, + "default_swapchain_new_view_new_camera", + view: view, + ); + + await testHelper.capture( + viewer, + "default_swapchain_default_view_main_camera_no_change", + ); + + await viewer.dispose(); + }); + + test('create secondary view, different swapchain', () async { + var viewer = await testHelper.createViewer(); + await viewer.setBackgroundColor(1.0, 0, 0, 1); + final cube = await viewer.createGeometry(GeometryHelper.cube()); + + var mainCamera = await viewer.getMainCamera(); + mainCamera.setTransform(Matrix4.translation(Vector3(0, 0, 5))); + final swapChain = await viewer.createSwapChain(200, 400); + await testHelper.capture( + viewer, "create_swapchain_default_view_default_swapchain"); + + final view = await viewer.createView(); + + final texture = await testHelper.createTexture(400, 400); + final renderTarget = await viewer.createRenderTarget(400, 400, texture); await view.setRenderTarget(renderTarget); - await viewer.setBackgroundColor(1.0, 0.0, 0.0, 1.0); - await testHelper.capture(viewer, "create_view_with_render_target", renderTarget: renderTarget); + + await view.updateViewport(400, 400); + view.setCamera(mainCamera); + + await testHelper.capture( + viewer, + view: view, + swapChain: swapChain, + renderTarget: renderTarget, + "create_swapchain_new_view_new_swapchain", + ); + + // var newCamera = await viewer.createCamera(); + // newCamera.setTransform(Matrix4.translation(Vector3(0.0, 0.0, 10.0))); + // newCamera.setLensProjection(); + // view.setCamera(newCamera); + + // await testHelper.capture( + // viewer, + // "created_view_with_new_camera", + // view: view, + // ); + + // await testHelper.capture( + // viewer, + // "default_view_main_camera_no_change", + // ); + + // // await view.updateViewport(200, 400); + // // await view.setRenderTarget(renderTarget); + // // await viewer.setBackgroundColor(1.0, 0.0, 0.0, 1.0); + // // await testHelper.capture(viewer, "create_view_with_render_target", + // // renderTarget: renderTarget); await viewer.dispose(); }); });