feat: more work on multiple views/swapchains

This commit is contained in:
Nick Fisher
2024-09-28 11:19:06 +08:00
parent 7f11250b79
commit a2a26555e2
15 changed files with 458 additions and 310 deletions

View File

@@ -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<TCamera> camera;
final Pointer<TEngine> 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;

View File

@@ -1832,6 +1832,19 @@ external void unproject_texture_render_thread(
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>> callback,
);
@ffi.Native<TViewport Function(ffi.Pointer<TView>)>(isLeaf: true)
external TViewport View_getViewport(
ffi.Pointer<TView> view,
);
@ffi.Native<ffi.Void Function(ffi.Pointer<TView>, ffi.Uint32, ffi.Uint32)>(
isLeaf: true)
external void View_updateViewport(
ffi.Pointer<TView> view,
int width,
int height,
);
@ffi.Native<ffi.Void Function(ffi.Pointer<TView>, ffi.Pointer<TRenderTarget>)>(
isLeaf: true)
external void View_setRenderTarget(
@@ -1845,14 +1858,6 @@ external void View_setFrustumCullingEnabled(
bool enabled,
);
@ffi.Native<ffi.Void Function(ffi.Pointer<TView>, ffi.Uint32, ffi.Uint32)>(
isLeaf: true)
external void View_updateViewport(
ffi.Pointer<TView> tView,
int width,
int height,
);
@ffi.Native<ffi.Void Function(ffi.Pointer<TView>, ffi.Bool)>(isLeaf: true)
external void View_setPostProcessing(
ffi.Pointer<TView> tView,
@@ -2197,6 +2202,20 @@ typedef FilamentRenderCallbackFunction = ffi.Void Function(
typedef DartFilamentRenderCallbackFunction = void Function(
ffi.Pointer<ffi.Void> 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;

View File

@@ -162,7 +162,7 @@ class ThermionViewerFFI extends ThermionViewer {
{Pointer<Void>? surface}) async {
var swapChain = await withPointerCallback<TSwapChain>((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<Uint8List> 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<Camera?> 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<double> 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<double> 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<double> 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<Camera> 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<Camera> 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 = <Future Function()>[];
@@ -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<Viewport> getViewport() async {
TViewport vp = View_getViewport(view);
return Viewport(vp.left, vp.bottom, vp.width, vp.height);
}
}

View File

@@ -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<Viewport> getViewport();
Future updateViewport(int width, int height);
Future setRenderTarget(covariant RenderTarget renderTarget);
Future setCamera(covariant Camera camera);
}

View File

@@ -74,7 +74,7 @@ abstract class ThermionViewer {
/// Render a single frame and copy the pixel buffer to [out].
///
Future<Uint8List> capture(covariant SwapChain swapChain,
{covariant RenderTarget? renderTarget});
{covariant View? view, covariant RenderTarget? renderTarget});
///
///

View File

@@ -45,13 +45,6 @@ namespace thermion_filament
using namespace gltfio;
using namespace camutils;
enum ToneMapping
{
ACES,
FILMIC,
LINEAR
};
class FilamentViewer
{

View File

@@ -321,6 +321,7 @@ namespace thermion_filament
gltfio::TextureProvider *_ktxDecoder = nullptr;
std::mutex _mutex;
std::mutex _stencilMutex;
std::vector<MaterialInstance*> _materialInstances;
utils::NameComponentManager *_ncm;

View File

@@ -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 <stdbool.h> 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 <stddef.h>
#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

View File

@@ -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];

View File

@@ -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;
}

View File

@@ -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 <thread>
#include <functional>
#ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>
#endif
using namespace thermion_filament;
extern "C"
{
#include <filament/View.h>
#include <filament/Viewport.h>
#include <filament/Engine.h>
#include <filament/ToneMapper.h>
#include <filament/ColorGrading.h>
#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<View *>(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<View *>(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<View *>(tView);
auto engine = reinterpret_cast<Engine *>(tEngine);
@@ -139,4 +145,8 @@ extern "C"
view->setCamera(camera);
}
}
#ifdef __cplusplus
}
}
#endif

View File

@@ -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");
});
});
}

View File

@@ -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<Uint8List> 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<ResourceLoaderWrapper>(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());

View File

@@ -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();

View File

@@ -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();
});
});