Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
390b3d063b | ||
|
|
a0cdbde89f | ||
|
|
4088853705 | ||
|
|
b46cf85c83 | ||
|
|
e71f89e54f | ||
|
|
91d3894c83 | ||
|
|
6cf8e58bed | ||
|
|
025bdf662e | ||
|
|
4ebd5a6645 | ||
|
|
df4f647333 | ||
|
|
b089ddd501 | ||
|
|
57cff7fa47 | ||
|
|
450aacf0cc | ||
|
|
1a71e199aa | ||
|
|
91e50cf0ef | ||
|
|
0276c29d10 | ||
|
|
64309eeb54 | ||
|
|
504cc8ca30 | ||
|
|
28e26a722f | ||
|
|
18274ab14d | ||
|
|
1fe130d77f | ||
|
|
eada8d23bd | ||
|
|
7f107708b5 | ||
|
|
72ec72660d | ||
|
|
6bddc94b3d | ||
|
|
4eebe02f29 | ||
|
|
6327c8063b | ||
|
|
05bc5b122e | ||
|
|
d655672587 | ||
|
|
8358c0b236 | ||
|
|
80388c059d | ||
|
|
3b2d87536a | ||
|
|
1be2367b4f | ||
|
|
08ba9400d3 | ||
|
|
4d827badc4 | ||
|
|
e3408625bc | ||
|
|
78af8e12a0 |
263
README.md
263
README.md
@@ -1,46 +1,40 @@
|
||||
# Flutter Filament
|
||||
|
||||
Cross-platform, Physically-based rendering inside Flutter applications.
|
||||
Cross-platform, 3D PBR rendering and animation for [Flutter](https://github.com/google/filament).
|
||||
|
||||
Flutter plugin wrapping the Filament renderer https://github.com/google/filament.
|
||||
Wraps the [the Filament rendering library](https://github.com/google/filament).
|
||||
|
||||
Powers the Polyvox and odd-io engines.
|
||||
|
||||
# Sponsors
|
||||
This is still in beta: bugs/missing features are to be expected.
|
||||
|
||||
Thank you to odd-io for sponsoring work on supporting Windows, raycasting, testing and documentation.
|
||||
|
||||
# Overview
|
||||
https://github.com/nmfisher/polyvox_filament/assets/7238578/abaed1c8-c97b-4999-97b2-39e85e0fa7dd
|
||||
|
||||
## Versioning
|
||||
|
||||
Last tested on Flutter `3.15.0-15.2.pre`. This is on the Flutter beta channel, so run:
|
||||
|Feature|Supported|
|
||||
|---|---|
|
||||
|Platforms|✅ iOS (arm64)<br/>✅ MacOS (arm64)<br/>✅ Android (arm64) <br/>✅ Windows (x64)<br/>⚠️ Linux (x64 - broken)<br/>⚠️ Web (planned)|
|
||||
|Formats|✅ glb <br/>⚠️ glTF (partial - see Known Issues)|
|
||||
|Texture support|✅ PNG <br/>✅ JPEG <br/>✅ KTX <br/>⚠️ KTX2 (planned)|
|
||||
|Camera movement|✅ Desktop (mouse)<br/>✅ Mobile (swipe/pinch)|
|
||||
|Animation|✅ Embedded glTF skinning animations<br/>✅ Embedded glTF morph animations<br/> ✅ Runtime/dynamic morph animations<br/> ⚠️ Runtime/dynamic skinning animations <br/>
|
||||
|Entity manipulation|✅ Viewport selection<br/>⚠️ Entity/transform parenting (planned)<br/> ⚠️ Transform manipulation (mouse/gesture to rotate/translate/scale object) (partial)<br/>⚠️ Runtime material changes (planned)|
|
||||
|
||||
Special thanks to odd-io for sponsoring work on supporting Windows, raycasting, testing and documentation.
|
||||
|
||||
PRs are welcome but please create a placeholder PR to discuss before writing any code. This will help with feature planning, avoid clashes with existing work and keep the project structure consistent.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This package is currently only tested on Flutter >= `3.15.0-15.2.pre`, so you will need to first switch to the `beta` channel:
|
||||
|
||||
```
|
||||
flutter channel beta
|
||||
flutter upgrade
|
||||
```
|
||||
|
||||
||Android|iOS|MacOS|Windows|Linux|WebGL
|
||||
|---|---|---|---|---|---||
|
||||
|Filament|v1.43.1 (arm64/armeabi-v7a/x86/x86_64)|v1.43.1* (arm64)|v1.43.1 (arm64)|v1.32.4 (x86_64)|TODO**|TODO***|
|
||||
|Flutter||3.15.0-15.2.pre|3.15.0-15.2.pre|3.15.0-15.2.pre
|
||||
|
||||
* iOS release build has a skybox bug so the debug versions are currently shipped on iOS
|
||||
** (Waiting for https://github.com/google/filament/issues/7078 to be resolved before upgrading, not sure exactly when the bug was introduced but it was somewhere between v1.32.4 and v1.40.0)
|
||||
*** Texture widget not currently supported on web in Flutter.
|
||||
|
||||
## Features
|
||||
|
||||
|Feature|Supported|
|
||||
|---|---|
|
||||
|glTF|Y|
|
||||
|glb|Y|
|
||||
|
||||
# Basic Setup
|
||||
|
||||
## Clone flutter_filament
|
||||
|
||||
This plugin is not yet published to pub.dev. To use in your project, simply clone the repository and pull the latest binaries from Git LFS:
|
||||
Next, clone this repository and pull the latest binaries from Git LFS:
|
||||
|
||||
```
|
||||
cd $HOME
|
||||
@@ -48,17 +42,17 @@ git clone <repo> && cd flutter_filament
|
||||
git lfs pull
|
||||
```
|
||||
|
||||
You *do not need to build Filament yourself*. The repository is bundled with all necessary headers/static libraries (`windows/lib`, `ios/lib`, `macos/lib` and `linux/lib`) and the Flutter plugin has been configured to link at build time.
|
||||
(this step won't be needed after the plugin is published to pub.dev).
|
||||
|
||||
If you want to run the example project to check:
|
||||
> You *do not need to build Filament yourself*. The repository is bundled with all necessary headers/static libraries (`windows/lib`, `ios/lib`, `macos/lib` and `linux/lib`) and the Flutter plugin has been configured to link at build time.
|
||||
|
||||
Run the example project to check:
|
||||
|
||||
```
|
||||
cd example && flutter run -d <macos/windows/Your iPhone/etc>
|
||||
```
|
||||
|
||||
## Add dependency
|
||||
|
||||
Add the plugin as a dependency in the pubspec.yaml for your application:
|
||||
To use the plugin in your own project, add the plugin to your pubspec.yaml:
|
||||
|
||||
```
|
||||
name: your_project
|
||||
@@ -71,15 +65,14 @@ dependencies:
|
||||
path: <path where you cloned the repository>
|
||||
```
|
||||
|
||||
# Basic Usage
|
||||
## Basic Usage
|
||||
|
||||
See the `example` project for a complete sample of the below steps.
|
||||
See the `example` project for a complete sample that incorporates many of the below steps, and more.
|
||||
|
||||
## Creating the viewport widget and controller
|
||||
### Creating the viewport widget and controller
|
||||
|
||||
To embed a viewport in your app, create an instance of `FilamentControllerFFI` somewhere in your app:
|
||||
Create an instance of `FilamentControllerFFI` somewhere in your app where it will not be garbage collected until you no longer need a rendering canvas:
|
||||
|
||||
e.g.
|
||||
```
|
||||
class MyApp extends StatelessWidget {
|
||||
|
||||
@@ -88,9 +81,8 @@ class MyApp extends StatelessWidget {
|
||||
}
|
||||
|
||||
```
|
||||
Constructing this object only load symbols from the native FFI library.
|
||||
|
||||
(Note this is not (yet) a singleton, so ensure it is placed somewhere in the widget hierachy where it will not be garbage-collected until you no longer need a rendering canvas).
|
||||
This is a relatively lightweight object, however its constructor will load/bind symbols from the native library. This may momentarily block the UI, so you may wish to structure your app so that this is hidden behind a static widget until it is available.
|
||||
|
||||
Next, create an instance of `FilamentWidget` in the widget hierarchy where you want the rendering canvas to appear. This can be sized as large or as small as you want. Flutter widgets can be positioned above or below the `FilamentWidget`.
|
||||
|
||||
@@ -115,19 +107,49 @@ class MyApp extends StatelessWidget {
|
||||
When a `FilamentWidget` is added to the widget hierarchy:
|
||||
1) on the first frame, by default a Container will be rendered with solid red. If you want to change this, pass a widget as the `initial` paramer to the `FilamentWidget` constructor.
|
||||
2) on the second frame, `FilamentWidget` will retrieve its actual size and request the `FilamentController` to create:
|
||||
a) the backing textures needed to insert a `Texture` widget into
|
||||
b) a rendering thread
|
||||
c) a `FilamentViewer` and an `AssetManager`, which will allow you to load assets/cameras/lighting/etc via the `FilamentController`
|
||||
* the backing textures needed to insert a `Texture` widget into
|
||||
* a rendering thread
|
||||
* a `FilamentViewer` and an `AssetManager`, which will allow you to load assets/cameras/lighting/etc via the `FilamentController`
|
||||
3) after an indeterminate number of frames, `FilamentController` will notify `FilamentWidget` when a texture is available the viewport
|
||||
4) `FilamentWidget` will replace the default `initial` Widget with the viewport (which will initially be solid black or white, depending on your platform).
|
||||
|
||||
If this was successful, the viewport should turn from red to black.
|
||||
It's important to note that there *will* be a delay between adding a `FilamentWidget` and the actual rendering viewport becoming available. This is why we fill `FilamentWidget` with red - to make it abundantly clear that you need to handle this asynchronous delay appropriately. You can call `await _filamentController.isReadyForScene` if you need to wait until the viewport is actually ready for rendering.
|
||||
|
||||
### Rendering
|
||||
Congratulations! You now have a scene. It's completely empty, so you probably want to add.
|
||||
|
||||
### Load a background
|
||||
|
||||
You probably want to set a background for your scene. You can load a skybox:
|
||||
```
|
||||
await _filamentController.loadSkybox("assets/default_env/default_env_skybox.ktx)
|
||||
```
|
||||
|
||||
or a static background image:
|
||||
|
||||
```
|
||||
await _filamentController.setBackgroundImage("assets/background.ktx)
|
||||
```
|
||||
|
||||
or a solid background color:
|
||||
|
||||
```
|
||||
await _filamentController.setBackgroundColor(0.0, 1.0, 0.0, 1.0); // solid green
|
||||
```
|
||||
|
||||
At this point, you might not see any change in the viewport. This is because the FilamentController will only actually render into the viewport once `render` has been called.
|
||||
|
||||
By default, the FilamentController will only render into the viewport by manually calling `render()` on the FilamentController. This is to avoid needlessly running a render loop when there is nothing to display.
|
||||
|
||||
To automatically render at 60fps, call `setRendering(true)` on `FilamentController`.
|
||||
```
|
||||
await _filamentController.render()
|
||||
```
|
||||
|
||||
### Assets
|
||||
You should now see your background displayed in the scene. To automatically render at 60fps, call `setRendering`:
|
||||
```
|
||||
await _filamentController.setRendering(true);
|
||||
```
|
||||
|
||||
### Load an asset
|
||||
|
||||
To add a glTF asset to the scene, call `loadGlb()` on `FilamentController` with the Flutter asset path to your .glb file.
|
||||
|
||||
@@ -140,19 +162,44 @@ flutter:
|
||||
|
||||
Then you would call the following
|
||||
```
|
||||
var entity = _filamentController.loadGlb("assets/models/bob.glb");
|
||||
var entity = await _filamentController.loadGlb("assets/models/bob.glb");
|
||||
```
|
||||
You can also pass a URI to indicate that the glTF file should be loaded from the filesystem:
|
||||
```
|
||||
var entity = _filamentController.loadGlb("file:///tmp/bob.glb");
|
||||
var entity = await _filamentController.loadGlb("file:///tmp/bob.glb");
|
||||
```
|
||||
|
||||
The returned value is an integer handle that be used to manipulate the asset (better referred to as the "entity") in the scene.
|
||||
|
||||
E.g. to remove the asset:
|
||||
The return type `FilamentEntity` is simply an integer handle that be used to manipulate the entity in the scene. For example, to remove the asset:
|
||||
```
|
||||
_filamentController.removeAsset(entity);
|
||||
await _filamentController.removeAsset(entity);
|
||||
entity = null;
|
||||
```
|
||||
> Removing an entity from the scene will invalidate the corresponding `FilamentEntity` handle, so ensure you don't retain any references to it after calling `removeAsset` or `clearAssets`. Removing one `FilamentEntity` does not invalidate/change any other `FilamentEntity` handles; you can continue to safely manipulate these via the `FilamentController`.
|
||||
|
||||
### Lighting
|
||||
|
||||
You should now see your object in the viewport, but since we haven't added a light, this will be solid black.
|
||||
|
||||
Add an image-based light from a KTX file:
|
||||
|
||||
```
|
||||
await _filamentController.loadIbl("assets/default_env/default_env_ibl.ktx");
|
||||
```
|
||||
|
||||
You can also add dynamic lights:
|
||||
|
||||
```
|
||||
var sun = await _filamentController.addLight(
|
||||
```
|
||||
|
||||
### Manipulating entity transforms
|
||||
|
||||
To set the world space position of the asset:
|
||||
```
|
||||
_filamentController.setPositon(entity, 1.0, 1.0, 1.0);
|
||||
```
|
||||
|
||||
On desktop, you can also click any renderable object in the viewport to retrieve its associated FilamentEntity (see below).
|
||||
|
||||
### Camera movement
|
||||
|
||||
@@ -168,8 +215,7 @@ class MyApp extends StatelessWidget {
|
||||
return MaterialApp(
|
||||
color: Colors.white,
|
||||
home: Scaffold(backgroundColor: Colors.white, body: Stack(children:[
|
||||
Container(color:Colors.green, height:100, width:100),
|
||||
Positioned.fill(top:100, left:100,child:FilamentGestureDetector(
|
||||
Positioned.fill(child:FilamentGestureDetector(
|
||||
controller: _filamentController,
|
||||
child:FilamentWidget(
|
||||
controller:_filamentController
|
||||
@@ -180,6 +226,69 @@ class MyApp extends StatelessWidget {
|
||||
}
|
||||
```
|
||||
|
||||
On desktop:
|
||||
1) hold the middle mouse button and move the mouse to rotate the camera
|
||||
2) hold the left mouse button and move the mouse to pan the camera
|
||||
3) scroll up/down with the scrollwheel to zoom in/out.
|
||||
|
||||
On mobile:
|
||||
1) swipe with your finger to pan the camera
|
||||
2) double tap the viewport, then swipe with your finger to rotate the camera (double-tap again to return to pan)
|
||||
3) pinch with two fingers in/out to zoom in/out.
|
||||
|
||||
### Changing the active camera
|
||||
|
||||
Every scene has a default camera. Whenever you rotate/pan/zoom the viewport, you are moving the default camera.
|
||||
|
||||
If you have added an entity to the scene that contains one or more camera nodes, you can change the active scene camera to one of those camera nodes.
|
||||
|
||||
```
|
||||
var asset = await _filamentController.loadGlb("assets/some_asset_with_camera.glb");
|
||||
await _filamentController.setCamera(asset, "Camera.002"); // pass the node name to load a specific camera under that entity node
|
||||
await _filamentController.setCamera(asset, null); // pass null to load the first camera found under that entity
|
||||
```
|
||||
|
||||
### Picking entities
|
||||
|
||||
On desktop, left-clicking an object in the viewport will retrieve the FilamentEntity for the top-most renderable instance at that cursor position (if any).
|
||||
|
||||
Note this is an asynchronous operation, so you will need to subscribe to the [pickResult] stream on your [FilamentController] to do something with the result.
|
||||
|
||||
```
|
||||
class MyApp extends StatefulWidget {
|
||||
...
|
||||
}
|
||||
|
||||
|
||||
class _MyAppState extends State<MyApp> {
|
||||
|
||||
final _filamentController = FilamentControllerFFI();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_filamentController.pickResult.listen((FilamentEntity filamentEntity) async {
|
||||
var entityName = _filamentController.getNameForEntity(filamentEntity);
|
||||
await showDialog(builder:(ctx) {
|
||||
return Container(child:Text("You clicked $entityName"));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
color: Colors.white,
|
||||
home: Scaffold(backgroundColor: Colors.white, body: Stack(children:[
|
||||
Positioned.fill(child:FilamentGestureDetector(
|
||||
controller: _filamentController,
|
||||
child:FilamentWidget(
|
||||
controller:_filamentController
|
||||
))),
|
||||
])));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
If you want to work with custom materials, you will need some (basic knowledge of the underlying Filament library)[https://google.github.io/filament/Materials.html#compilingmaterials].
|
||||
@@ -196,7 +305,43 @@ uberz -TSHADINGMODEL=lit -TBLENDING=opaque -o lit_opaque_43.uberz lit_opaque
|
||||
|
||||
(note that the number in the filename corresponds to the Material version, not the Filament version. Not every Filament version requires a new Material version).
|
||||
|
||||
## Releasing your app
|
||||
|
||||
If you build your app in release mode, you will need to ensure that "Dead Strip" is set to false.
|
||||
|
||||
This is because we only invoke the library at runtime via FFI, so at link time these symbols are otherwise treated as redundant.
|
||||
|
||||
## Versioning
|
||||
|
||||
||Android|iOS|MacOS|Windows|Linux|WebGL|
|
||||
|---|---|---|---|---|---||
|
||||
|Filament|v1.43.1 (arm64/armeabi-v7a/x86/x86_64)|v1.43.1* (arm64)|v1.43.1 (arm64)|v1.32.4 (x86_64)|TODO**|TODO***|
|
||||
|Flutter||3.15.0-15.2.pre|3.15.0-15.2.pre|3.15.0-15.2.pre
|
||||
|
||||
* iOS release build has a skybox bug so the debug versions are currently shipped on iOS
|
||||
** (Waiting for https://github.com/google/filament/issues/7078 to be resolved before upgrading, not sure exactly when the bug was introduced but it was somewhere between v1.32.4 and v1.40.0)
|
||||
*** Texture widget not currently supported on web in Flutter.
|
||||
|
||||
|
||||
## Testing
|
||||
|
||||
We automate testing by running the example project on actual iOS/Android/MacOS/Windows devices and executing various operations.
|
||||
|
||||
Eventually we want to compare screenshots after each operation to a set of goldens for every platform.
|
||||
|
||||
Currently this is only possible on iOS (see https://github.com/flutter/flutter/issues/123063 and https://github.com/flutter/flutter/issues/127306).
|
||||
|
||||
To re-generate the golden screenshots for a given device:
|
||||
|
||||
```
|
||||
./regenerate_goldens.sh <your_device_id>
|
||||
```
|
||||
To run the tests and compare against those goldens:
|
||||
```
|
||||
./compare_goldens.sh <your_device_id>
|
||||
```
|
||||
|
||||
The results will depend on the actual device used to generate the golden, therefore if you are using a different device (which is likely), your results may not be the same. This is expected.
|
||||
|
||||
# Building Filament from source
|
||||
|
||||
@@ -215,7 +360,6 @@ git checkout flutter-filament-windows
|
||||
mkdir out && cd out
|
||||
```
|
||||
|
||||
|
||||
Building notes:
|
||||
On Android/iOS, we remove -fno-exceptions from CMakeLists.txt
|
||||
|
||||
@@ -249,19 +393,16 @@ EMCC_CFLAGS="-I/Users/nickfisher/Documents/filament/libs/utils/include -I/Users/
|
||||
|
||||
## Materials
|
||||
|
||||
glTF assets The default
|
||||
|
||||
- there is a simple material (unlit/opaque) used for background images. This is created by:
|
||||
We use a single material (no lighting and no transparency) for background images:
|
||||
```
|
||||
filament/out/release/filament/bin/matc -a opengl -a metal -o materials/image.filamat materials/image.mat
|
||||
filament/out/release/filament/bin/resgen -c -p image -x ios/include/material/ materials/image.filamat
|
||||
```
|
||||
|
||||
|
||||
# Known issues
|
||||
|
||||
On Windows, loading a glTF (but NOT a glb) may crash due to a race condition between uploading resource data to GPU memory and being freed on the host side.
|
||||
|
||||
This has been fixed in recent versions of Filament, but other bugs on Windows prevent upgrading.
|
||||
|
||||
Only workaround is to load a .glb file.
|
||||
Only workaround is to load a .glb file.
|
||||
|
||||
@@ -201,6 +201,7 @@ class PolyvoxFilamentPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, Lo
|
||||
_surfaceTextureEntry!!.release();
|
||||
_surface = null
|
||||
_surfaceTextureEntry = null
|
||||
result.success(true)
|
||||
}
|
||||
"resize" -> {
|
||||
val args = call.arguments as List<Any>
|
||||
|
||||
25
example/.gitattributes
vendored
25
example/.gitattributes
vendored
@@ -3,3 +3,28 @@ assets/BusterDrone filter=lfs diff=lfs merge=lfs -text
|
||||
assets/FlightHelmet filter=lfs diff=lfs merge=lfs -text
|
||||
assets/lit_opaque_32.uberz filter=lfs diff=lfs merge=lfs -text
|
||||
windows/lib/**/*.* filter=lfs diff=lfs merge=lfs -text
|
||||
integration_test/goldens/ios/12_Settonemappingtolinear.png filter=lfs diff=lfs merge=lfs -text
|
||||
integration_test/goldens/ios/3_loadIBL.png filter=lfs diff=lfs merge=lfs -text
|
||||
integration_test/goldens/ios/6_zoomin.png filter=lfs diff=lfs merge=lfs -text
|
||||
integration_test/goldens/ios/13_Movecameratoasset.png filter=lfs diff=lfs merge=lfs -text
|
||||
integration_test/goldens/ios/14_movecamerato.png filter=lfs diff=lfs merge=lfs -text
|
||||
integration_test/goldens/ios/7_rotate.png filter=lfs diff=lfs merge=lfs -text
|
||||
integration_test/goldens/ios/9_transformtounitcube.png filter=lfs diff=lfs merge=lfs -text
|
||||
integration_test/goldens/ios/0_fresh.png filter=lfs diff=lfs merge=lfs -text
|
||||
integration_test/goldens/ios/10_setshapespositionto.png filter=lfs diff=lfs merge=lfs -text
|
||||
integration_test/goldens/ios/2_loadskybox.png filter=lfs diff=lfs merge=lfs -text
|
||||
integration_test/goldens/ios/4_Renderingfalse.png filter=lfs diff=lfs merge=lfs -text
|
||||
integration_test/goldens/ios/8_pan.png filter=lfs diff=lfs merge=lfs -text
|
||||
integration_test/goldens/ios/11_Disablefrustumculling.png filter=lfs diff=lfs merge=lfs -text
|
||||
integration_test/goldens/ios/15_setcameratofirstcamerainshapesGLB.png filter=lfs diff=lfs merge=lfs -text
|
||||
integration_test/goldens/ios/1_createviewerdefaultubershader.png filter=lfs diff=lfs merge=lfs -text
|
||||
integration_test/goldens/ios/5_loadshapesGLB.png filter=lfs diff=lfs merge=lfs -text
|
||||
integration_test/goldens/ios filter=lfs diff=lfs merge=lfs -text
|
||||
integration_test/goldens/ios/16_resize.png filter=lfs diff=lfs merge=lfs -text
|
||||
integration_test/goldens/ios/3_loadskybox.png filter=lfs diff=lfs merge=lfs -text
|
||||
integration_test/goldens/ios/4_loadIBL.png filter=lfs diff=lfs merge=lfs -text
|
||||
integration_test/goldens/ios/19_resize.png filter=lfs diff=lfs merge=lfs -text
|
||||
integration_test/goldens/ios/17_resize.png filter=lfs diff=lfs merge=lfs -text
|
||||
integration_test/goldens/ios/2_Renderingfalse.png filter=lfs diff=lfs merge=lfs -text
|
||||
integration_test/goldens/ios/18_resize.png filter=lfs diff=lfs merge=lfs -text
|
||||
integration_test/goldens/ios/diffs filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
9
example/compare_goldens.sh
Normal file
9
example/compare_goldens.sh
Normal file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
device=$1
|
||||
if [ -z "$device" ]; then
|
||||
echo "Usage: $0 <device_id>"
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
rm -f integration_test/goldens/{ios,macos,windows,android}/diffs/*.png
|
||||
flutter drive --driver=test_driver/integration_test.dart -d $1 --target=integration_test/plugin_integration_test.dart
|
||||
3
example/integration_test/goldens/ios/0_fresh.png
Normal file
3
example/integration_test/goldens/ios/0_fresh.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ec2a9627f23ffe07750323c25afa78c6c7fcb7d26b5ed36d119363ced0b60f29
|
||||
size 258304
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:53256c9443c6d36edfb8c1234711173c847126e888d714151ae026c57495fe6b
|
||||
size 1875814
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b0c715cadbf84c2161099e8373a404c7d6dbb093faf4157aa0e0aa3825c28217
|
||||
size 1868778
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:56a431b561db7038ad7fe92d025fc2fe2806dfa2cf68c29a0380c3cbd2b66fb5
|
||||
size 1742539
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:14193a392baf348f77895ca7bfb8cee160407c762d03c01bf1f7b5b771a9b1ab
|
||||
size 2282401
|
||||
3
example/integration_test/goldens/ios/14_movecamerato.png
Normal file
3
example/integration_test/goldens/ios/14_movecamerato.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:77badd0b2a59122949e497297052064c403006272daa4488db43e492d60093e2
|
||||
size 1959141
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bdf63e19e5a992f2c848cf71b55ceddf8930d6a9368c9b3adcc13e7c478c7146
|
||||
size 1889777
|
||||
3
example/integration_test/goldens/ios/16_resize.png
Normal file
3
example/integration_test/goldens/ios/16_resize.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:651c19434ae3753488eb840695955b98db9d66a20b79bee1e21b3384c2358025
|
||||
size 1609541
|
||||
3
example/integration_test/goldens/ios/17_resize.png
Normal file
3
example/integration_test/goldens/ios/17_resize.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f11462c0fdf30d101b6ed6c430734adcf6451818f3c7824126c8423014836773
|
||||
size 2060944
|
||||
3
example/integration_test/goldens/ios/18_resize.png
Normal file
3
example/integration_test/goldens/ios/18_resize.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:beb941d33777ad2e1d6ef3704ba6a63b3a5608ca885a516a1c9e807a31b533fc
|
||||
size 1608407
|
||||
3
example/integration_test/goldens/ios/19_resize.png
Normal file
3
example/integration_test/goldens/ios/19_resize.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2bcae11fd2eb593c419a62731e1799f7340408b28ad56575fab5f79db608ae17
|
||||
size 2062339
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:dc3b3c5e0678a5b71728cea8961925e6c0fb44a59340aa2d77d4ea92a24cc7d6
|
||||
size 316763
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6dbbb518a86c546f188134c1d369762664f188dd6b9a38ec13963b6187cb4b25
|
||||
size 1456648
|
||||
3
example/integration_test/goldens/ios/3_loadskybox.png
Normal file
3
example/integration_test/goldens/ios/3_loadskybox.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2d7951418e5875da7c7ed8dea482dbf1cbe81a42b47d255db7f1b6e11c16a193
|
||||
size 1876507
|
||||
3
example/integration_test/goldens/ios/4_loadIBL.png
Normal file
3
example/integration_test/goldens/ios/4_loadIBL.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:80cd11f5be75012cfdf953569e409a355826ca23af35feba41aac78f0e3d35d8
|
||||
size 1874857
|
||||
3
example/integration_test/goldens/ios/5_loadshapesGLB.png
Normal file
3
example/integration_test/goldens/ios/5_loadshapesGLB.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e605bb18426ea657b9352ecca51f0b55daac904b15dba3afdcffc412a445548a
|
||||
size 1941423
|
||||
3
example/integration_test/goldens/ios/6_zoomin.png
Normal file
3
example/integration_test/goldens/ios/6_zoomin.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c026e8723922a6c56ab8e28f99a29805c53627ff5a659971590c95d89d7ac6bd
|
||||
size 1940845
|
||||
3
example/integration_test/goldens/ios/7_rotate.png
Normal file
3
example/integration_test/goldens/ios/7_rotate.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fffa285edd2c5474e16475cafc67d2519e000b74178ce1a0f8d88229827cfabf
|
||||
size 1937116
|
||||
3
example/integration_test/goldens/ios/8_pan.png
Normal file
3
example/integration_test/goldens/ios/8_pan.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1db7a2f831d83e75e42dfe4f0547e9ed1f171152f45103e8f250d2b52ee3a8e1
|
||||
size 1937776
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:00c70e7e77b98e4e72d0dde82482066096ebd502d25cccfada9cb87de332ecac
|
||||
size 1941844
|
||||
@@ -1,25 +1,162 @@
|
||||
// This is a basic Flutter integration test.
|
||||
//
|
||||
// Since integration tests run in a full Flutter application, they can interact
|
||||
// with the host side of a plugin implementation, unlike Dart unit tests.
|
||||
//
|
||||
// For more information about Flutter integration tests, please see
|
||||
// https://docs.flutter.dev/cookbook/testing/integration/introduction
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'package:polyvox_filament/polyvox_filament.dart';
|
||||
import 'package:golden_toolkit/golden_toolkit.dart';
|
||||
import 'package:polyvox_filament/widgets/filament_widget.dart';
|
||||
import '../lib/main.dart' as app;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized()
|
||||
as IntegrationTestWidgetsFlutterBinding;
|
||||
|
||||
testWidgets('getPlatformVersion test', (WidgetTester tester) async {
|
||||
final PolyvoxFilament plugin = PolyvoxFilament();
|
||||
final String? version = await plugin.getPlatformVersion();
|
||||
// The version string depends on the host platform running the test, so
|
||||
// just assert that some non-empty string is returned.
|
||||
expect(version?.isNotEmpty, true);
|
||||
late String platformIdentifier;
|
||||
if (Platform.isIOS) {
|
||||
platformIdentifier = "ios";
|
||||
} else if (Platform.isAndroid) {
|
||||
platformIdentifier = "android";
|
||||
} else if (Platform.isMacOS) {
|
||||
platformIdentifier = "macos";
|
||||
} else if (Platform.isWindows) {
|
||||
platformIdentifier = "windows";
|
||||
} else if (Platform.isLinux) {
|
||||
platformIdentifier = "linux";
|
||||
} else {
|
||||
throw Exception("Unexpected platform");
|
||||
}
|
||||
|
||||
int _counter = 0;
|
||||
|
||||
Future _snapshot(WidgetTester tester, Device device, String label,
|
||||
[int seconds = 0]) async {
|
||||
await tester.pumpAndSettle(Duration(milliseconds: 16));
|
||||
for (int i = 0; i < seconds; i++) {
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
await tester.pumpAndSettle(Duration(milliseconds: 16));
|
||||
}
|
||||
await tester.pumpAndSettle(Duration(milliseconds: 16));
|
||||
var screenshotPath = '$platformIdentifier/${_counter}_$label';
|
||||
if (Platform.isIOS) {
|
||||
// this is currently hanging on Android
|
||||
// see https://github.com/flutter/flutter/issues/127306
|
||||
// it is also not yet implemented on Windows or MacOS
|
||||
await binding.convertFlutterSurfaceToImage();
|
||||
final bytes = await binding.takeScreenshot(screenshotPath);
|
||||
}
|
||||
_counter++;
|
||||
}
|
||||
|
||||
late Device device;
|
||||
|
||||
Future tap(WidgetTester tester, String label, [int seconds = 0]) async {
|
||||
var target = find.text(label).first;
|
||||
await tester.dragUntilVisible(
|
||||
target,
|
||||
find.byType(SingleChildScrollView),
|
||||
// widget you want to scroll
|
||||
const Offset(0, 500), // delta to move
|
||||
duration: Duration(milliseconds: 10));
|
||||
await tester.tap(target);
|
||||
await _snapshot(
|
||||
tester, device, label.replaceAll(RegExp("[ -:]"), ""), seconds);
|
||||
}
|
||||
|
||||
Future<void> pumpUntilFound(
|
||||
WidgetTester tester,
|
||||
Finder finder, {
|
||||
Duration timeout = const Duration(seconds: 30),
|
||||
}) async {
|
||||
bool timerDone = false;
|
||||
final timer = Timer(
|
||||
timeout, () => throw TimeoutException("Pump until has timed out"));
|
||||
while (timerDone != true) {
|
||||
await tester.pump();
|
||||
|
||||
final found = tester.any(finder);
|
||||
if (found) {
|
||||
timerDone = true;
|
||||
}
|
||||
}
|
||||
timer.cancel();
|
||||
}
|
||||
|
||||
testGoldens('test', (WidgetTester tester) async {
|
||||
app.main();
|
||||
await pumpUntilFound(tester, find.byType(app.ExampleWidget));
|
||||
device = Device(size: Size(800, 600), name: "desktop");
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await _snapshot(tester, device, "fresh");
|
||||
|
||||
await tap(tester, "create viewer (default ubershader)", 4);
|
||||
|
||||
await tap(tester, "Rendering: false", 2);
|
||||
|
||||
await tap(tester, "load skybox", 2);
|
||||
await tap(tester, "load IBL", 2);
|
||||
await tap(tester, "load shapes GLB", 2);
|
||||
|
||||
final Offset pointerLocation =
|
||||
tester.getCenter(find.byType(FilamentWidget));
|
||||
TestPointer testPointer = TestPointer(1, PointerDeviceKind.mouse);
|
||||
|
||||
// scroll/zoom
|
||||
testPointer.hover(pointerLocation);
|
||||
await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, 1.0)));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, 1.0)));
|
||||
await tester.pumpAndSettle();
|
||||
await _snapshot(tester, device, "zoomin");
|
||||
|
||||
// rotate
|
||||
testPointer =
|
||||
TestPointer(1, PointerDeviceKind.mouse, null, kTertiaryButton);
|
||||
testPointer.hover(pointerLocation);
|
||||
await tester.sendEventToBinding(testPointer.down(pointerLocation));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.sendEventToBinding(
|
||||
testPointer.move(pointerLocation + Offset(10.0, 10.0)));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.sendEventToBinding(
|
||||
testPointer.move(pointerLocation + Offset(20.0, 20.0)));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.sendEventToBinding(testPointer.up());
|
||||
|
||||
await _snapshot(tester, device, "rotate", 2);
|
||||
|
||||
// pan
|
||||
testPointer = TestPointer(1, PointerDeviceKind.mouse, null, kPrimaryButton);
|
||||
testPointer.hover(pointerLocation);
|
||||
await tester.sendEventToBinding(testPointer.down(pointerLocation));
|
||||
await tester
|
||||
.sendEventToBinding(testPointer.move(pointerLocation + Offset(0, 1.0)));
|
||||
|
||||
for (int i = 0; i < 60; i++) {
|
||||
await tester.sendEventToBinding(testPointer
|
||||
.move(pointerLocation + Offset(i.toDouble() * 2, i.toDouble() * 2)));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
await tester.sendEventToBinding(testPointer.up());
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await _snapshot(tester, device, "pan");
|
||||
|
||||
await tap(tester, "transform to unit cube");
|
||||
await tap(tester, "set shapes position to 1, 1, -1");
|
||||
await tap(tester, "Disable frustum culling");
|
||||
await tap(tester, "Set tone mapping to linear");
|
||||
await tap(tester, "Move camera to asset");
|
||||
await tap(tester, "move camera to 1, 1, -1");
|
||||
await tap(tester, 'set camera to first camera in shapes GLB');
|
||||
|
||||
await tap(tester, 'resize');
|
||||
await tap(tester, 'resize');
|
||||
await tap(tester, 'resize');
|
||||
await tap(tester, 'resize');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,21 +1,34 @@
|
||||
PODS:
|
||||
- Flutter (1.0.0)
|
||||
- integration_test (0.0.1):
|
||||
- Flutter
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- polyvox_filament (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- Flutter (from `Flutter`)
|
||||
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- polyvox_filament (from `.symlinks/plugins/polyvox_filament/ios`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
integration_test:
|
||||
:path: ".symlinks/plugins/integration_test/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
polyvox_filament:
|
||||
:path: ".symlinks/plugins/polyvox_filament/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
polyvox_filament: 99047b2e0d56e073f5db603dd6152a1598c2a345
|
||||
integration_test: 13825b8a9334a850581300559b8839134b124670
|
||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||
polyvox_filament: 35fece7761e74c973afd80fe3aa0ca225eaace32
|
||||
|
||||
PODFILE CHECKSUM: 7adbc9d59f05e1b01f554ea99b6c79e97f2214a2
|
||||
|
||||
|
||||
@@ -138,6 +138,7 @@
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
F9FAB8A67CF505858CCDA424 /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -249,6 +250,23 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||
};
|
||||
F9FAB8A67CF505858CCDA424 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@@ -343,6 +361,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
DEVELOPMENT_TEAM = TM2B4SJXNJ;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -354,6 +373,8 @@
|
||||
OTHER_CFLAGS = "-fvisibility=default";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.polyvox.example;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRIP_INSTALLED_PRODUCT = NO;
|
||||
STRIP_STYLE = "non-global";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
@@ -479,6 +500,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
DEVELOPMENT_TEAM = TM2B4SJXNJ;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -490,6 +512,8 @@
|
||||
OTHER_CFLAGS = "-fvisibility=default";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.polyvox.example;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRIP_INSTALLED_PRODUCT = NO;
|
||||
STRIP_STYLE = "non-global";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -505,6 +529,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
DEVELOPMENT_TEAM = TM2B4SJXNJ;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -516,6 +541,8 @@
|
||||
OTHER_CFLAGS = "-fvisibility=default";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.polyvox.example;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
STRIP_INSTALLED_PRODUCT = NO;
|
||||
STRIP_STYLE = "non-global";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
|
||||
@@ -41,41 +41,34 @@ class ExampleWidget extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ExampleWidgetState extends State<ExampleWidget> {
|
||||
FilamentControllerFFI? _filamentController;
|
||||
FilamentController? _filamentController;
|
||||
|
||||
FilamentEntity? _shapes;
|
||||
FilamentEntity? _flightHelmet;
|
||||
List<String>? _animations;
|
||||
FilamentEntity? _light;
|
||||
|
||||
late StreamSubscription _pickResultListener;
|
||||
StreamSubscription? _pickResultListener;
|
||||
String? picked;
|
||||
|
||||
final weights = List.filled(255, 0.0);
|
||||
|
||||
bool _loop = false;
|
||||
bool _vertical = false;
|
||||
EdgeInsets _viewportMargin = EdgeInsets.zero;
|
||||
|
||||
bool _readyForScene = false;
|
||||
|
||||
bool _rendering = false;
|
||||
int _framerate = 60;
|
||||
bool _postProcessing = true;
|
||||
bool _active = false;
|
||||
|
||||
bool _coneHidden = false;
|
||||
bool _frustumCulling = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
getApplicationSupportDirectory().then((dir) {
|
||||
print(dir);
|
||||
});
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_pickResultListener.cancel();
|
||||
_pickResultListener?.cancel();
|
||||
}
|
||||
|
||||
Widget _item(void Function() onTap, String text) {
|
||||
@@ -96,7 +89,12 @@ class _ExampleWidgetState extends State<ExampleWidget> {
|
||||
FilamentControllerFFI(uberArchivePath: uberArchivePath);
|
||||
_filamentController!.pickResult.listen((entityId) {
|
||||
setState(() {
|
||||
picked = _filamentController!.getNameForEntity(entityId);
|
||||
picked = _filamentController!.getNameForEntity(entityId!);
|
||||
});
|
||||
});
|
||||
_filamentController!.isReadyForScene.then((readyForScene) {
|
||||
setState(() {
|
||||
_readyForScene = readyForScene;
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -117,17 +115,14 @@ class _ExampleWidgetState extends State<ExampleWidget> {
|
||||
: "assets/lit_opaque_43.uberz");
|
||||
}, "create viewer (custom ubershader - lit opaque only)"),
|
||||
]);
|
||||
} else {
|
||||
}
|
||||
|
||||
if (_readyForScene) {
|
||||
children.addAll([
|
||||
_item(() {
|
||||
_filamentController!.destroy();
|
||||
_filamentController = null;
|
||||
}, "destroy viewer/texture")
|
||||
]);
|
||||
}
|
||||
|
||||
if (_filamentController != null) {
|
||||
children.addAll([
|
||||
}, "destroy viewer/texture"),
|
||||
_item(() {
|
||||
_filamentController!.render();
|
||||
}, "render"),
|
||||
@@ -136,7 +131,7 @@ class _ExampleWidgetState extends State<ExampleWidget> {
|
||||
_rendering = !_rendering;
|
||||
_filamentController!.setRendering(_rendering);
|
||||
});
|
||||
}, "Rendering: $_rendering "),
|
||||
}, "Rendering: $_rendering"),
|
||||
_item(() {
|
||||
setState(() {
|
||||
_framerate = _framerate == 60 ? 30 : 60;
|
||||
@@ -161,6 +156,13 @@ class _ExampleWidgetState extends State<ExampleWidget> {
|
||||
_filamentController!
|
||||
.loadIbl('assets/default_env/default_env_ibl.ktx');
|
||||
}, 'load IBL'),
|
||||
_item(() async {
|
||||
_light = await _filamentController!
|
||||
.addLight(1, 6500, 150000, 0, 1, 0, 0, -1, 0, true);
|
||||
}, "add directional light"),
|
||||
_item(() async {
|
||||
await _filamentController!.clearLights();
|
||||
}, "clear lights"),
|
||||
_item(() {
|
||||
setState(() {
|
||||
_postProcessing = !_postProcessing;
|
||||
@@ -207,8 +209,8 @@ class _ExampleWidgetState extends State<ExampleWidget> {
|
||||
_filamentController!.setPosition(_shapes!, 1.0, 1.0, -1.0);
|
||||
}, 'set shapes position to 1, 1, -1'),
|
||||
_item(() async {
|
||||
_filamentController!.setPosition(_shapes!, 1.0, 1.0, -1.0);
|
||||
}, 'move camera to shapes position'),
|
||||
_filamentController!.setCameraPosition(1.0, 1.0, -1.0);
|
||||
}, 'move camera to 1, 1, -1'),
|
||||
_item(() async {
|
||||
var frameData = Float32List.fromList(
|
||||
List<double>.generate(120, (i) => i / 120).expand((x) {
|
||||
@@ -299,7 +301,14 @@ class _ExampleWidgetState extends State<ExampleWidget> {
|
||||
_item(() {
|
||||
_loop = !_loop;
|
||||
setState(() {});
|
||||
}, "toggle animation looping ${_loop ? "OFF" : "ON"}")
|
||||
}, "toggle animation looping ${_loop ? "OFF" : "ON"}"),
|
||||
_item(() {
|
||||
setState(() {
|
||||
_viewportMargin = _viewportMargin == EdgeInsets.zero
|
||||
? EdgeInsets.all(50)
|
||||
: EdgeInsets.zero;
|
||||
});
|
||||
}, "resize")
|
||||
]);
|
||||
if (_animations != null) {
|
||||
children.addAll(_animations!.map((a) => _item(() {
|
||||
@@ -332,28 +341,34 @@ class _ExampleWidgetState extends State<ExampleWidget> {
|
||||
return Stack(children: [
|
||||
_filamentController != null
|
||||
? Positioned.fill(
|
||||
child: FilamentGestureDetector(
|
||||
showControlOverlay: true,
|
||||
controller: _filamentController!,
|
||||
child: FilamentWidget(
|
||||
controller: _filamentController!,
|
||||
)))
|
||||
child: Padding(
|
||||
padding: _viewportMargin,
|
||||
child: FilamentGestureDetector(
|
||||
showControlOverlay: true,
|
||||
controller: _filamentController!,
|
||||
child: FilamentWidget(
|
||||
controller: _filamentController!,
|
||||
))))
|
||||
: Container(),
|
||||
Positioned(
|
||||
right: 50,
|
||||
top: 50,
|
||||
child: Text(picked ?? "",
|
||||
style: TextStyle(color: Colors.green, fontSize: 24))),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: 200,
|
||||
child: Container(
|
||||
color: Colors.white,
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(children: children
|
||||
// _item(24 () async { 'rotate by pi around Y axis'),
|
||||
style: const TextStyle(color: Colors.green, fontSize: 24))),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: OrientationBuilder(builder: (ctx, orientation) {
|
||||
return Container(
|
||||
alignment: Alignment.bottomCenter,
|
||||
height: orientation == Orientation.landscape ? 100 : 200,
|
||||
color: Colors.white.withOpacity(0.75),
|
||||
child: SingleChildScrollView(child: Wrap(children: children)));
|
||||
}))
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// _item(24 () async { 'rotate by pi around Y axis'),
|
||||
// _item(5 () async { 'load flight helmet'),
|
||||
|
||||
// _item(7 () async { 'set all weights to 1'),
|
||||
@@ -384,10 +399,6 @@ class _ExampleWidgetState extends State<ExampleWidget> {
|
||||
// _item(30 () async { 'remove light'),
|
||||
// _item(31 () async { 'clear all lights'),
|
||||
// _item(32 () async { 'set camera model matrix'),
|
||||
))))
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// case -1:
|
||||
|
||||
@@ -464,11 +475,6 @@ class _ExampleWidgetState extends State<ExampleWidget> {
|
||||
// case 24:
|
||||
// _filamentController!.setRotation(_shapes!, pi / 2, 0.0, 1.0, 0.0);
|
||||
// break;
|
||||
// case 25:
|
||||
// setState(() {
|
||||
// _vertical = !_vertical;
|
||||
// });
|
||||
// break;
|
||||
// case 26:
|
||||
// _filamentController!.setCameraPosition(0, 0, 3);
|
||||
// _filamentController!.setCameraRotation(0, 0, 1, 0);
|
||||
@@ -480,10 +486,7 @@ class _ExampleWidgetState extends State<ExampleWidget> {
|
||||
// case 28:
|
||||
// _filamentController!.setBackgroundImagePosition(25, 25);
|
||||
// break;
|
||||
// case 29:
|
||||
// _light = await _filamentController!.addLight(
|
||||
// 1, 6500, 15000000, 0, 1, 0, 0, -1, 0, true);
|
||||
// break;
|
||||
|
||||
// case 30:
|
||||
// if (_light != null) {
|
||||
// _filamentController!.removeLight(_light!);
|
||||
@@ -491,7 +494,7 @@ class _ExampleWidgetState extends State<ExampleWidget> {
|
||||
// }
|
||||
// break;
|
||||
// case 31:
|
||||
// _filamentController!.clearLights();
|
||||
|
||||
// break;
|
||||
// case 32:
|
||||
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
PODS:
|
||||
- FlutterMacOS (1.0.0)
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- polyvox_filament (0.0.1):
|
||||
- FlutterMacOS
|
||||
|
||||
DEPENDENCIES:
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- polyvox_filament (from `Flutter/ephemeral/.symlinks/plugins/polyvox_filament/macos`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
FlutterMacOS:
|
||||
:path: Flutter/ephemeral
|
||||
path_provider_foundation:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
|
||||
polyvox_filament:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/polyvox_filament/macos
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||
polyvox_filament: 59a161de3df49c867bc2003d4ff73b8a304d2feb
|
||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||
polyvox_filament: 410c2b06ba59f1182e2fa4338b583903631fb95f
|
||||
|
||||
PODFILE CHECKSUM: 9cc8fc8fc62b1d9a89fd6f974ad4157b35254030
|
||||
|
||||
|
||||
@@ -575,6 +575,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 13;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
@@ -723,6 +724,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 13;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<false/>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
|
||||
@@ -28,5 +28,7 @@
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -26,7 +26,12 @@ dependencies:
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
integration_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^1.0.0
|
||||
golden_toolkit: ^0.15.0
|
||||
crypto:
|
||||
image_compare: ^1.1.2
|
||||
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
|
||||
8
example/regenerate_goldens.sh
Normal file
8
example/regenerate_goldens.sh
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
device=$1
|
||||
if [ -z "$device" ]; then
|
||||
echo "Usage: $0 <device_id>"
|
||||
exit 1;
|
||||
fi
|
||||
rm -f integration_test/goldens/{ios,macos,windows,android}/*.png
|
||||
flutter drive --driver=test_driver/integration_test_update_goldens.dart -d $1 --target=integration_test/plugin_integration_test.dart
|
||||
58
example/test_driver/integration_test.dart
Normal file
58
example/test_driver/integration_test.dart
Normal file
@@ -0,0 +1,58 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:integration_test/integration_test_driver_extended.dart';
|
||||
import 'package:image_compare/image_compare.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
await integrationDriver(
|
||||
onScreenshot: (
|
||||
String screenshotName,
|
||||
List<int> screenshotBytes, [
|
||||
Map<String, Object?>? args,
|
||||
]) async {
|
||||
final dir = screenshotName.split("/")[0];
|
||||
final name = screenshotName.split("/")[1];
|
||||
final File golden = await File('integration_test/goldens/$dir/$name.png');
|
||||
|
||||
if (!golden.existsSync()) {
|
||||
throw Exception(
|
||||
"Golden image ${golden.path} doesn't exist yet. Make sure you have run integraton_test_update_goldens.dart first");
|
||||
}
|
||||
|
||||
var result = await compareImages(
|
||||
src1: screenshotBytes,
|
||||
src2: golden.readAsBytesSync(),
|
||||
algorithm: ChiSquareDistanceHistogram());
|
||||
|
||||
print(result);
|
||||
|
||||
// TODO - it would be preferable to use Flutter's GoldenFileComparator here, e.g.
|
||||
//
|
||||
// ```var comparator = LocalFileComparator(testImage.uri);
|
||||
// comparator.compare(imageBytes, golden)
|
||||
// comparator.getFailureFile(failure, golden, basedir)
|
||||
// var result = await comparator.compare(
|
||||
// Uint8List.fromList(screenshotBytes), golden.uri);
|
||||
// if (!result.passed) {
|
||||
// for (var key in result.diffs!.keys) {
|
||||
// var byteData = await result.diffs![key]!.toByteData();
|
||||
// File('integration_test/goldens/$dir/diffs/$name.png')
|
||||
// .writeAsBytesSync(
|
||||
// byteData!.buffer.asUint8List(byteData!.offsetInBytes));
|
||||
// }
|
||||
// return false;
|
||||
// }```
|
||||
// but this is only available via a Flutter shell which is currently unavailable (this script is run as a plain Dart file I guess).
|
||||
// let's revisit if/when this changes
|
||||
// see https://github.com/flutter/flutter/issues/51890 and https://github.com/flutter/flutter/issues/103222
|
||||
|
||||
if (result > 0.005) {
|
||||
File('integration_test/goldens/$dir/diffs/$name.png')
|
||||
.writeAsBytesSync(screenshotBytes);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
);
|
||||
}
|
||||
22
example/test_driver/integration_test_update_goldens.dart
Normal file
22
example/test_driver/integration_test_update_goldens.dart
Normal file
@@ -0,0 +1,22 @@
|
||||
import 'dart:io';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:integration_test/integration_test_driver_extended.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
await integrationDriver(
|
||||
onScreenshot: (
|
||||
String screenshotName,
|
||||
List<int> screenshotBytes, [
|
||||
Map<String, Object?>? args,
|
||||
]) async {
|
||||
final dir = screenshotName.split("/")[0];
|
||||
final name = screenshotName.split("/")[1];
|
||||
final File image = await File('integration_test/goldens/$dir/$name.png')
|
||||
.create(recursive: true);
|
||||
|
||||
image.writeAsBytesSync(screenshotBytes);
|
||||
|
||||
return true;
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -187,6 +187,7 @@ public class SwiftPolyvoxFilamentPlugin: NSObject, FlutterPlugin, FlutterTexture
|
||||
}
|
||||
self.flutterTextureId = nil
|
||||
self.pixelBuffer = nil
|
||||
result(true)
|
||||
case "resize":
|
||||
let args = call.arguments as! [Any]
|
||||
let width = UInt32(args[0] as! Int64)
|
||||
|
||||
@@ -17,6 +17,7 @@ typedef void (*FilamentRenderCallback)(void* const owner);
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void* const create_filament_viewer_ffi(void* const context, void* const platform, const char* uberArchivePath, const ResourceLoaderWrapper* const loader, void (*renderCallback)(void* const renderCallbackOwner), void* const renderCallbackOwner);
|
||||
FLUTTER_PLUGIN_EXPORT void create_swap_chain_ffi(void* const viewer, void* const surface, uint32_t width, uint32_t height);
|
||||
FLUTTER_PLUGIN_EXPORT void destroy_swap_chain_ffi(void* const viewer);
|
||||
FLUTTER_PLUGIN_EXPORT void create_render_target_ffi(void* const viewer, intptr_t nativeTextureId, uint32_t width, uint32_t height);
|
||||
FLUTTER_PLUGIN_EXPORT void destroy_filament_viewer_ffi(void* const viewer);
|
||||
FLUTTER_PLUGIN_EXPORT void render_ffi(void* const viewer);
|
||||
|
||||
@@ -20,7 +20,7 @@ extern "C" {
|
||||
#if defined(__cplusplus) && !defined(__ANDROID__) && !defined(__APPLE__)
|
||||
ResourceBuffer(const void* const data, const int32_t size, const int32_t id) : data(data), size(size), id(id) {};
|
||||
ResourceBuffer(const ResourceBuffer& rb) : data(rb.data), size(rb.size), id(rb.id) { };
|
||||
ResourceBuffer(const ResourceBuffer&& rb) : data(rb.data), size(rb.size), id(rb.id) { };
|
||||
ResourceBuffer(const ResourceBuffer&& rb) noexcept : data(rb.data), size(rb.size), id(rb.id) { };
|
||||
ResourceBuffer& operator=(const ResourceBuffer& other) = delete;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -2,459 +2,446 @@
|
||||
#include "PolyvoxFilamentFFIApi.h"
|
||||
|
||||
#include "FilamentViewer.hpp"
|
||||
#include "filament/LightManager.h"
|
||||
#include "Log.hpp"
|
||||
#include "ThreadPool.hpp"
|
||||
#include "filament/LightManager.h"
|
||||
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
using namespace polyvox;
|
||||
|
||||
class RenderLoop
|
||||
{
|
||||
class RenderLoop {
|
||||
public:
|
||||
explicit RenderLoop()
|
||||
{
|
||||
_t = new std::thread([this]()
|
||||
{
|
||||
while(!_stop) {
|
||||
if(_rendering) {
|
||||
doRender();
|
||||
}
|
||||
std::function<void()> task;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_access);
|
||||
if(_tasks.empty()) {
|
||||
_cond.wait_for(lock, std::chrono::duration<float, std::milli>(_frameIntervalInMilliseconds));
|
||||
continue;
|
||||
}
|
||||
task = std::move(_tasks.front());
|
||||
_tasks.pop_front();
|
||||
}
|
||||
task();
|
||||
} });
|
||||
}
|
||||
~RenderLoop()
|
||||
{
|
||||
_stop = true;
|
||||
_t->join();
|
||||
}
|
||||
explicit RenderLoop() {
|
||||
_t = new std::thread([this]() {
|
||||
while (!_stop) {
|
||||
{
|
||||
if (_rendering) {
|
||||
doRender();
|
||||
}
|
||||
}
|
||||
std::function<void()> task;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_access);
|
||||
if (_tasks.empty()) {
|
||||
_cond.wait_for(lock, std::chrono::duration<float, std::milli>(
|
||||
_frameIntervalInMilliseconds));
|
||||
continue;
|
||||
}
|
||||
task = std::move(_tasks.front());
|
||||
_tasks.pop_front();
|
||||
}
|
||||
task();
|
||||
}
|
||||
});
|
||||
}
|
||||
~RenderLoop() {
|
||||
_stop = true;
|
||||
_t->join();
|
||||
}
|
||||
|
||||
void* const createViewer(
|
||||
void* const context,
|
||||
void* const platform,
|
||||
const char* uberArchivePath,
|
||||
const ResourceLoaderWrapper* const loader,
|
||||
void (*renderCallback)(void*), void* const owner
|
||||
) {
|
||||
_renderCallback = renderCallback;
|
||||
_renderCallbackOwner = owner;
|
||||
std::packaged_task<FilamentViewer*()> lambda([&]() mutable
|
||||
{
|
||||
return new FilamentViewer(context, loader, platform, uberArchivePath);
|
||||
});
|
||||
auto fut = add_task(lambda);
|
||||
fut.wait();
|
||||
_viewer = fut.get();
|
||||
return (void* const)_viewer;
|
||||
}
|
||||
void *const createViewer(void *const context, void *const platform,
|
||||
const char *uberArchivePath,
|
||||
const ResourceLoaderWrapper *const loader,
|
||||
void (*renderCallback)(void *), void *const owner) {
|
||||
_renderCallback = renderCallback;
|
||||
_renderCallbackOwner = owner;
|
||||
std::packaged_task<FilamentViewer *()> lambda([&]() mutable {
|
||||
std::thread::id this_id = std::this_thread::get_id();
|
||||
return new FilamentViewer(context, loader, platform, uberArchivePath);
|
||||
});
|
||||
auto fut = add_task(lambda);
|
||||
fut.wait();
|
||||
_viewer = fut.get();
|
||||
return (void *const)_viewer;
|
||||
}
|
||||
|
||||
void destroyViewer() {
|
||||
std::packaged_task<void()> lambda([&]() mutable {
|
||||
_rendering = false;
|
||||
destroy_filament_viewer(_viewer);
|
||||
_viewer = nullptr;
|
||||
});
|
||||
auto fut = add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
void destroyViewer() {
|
||||
std::packaged_task<void()> lambda([&]() mutable {
|
||||
_rendering = false;
|
||||
destroy_filament_viewer(_viewer);
|
||||
_viewer = nullptr;
|
||||
});
|
||||
auto fut = add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
void setRendering(bool rendering)
|
||||
{
|
||||
_rendering = rendering;
|
||||
}
|
||||
void setRendering(bool rendering) {
|
||||
std::packaged_task<void()> lambda(
|
||||
[&]() mutable { this->_rendering = rendering; });
|
||||
auto fut = add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
void doRender()
|
||||
{
|
||||
render(_viewer, 0, nullptr, nullptr, nullptr);
|
||||
_renderCallback(_renderCallbackOwner);
|
||||
}
|
||||
void doRender() {
|
||||
render(_viewer, 0, nullptr, nullptr, nullptr);
|
||||
_renderCallback(_renderCallbackOwner);
|
||||
}
|
||||
|
||||
void setFrameIntervalInMilliseconds(float frameIntervalInMilliseconds) {
|
||||
_frameIntervalInMilliseconds = frameIntervalInMilliseconds;
|
||||
}
|
||||
void setFrameIntervalInMilliseconds(float frameIntervalInMilliseconds) {
|
||||
_frameIntervalInMilliseconds = frameIntervalInMilliseconds;
|
||||
}
|
||||
|
||||
template <class Rt>
|
||||
auto add_task(std::packaged_task<Rt()> &pt) -> std::future<Rt>
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_access);
|
||||
auto ret = pt.get_future();
|
||||
_tasks.push_back([pt = std::make_shared<std::packaged_task<Rt()>>(std::move(pt))]
|
||||
{ (*pt)(); });
|
||||
_cond.notify_one();
|
||||
return ret;
|
||||
}
|
||||
template <class Rt>
|
||||
auto add_task(std::packaged_task<Rt()> &pt) -> std::future<Rt> {
|
||||
std::unique_lock<std::mutex> lock(_access);
|
||||
auto ret = pt.get_future();
|
||||
_tasks.push_back([pt = std::make_shared<std::packaged_task<Rt()>>(
|
||||
std::move(pt))] { (*pt)(); });
|
||||
_cond.notify_one();
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
bool _stop = false;
|
||||
bool _rendering = false;
|
||||
float _frameIntervalInMilliseconds = 1000.0 / 60.0;
|
||||
std::mutex _access;
|
||||
FilamentViewer *_viewer = nullptr;
|
||||
void (*_renderCallback)(void* const) = nullptr;
|
||||
void *_renderCallbackOwner = nullptr;
|
||||
std::thread *_t = nullptr;
|
||||
std::condition_variable _cond;
|
||||
std::deque<std::function<void()>> _tasks;
|
||||
bool _stop = false;
|
||||
bool _rendering = false;
|
||||
float _frameIntervalInMilliseconds = 1000.0 / 60.0;
|
||||
std::mutex _access;
|
||||
FilamentViewer *_viewer = nullptr;
|
||||
void (*_renderCallback)(void *const) = nullptr;
|
||||
void *_renderCallbackOwner = nullptr;
|
||||
std::thread *_t = nullptr;
|
||||
std::condition_variable _cond;
|
||||
std::deque<std::function<void()>> _tasks;
|
||||
};
|
||||
|
||||
extern "C"
|
||||
{
|
||||
extern "C" {
|
||||
|
||||
static RenderLoop *_rl;
|
||||
static RenderLoop *_rl;
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void* const create_filament_viewer_ffi(
|
||||
void* const context,
|
||||
void* const platform,
|
||||
const char* uberArchivePath,
|
||||
const ResourceLoaderWrapper* const loader,
|
||||
void (*renderCallback)(void* const renderCallbackOwner),
|
||||
void* const renderCallbackOwner) {
|
||||
if (!_rl)
|
||||
{
|
||||
_rl = new RenderLoop();
|
||||
}
|
||||
return _rl->createViewer(context, platform,uberArchivePath, loader, renderCallback, renderCallbackOwner);
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void destroy_filament_viewer_ffi(void* const viewer) {
|
||||
_rl->destroyViewer();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void create_swap_chain_ffi(void* const viewer, void* const surface, uint32_t width, uint32_t height)
|
||||
{
|
||||
Log("Creating swapchain %dx%d", width, height);
|
||||
std::packaged_task<void()> lambda([&]() mutable
|
||||
{
|
||||
create_swap_chain(viewer, surface, width, height);
|
||||
});
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void create_render_target_ffi(void* const viewer, intptr_t nativeTextureId, uint32_t width, uint32_t height)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]() mutable
|
||||
{ create_render_target(viewer, nativeTextureId, width, height); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void update_viewport_and_camera_projection_ffi(void* const viewer, const uint32_t width, const uint32_t height, const float scaleFactor)
|
||||
{
|
||||
Log("Update viewport %dx%d", width, height);
|
||||
std::packaged_task<void()> lambda([&]() mutable
|
||||
{
|
||||
update_viewport_and_camera_projection(viewer, width, height, scaleFactor);
|
||||
});
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void set_rendering_ffi(void* const viewer, bool rendering)
|
||||
{
|
||||
if (!_rl)
|
||||
{
|
||||
Log("No render loop!"); // PANIC?
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rendering)
|
||||
{
|
||||
Log("Set rendering to true");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log("Set rendering to false");
|
||||
}
|
||||
_rl->setRendering(rendering);
|
||||
}
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void set_frame_interval_ffi(float frameIntervalInMilliseconds) {
|
||||
_rl->setFrameIntervalInMilliseconds(frameIntervalInMilliseconds);
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void render_ffi(void* const viewer)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]() mutable
|
||||
{
|
||||
_rl->doRender();
|
||||
});
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void set_background_color_ffi(void* const viewer, const float r, const float g, const float b, const float a)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]() mutable
|
||||
{ set_background_color(viewer, r, g, b, a); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT EntityId load_gltf_ffi(void* const assetManager, const char *path, const char *relativeResourcePath)
|
||||
{
|
||||
std::packaged_task<EntityId()> lambda([&]() mutable
|
||||
{ return load_gltf(assetManager, path, relativeResourcePath); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
return fut.get();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT EntityId load_glb_ffi(void* const assetManager, const char *path, bool unlit)
|
||||
{
|
||||
std::packaged_task<EntityId()> lambda([&]() mutable
|
||||
{ return load_glb(assetManager, path, unlit); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
return fut.get();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void clear_background_image_ffi(void* const viewer)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]
|
||||
{
|
||||
clear_background_image(viewer);
|
||||
});
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void set_background_image_ffi(void* const viewer, const char *path, bool fillHeight)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]
|
||||
{
|
||||
set_background_image(viewer, path, fillHeight);
|
||||
});
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
FLUTTER_PLUGIN_EXPORT void set_background_image_position_ffi(void* const viewer, float x, float y, bool clamp)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]
|
||||
{ set_background_image_position(viewer, x, y, clamp); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
FLUTTER_PLUGIN_EXPORT void set_tone_mapping_ffi(void* const viewer, int toneMapping)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]
|
||||
{ set_tone_mapping(viewer, toneMapping); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
FLUTTER_PLUGIN_EXPORT void set_bloom_ffi(void* const viewer, float strength)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]
|
||||
{ set_bloom(viewer, strength); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
FLUTTER_PLUGIN_EXPORT void load_skybox_ffi(void* const viewer, const char *skyboxPath)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]
|
||||
{ load_skybox(viewer, skyboxPath); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
FLUTTER_PLUGIN_EXPORT void load_ibl_ffi(void* const viewer, const char *iblPath, float intensity)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]
|
||||
{ load_ibl(viewer, iblPath, intensity); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
FLUTTER_PLUGIN_EXPORT void remove_skybox_ffi(void* const viewer)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]
|
||||
{ remove_skybox(viewer); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void remove_ibl_ffi(void* const viewer)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]
|
||||
{ remove_ibl(viewer); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
EntityId add_light_ffi(void* const viewer, uint8_t type, float colour, float intensity, float posX, float posY, float posZ, float dirX, float dirY, float dirZ, bool shadows)
|
||||
{
|
||||
std::packaged_task<EntityId()> lambda([&]
|
||||
{ return add_light(viewer, type, colour, intensity, posX, posY, posZ, dirX, dirY, dirZ, shadows); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
return fut.get();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void remove_light_ffi(void* const viewer, EntityId entityId)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]
|
||||
{ remove_light(viewer, entityId); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void clear_lights_ffi(void* const viewer)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]
|
||||
{ clear_lights(viewer); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void remove_asset_ffi(void* const viewer, EntityId asset)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]
|
||||
{ remove_asset(viewer, asset); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
FLUTTER_PLUGIN_EXPORT void clear_assets_ffi(void* const viewer)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]
|
||||
{ clear_assets(viewer); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT bool set_camera_ffi(void* const viewer, EntityId asset, const char *nodeName)
|
||||
{
|
||||
std::packaged_task<bool()> lambda([&]
|
||||
{ return set_camera(viewer, asset, nodeName); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
return fut.get();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void set_bone_animation_ffi(
|
||||
void *assetManager,
|
||||
EntityId asset,
|
||||
const float *const frameData,
|
||||
int numFrames,
|
||||
int numBones,
|
||||
const char **const boneNames,
|
||||
const char **const meshName,
|
||||
int numMeshTargets,
|
||||
float frameLengthInMs)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]
|
||||
{ set_bone_animation(
|
||||
assetManager, asset, frameData, numFrames, numBones,
|
||||
boneNames, meshName, numMeshTargets, frameLengthInMs); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void get_morph_target_name_ffi(void *assetManager, EntityId asset, const char *meshName, char *const outPtr, int index)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]
|
||||
{ get_morph_target_name(assetManager, asset, meshName, outPtr, index); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT int get_morph_target_name_count_ffi(void *assetManager, EntityId asset, const char *meshName)
|
||||
{
|
||||
std::packaged_task<int()> lambda([&]
|
||||
{ return get_morph_target_name_count(assetManager, asset, meshName); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
return fut.get();
|
||||
}
|
||||
|
||||
void set_morph_target_weights_ffi(
|
||||
void* const assetManager,
|
||||
EntityId asset,
|
||||
const char *const entityName,
|
||||
const float *const morphData,
|
||||
int numWeights)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void play_animation_ffi(void* const assetManager, EntityId asset, int index, bool loop, bool reverse, bool replaceActive, float crossfade)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]
|
||||
{ play_animation(assetManager, asset, index, loop, reverse, replaceActive, crossfade); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void set_animation_frame_ffi(void* const assetManager, EntityId asset, int animationIndex, int animationFrame)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]
|
||||
{ set_animation_frame(assetManager, asset, animationIndex, animationFrame); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void stop_animation_ffi(void* const assetManager, EntityId asset, int index)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]
|
||||
{ stop_animation(assetManager, asset, index); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT int get_animation_count_ffi(void* const assetManager, EntityId asset)
|
||||
{
|
||||
std::packaged_task<int()> lambda([&]
|
||||
{ return get_animation_count(assetManager, asset); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
return fut.get();
|
||||
}
|
||||
FLUTTER_PLUGIN_EXPORT void get_animation_name_ffi(void* const assetManager, EntityId asset, char *const outPtr, int index)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&] {
|
||||
get_animation_name(assetManager, asset, outPtr, index);
|
||||
});
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void set_post_processing_ffi(void* const viewer, bool enabled) {
|
||||
std::packaged_task<void()> lambda([&] {
|
||||
set_post_processing(viewer, enabled);
|
||||
});
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void pick_ffi(void* const viewer, int x, int y, EntityId* entityId) {
|
||||
std::packaged_task<void()> lambda([&] {
|
||||
pick(viewer, x, y, entityId);
|
||||
});
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT const char* get_name_for_entity_ffi(void* const assetManager, const EntityId entityId) {
|
||||
std::packaged_task<const char*()> lambda([&] {
|
||||
return get_name_for_entity(assetManager, entityId);
|
||||
});
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
return fut.get();
|
||||
}
|
||||
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void ios_dummy_ffi() {
|
||||
Log("Dummy called");
|
||||
}
|
||||
FLUTTER_PLUGIN_EXPORT void *const create_filament_viewer_ffi(
|
||||
void *const context, void *const platform, const char *uberArchivePath,
|
||||
const ResourceLoaderWrapper *const loader,
|
||||
void (*renderCallback)(void *const renderCallbackOwner),
|
||||
void *const renderCallbackOwner) {
|
||||
if (!_rl) {
|
||||
_rl = new RenderLoop();
|
||||
}
|
||||
return _rl->createViewer(context, platform, uberArchivePath, loader,
|
||||
renderCallback, renderCallbackOwner);
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void destroy_filament_viewer_ffi(void *const viewer) {
|
||||
_rl->destroyViewer();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void create_swap_chain_ffi(void *const viewer,
|
||||
void *const surface,
|
||||
uint32_t width,
|
||||
uint32_t height) {
|
||||
Log("Creating swapchain %dx%d", width, height);
|
||||
std::packaged_task<void()> lambda(
|
||||
[&]() mutable { create_swap_chain(viewer, surface, width, height); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void destroy_swap_chain_ffi(void *const viewer) {
|
||||
Log("Destroying swapchain");
|
||||
std::packaged_task<void()> lambda(
|
||||
[&]() mutable {
|
||||
destroy_swap_chain(viewer);
|
||||
});
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void create_render_target_ffi(void *const viewer,
|
||||
intptr_t nativeTextureId,
|
||||
uint32_t width,
|
||||
uint32_t height) {
|
||||
std::packaged_task<void()> lambda([&]() mutable {
|
||||
create_render_target(viewer, nativeTextureId, width, height);
|
||||
});
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void update_viewport_and_camera_projection_ffi(
|
||||
void *const viewer, const uint32_t width, const uint32_t height,
|
||||
const float scaleFactor) {
|
||||
Log("Update viewport %dx%d", width, height);
|
||||
std::packaged_task<void()> lambda([&]() mutable {
|
||||
update_viewport_and_camera_projection(viewer, width, height, scaleFactor);
|
||||
});
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void set_rendering_ffi(void *const viewer,
|
||||
bool rendering) {
|
||||
if (!_rl) {
|
||||
Log("No render loop!"); // PANIC?
|
||||
} else {
|
||||
if (rendering) {
|
||||
Log("Set rendering to true");
|
||||
} else {
|
||||
Log("Set rendering to false");
|
||||
}
|
||||
_rl->setRendering(rendering);
|
||||
}
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void
|
||||
set_frame_interval_ffi(float frameIntervalInMilliseconds) {
|
||||
_rl->setFrameIntervalInMilliseconds(frameIntervalInMilliseconds);
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void render_ffi(void *const viewer) {
|
||||
std::packaged_task<void()> lambda([&]() mutable { _rl->doRender(); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void
|
||||
set_background_color_ffi(void *const viewer, const float r, const float g,
|
||||
const float b, const float a) {
|
||||
std::packaged_task<void()> lambda(
|
||||
[&]() mutable { set_background_color(viewer, r, g, b, a); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT EntityId load_gltf_ffi(void *const assetManager,
|
||||
const char *path,
|
||||
const char *relativeResourcePath) {
|
||||
std::packaged_task<EntityId()> lambda([&]() mutable {
|
||||
return load_gltf(assetManager, path, relativeResourcePath);
|
||||
});
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
return fut.get();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT EntityId load_glb_ffi(void *const assetManager,
|
||||
const char *path, bool unlit) {
|
||||
std::packaged_task<EntityId()> lambda(
|
||||
[&]() mutable { return load_glb(assetManager, path, unlit); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
return fut.get();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void clear_background_image_ffi(void *const viewer) {
|
||||
std::packaged_task<void()> lambda([&] { clear_background_image(viewer); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void set_background_image_ffi(void *const viewer,
|
||||
const char *path,
|
||||
bool fillHeight) {
|
||||
std::packaged_task<void()> lambda(
|
||||
[&] { set_background_image(viewer, path, fillHeight); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
FLUTTER_PLUGIN_EXPORT void set_background_image_position_ffi(void *const viewer,
|
||||
float x, float y,
|
||||
bool clamp) {
|
||||
std::packaged_task<void()> lambda(
|
||||
[&] { set_background_image_position(viewer, x, y, clamp); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
FLUTTER_PLUGIN_EXPORT void set_tone_mapping_ffi(void *const viewer,
|
||||
int toneMapping) {
|
||||
std::packaged_task<void()> lambda(
|
||||
[&] { set_tone_mapping(viewer, toneMapping); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
FLUTTER_PLUGIN_EXPORT void set_bloom_ffi(void *const viewer, float strength) {
|
||||
std::packaged_task<void()> lambda([&] { set_bloom(viewer, strength); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
FLUTTER_PLUGIN_EXPORT void load_skybox_ffi(void *const viewer,
|
||||
const char *skyboxPath) {
|
||||
std::packaged_task<void()> lambda([&] { load_skybox(viewer, skyboxPath); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
FLUTTER_PLUGIN_EXPORT void load_ibl_ffi(void *const viewer, const char *iblPath,
|
||||
float intensity) {
|
||||
std::packaged_task<void()> lambda(
|
||||
[&] { load_ibl(viewer, iblPath, intensity); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
FLUTTER_PLUGIN_EXPORT void remove_skybox_ffi(void *const viewer) {
|
||||
std::packaged_task<void()> lambda([&] { remove_skybox(viewer); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void remove_ibl_ffi(void *const viewer) {
|
||||
std::packaged_task<void()> lambda([&] { remove_ibl(viewer); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
EntityId add_light_ffi(void *const viewer, uint8_t type, float colour,
|
||||
float intensity, float posX, float posY, float posZ,
|
||||
float dirX, float dirY, float dirZ, bool shadows) {
|
||||
std::packaged_task<EntityId()> lambda([&] {
|
||||
return add_light(viewer, type, colour, intensity, posX, posY, posZ, dirX,
|
||||
dirY, dirZ, shadows);
|
||||
});
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
return fut.get();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void remove_light_ffi(void *const viewer,
|
||||
EntityId entityId) {
|
||||
std::packaged_task<void()> lambda([&] { remove_light(viewer, entityId); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void clear_lights_ffi(void *const viewer) {
|
||||
std::packaged_task<void()> lambda([&] { clear_lights(viewer); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void remove_asset_ffi(void *const viewer,
|
||||
EntityId asset) {
|
||||
std::packaged_task<void()> lambda([&] { remove_asset(viewer, asset); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
FLUTTER_PLUGIN_EXPORT void clear_assets_ffi(void *const viewer) {
|
||||
std::packaged_task<void()> lambda([&] { clear_assets(viewer); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT bool set_camera_ffi(void *const viewer, EntityId asset,
|
||||
const char *nodeName) {
|
||||
std::packaged_task<bool()> lambda(
|
||||
[&] { return set_camera(viewer, asset, nodeName); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
return fut.get();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void set_bone_animation_ffi(
|
||||
void *assetManager, EntityId asset, const float *const frameData,
|
||||
int numFrames, int numBones, const char **const boneNames,
|
||||
const char **const meshName, int numMeshTargets, float frameLengthInMs) {
|
||||
std::packaged_task<void()> lambda([&] {
|
||||
set_bone_animation(assetManager, asset, frameData, numFrames, numBones,
|
||||
boneNames, meshName, numMeshTargets, frameLengthInMs);
|
||||
});
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void
|
||||
get_morph_target_name_ffi(void *assetManager, EntityId asset,
|
||||
const char *meshName, char *const outPtr, int index) {
|
||||
std::packaged_task<void()> lambda([&] {
|
||||
get_morph_target_name(assetManager, asset, meshName, outPtr, index);
|
||||
});
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT int
|
||||
get_morph_target_name_count_ffi(void *assetManager, EntityId asset,
|
||||
const char *meshName) {
|
||||
std::packaged_task<int()> lambda([&] {
|
||||
return get_morph_target_name_count(assetManager, asset, meshName);
|
||||
});
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
return fut.get();
|
||||
}
|
||||
|
||||
void set_morph_target_weights_ffi(void *const assetManager, EntityId asset,
|
||||
const char *const entityName,
|
||||
const float *const morphData,
|
||||
int numWeights) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void play_animation_ffi(void *const assetManager,
|
||||
EntityId asset, int index,
|
||||
bool loop, bool reverse,
|
||||
bool replaceActive,
|
||||
float crossfade) {
|
||||
std::packaged_task<void()> lambda([&] {
|
||||
play_animation(assetManager, asset, index, loop, reverse, replaceActive,
|
||||
crossfade);
|
||||
});
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void set_animation_frame_ffi(void *const assetManager,
|
||||
EntityId asset,
|
||||
int animationIndex,
|
||||
int animationFrame) {
|
||||
std::packaged_task<void()> lambda([&] {
|
||||
set_animation_frame(assetManager, asset, animationIndex, animationFrame);
|
||||
});
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void stop_animation_ffi(void *const assetManager,
|
||||
EntityId asset, int index) {
|
||||
std::packaged_task<void()> lambda(
|
||||
[&] { stop_animation(assetManager, asset, index); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT int get_animation_count_ffi(void *const assetManager,
|
||||
EntityId asset) {
|
||||
std::packaged_task<int()> lambda(
|
||||
[&] { return get_animation_count(assetManager, asset); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
return fut.get();
|
||||
}
|
||||
FLUTTER_PLUGIN_EXPORT void get_animation_name_ffi(void *const assetManager,
|
||||
EntityId asset,
|
||||
char *const outPtr,
|
||||
int index) {
|
||||
std::packaged_task<void()> lambda(
|
||||
[&] { get_animation_name(assetManager, asset, outPtr, index); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void set_post_processing_ffi(void *const viewer,
|
||||
bool enabled) {
|
||||
std::packaged_task<void()> lambda(
|
||||
[&] { set_post_processing(viewer, enabled); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void pick_ffi(void *const viewer, int x, int y,
|
||||
EntityId *entityId) {
|
||||
std::packaged_task<void()> lambda([&] { pick(viewer, x, y, entityId); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT const char *
|
||||
get_name_for_entity_ffi(void *const assetManager, const EntityId entityId) {
|
||||
std::packaged_task<const char *()> lambda(
|
||||
[&] { return get_name_for_entity(assetManager, entityId); });
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
return fut.get();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void ios_dummy_ffi() { Log("Dummy called"); }
|
||||
}
|
||||
|
||||
@@ -5,15 +5,21 @@ import 'package:polyvox_filament/animations/bone_animation_data.dart';
|
||||
import 'package:polyvox_filament/animations/morph_animation_data.dart';
|
||||
|
||||
typedef FilamentEntity = int;
|
||||
const FilamentEntity FILAMENT_ASSET_ERROR = 0;
|
||||
|
||||
enum ToneMapper { ACES, FILMIC, LINEAR }
|
||||
|
||||
class TextureDetails {
|
||||
final int textureId;
|
||||
final int width;
|
||||
final int height;
|
||||
|
||||
TextureDetails({required this.textureId, required this.width, required this.height});
|
||||
}
|
||||
|
||||
abstract class FilamentController {
|
||||
// the current target size of the viewport, in logical pixels
|
||||
ui.Size size = ui.Size.zero;
|
||||
|
||||
Stream<int?> get textureId;
|
||||
Future get isReadyForScene;
|
||||
|
||||
///
|
||||
@@ -46,7 +52,8 @@ abstract class FilamentController {
|
||||
void setPixelRatio(double ratio);
|
||||
|
||||
///
|
||||
/// Destroys the viewer and all backing textures. You can leave the FilamentWidget in the hierarchy after this is called, but
|
||||
/// Destroys the viewer and all backing textures. You can leave the FilamentWidget in the hierarchy after this is called, but you will need to manually call [createViewer] to
|
||||
///
|
||||
Future destroy();
|
||||
|
||||
///
|
||||
@@ -54,15 +61,17 @@ abstract class FilamentController {
|
||||
///
|
||||
Future destroyViewer();
|
||||
|
||||
|
||||
///
|
||||
/// Destroys the backing texture. You probably want to call [destroy] instead of this; this is exposed mostly for lifecycle changes which are handled by FilamentWidget.
|
||||
///
|
||||
Future destroyTexture();
|
||||
|
||||
///
|
||||
/// You can insert a Filament viewport into the Flutter rendering hierarchy as follows:
|
||||
/// Called by [FilamentWidget]; you generally will not need to call this yourself.
|
||||
/// To recap, you can create a viewport is created in the Flutter rendering hierarchy by:
|
||||
/// 1) Create a FilamentController
|
||||
/// 2) Insert a FilamentWidget into the rendering tree, passing this instance of FilamentController
|
||||
/// 2) Insert a FilamentWidget into the rendering tree, passing your FilamentController
|
||||
/// 3) Initially, the FilamentWidget will only contain an empty Container (by default, with a solid red background).
|
||||
/// This widget will render a single frame to get its actual size, then will itself call [createViewer]. You do not need to call [createViewer] yourself.
|
||||
/// This will dispatch a request to the native platform to create a hardware texture (Metal on iOS, OpenGL on Linux, GLES on Android and Windows) and a FilamentViewer (the main interface for manipulating the 3D scene) .
|
||||
@@ -70,27 +79,69 @@ abstract class FilamentController {
|
||||
/// 5) The FilamentWidget will replace the empty Container with a Texture widget
|
||||
/// If you need to wait until a FilamentViewer has been created, [await] the [isReadyForScene] Future.
|
||||
///
|
||||
Future createViewer(int width, int height);
|
||||
Future resize(int width, int height, {double scaleFactor = 1.0});
|
||||
Future<TextureDetails> createViewer(int width, int height);
|
||||
|
||||
Future clearBackgroundImage();
|
||||
///
|
||||
/// Resize the viewport & backing texture.
|
||||
/// This is called by FilamentWidget; you shouldn't need to invoke this manually.
|
||||
///
|
||||
Future<TextureDetails> resize(int width, int height, {double scaleFactor = 1.0});
|
||||
|
||||
///
|
||||
/// Set the background image to [path] (which should have a file extension .png, .jpg, or .ktx).
|
||||
/// This will be rendered at the maximum depth (i.e. behind all other objects including the skybox).
|
||||
/// If [fillHeight] is false, the image will be rendered at its original size. Note this may cause issues with pixel density so be sure to specify the correct resolution
|
||||
/// If [fillHeight] is true, the image will be stretched/compressed to fit the height of the viewport.
|
||||
///
|
||||
Future setBackgroundImage(String path, {bool fillHeight = false});
|
||||
|
||||
Future setBackgroundColor(Color color);
|
||||
///
|
||||
/// Moves the background image to the relative offset from the origin (bottom-left) specified by [x] and [y].
|
||||
/// If [clamp] is true, the image cannot be positioned outside the bounds of the viewport.
|
||||
///
|
||||
Future setBackgroundImagePosition(double x, double y, {bool clamp = false});
|
||||
|
||||
///
|
||||
/// Removes the background image.
|
||||
///
|
||||
Future clearBackgroundImage();
|
||||
|
||||
///
|
||||
/// Sets the color for the background plane (positioned at the maximum depth, i.e. behind all other objects including the skybox).
|
||||
///
|
||||
Future setBackgroundColor(Color color);
|
||||
|
||||
///
|
||||
/// Load a skybox from [skyboxPath] (which must be a .ktx file)
|
||||
///
|
||||
Future loadSkybox(String skyboxPath);
|
||||
Future loadIbl(String lightingPath, {double intensity = 30000});
|
||||
|
||||
///
|
||||
/// Removes the skybox from the scene.
|
||||
///
|
||||
Future removeSkybox();
|
||||
|
||||
///
|
||||
/// Loads an image-based light from the specified path at the given intensity.
|
||||
/// Only one IBL can be active at any given time; if an IBL has already been loaded, it will be replaced.
|
||||
///
|
||||
Future loadIbl(String lightingPath, {double intensity = 30000});
|
||||
|
||||
///
|
||||
/// Removes the image-based light from the scene.
|
||||
///
|
||||
Future removeIbl();
|
||||
|
||||
// copied from LightManager.h
|
||||
// enum class Type : uint8_t {
|
||||
// SUN, //!< Directional light that also draws a sun's disk in the sky.
|
||||
// DIRECTIONAL, //!< Directional light, emits light in a given direction.
|
||||
// POINT, //!< Point light, emits light from a position, in all directions.
|
||||
// FOCUSED_SPOT, //!< Physically correct spot light.
|
||||
// SPOT, //!< Spot light with coupling of outer cone and illumination disabled.
|
||||
// };
|
||||
///
|
||||
/// Adds a dynamic light to the scene.
|
||||
/// copied from filament LightManager.h
|
||||
/// enum class Type : uint8_t {
|
||||
/// SUN, //!< Directional light that also draws a sun's disk in the sky.
|
||||
/// DIRECTIONAL, //!< Directional light, emits light in a given direction.
|
||||
/// POINT, //!< Point light, emits light from a position, in all directions.
|
||||
/// FOCUSED_SPOT, //!< Physically correct spot light.
|
||||
/// SPOT, //!< Spot light with coupling of outer cone and illumination disabled.
|
||||
/// };
|
||||
Future<FilamentEntity> addLight(
|
||||
int type,
|
||||
double colour,
|
||||
@@ -148,17 +199,18 @@ abstract class FilamentController {
|
||||
/// Set the weights for all morph targets under node [meshName] in [asset] to [weights].
|
||||
///
|
||||
Future setMorphTargetWeights(
|
||||
FilamentEntity asset, String meshName, List<double> weights);
|
||||
FilamentEntity entity, String meshName, List<double> weights);
|
||||
|
||||
Future<List<String>> getMorphTargetNames(
|
||||
FilamentEntity asset, String meshName);
|
||||
FilamentEntity entity, String meshName);
|
||||
|
||||
Future<List<String>> getAnimationNames(FilamentEntity asset);
|
||||
Future<List<String>> getAnimationNames(FilamentEntity entity);
|
||||
|
||||
///
|
||||
/// Returns the length (in seconds) of the animation at the given index.
|
||||
///
|
||||
Future<double> getAnimationDuration(FilamentEntity asset, int animationIndex);
|
||||
Future<double> getAnimationDuration(
|
||||
FilamentEntity entity, int animationIndex);
|
||||
|
||||
///
|
||||
/// Create/start a dynamic morph target animation for [asset].
|
||||
@@ -167,7 +219,7 @@ abstract class FilamentController {
|
||||
/// Each frame is [numWeights] in length, and each entry is the weight to be applied to the morph target located at that index in the mesh primitive at that frame.
|
||||
///
|
||||
Future setMorphAnimationData(
|
||||
FilamentEntity asset, MorphAnimationData animation);
|
||||
FilamentEntity entity, MorphAnimationData animation);
|
||||
|
||||
///
|
||||
/// Animates morph target weights/bone transforms (where each frame requires a duration of [frameLengthInMs].
|
||||
@@ -175,13 +227,13 @@ abstract class FilamentController {
|
||||
/// Each frame is [numWeights] in length, and each entry is the weight to be applied to the morph target located at that index in the mesh primitive at that frame.
|
||||
/// for now we only allow animating a single bone (though multiple skinned targets are supported)
|
||||
///
|
||||
Future setBoneAnimation(FilamentEntity asset, BoneAnimationData animation);
|
||||
Future setBoneAnimation(FilamentEntity entity, BoneAnimationData animation);
|
||||
|
||||
///
|
||||
/// Removes/destroys the specified entity from the scene.
|
||||
/// [asset] will no longer be a valid handle after this method is called; ensure you immediately discard all references once this method is complete.
|
||||
///
|
||||
Future removeAsset(FilamentEntity asset);
|
||||
Future removeAsset(FilamentEntity entity);
|
||||
|
||||
///
|
||||
/// Removes/destroys all renderable entities from the scene (including cameras).
|
||||
@@ -207,18 +259,19 @@ abstract class FilamentController {
|
||||
///
|
||||
/// Schedules the glTF animation at [index] in [asset] to start playing on the next frame.
|
||||
///
|
||||
Future playAnimation(FilamentEntity asset, int index,
|
||||
Future playAnimation(FilamentEntity entity, int index,
|
||||
{bool loop = false,
|
||||
bool reverse = false,
|
||||
bool replaceActive = true,
|
||||
double crossfade = 0.0});
|
||||
Future setAnimationFrame(FilamentEntity asset, int index, int animationFrame);
|
||||
Future stopAnimation(FilamentEntity asset, int animationIndex);
|
||||
Future setAnimationFrame(
|
||||
FilamentEntity entity, int index, int animationFrame);
|
||||
Future stopAnimation(FilamentEntity entity, int animationIndex);
|
||||
|
||||
///
|
||||
/// Sets the current scene camera to the glTF camera under [name] in [asset].
|
||||
///
|
||||
Future setCamera(FilamentEntity asset, String? name);
|
||||
Future setCamera(FilamentEntity entity, String? name);
|
||||
|
||||
///
|
||||
/// Sets the tone mapping (requires postprocessing).
|
||||
@@ -236,7 +289,7 @@ abstract class FilamentController {
|
||||
///
|
||||
/// Repositions the camera to the last vertex of the bounding box of [asset], looking at the penultimate vertex.
|
||||
///
|
||||
Future moveCameraToAsset(FilamentEntity asset);
|
||||
Future moveCameraToAsset(FilamentEntity entity);
|
||||
|
||||
///
|
||||
/// Enables/disables frustum culling. Currently we don't expose a method for manipulating the camera projection/culling matrices so this is your only option to deal with unwanted near/far clipping.
|
||||
@@ -248,27 +301,39 @@ abstract class FilamentController {
|
||||
Future setCameraModelMatrix(List<double> matrix);
|
||||
|
||||
Future setMaterialColor(
|
||||
FilamentEntity asset, String meshName, int materialIndex, Color color);
|
||||
FilamentEntity entity, String meshName, int materialIndex, Color color);
|
||||
|
||||
///
|
||||
/// Scales [asset] up/down so it fits within a unit cube.
|
||||
///
|
||||
Future transformToUnitCube(FilamentEntity asset);
|
||||
Future transformToUnitCube(FilamentEntity entity);
|
||||
|
||||
///
|
||||
/// Sets the world space position for [asset] to the given coordinates.
|
||||
///
|
||||
Future setPosition(FilamentEntity asset, double x, double y, double z);
|
||||
Future setPosition(FilamentEntity entity, double x, double y, double z);
|
||||
|
||||
///
|
||||
/// Enable/disable postprocessing.
|
||||
///
|
||||
Future setPostProcessing(bool enabled);
|
||||
Future setScale(FilamentEntity asset, double scale);
|
||||
|
||||
///
|
||||
/// Sets the scale for the given entity.
|
||||
///
|
||||
Future setScale(FilamentEntity entity, double scale);
|
||||
Future setRotation(
|
||||
FilamentEntity asset, double rads, double x, double y, double z);
|
||||
Future hide(FilamentEntity asset, String meshName);
|
||||
Future reveal(FilamentEntity asset, String meshName);
|
||||
FilamentEntity entity, double rads, double x, double y, double z);
|
||||
|
||||
///
|
||||
/// Reveal the node [meshName] under [entity]. Only applicable if [hide] had previously been called; this is a no-op otherwise.
|
||||
///
|
||||
Future reveal(FilamentEntity entity, String meshName);
|
||||
|
||||
///
|
||||
/// Hide the node [meshName] under [entity]. The node is still loaded, but is no longer being rendered into the scene. Call [reveal] to re-commence rendering.
|
||||
///
|
||||
Future hide(FilamentEntity entity, String meshName);
|
||||
|
||||
///
|
||||
/// Used to select the entity in the scene at the given viewport coordinates.
|
||||
@@ -277,4 +342,9 @@ abstract class FilamentController {
|
||||
/// [x] and [y] must be in local logical coordinates (i.e. where 0,0 is at top-left of the FilamentWidget).
|
||||
///
|
||||
void pick(int x, int y);
|
||||
|
||||
///
|
||||
/// Retrieves the name assigned to the given FilamentEntity (usually corresponds to the glTF mesh name).
|
||||
///
|
||||
String? getNameForEntity(FilamentEntity entity);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:ffi/ffi.dart';
|
||||
@@ -10,14 +9,14 @@ import 'package:polyvox_filament/animations/bone_animation_data.dart';
|
||||
import 'package:polyvox_filament/animations/morph_animation_data.dart';
|
||||
import 'package:polyvox_filament/generated_bindings.dart';
|
||||
|
||||
const FilamentEntity _FILAMENT_ASSET_ERROR = 0;
|
||||
|
||||
class FilamentControllerFFI extends FilamentController {
|
||||
late MethodChannel _channel = MethodChannel("app.polyvox.filament/event");
|
||||
|
||||
double _pixelRatio = 1.0;
|
||||
|
||||
int? _textureId;
|
||||
final _textureIdController = StreamController<int?>.broadcast();
|
||||
Stream<int?> get textureId => _textureIdController.stream;
|
||||
|
||||
Completer _isReadyForScene = Completer();
|
||||
Future get isReadyForScene => _isReadyForScene.future;
|
||||
@@ -52,11 +51,14 @@ class FilamentControllerFFI extends FilamentController {
|
||||
_lib = NativeLibrary(dl);
|
||||
}
|
||||
|
||||
bool _rendering = false;
|
||||
|
||||
@override
|
||||
Future setRendering(bool render) async {
|
||||
if (_viewer == null || _resizing) {
|
||||
throw Exception("No viewer available, ignoring");
|
||||
}
|
||||
_rendering = render;
|
||||
_lib.set_rendering_ffi(_viewer!, render);
|
||||
}
|
||||
|
||||
@@ -101,15 +103,21 @@ class FilamentControllerFFI extends FilamentController {
|
||||
|
||||
@override
|
||||
Future destroyTexture() async {
|
||||
await _channel.invokeMethod("destroyTexture");
|
||||
if (_textureId != null) {
|
||||
throw Exception("No texture available");
|
||||
}
|
||||
print("Destroying texture");
|
||||
// we need to flush all references to the previous texture ID before calling destroy, otherwise the Texture widget will attempt to render a non-existent texture and crash.
|
||||
// however, this is not a synchronous stream, so we need to ensure the Texture widget has been removed from the hierarchy before destroying
|
||||
_textureId = null;
|
||||
_textureIdController.add(null);
|
||||
await _channel.invokeMethod("destroyTexture", _textureId!);
|
||||
print("Texture destroyed");
|
||||
}
|
||||
|
||||
///
|
||||
/// Called by `FilamentWidget`. You do not need to call this yourself.
|
||||
///
|
||||
Future createViewer(int width, int height) async {
|
||||
Future<TextureDetails> createViewer(int width, int height) async {
|
||||
if (_viewer != null) {
|
||||
throw Exception(
|
||||
"Viewer already exists, make sure you call destroyViewer first");
|
||||
@@ -124,8 +132,6 @@ class FilamentControllerFFI extends FilamentController {
|
||||
throw Exception("Failed to get resource loader");
|
||||
}
|
||||
|
||||
print("Using loader ${loader.address}");
|
||||
|
||||
size = ui.Size(width * _pixelRatio, height * _pixelRatio);
|
||||
|
||||
print("Creating viewer with size $size");
|
||||
@@ -186,27 +192,100 @@ class FilamentControllerFFI extends FilamentController {
|
||||
|
||||
_assetManager = _lib.get_asset_manager(_viewer!);
|
||||
|
||||
_textureIdController.add(_textureId);
|
||||
|
||||
_isReadyForScene.complete(true);
|
||||
return TextureDetails(textureId: _textureId!, width: width, height: height);
|
||||
}
|
||||
|
||||
///
|
||||
/// I'm not exactly sure how to resize the backing textures on all platforms.
|
||||
/// So for now, I'm sticking with the safe option when the widget is resized: destroying the swapchain, recreating the textures, and creating a new swapchain.
|
||||
/// When a FilamentWidget is resized, it will call [resize]. This method will tear down/recreate the swapchain and propagate a new texture ID back to the FilamentWidget.
|
||||
/// For "once-off" resizes, this is fine.
|
||||
/// However, this can be problematic for consecutive resizes (e.g. dragging to expand/contract the parent window on desktop, or animating the size of the FilamentWidget itself).
|
||||
/// It is too expensive to recreate the swapchain multiple times per second.
|
||||
/// We therefore add a timer to FilamentWidget so that the call to [resize] is delayed (e.g. 50ms).
|
||||
/// Any subsequent resizes before the delay window elapses will cancel the earlier call.
|
||||
///
|
||||
/// The overall process looks like this:
|
||||
/// 1) the window is resized
|
||||
/// 2) (Windows only) PixelBufferTexture is requested to provide a new pixel buffer with a new size, and we return an empty texture
|
||||
/// 3) After Xms, [resize] is invoked
|
||||
/// 4) the viewer is instructed to stop rendering (synchronous)
|
||||
/// 5) the existing Filament swapchain is destroyed (synchronous)
|
||||
/// 6) the Flutter texture is unregistered
|
||||
/// a) this is asynchronous, but
|
||||
/// b) *** SEE NOTE BELOW ON WINDOWS *** by passing the method channel result through to the callback, we make this synchronous from the Flutter side,
|
||||
// c) in this async callback, the glTexture is destroyed
|
||||
/// 7) a new Flutter/OpenGL texture is created (synchronous)
|
||||
/// 8) a new swapchain is created (synchronous)
|
||||
/// 9) if the viewer was rendering prior to the resize, the viewer is instructed to recommence rendering
|
||||
/// 10) the new texture ID is pushed to the FilamentWidget
|
||||
/// 11) the FilamentWidget updates the Texture widget with the new texture.
|
||||
///
|
||||
/// #### (Windows-only) ############################################################
|
||||
/// # As soon as the widget/window is resized, the PixelBufferTexture will be
|
||||
/// # requested to provide a new pixel buffer for the new size.
|
||||
/// # Even with zero delay to the call to [resize], this will be triggered *before*
|
||||
/// # we have had a chance to anything else (like tear down the swapchain).
|
||||
/// # On the backend, we deal with this by simply returning an empty texture as soon
|
||||
/// # as the size changes, and will rely on the followup call to [resize] to actually
|
||||
/// # destroy/recreate the pixel buffer and Flutter texture.
|
||||
///
|
||||
/// NOTE RE ASYNC CALLBACK
|
||||
/// # The bigger problem is a race condition when resize is called multiple times in quick succession (e.g dragging to resize on Windows).
|
||||
/// # It looks like occasionally, the backend OpenGL texture is being destroyed while its corresponding swapchain is still active, causing a crash.
|
||||
/// # I'm not exactly sure how/where this is occurring, but something clearly isn't synchronized between destroy_swap_chain_ffi and
|
||||
/// # the asynchronous callback passed to FlutterTextureRegistrar::UnregisterTexture.
|
||||
/// # Theoretically this could occur if resize_2 starts before resize_1 completes, i.e.
|
||||
/// # 1) resize_1 destroys swapchain/texture and creates new texture
|
||||
/// # 2) resize_2 destroys swapchain/texture
|
||||
/// # 3) resize_1 creates new swapchain but texture isn't available, ergo crash
|
||||
/// #
|
||||
/// # I don't think this should happen if:
|
||||
/// # 1) we add a flag on the Flutter side to ensure only one call to destroy/recreate the swapchain/texture is active at any given time, and
|
||||
/// # 2) on the Flutter side, we are sure that calling destroyTexture only returns once the async callback on the native side has completed.
|
||||
/// # For (1), checking if textureId is null at the entrypoint should be sufficient.
|
||||
/// # For (2), we invoke flutter::MethodResult<flutter::EncodableValue>->Success in the UnregisterTexture callback.
|
||||
/// #
|
||||
/// # Maybe (2) doesn't actually make Flutter wait?
|
||||
/// #
|
||||
/// # The other possibility is that both (1) and (2) are fine and the issue is elsewhere.
|
||||
/// #
|
||||
/// # Either way, the current solution is to basically setup a double-buffer on resize.
|
||||
/// # When destroyTexture is called, the active texture isn't destroyed yet, it's only marked as inactive.
|
||||
/// # On subsequent calls to destroyTexture, the inactive texture is destroyed.
|
||||
/// # This seems to work fine.
|
||||
///
|
||||
/// # Another option is to only use a single large (e.g. 4k) texture and simply crop whenever a resize is requested.
|
||||
/// # This might be preferable for other reasons (e.g. don't need to destroy/recreate the pixel buffer or swapchain).
|
||||
/// # Given we don't do this on other platforms, I'm OK to stick with the existing solution for the time being.
|
||||
/// ############################################################################
|
||||
///
|
||||
|
||||
///
|
||||
///
|
||||
/// Other options:
|
||||
/// 1) never destroy the texture, simply allocate a large (4k?) texture and crop as needed
|
||||
/// 2) double-buffering?
|
||||
@override
|
||||
Future resize(int width, int height, {double scaleFactor = 1.0}) async {
|
||||
_resizing = true;
|
||||
setRendering(false);
|
||||
_lib.destroy_swap_chain(_viewer!);
|
||||
await destroyTexture();
|
||||
Future<TextureDetails> resize(int width, int height,
|
||||
{double scaleFactor = 1.0}) async {
|
||||
if (_textureId == null) {
|
||||
throw Exception("No texture created, ignoring call to resize.");
|
||||
}
|
||||
var textureId = _textureId;
|
||||
_textureId = null;
|
||||
|
||||
_lib.set_rendering_ffi(_viewer!, false);
|
||||
|
||||
if (_viewer != null) {
|
||||
_lib.destroy_swap_chain_ffi(_viewer!);
|
||||
}
|
||||
|
||||
await _channel.invokeMethod("destroyTexture", textureId);
|
||||
|
||||
size = ui.Size(width * _pixelRatio, height * _pixelRatio);
|
||||
|
||||
var textures =
|
||||
await _channel.invokeMethod("createTexture", [size.width, size.height]);
|
||||
var flutterTextureId = textures[0];
|
||||
_textureId = flutterTextureId;
|
||||
|
||||
// void* on iOS (pointer to pixel buffer), void* on Android (pointer to native window), null on Windows/macOS
|
||||
var surfaceAddress = textures[1] as int? ?? 0;
|
||||
@@ -229,9 +308,11 @@ class FilamentControllerFFI extends FilamentController {
|
||||
_lib.update_viewport_and_camera_projection_ffi(
|
||||
_viewer!, size.width.toInt(), size.height.toInt(), 1.0);
|
||||
|
||||
_textureIdController.add(_textureId);
|
||||
_resizing = false;
|
||||
setRendering(true);
|
||||
await setRendering(_rendering);
|
||||
|
||||
_textureId = textures[0];
|
||||
|
||||
return TextureDetails(textureId: _textureId!, width: width, height: height);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -352,7 +433,7 @@ class FilamentControllerFFI extends FilamentController {
|
||||
}
|
||||
var asset = _lib.load_glb_ffi(
|
||||
_assetManager!, path.toNativeUtf8().cast<Char>(), unlit);
|
||||
if (asset == FILAMENT_ASSET_ERROR) {
|
||||
if (asset == _FILAMENT_ASSET_ERROR) {
|
||||
throw Exception("An error occurred loading the asset at $path");
|
||||
}
|
||||
return asset;
|
||||
@@ -372,7 +453,7 @@ class FilamentControllerFFI extends FilamentController {
|
||||
_assetManager!,
|
||||
path.toNativeUtf8().cast<Char>(),
|
||||
relativeResourcePath.toNativeUtf8().cast<Char>());
|
||||
if (asset == FILAMENT_ASSET_ERROR) {
|
||||
if (asset == _FILAMENT_ASSET_ERROR) {
|
||||
throw Exception("An error occurred loading the asset at $path");
|
||||
}
|
||||
return asset;
|
||||
@@ -809,14 +890,17 @@ class FilamentControllerFFI extends FilamentController {
|
||||
}
|
||||
final outPtr = calloc<EntityId>(1);
|
||||
outPtr.value = 0;
|
||||
print("height ${size.height.toInt()} y $y");
|
||||
|
||||
_lib.pick_ffi(_viewer!, x, size.height.toInt() - y, outPtr);
|
||||
int wait = 0;
|
||||
while (outPtr.value == 0) {
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
print("Waiting");
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
wait++;
|
||||
if (wait > 10) {
|
||||
calloc.free(outPtr);
|
||||
throw Exception("Failed to get picking result");
|
||||
}
|
||||
}
|
||||
|
||||
var entityId = outPtr.value;
|
||||
_pickResultController.add(entityId);
|
||||
calloc.free(outPtr);
|
||||
|
||||
@@ -9,6 +9,8 @@ import 'filament_controller.dart';
|
||||
|
||||
typedef AssetManager = int;
|
||||
|
||||
const FilamentEntity _FILAMENT_ASSET_ERROR = 0;
|
||||
|
||||
///
|
||||
/// This is a previous iteration of FilamentController that used platform channels for every distinct platform.
|
||||
/// This is no longer used; currently kept only for reference/posterity.
|
||||
@@ -89,7 +91,8 @@ class FilamentControllerMethodChannel extends FilamentController {
|
||||
/// 3) Initially, this widget will only contain an empty Container. After the first frame is rendered, the widget itself will automatically call [createViewer] with the width/height from its constraints
|
||||
/// 4) The FilamentWidget will replace the empty Container with the Texture widget.
|
||||
///
|
||||
Future createViewer(int width, int height) async {
|
||||
Future<TextureDetails> createViewer(int width, int height) async {
|
||||
throw Exception();
|
||||
if (_viewer != null) {
|
||||
throw Exception(
|
||||
"Viewer already exists, make sure you call destroyViewer first");
|
||||
@@ -117,7 +120,9 @@ class FilamentControllerMethodChannel extends FilamentController {
|
||||
|
||||
bool _resizing = false;
|
||||
|
||||
Future resize(int width, int height, {double scaleFactor = 1.0}) async {
|
||||
|
||||
Future<TextureDetails> resize(int width, int height, {double scaleFactor = 1.0}) async {
|
||||
throw Exception();
|
||||
_resizing = true;
|
||||
_textureId = await _channel.invokeMethod(
|
||||
"resize", [width * _pixelRatio, height * _pixelRatio, scaleFactor]);
|
||||
@@ -245,7 +250,7 @@ class FilamentControllerMethodChannel extends FilamentController {
|
||||
}
|
||||
var asset =
|
||||
await _channel.invokeMethod("loadGlb", [_assetManager, path, unlit]);
|
||||
if (asset == FILAMENT_ASSET_ERROR) {
|
||||
if (asset == _FILAMENT_ASSET_ERROR) {
|
||||
throw Exception("An error occurred loading the asset at $path");
|
||||
}
|
||||
return asset;
|
||||
@@ -659,4 +664,10 @@ class FilamentControllerMethodChannel extends FilamentController {
|
||||
@override
|
||||
// TODO: implement pickResult
|
||||
Stream<FilamentEntity?> get pickResult => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
String? getNameForEntity(FilamentEntity entity) {
|
||||
// TODO: implement getNameForEntity
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1434,6 +1434,20 @@ class NativeLibrary {
|
||||
late final _create_swap_chain_ffi = _create_swap_chain_ffiPtr.asFunction<
|
||||
void Function(ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>, int, int)>();
|
||||
|
||||
void destroy_swap_chain_ffi(
|
||||
ffi.Pointer<ffi.Void> viewer,
|
||||
) {
|
||||
return _destroy_swap_chain_ffi(
|
||||
viewer,
|
||||
);
|
||||
}
|
||||
|
||||
late final _destroy_swap_chain_ffiPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
|
||||
'destroy_swap_chain_ffi');
|
||||
late final _destroy_swap_chain_ffi = _destroy_swap_chain_ffiPtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Void>)>();
|
||||
|
||||
void create_render_target_ffi(
|
||||
ffi.Pointer<ffi.Void> viewer,
|
||||
int nativeTextureId,
|
||||
|
||||
@@ -110,6 +110,7 @@ class _FilamentGestureDetectorMobileState
|
||||
}
|
||||
|
||||
Timer? _scrollTimer;
|
||||
double _lastScale = 0;
|
||||
|
||||
// pinch zoom on mobile
|
||||
// couldn't find any equivalent for pointerCount in Listener so we use two widgets:
|
||||
@@ -133,33 +134,26 @@ class _FilamentGestureDetectorMobileState
|
||||
onScaleStart: (d) async {
|
||||
if (d.pointerCount == 2) {
|
||||
_scaling = true;
|
||||
widget.controller.zoomBegin();
|
||||
await widget.controller.zoomBegin();
|
||||
} else if (!_scaling) {
|
||||
if (_rotateOnPointerMove) {
|
||||
widget.controller
|
||||
.rotateStart(d.focalPoint.dx, d.focalPoint.dy);
|
||||
widget.controller.rotateStart(
|
||||
d.localFocalPoint.dx, d.localFocalPoint.dy);
|
||||
} else {
|
||||
widget.controller
|
||||
.panStart(d.focalPoint.dx, d.focalPoint.dy);
|
||||
}
|
||||
}
|
||||
},
|
||||
onScaleEnd: (d) async {
|
||||
if (d.pointerCount == 2) {
|
||||
widget.controller.zoomEnd();
|
||||
_scaling = false;
|
||||
} else if (!_scaling) {
|
||||
if (_rotateOnPointerMove) {
|
||||
widget.controller.rotateEnd();
|
||||
} else {
|
||||
widget.controller.panEnd();
|
||||
.panStart(d.localFocalPoint.dx, d.localFocalPoint.dy);
|
||||
}
|
||||
}
|
||||
},
|
||||
onScaleUpdate: (ScaleUpdateDetails d) async {
|
||||
if (d.pointerCount == 2) {
|
||||
widget.controller.zoomUpdate(d.localFocalPoint.dx,
|
||||
d.localFocalPoint.dy, d.horizontalScale > 1 ? 0.1 : -0.1);
|
||||
if (d.horizontalScale != _lastScale) {
|
||||
widget.controller.zoomUpdate(
|
||||
d.localFocalPoint.dx,
|
||||
d.localFocalPoint.dy,
|
||||
d.horizontalScale > _lastScale ? 0.1 : -0.1);
|
||||
_lastScale = d.horizontalScale;
|
||||
}
|
||||
} else if (!_scaling) {
|
||||
if (_rotateOnPointerMove) {
|
||||
widget.controller
|
||||
@@ -170,6 +164,18 @@ class _FilamentGestureDetectorMobileState
|
||||
}
|
||||
}
|
||||
},
|
||||
onScaleEnd: (d) async {
|
||||
if (d.pointerCount == 2) {
|
||||
widget.controller.zoomEnd();
|
||||
} else if (!_scaling) {
|
||||
if (_rotateOnPointerMove) {
|
||||
widget.controller.rotateEnd();
|
||||
} else {
|
||||
widget.controller.panEnd();
|
||||
}
|
||||
}
|
||||
_scaling = false;
|
||||
},
|
||||
child: widget.child)),
|
||||
widget.showControlOverlay
|
||||
? Align(
|
||||
|
||||
@@ -55,38 +55,31 @@ class FilamentWidget extends StatefulWidget {
|
||||
/// The content to render before the texture widget is available.
|
||||
/// The default is a solid red Container, intentionally chosen to make it clear that there will be at least one frame where the Texture widget is not being rendered.
|
||||
///
|
||||
late final Widget initial;
|
||||
final void Function()? onResize;
|
||||
final Widget? initial;
|
||||
|
||||
FilamentWidget(
|
||||
{Key? key, required this.controller, this.onResize, Widget? initial})
|
||||
: super(key: key) {
|
||||
if (initial != null) {
|
||||
this.initial = initial;
|
||||
} else {
|
||||
this.initial = Container(color: Colors.red);
|
||||
}
|
||||
}
|
||||
const FilamentWidget({Key? key, required this.controller, this.initial})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
_FilamentWidgetState createState() => _FilamentWidgetState();
|
||||
}
|
||||
|
||||
class _FilamentWidgetState extends State<FilamentWidget> {
|
||||
StreamSubscription? _textureIdListener;
|
||||
int? _textureId;
|
||||
bool _resizing = false;
|
||||
TextureDetails? _textureDetails;
|
||||
|
||||
late final AppLifecycleListener _listener;
|
||||
AppLifecycleState? _lastState;
|
||||
|
||||
Timer? _resizeTimer;
|
||||
String? _error;
|
||||
|
||||
int? _width;
|
||||
int? _height;
|
||||
|
||||
void _handleStateChange(AppLifecycleState state) async {
|
||||
switch (state) {
|
||||
case AppLifecycleState.detached:
|
||||
print("Detached");
|
||||
_textureId = null;
|
||||
_textureDetails = null;
|
||||
|
||||
await widget.controller.destroyViewer();
|
||||
await widget.controller.destroyTexture();
|
||||
@@ -94,7 +87,7 @@ class _FilamentWidgetState extends State<FilamentWidget> {
|
||||
case AppLifecycleState.hidden:
|
||||
print("Hidden");
|
||||
if (Platform.isIOS) {
|
||||
_textureId = null;
|
||||
_textureDetails = null;
|
||||
await widget.controller.destroyViewer();
|
||||
await widget.controller.destroyTexture();
|
||||
}
|
||||
@@ -107,12 +100,15 @@ class _FilamentWidgetState extends State<FilamentWidget> {
|
||||
break;
|
||||
case AppLifecycleState.resumed:
|
||||
print("Resumed");
|
||||
if (_textureId == null) {
|
||||
var size = ((context.findRenderObject()) as RenderBox).size;
|
||||
print("Size after resuming : $size");
|
||||
await widget.controller
|
||||
.createViewer(size.width.toInt(), size.height.toInt());
|
||||
print("Created viewer Size after resuming");
|
||||
if (!Platform.isWindows) {
|
||||
if (_textureDetails == null) {
|
||||
var size = ((context.findRenderObject()) as RenderBox).size;
|
||||
print("Size after resuming : $size");
|
||||
_height = size.height.ceil();
|
||||
_width = size.width.ceil();
|
||||
await widget.controller.createViewer(_width!, _height!);
|
||||
print("Created viewer Size after resuming");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -133,17 +129,16 @@ class _FilamentWidgetState extends State<FilamentWidget> {
|
||||
await Future.delayed(Duration(seconds: 2));
|
||||
}
|
||||
var size = ((context.findRenderObject()) as RenderBox).size;
|
||||
|
||||
widget.controller.createViewer(size.width.toInt(), size.height.toInt());
|
||||
});
|
||||
|
||||
_textureIdListener = widget.controller.textureId.listen((int? textureId) {
|
||||
var size = ((context.findRenderObject()) as RenderBox).size;
|
||||
print(
|
||||
"Received new texture ID $textureId at size $size (current textureID $_textureId)");
|
||||
setState(() {
|
||||
_textureId = textureId;
|
||||
});
|
||||
_width = size.width.ceil();
|
||||
_height = size.height.ceil();
|
||||
try {
|
||||
_textureDetails =
|
||||
await widget.controller.createViewer(_width!, _height!);
|
||||
} catch (err) {
|
||||
setState(() {
|
||||
_error = err.toString();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
super.initState();
|
||||
@@ -151,58 +146,69 @@ class _FilamentWidgetState extends State<FilamentWidget> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_textureIdListener?.cancel();
|
||||
_listener.dispose();
|
||||
_resizeTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Timer? _resizeTimer;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(builder: ((context, constraints) {
|
||||
if (_textureId == null) {
|
||||
return widget.initial;
|
||||
}
|
||||
var texture = Texture(
|
||||
key: ObjectKey("texture_$_textureId"),
|
||||
textureId: _textureId!,
|
||||
// if an error was encountered in creating a viewer, display the error message and don't even try to display a Texture widget.
|
||||
if (_error != null) {
|
||||
return Container(
|
||||
color: Colors.white,
|
||||
child: Column(children: [
|
||||
const Text("A fatal error was encountered"),
|
||||
Text(_error!)
|
||||
]));
|
||||
}
|
||||
|
||||
// if no texture ID is available, display the [initial] widget (solid red by default)
|
||||
late Widget content;
|
||||
|
||||
if (_textureDetails == null ||
|
||||
_textureDetails!.height != _height ||
|
||||
_textureDetails!.width != _width) {
|
||||
content = widget.initial ?? Container(color: Colors.red);
|
||||
} else {
|
||||
content = Texture(
|
||||
key: ObjectKey("texture_${_textureDetails!.textureId}"),
|
||||
textureId: _textureDetails!.textureId,
|
||||
filterQuality: FilterQuality.none,
|
||||
freeze: false,
|
||||
);
|
||||
return SizedBox(
|
||||
height: constraints.maxHeight,
|
||||
width: constraints.maxWidth,
|
||||
child: ResizeObserver(
|
||||
onResized: (Size oldSize, Size newSize) async {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
if (!_resizing) {
|
||||
setState(() {
|
||||
_resizing = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_resizeTimer?.cancel();
|
||||
// see [FilamentControllerFFI.resize] for an explanation of how we deal with resizing
|
||||
return ResizeObserver(
|
||||
onResized: (Size oldSize, Size newSize) async {
|
||||
_resizeTimer?.cancel();
|
||||
|
||||
_resizeTimer = Timer(Duration(milliseconds: 500), () async {
|
||||
await widget.controller
|
||||
.resize(newSize.width.toInt(), newSize.height.toInt());
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
setState(() {
|
||||
_resizing = false;
|
||||
widget.onResize?.call();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
child: _resizing
|
||||
? Container()
|
||||
: Platform.isLinux || Platform.isWindows
|
||||
? Transform(
|
||||
alignment: Alignment.center,
|
||||
transform: Matrix4.rotationX(
|
||||
pi), // TODO - this rotation is due to OpenGL texture coordinate working in a different space from Flutter, can we move this to the C++ side somewhere?
|
||||
child: texture)
|
||||
: texture));
|
||||
}));
|
||||
_resizeTimer = Timer(const Duration(milliseconds: 50), () async {
|
||||
var newWidth = newSize.width.ceil();
|
||||
var newHeight = newSize.height.ceil();
|
||||
try {
|
||||
_textureDetails =
|
||||
await widget.controller.resize(newWidth, newHeight);
|
||||
setState(() {
|
||||
_width = newWidth;
|
||||
_height = newHeight;
|
||||
});
|
||||
} catch (err) {
|
||||
print(err);
|
||||
}
|
||||
});
|
||||
},
|
||||
child: Stack(children: [
|
||||
Positioned.fill(
|
||||
child: Platform.isLinux || Platform.isWindows
|
||||
? Transform(
|
||||
alignment: Alignment.center,
|
||||
transform: Matrix4.rotationX(
|
||||
pi), // TODO - this rotation is due to OpenGL texture coordinate working in a different space from Flutter, can we move this to the C++ side somewhere?
|
||||
child: content)
|
||||
: content)
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,6 +162,7 @@ public class SwiftPolyvoxFilamentPlugin: NSObject, FlutterPlugin, FlutterTexture
|
||||
self.flutterTextureId = nil
|
||||
self.pixelBuffer = nil
|
||||
self.metalTexture = nil
|
||||
result(true)
|
||||
case "resize":
|
||||
let args = call.arguments as! [Any]
|
||||
let width = UInt32(args[0] as! Int64)
|
||||
|
||||
@@ -17,6 +17,7 @@ typedef void (*FilamentRenderCallback)(void* const owner);
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void* const create_filament_viewer_ffi(void* const context, void* const platform, const char* uberArchivePath, const ResourceLoaderWrapper* const loader, void (*renderCallback)(void* const renderCallbackOwner), void* const renderCallbackOwner);
|
||||
FLUTTER_PLUGIN_EXPORT void create_swap_chain_ffi(void* const viewer, void* const surface, uint32_t width, uint32_t height);
|
||||
FLUTTER_PLUGIN_EXPORT void destroy_swap_chain_ffi(void* const viewer);
|
||||
FLUTTER_PLUGIN_EXPORT void create_render_target_ffi(void* const viewer, intptr_t nativeTextureId, uint32_t width, uint32_t height);
|
||||
FLUTTER_PLUGIN_EXPORT void destroy_filament_viewer_ffi(void* const viewer);
|
||||
FLUTTER_PLUGIN_EXPORT void render_ffi(void* const viewer);
|
||||
|
||||
@@ -32,7 +32,7 @@ A new Flutter plugin project.
|
||||
|
||||
s.pod_target_xcconfig = {
|
||||
'DEFINES_MODULE' => 'YES',
|
||||
'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386',
|
||||
'EXCLUDED_ARCHS' => 'x86_64',
|
||||
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17",
|
||||
'OTHER_CXXFLAGS' => '"--std=c++17" "-fmodules" "-fcxx-modules" "-fvisibility=default" "$(inherited)"',
|
||||
'OTHER_CFLAGS' => '"-fvisibility=default" "$(inherited)"',
|
||||
|
||||
@@ -1155,10 +1155,6 @@ namespace polyvox
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Log("Updating grab at %f %f", x, y);
|
||||
|
||||
|
||||
if (_manipulator)
|
||||
{
|
||||
_manipulator->grabUpdate(x, y);
|
||||
|
||||
@@ -72,7 +72,12 @@ public:
|
||||
|
||||
void setRendering(bool rendering)
|
||||
{
|
||||
_rendering = rendering;
|
||||
std::packaged_task<void()> lambda([&]() mutable
|
||||
{
|
||||
this->_rendering = rendering;
|
||||
});
|
||||
auto fut = add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
void doRender()
|
||||
@@ -143,6 +148,17 @@ extern "C"
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void destroy_swap_chain_ffi(void* const viewer)
|
||||
{
|
||||
Log("Destroying swapchain");
|
||||
std::packaged_task<void()> lambda([&]() mutable
|
||||
{
|
||||
destroy_swap_chain(viewer);
|
||||
});
|
||||
auto fut = _rl->add_task(lambda);
|
||||
fut.wait();
|
||||
}
|
||||
|
||||
FLUTTER_PLUGIN_EXPORT void create_render_target_ffi(void* const viewer, intptr_t nativeTextureId, uint32_t width, uint32_t height)
|
||||
{
|
||||
std::packaged_task<void()> lambda([&]() mutable
|
||||
|
||||
@@ -12,6 +12,7 @@ set(PLUGIN_NAME "polyvox_filament_plugin")
|
||||
list(APPEND PLUGIN_SOURCES
|
||||
"polyvox_filament_plugin.cpp"
|
||||
"polyvox_filament_plugin.h"
|
||||
"opengl_texture_buffer.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/../ios/src/AssetManager.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/../ios/src/FilamentViewer.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/../ios/src/PolyvoxFilamentApi.cpp"
|
||||
@@ -69,8 +70,8 @@ if(USE_ANGLE)
|
||||
else()
|
||||
list(APPEND GL_LIBS
|
||||
bluegl
|
||||
bluevk
|
||||
vkshaders
|
||||
# bluevk
|
||||
# vkshaders
|
||||
opengl32
|
||||
)
|
||||
set(ANGLE_OR_OPENGL_DIR opengl)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1a92c10991b982967746b267ba90e0e4e766dc41f2648232fece244c35981981
|
||||
size 54897670
|
||||
oid sha256:3827c9ad4d7849dd1266e317976fbea04a9c754594ece7d604e48b76d07e120b
|
||||
size 20577924
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5afb9555f719e7b7c60313b158b43c0464f122d09ed81e072b2a13db5d67e049
|
||||
size 2066946
|
||||
oid sha256:f032b3bda4d738d935e56dd89b10001480ce0146e0ffd94add2e79d7f76087d5
|
||||
size 1706380
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:13b87d4eefacd3e4136d368697279c0e9c60da4ed2fb8dd5ce3490be1874ca8d
|
||||
size 155917302
|
||||
oid sha256:ce9d22143ce3d4cf6a680fde8a7f6ddfd8edeb18c0a8c38c3843915cdc8060a8
|
||||
size 152979336
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8aec12bdd40d4ac62d0d1ee3d7bd84b5719cf117e68acc0b75cbc85cb335970e
|
||||
size 8271488
|
||||
oid sha256:fb6214dcbfc505987921d21289934fa44d4e42e1e5e24aa243a61688d8f644b5
|
||||
size 3615830
|
||||
|
||||
3
windows/lib/Release/opengl/backend.lib
Normal file
3
windows/lib/Release/opengl/backend.lib
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:db7c2105955776ca6a946dc28b02114e1b670a2272cded6d9ae575f9390cb3d0
|
||||
size 4430582
|
||||
3
windows/lib/Release/opengl/backend.pdb
Normal file
3
windows/lib/Release/opengl/backend.pdb
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bf0190be5237c7cda21fb5b23462a4eca37149eb7d3ec27158a9692a7f4f7fe0
|
||||
size 2797568
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d8594c1993f55db256e67202d987e8940765312f5e90ae827182670b0deb0970
|
||||
size 2237062
|
||||
oid sha256:af44e04c48df8675168d9d4976faa569868f25fc0995a7854947bd64db00f294
|
||||
size 1876560
|
||||
|
||||
3
windows/lib/Release/opengl/bluegl.pdb
Normal file
3
windows/lib/Release/opengl/bluegl.pdb
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a0549e15af4181cfba098f6f2efc168f77373fb3d51c5a0068953ed41280c3e3
|
||||
size 708608
|
||||
3
windows/lib/Release/opengl/filament.lib
Normal file
3
windows/lib/Release/opengl/filament.lib
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:45ede612fb0dfe8a94c21818ab71947e1d3f5ebdfde086b4d5f6bd2520266c3d
|
||||
size 27931534
|
||||
3
windows/lib/Release/opengl/filament.pdb
Normal file
3
windows/lib/Release/opengl/filament.pdb
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:26767b8aaac06dddbef4094cd55bcf4dc0477945a90a56ce1df21fcc6ca4f760
|
||||
size 8581120
|
||||
3
windows/lib/Release/opengl/uberarchive.lib
Normal file
3
windows/lib/Release/opengl/uberarchive.lib
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3c40dbb6b83ba05cfb04426c1f20af992e5a446a9637325bca7b6237d70807af
|
||||
size 61906
|
||||
3
windows/lib/Release/opengl/uberarchive.pdb
Normal file
3
windows/lib/Release/opengl/uberarchive.pdb
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2cecdf0be05ee31b1768a21415d09910127d84a33e7f3379de8f3ff044153846
|
||||
size 69632
|
||||
129
windows/opengl_texture_buffer.cpp
Normal file
129
windows/opengl_texture_buffer.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
#include "opengl_texture_buffer.h"
|
||||
|
||||
#include <flutter/method_channel.h>
|
||||
#include <flutter/plugin_registrar_windows.h>
|
||||
#include <flutter/standard_method_codec.h>
|
||||
#include <flutter/texture_registrar.h>
|
||||
|
||||
#include <thread>
|
||||
|
||||
namespace polyvox_filament {
|
||||
|
||||
void _release_callback(void *releaseContext) {
|
||||
// ((OpenGLTextureBuffer*)releaseContext)->unlock();
|
||||
}
|
||||
|
||||
OpenGLTextureBuffer::OpenGLTextureBuffer(
|
||||
flutter::PluginRegistrarWindows *pluginRegistrar,
|
||||
flutter::TextureRegistrar *textureRegistrar,
|
||||
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result,
|
||||
uint32_t width, uint32_t height, HGLRC context,
|
||||
std::shared_ptr<std::mutex> renderMutex)
|
||||
: _pluginRegistrar(pluginRegistrar), _textureRegistrar(textureRegistrar),
|
||||
_width(width), _height(height), _context(context),
|
||||
_renderMutex(renderMutex) {
|
||||
|
||||
HWND hwnd = _pluginRegistrar->GetView()->GetNativeWindow();
|
||||
|
||||
HDC whdc = GetDC(hwnd);
|
||||
|
||||
if (!_context || !wglMakeCurrent(whdc, _context)) {
|
||||
result->Error("ERROR", "Failed to switch OpenGL context in constructor.");
|
||||
return;
|
||||
}
|
||||
|
||||
glGenTextures(1, &glTextureId);
|
||||
|
||||
if (glTextureId == 0) {
|
||||
result->Error("ERROR", "Failed to generate texture, OpenGL err was %d",
|
||||
glGetError());
|
||||
return;
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, glTextureId);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, _width, _height, 0, GL_RGBA,
|
||||
GL_UNSIGNED_BYTE, 0);
|
||||
|
||||
GLenum err = glGetError();
|
||||
|
||||
if (err != GL_NO_ERROR) {
|
||||
result->Error("ERROR", "Failed to generate texture, GL error was %d", err);
|
||||
return;
|
||||
}
|
||||
wglMakeCurrent(NULL, NULL);
|
||||
|
||||
pixelBuffer = std::make_unique<FlutterDesktopPixelBuffer>();
|
||||
pixelData.reset(new uint8_t[_width * _height * 4]);
|
||||
|
||||
pixelBuffer->buffer = pixelData.get();
|
||||
pixelBuffer->width = size_t(_width);
|
||||
pixelBuffer->height = size_t(_height);
|
||||
pixelBuffer->release_callback = _release_callback;
|
||||
pixelBuffer->release_context = this;
|
||||
|
||||
std::cout << "Created initial pixel data/buffer of size " << _width << "x"
|
||||
<< _height << std::endl;
|
||||
|
||||
texture =
|
||||
std::make_unique<flutter::TextureVariant>(flutter::PixelBufferTexture(
|
||||
[=](size_t width,
|
||||
size_t height) -> const FlutterDesktopPixelBuffer * {
|
||||
if (width != this->_width || height != this->_height) {
|
||||
std::cout << "Front-end widget has been resized, you need to "
|
||||
"teardown/rebuild the swapchain. This pixel buffer "
|
||||
"will be discarded."
|
||||
<< std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
uint8_t *data = (uint8_t *)pixelData.get();
|
||||
|
||||
if (!_context || !wglMakeCurrent(whdc, _context)) {
|
||||
std::cout << "Failed to switch OpenGL context in callback."
|
||||
<< std::endl;
|
||||
} else {
|
||||
// It seems there's at least 1 frame delay between resizing a
|
||||
// front-end widget and the layout operation being performed on
|
||||
// Windows. I haven't found a way to guarantee that we can resize
|
||||
// the OpenGL texture before the pixel buffer callback here. (If
|
||||
// you can find/suggest a way, please let me know). This means we
|
||||
// need to manually check that the requested size matches the
|
||||
// current size of our GL texture, and return an empty pixel
|
||||
// buffer if not.
|
||||
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
|
||||
|
||||
GLenum err = glGetError();
|
||||
|
||||
if (err != GL_NO_ERROR) {
|
||||
if (err == GL_INVALID_OPERATION) {
|
||||
std::cout << "Invalid op" << std::endl;
|
||||
} else if (err == GL_INVALID_VALUE) {
|
||||
std::cout << "Invalid value" << std::endl;
|
||||
} else if (err == GL_OUT_OF_MEMORY) {
|
||||
std::cout << "Out of mem" << std::endl;
|
||||
} else if (err == GL_INVALID_ENUM) {
|
||||
std::cout << "Invalid enum" << std::endl;
|
||||
} else {
|
||||
std::cout << "Unknown error" << std::endl;
|
||||
}
|
||||
}
|
||||
wglMakeCurrent(NULL, NULL);
|
||||
}
|
||||
pixelBuffer->buffer = pixelData.get();
|
||||
return pixelBuffer.get();
|
||||
}));
|
||||
|
||||
flutterTextureId = textureRegistrar->RegisterTexture(texture.get());
|
||||
std::cout << "Registered Flutter texture ID " << flutterTextureId
|
||||
<< std::endl;
|
||||
std::vector<flutter::EncodableValue> resultList;
|
||||
resultList.push_back(flutter::EncodableValue(flutterTextureId));
|
||||
resultList.push_back(flutter::EncodableValue((int64_t) nullptr));
|
||||
resultList.push_back(flutter::EncodableValue(glTextureId));
|
||||
result->Success(resultList);
|
||||
}
|
||||
|
||||
OpenGLTextureBuffer::~OpenGLTextureBuffer() {}
|
||||
|
||||
} // namespace polyvox_filament
|
||||
53
windows/opengl_texture_buffer.h
Normal file
53
windows/opengl_texture_buffer.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef _OPENGL_TEXTURE_BUFFER_H
|
||||
#define _OPENGL_TEXTURE_BUFFER_H
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <flutter/texture_registrar.h>
|
||||
#include <flutter/method_channel.h>
|
||||
#include <flutter/plugin_registrar_windows.h>
|
||||
|
||||
#include "GL/GL.h"
|
||||
#include "GL/GLu.h"
|
||||
#include "GL/wglext.h"
|
||||
|
||||
#include <Windows.h>
|
||||
#include <wrl.h>
|
||||
|
||||
typedef uint32_t GLuint;
|
||||
|
||||
namespace polyvox_filament {
|
||||
|
||||
class OpenGLTextureBuffer {
|
||||
public:
|
||||
|
||||
OpenGLTextureBuffer(
|
||||
flutter::PluginRegistrarWindows* pluginRegistrar,
|
||||
flutter::TextureRegistrar* textureRegistrar,
|
||||
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result,
|
||||
uint32_t width,
|
||||
uint32_t height,
|
||||
HGLRC context,
|
||||
std::shared_ptr<std::mutex> renderMutex);
|
||||
|
||||
~OpenGLTextureBuffer();
|
||||
GLuint glTextureId = 0;
|
||||
int64_t flutterTextureId = 0;
|
||||
std::unique_ptr<FlutterDesktopPixelBuffer> pixelBuffer;
|
||||
std::unique_ptr<uint8_t> pixelData;
|
||||
std::unique_ptr<flutter::TextureVariant> texture;
|
||||
|
||||
private:
|
||||
flutter::PluginRegistrarWindows* _pluginRegistrar;
|
||||
flutter::TextureRegistrar* _textureRegistrar;
|
||||
uint32_t _width = 0;
|
||||
uint32_t _height = 0;
|
||||
HGLRC _context = NULL;
|
||||
bool logged = false;
|
||||
std::shared_ptr<std::mutex> _renderMutex;
|
||||
};
|
||||
|
||||
}
|
||||
#endif // _OPENGL_TEXTURE_BUFFER_H
|
||||
@@ -33,10 +33,6 @@
|
||||
#include "PlatformANGLE.h"
|
||||
#endif
|
||||
|
||||
#include "GL/GL.h"
|
||||
#include "GL/GLu.h"
|
||||
#include "GL/wglext.h"
|
||||
|
||||
#include <Windows.h>
|
||||
#include <wrl.h>
|
||||
|
||||
@@ -83,7 +79,7 @@ ResourceBuffer PolyvoxFilamentPlugin::loadResource(const char *name) {
|
||||
name_str = name_str.substr(8);
|
||||
}
|
||||
|
||||
TCHAR pBuf[256];
|
||||
TCHAR pBuf[512];
|
||||
size_t len = sizeof(pBuf);
|
||||
int bytes = GetModuleFileName(NULL, pBuf, len);
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
|
||||
@@ -145,7 +141,10 @@ void PolyvoxFilamentPlugin::RenderCallback() {
|
||||
_internalD3DTexture2D.Get());
|
||||
_D3D11DeviceContext->Flush();
|
||||
#endif
|
||||
_textureRegistrar->MarkTextureFrameAvailable(_flutterTextureId);
|
||||
std::lock_guard<std::mutex> guard(*(_renderMutex.get()));
|
||||
if (_active) {
|
||||
_textureRegistrar->MarkTextureFrameAvailable(_active->flutterTextureId);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_ANGLE
|
||||
@@ -293,8 +292,9 @@ bool PolyvoxFilamentPlugin::MakeD3DTexture(uint32_t width, uint32_t height,std::
|
||||
}
|
||||
#else
|
||||
bool PolyvoxFilamentPlugin::MakeOpenGLTexture(uint32_t width, uint32_t height,std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
|
||||
|
||||
HWND hwnd = _pluginRegistrar->GetView()
|
||||
->GetNativeWindow();;
|
||||
->GetNativeWindow();
|
||||
|
||||
HDC whdc = GetDC(hwnd);
|
||||
if (whdc == NULL) {
|
||||
@@ -302,145 +302,99 @@ bool PolyvoxFilamentPlugin::MakeOpenGLTexture(uint32_t width, uint32_t height,st
|
||||
return false;
|
||||
}
|
||||
|
||||
PIXELFORMATDESCRIPTOR pfd = {
|
||||
sizeof(PIXELFORMATDESCRIPTOR),
|
||||
1,
|
||||
PFD_DRAW_TO_BITMAP | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, // Flags
|
||||
PFD_TYPE_RGBA, // The kind of framebuffer. RGBA or palette.
|
||||
32, // Colordepth of the framebuffer.
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0, 0, 0, 0,
|
||||
32, // Number of bits for the depthbuffer
|
||||
0, // Number of bits for the stencilbuffer
|
||||
0, // Number of Aux buffers in the framebuffer.
|
||||
PFD_MAIN_PLANE,
|
||||
0,
|
||||
0, 0, 0
|
||||
};
|
||||
if(!_renderMutex.get()) {
|
||||
_renderMutex = std::make_shared<std::mutex>();
|
||||
}
|
||||
|
||||
int pixelFormat = ChoosePixelFormat(whdc, &pfd);
|
||||
SetPixelFormat(whdc, pixelFormat, &pfd);
|
||||
|
||||
// We need a tmp context to retrieve and call wglCreateContextAttribsARB.
|
||||
HGLRC tempContext = wglCreateContext(whdc);
|
||||
if (!wglMakeCurrent(whdc, tempContext)) {
|
||||
result->Error("ERROR", "Failed to acquire temporary context", nullptr);
|
||||
// we need a single context (since this will be passed to the renderer)
|
||||
// if this is the first time we are attempting to create a texture, let's create the context
|
||||
if(_context == NULL) {
|
||||
|
||||
std::cout << "No GL context exists, creating" << std::endl;
|
||||
|
||||
PIXELFORMATDESCRIPTOR pfd = {
|
||||
sizeof(PIXELFORMATDESCRIPTOR),
|
||||
1,
|
||||
PFD_DRAW_TO_BITMAP | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, // Flags
|
||||
PFD_TYPE_RGBA, // The kind of framebuffer. RGBA or palette.
|
||||
32, // Colordepth of the framebuffer.
|
||||
0, 0, 0, 0, 0, 0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0, 0, 0, 0,
|
||||
32, // Number of bits for the depthbuffer
|
||||
0, // Number of bits for the stencilbuffer
|
||||
0, // Number of Aux buffers in the framebuffer.
|
||||
PFD_MAIN_PLANE,
|
||||
0,
|
||||
0, 0, 0
|
||||
};
|
||||
|
||||
int pixelFormat = ChoosePixelFormat(whdc, &pfd);
|
||||
SetPixelFormat(whdc, pixelFormat, &pfd);
|
||||
|
||||
// We need a tmp context to retrieve and call wglCreateContextAttribsARB.
|
||||
HGLRC tempContext = wglCreateContext(whdc);
|
||||
if (!wglMakeCurrent(whdc, tempContext)) {
|
||||
result->Error("ERROR", "Failed to acquire temporary context", nullptr);
|
||||
}
|
||||
|
||||
GLenum err = glGetError();
|
||||
|
||||
if(err != GL_NO_ERROR) {
|
||||
result->Error("ERROR", "GL Error @ 455 %d", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribs = nullptr;
|
||||
|
||||
wglCreateContextAttribs =
|
||||
(PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress(
|
||||
"wglCreateContextAttribsARB");
|
||||
|
||||
if (!wglCreateContextAttribs) {
|
||||
result->Error("ERROR", "Failed to resolve wglCreateContextAttribsARB",
|
||||
nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int minor = 5; minor >= 1; minor--) {
|
||||
std::vector<int> mAttribs = {WGL_CONTEXT_MAJOR_VERSION_ARB, 4,
|
||||
WGL_CONTEXT_MINOR_VERSION_ARB, minor, 0};
|
||||
_context = wglCreateContextAttribs(whdc, nullptr, mAttribs.data());
|
||||
if (_context) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
wglMakeCurrent(NULL, NULL);
|
||||
wglDeleteContext(tempContext);
|
||||
|
||||
if (!_context || !wglMakeCurrent(whdc, _context)) {
|
||||
result->Error("ERROR", "Failed to create OpenGL context.");
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(_active.get()) {
|
||||
result->Error("ERROR", "Texture already exists. You must call destroyTexture before attempting to create a new one.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
GLenum err = glGetError();
|
||||
|
||||
if(err != GL_NO_ERROR) {
|
||||
result->Error("ERROR", "GL Error @ 455 %d", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribs = nullptr;
|
||||
|
||||
wglCreateContextAttribs =
|
||||
(PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress(
|
||||
"wglCreateContextAttribsARB");
|
||||
|
||||
if (!wglCreateContextAttribs) {
|
||||
result->Error("ERROR", "Failed to resolve wglCreateContextAttribsARB",
|
||||
nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int minor = 5; minor >= 1; minor--) {
|
||||
std::vector<int> mAttribs = {WGL_CONTEXT_MAJOR_VERSION_ARB, 4,
|
||||
WGL_CONTEXT_MINOR_VERSION_ARB, minor, 0};
|
||||
_context = wglCreateContextAttribs(whdc, nullptr, mAttribs.data());
|
||||
if (_context) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
wglMakeCurrent(NULL, NULL);
|
||||
wglDeleteContext(tempContext);
|
||||
|
||||
|
||||
if (!_context || !wglMakeCurrent(whdc, _context)) {
|
||||
result->Error("ERROR", "Failed to create OpenGL context.");
|
||||
return false;
|
||||
}
|
||||
|
||||
glGenTextures(1, &_glTextureId);
|
||||
|
||||
if(_glTextureId == 0) {
|
||||
result->Error("ERROR", "Failed to generate texture, OpenGL err was %d", glGetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, _glTextureId);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA,
|
||||
GL_UNSIGNED_BYTE, 0);
|
||||
|
||||
err = glGetError();
|
||||
|
||||
if (err != GL_NO_ERROR) {
|
||||
result->Error("ERROR", "Failed to generate texture, GL error was %d", err);
|
||||
return false;
|
||||
}
|
||||
wglMakeCurrent(NULL, NULL);
|
||||
|
||||
_pixelData.reset(new uint8_t[width * height * 4]);
|
||||
_pixelBuffer = std::make_unique<FlutterDesktopPixelBuffer>();
|
||||
_pixelBuffer->buffer = _pixelData.get();
|
||||
|
||||
_pixelBuffer->width = size_t(width);
|
||||
_pixelBuffer->height = size_t(height);
|
||||
|
||||
_texture = std::make_unique<flutter::TextureVariant>(flutter::PixelBufferTexture(
|
||||
[=](size_t width,
|
||||
size_t height) -> const FlutterDesktopPixelBuffer * {
|
||||
std::lock_guard<std::mutex> guard(_renderMutex);
|
||||
|
||||
if(!_context || !wglMakeCurrent(whdc, _context)) {
|
||||
std::cout << "Failed to switch OpenGL context." << std::endl;
|
||||
} else {
|
||||
uint8_t* data = new uint8_t[width*height*4];
|
||||
glBindTexture(GL_TEXTURE_2D, _glTextureId);
|
||||
glGetTexImage(GL_TEXTURE_2D,0,GL_RGBA,GL_UNSIGNED_BYTE,data);
|
||||
|
||||
GLenum err = glGetError();
|
||||
|
||||
if(err != GL_NO_ERROR) {
|
||||
if(err == GL_INVALID_OPERATION) {
|
||||
std::cout << "Invalid op" << std::endl;
|
||||
} else if(err == GL_INVALID_VALUE) {
|
||||
std::cout << "Invalid value" << std::endl;
|
||||
} else if(err == GL_OUT_OF_MEMORY) {
|
||||
std::cout << "Out of mem" << std::endl;
|
||||
} else if(err == GL_INVALID_ENUM ) {
|
||||
std::cout << "Invalid enum" << std::endl;
|
||||
} else {
|
||||
std::cout << "Unknown error" << std::endl;
|
||||
}
|
||||
}
|
||||
glFinish();
|
||||
_pixelData.reset(data);
|
||||
wglMakeCurrent(NULL, NULL);
|
||||
}
|
||||
_pixelBuffer->buffer = _pixelData.get();
|
||||
|
||||
return _pixelBuffer.get();
|
||||
}));
|
||||
|
||||
_flutterTextureId = _textureRegistrar->RegisterTexture(_texture.get());
|
||||
std::cout << "Registered Flutter texture ID " << _flutterTextureId << std::endl;
|
||||
|
||||
std::vector<flutter::EncodableValue> resultList;
|
||||
resultList.push_back(flutter::EncodableValue(_flutterTextureId));
|
||||
resultList.push_back(flutter::EncodableValue((int64_t)nullptr));
|
||||
resultList.push_back(flutter::EncodableValue(_glTextureId));
|
||||
result->Success(resultList);
|
||||
return true;
|
||||
_active = std::make_unique<OpenGLTextureBuffer>(
|
||||
_pluginRegistrar,
|
||||
_textureRegistrar,
|
||||
std::move(result),
|
||||
width,
|
||||
height,
|
||||
_context,
|
||||
_renderMutex
|
||||
);
|
||||
|
||||
return _active->flutterTextureId != -1;
|
||||
|
||||
}
|
||||
#endif
|
||||
@@ -449,7 +403,7 @@ void PolyvoxFilamentPlugin::CreateTexture(
|
||||
const flutter::MethodCall<flutter::EncodableValue> &methodCall,
|
||||
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
|
||||
|
||||
const auto *args =
|
||||
const auto *args =
|
||||
std::get_if<flutter::EncodableList>(methodCall.arguments());
|
||||
|
||||
const auto width = (uint32_t)round(*(std::get_if<double>(&(args->at(0)))));
|
||||
@@ -462,6 +416,60 @@ void PolyvoxFilamentPlugin::CreateTexture(
|
||||
#endif
|
||||
}
|
||||
|
||||
void PolyvoxFilamentPlugin::DestroyTexture(
|
||||
const flutter::MethodCall<flutter::EncodableValue> &methodCall,
|
||||
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
|
||||
|
||||
const auto *flutterTextureId =
|
||||
std::get_if<int64_t>(methodCall.arguments());
|
||||
|
||||
if(!flutterTextureId) {
|
||||
result->Error("NOT_IMPLEMENTED", "Flutter texture ID must be provided");
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef USE_ANGLE
|
||||
// TODO
|
||||
result->Error("NOT_IMPLEMENTED", "Method is not implemented %s", methodCall.method_name());
|
||||
#else
|
||||
auto sh = std::make_shared<std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>>>(std::move(result));
|
||||
|
||||
if(!_active) {
|
||||
result->Success("Texture has already been detroyed, ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
if(_active->flutterTextureId != *flutterTextureId) {
|
||||
result->Error("TEXTURE_MISMATCH", "Specified texture ID is not active");
|
||||
return;
|
||||
}
|
||||
|
||||
_textureRegistrar->UnregisterTexture(_active->flutterTextureId, [=,
|
||||
sharedResult=std::move(sh)
|
||||
]() {
|
||||
|
||||
if(this->_inactive) {
|
||||
|
||||
HWND hwnd = _pluginRegistrar->GetView()->GetNativeWindow();
|
||||
|
||||
HDC whdc = GetDC(hwnd);
|
||||
|
||||
if (!wglMakeCurrent(whdc, _context)) {
|
||||
std::cout << "Failed to switch OpenGL context in destructor." << std::endl;
|
||||
// result->Error("CONTEXT", "Failed to switch OpenGL context.", nullptr);
|
||||
return;
|
||||
}
|
||||
glDeleteTextures(1, &this->_inactive->glTextureId);
|
||||
wglMakeCurrent(NULL, NULL);
|
||||
}
|
||||
this->_inactive = std::move(this->_active);
|
||||
auto unique = std::move(*(sharedResult.get()));
|
||||
unique->Success(flutter::EncodableValue(true));
|
||||
std::cout << "Destroyed OpenGLTextureBuffer." << std::endl;
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void PolyvoxFilamentPlugin::HandleMethodCall(
|
||||
const flutter::MethodCall<flutter::EncodableValue> &methodCall,
|
||||
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
|
||||
@@ -479,6 +487,8 @@ void PolyvoxFilamentPlugin::HandleMethodCall(
|
||||
result->Success(flutter::EncodableValue((int64_t)resourceLoader));
|
||||
} else if (methodCall.method_name() == "createTexture") {
|
||||
CreateTexture(methodCall, std::move(result));
|
||||
} else if (methodCall.method_name() == "destroyTexture") {
|
||||
DestroyTexture(methodCall, std::move(result));
|
||||
} else if(methodCall.method_name() == "getRenderCallback") {
|
||||
flutter::EncodableList resultList;
|
||||
resultList.push_back(flutter::EncodableValue((int64_t)&render_callback));
|
||||
@@ -490,8 +500,7 @@ void PolyvoxFilamentPlugin::HandleMethodCall(
|
||||
#else
|
||||
result->Success(flutter::EncodableValue((int64_t)nullptr));
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
result->Error("NOT_IMPLEMENTED", "Method is not implemented %s", methodCall.method_name());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#include "GLES2/gl2.h"
|
||||
#include "GLES2/gl2ext.h"
|
||||
#include "PlatformAngle.h"
|
||||
#else
|
||||
#include "opengl_texture_buffer.h"
|
||||
#endif
|
||||
|
||||
#include "PolyvoxFilamentApi.h"
|
||||
@@ -54,15 +56,8 @@ public:
|
||||
|
||||
std::map<uint32_t, ResourceBuffer> _resources;
|
||||
|
||||
std::unique_ptr<flutter::TextureVariant> _texture = nullptr;
|
||||
|
||||
std::unique_ptr<FlutterDesktopPixelBuffer> _pixelBuffer = nullptr;
|
||||
std::unique_ptr<uint8_t> _pixelData = nullptr;
|
||||
|
||||
std::unique_ptr<FlutterDesktopGpuSurfaceDescriptor> _textureDescriptor = nullptr;
|
||||
|
||||
int64_t _flutterTextureId;
|
||||
|
||||
#ifdef USE_ANGLE
|
||||
// Device
|
||||
ID3D11Device* _D3D11Device = nullptr;
|
||||
@@ -75,19 +70,22 @@ public:
|
||||
filament::backend::PlatformANGLE* _platform = nullptr;
|
||||
|
||||
bool MakeD3DTexture(uint32_t width, uint32_t height, std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
|
||||
#else
|
||||
// OpenGL
|
||||
HGLRC _context = NULL;
|
||||
GLuint _glTextureId = 0;
|
||||
std::mutex _renderMutex;
|
||||
#else
|
||||
std::shared_ptr<std::mutex> _renderMutex;
|
||||
std::unique_ptr<OpenGLTextureBuffer> _active = nullptr;
|
||||
std::unique_ptr<OpenGLTextureBuffer> _inactive = nullptr;
|
||||
|
||||
// shared OpenGLContext
|
||||
HGLRC _context = NULL;
|
||||
bool MakeOpenGLTexture(uint32_t width, uint32_t height, std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
|
||||
#endif
|
||||
|
||||
void CreateTexture(
|
||||
const flutter::MethodCall<flutter::EncodableValue> &methodCall,
|
||||
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
|
||||
|
||||
void DestroyTexture(
|
||||
const flutter::MethodCall<flutter::EncodableValue> &methodCall,
|
||||
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
|
||||
void RenderCallback();
|
||||
|
||||
ResourceBuffer loadResource(const char *path);
|
||||
|
||||
Reference in New Issue
Block a user