feat: support multiple ThermionWidget on Android

This commit is contained in:
Nick Fisher
2024-09-30 18:20:05 +08:00
parent e1efd5e4e0
commit 50ed0bdfda
25 changed files with 418 additions and 549 deletions

View File

@@ -2,6 +2,7 @@ import 'dart:ffi';
import 'package:thermion_dart/src/viewer/src/ffi/src/thermion_dart.g.dart'; import 'package:thermion_dart/src/viewer/src/ffi/src/thermion_dart.g.dart';
import 'package:thermion_dart/src/viewer/src/shared_types/camera.dart'; import 'package:thermion_dart/src/viewer/src/shared_types/camera.dart';
import 'package:thermion_dart/src/viewer/src/shared_types/shared_types.dart';
import '../../shared_types/view.dart'; import '../../shared_types/view.dart';
import '../thermion_viewer_ffi.dart'; import '../thermion_viewer_ffi.dart';
@@ -50,7 +51,7 @@ class FFIView extends View {
View_setPostProcessing(view, enabled); View_setPostProcessing(view, enabled);
} }
Future setRenderable(bool renderable) async { Future setRenderable(bool renderable, FFISwapChain swapChain) async {
Viewer_markViewRenderable(viewer, view, renderable); Viewer_setViewRenderable(viewer, swapChain.swapChain, view, renderable);
} }
} }

View File

@@ -46,11 +46,18 @@ external void Viewer_destroyRenderTarget(
); );
@ffi.Native< @ffi.Native<
ffi.Pointer<TSwapChain> Function(ffi.Pointer<TViewer>, ffi.Pointer<TSwapChain> Function(
ffi.Pointer<ffi.Void>, ffi.Uint32, ffi.Uint32)>(isLeaf: true) ffi.Pointer<TViewer>, ffi.Pointer<ffi.Void>)>(isLeaf: true)
external ffi.Pointer<TSwapChain> Viewer_createSwapChain( external ffi.Pointer<TSwapChain> Viewer_createSwapChain(
ffi.Pointer<TViewer> viewer, ffi.Pointer<TViewer> viewer,
ffi.Pointer<ffi.Void> window, ffi.Pointer<ffi.Void> window,
);
@ffi.Native<
ffi.Pointer<TSwapChain> Function(
ffi.Pointer<TViewer>, ffi.Uint32, ffi.Uint32)>(isLeaf: true)
external ffi.Pointer<TSwapChain> Viewer_createHeadlessSwapChain(
ffi.Pointer<TViewer> viewer,
int width, int width,
int height, int height,
); );
@@ -62,28 +69,9 @@ external void Viewer_destroySwapChain(
ffi.Pointer<TSwapChain> swapChain, ffi.Pointer<TSwapChain> swapChain,
); );
@ffi.Native< @ffi.Native<ffi.Void Function(ffi.Pointer<TViewer>)>(isLeaf: true)
ffi.Bool Function( external void Viewer_render(
ffi.Pointer<TViewer>,
ffi.Pointer<TSwapChain>,
ffi.Uint64,
ffi.Pointer<ffi.Void>,
ffi.Pointer<
ffi.NativeFunction<
ffi.Void Function(ffi.Pointer<ffi.Void> buf, ffi.Size size,
ffi.Pointer<ffi.Void> data)>>,
ffi.Pointer<ffi.Void>)>(isLeaf: true)
external bool Viewer_render(
ffi.Pointer<TViewer> viewer, ffi.Pointer<TViewer> viewer,
ffi.Pointer<TSwapChain> swapChain,
int frameTimeInNanos,
ffi.Pointer<ffi.Void> pixelBuffer,
ffi.Pointer<
ffi.NativeFunction<
ffi.Void Function(ffi.Pointer<ffi.Void> buf, ffi.Size size,
ffi.Pointer<ffi.Void> data)>>
callback,
ffi.Pointer<ffi.Void> data,
); );
@ffi.Native< @ffi.Native<
@@ -145,10 +133,11 @@ external ffi.Pointer<TSwapChain> Viewer_getSwapChainAt(
); );
@ffi.Native< @ffi.Native<
ffi.Void Function( ffi.Void Function(ffi.Pointer<TViewer>, ffi.Pointer<TSwapChain>,
ffi.Pointer<TViewer>, ffi.Pointer<TView>, ffi.Bool)>(isLeaf: true) ffi.Pointer<TView>, ffi.Bool)>(isLeaf: true)
external void Viewer_markViewRenderable( external void Viewer_setViewRenderable(
ffi.Pointer<TViewer> viewer, ffi.Pointer<TViewer> viewer,
ffi.Pointer<TSwapChain> swapChain,
ffi.Pointer<TView> view, ffi.Pointer<TView> view,
bool renderable, bool renderable,
); );
@@ -1374,7 +1363,7 @@ external void MaterialInstance_setParameterFloat2(
ffi.Pointer< ffi.Pointer<
ffi.NativeFunction< ffi.NativeFunction<
ffi.Void Function(ffi.Pointer<TViewer> viewer)>>)>(isLeaf: true) ffi.Void Function(ffi.Pointer<TViewer> viewer)>>)>(isLeaf: true)
external void create_filament_viewer_render_thread( external void Viewer_createOnRenderThread(
ffi.Pointer<ffi.Void> context, ffi.Pointer<ffi.Void> context,
ffi.Pointer<ffi.Void> platform, ffi.Pointer<ffi.Void> platform,
ffi.Pointer<ffi.Char> uberArchivePath, ffi.Pointer<ffi.Char> uberArchivePath,
@@ -1393,8 +1382,6 @@ external void create_filament_viewer_render_thread(
ffi.Void Function( ffi.Void Function(
ffi.Pointer<TViewer>, ffi.Pointer<TViewer>,
ffi.Pointer<ffi.Void>, ffi.Pointer<ffi.Void>,
ffi.Uint32,
ffi.Uint32,
ffi.Pointer< ffi.Pointer<
ffi ffi
.NativeFunction<ffi.Void Function(ffi.Pointer<TSwapChain>)>>)>( .NativeFunction<ffi.Void Function(ffi.Pointer<TSwapChain>)>>)>(
@@ -1402,6 +1389,21 @@ external void create_filament_viewer_render_thread(
external void Viewer_createSwapChainRenderThread( external void Viewer_createSwapChainRenderThread(
ffi.Pointer<TViewer> viewer, ffi.Pointer<TViewer> viewer,
ffi.Pointer<ffi.Void> surface, ffi.Pointer<ffi.Void> surface,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<TSwapChain>)>>
onComplete,
);
@ffi.Native<
ffi.Void Function(
ffi.Pointer<TViewer>,
ffi.Uint32,
ffi.Uint32,
ffi.Pointer<
ffi
.NativeFunction<ffi.Void Function(ffi.Pointer<TSwapChain>)>>)>(
isLeaf: true)
external void Viewer_createHeadlessSwapChainRenderThread(
ffi.Pointer<TViewer> viewer,
int width, int width,
int height, int height,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<TSwapChain>)>> ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<TSwapChain>)>>
@@ -1459,11 +1461,10 @@ external void Viewer_captureRenderTargetRenderThread(
); );
@ffi.Native< @ffi.Native<
ffi.Void Function(ffi.Pointer<TViewer>, ffi.Pointer<TSwapChain>, ffi.Void Function(ffi.Pointer<TViewer>,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>>)>(isLeaf: true) ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>>)>(isLeaf: true)
external void Viewer_requestFrameRenderThread( external void Viewer_requestFrameRenderThread(
ffi.Pointer<TViewer> viewer, ffi.Pointer<TViewer> viewer,
ffi.Pointer<TSwapChain> tSwapChain,
ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>> onComplete, ffi.Pointer<ffi.NativeFunction<ffi.Void Function()>> onComplete,
); );

View File

@@ -149,11 +149,24 @@ class ThermionViewerFFI extends ThermionViewer {
} }
} }
Future<SwapChain> createSwapChain(int width, int height, ///
{Pointer<Void>? surface}) async { ///
///
Future<SwapChain> createHeadlessSwapChain(int width, int height) async {
var swapChain = await withPointerCallback<TSwapChain>((callback) {
return Viewer_createHeadlessSwapChainRenderThread(
_viewer!, width, height, callback);
});
return FFISwapChain(swapChain, _viewer!);
}
///
///
///
Future<SwapChain> createSwapChain(int surface) async {
var swapChain = await withPointerCallback<TSwapChain>((callback) { var swapChain = await withPointerCallback<TSwapChain>((callback) {
return Viewer_createSwapChainRenderThread( return Viewer_createSwapChainRenderThread(
_viewer!, surface ?? nullptr, width, height, callback); _viewer!, Pointer<Void>.fromAddress(surface), callback);
}); });
return FFISwapChain(swapChain, _viewer!); return FFISwapChain(swapChain, _viewer!);
} }
@@ -167,7 +180,7 @@ class ThermionViewerFFI extends ThermionViewer {
nullptr; nullptr;
_viewer = await withPointerCallback( _viewer = await withPointerCallback(
(Pointer<NativeFunction<Void Function(Pointer<TViewer>)>> callback) { (Pointer<NativeFunction<Void Function(Pointer<TViewer>)>> callback) {
create_filament_viewer_render_thread( Viewer_createOnRenderThread(
_sharedContext, _sharedContext,
_driver, _driver,
uberarchivePtr, uberarchivePtr,
@@ -2039,10 +2052,8 @@ class ThermionViewerFFI extends ThermionViewer {
completer.complete(true); completer.complete(true);
}); });
final swapChain = Viewer_getSwapChainAt(_viewer!, 0);
Viewer_requestFrameRenderThread( Viewer_requestFrameRenderThread(
_viewer!, swapChain, callback.nativeFunction); _viewer!, callback.nativeFunction);
await completer.future.timeout(Duration(seconds: 1)); await completer.future.timeout(Duration(seconds: 1));
} }

View File

@@ -1,4 +1,5 @@
import 'package:thermion_dart/thermion_dart.dart'; import 'package:thermion_dart/thermion_dart.dart';
import 'swap_chain.dart';
class Viewport { class Viewport {
final int left; final int left;
@@ -17,5 +18,5 @@ abstract class View {
Camera getCamera(); Camera getCamera();
Future setPostProcessing(bool enabled); Future setPostProcessing(bool enabled);
Future setAntiAliasing(bool msaa, bool fxaa, bool taa); Future setAntiAliasing(bool msaa, bool fxaa, bool taa);
Future setRenderable(bool renderable); Future setRenderable(bool renderable, covariant SwapChain swapChain);
} }

View File

@@ -62,7 +62,12 @@ abstract class ThermionViewer {
/// ///
/// ///
/// ///
Future<SwapChain> createSwapChain(int width, int height); Future<SwapChain> createHeadlessSwapChain(int width, int height);
///
///
///
Future<SwapChain> createSwapChain(int handle);
/// ///
/// ///

View File

@@ -1006,12 +1006,6 @@ class ThermionViewerStub extends ThermionViewer {
throw UnimplementedError(); throw UnimplementedError();
} }
@override
Future<SwapChain> createSwapChain(int width, int height) {
// TODO: implement createSwapChain
throw UnimplementedError();
}
@override @override
Future<RenderTarget> createRenderTarget(int width, int height, int textureHandle) { Future<RenderTarget> createRenderTarget(int width, int height, int textureHandle) {
// TODO: implement createRenderTarget // TODO: implement createRenderTarget
@@ -1036,23 +1030,36 @@ class ThermionViewerStub extends ThermionViewer {
throw UnimplementedError(); throw UnimplementedError();
} }
@override
Future render(covariant SwapChain swapChain) {
// TODO: implement render
throw UnimplementedError();
}
@override
Future<Uint8List> capture(covariant SwapChain swapChain, {covariant View? view, covariant RenderTarget? renderTarget}) {
// TODO: implement capture
throw UnimplementedError();
}
@override @override
Future<Gizmo> createGizmo(covariant View view) { Future<Gizmo> createGizmo(covariant View view) {
// TODO: implement createGizmo // TODO: implement createGizmo
throw UnimplementedError(); throw UnimplementedError();
} }
@override
Future<SwapChain> createHeadlessSwapChain(int width, int height) {
// TODO: implement createHeadlessSwapChain
throw UnimplementedError();
}
@override
Future<Uint8List> capture({covariant SwapChain? swapChain, covariant View? view, covariant RenderTarget? renderTarget}) {
// TODO: implement capture
throw UnimplementedError();
}
@override
Future<SwapChain> createSwapChain(handle) {
// TODO: implement createSwapChain
throw UnimplementedError();
}
@override
Future render({covariant SwapChain? swapChain}) {
// TODO: implement render
throw UnimplementedError();
}
} }

View File

@@ -68,12 +68,9 @@ namespace thermion
void removeEntity(EntityId asset); void removeEntity(EntityId asset);
void clearEntities(); void clearEntities();
bool render( void render(
uint64_t frameTimeInNanos, uint64_t frameTimeInNanos
SwapChain* swapChain, );
void *pixelBuffer,
void (*callback)(void *buf, size_t size, void *data),
void *data);
void setFrameInterval(float interval); void setFrameInterval(float interval);
void setMainCamera(View *view); void setMainCamera(View *view);
@@ -83,7 +80,8 @@ namespace thermion
float getCameraFov(bool horizontal); float getCameraFov(bool horizontal);
void setCameraFov(double fovDegrees, bool horizontal); void setCameraFov(double fovDegrees, bool horizontal);
SwapChain* createSwapChain(const void *surface, uint32_t width, uint32_t height); SwapChain* createSwapChain(const void *surface);
SwapChain* createSwapChain(uint32_t width, uint32_t height);
void destroySwapChain(SwapChain* swapChain); void destroySwapChain(SwapChain* swapChain);
RenderTarget* createRenderTarget(intptr_t textureId, uint32_t width, uint32_t height); RenderTarget* createRenderTarget(intptr_t textureId, uint32_t width, uint32_t height);
@@ -91,21 +89,9 @@ namespace thermion
Renderer *getRenderer(); Renderer *getRenderer();
std::vector<View*> _renderable; std::map<SwapChain*, std::vector<View*>> _renderable;
void setRenderable(View* view, bool renderable) {
auto it = std::find(_renderable.begin(), _renderable.end(), view); void setRenderable(View* view, SwapChain* swapChain, bool renderable);
if(renderable) {
if(it == _renderable.end()) {
_renderable.push_back(view);
}
} else {
if(it != _renderable.end()) {
_renderable.erase(it);
}
}
}
void setBackgroundColor(const float r, const float g, const float b, const float a); void setBackgroundColor(const float r, const float g, const float b, const float a);
void setBackgroundImage(const char *resourcePath, bool fillHeight, uint32_t width, uint32_t height); void setBackgroundImage(const char *resourcePath, bool fillHeight, uint32_t width, uint32_t height);

View File

@@ -54,20 +54,16 @@ extern "C"
{ {
#endif #endif
EMSCRIPTEN_KEEPALIVE TViewer *create_filament_viewer(const void *const context, const void *const loader, void *const platform, const char *uberArchivePath); EMSCRIPTEN_KEEPALIVE TViewer *Viewer_create(const void *const context, const void *const loader, void *const platform, const char *uberArchivePath);
EMSCRIPTEN_KEEPALIVE void destroy_filament_viewer(TViewer *viewer); EMSCRIPTEN_KEEPALIVE void destroy_filament_viewer(TViewer *viewer);
EMSCRIPTEN_KEEPALIVE TSceneManager *Viewer_getSceneManager(TViewer *viewer); EMSCRIPTEN_KEEPALIVE TSceneManager *Viewer_getSceneManager(TViewer *viewer);
EMSCRIPTEN_KEEPALIVE TRenderTarget* Viewer_createRenderTarget(TViewer *viewer, intptr_t texture, uint32_t width, uint32_t height); EMSCRIPTEN_KEEPALIVE TRenderTarget* Viewer_createRenderTarget(TViewer *viewer, intptr_t texture, uint32_t width, uint32_t height);
EMSCRIPTEN_KEEPALIVE void Viewer_destroyRenderTarget(TViewer *viewer, TRenderTarget* tRenderTarget); EMSCRIPTEN_KEEPALIVE void Viewer_destroyRenderTarget(TViewer *viewer, TRenderTarget* tRenderTarget);
EMSCRIPTEN_KEEPALIVE TSwapChain *Viewer_createSwapChain(TViewer *viewer, const void *const window, uint32_t width, uint32_t height); EMSCRIPTEN_KEEPALIVE TSwapChain *Viewer_createSwapChain(TViewer *viewer, const void *const window);
EMSCRIPTEN_KEEPALIVE TSwapChain *Viewer_createHeadlessSwapChain(TViewer *viewer, uint32_t width, uint32_t height);
EMSCRIPTEN_KEEPALIVE void Viewer_destroySwapChain(TViewer *viewer, TSwapChain* swapChain); EMSCRIPTEN_KEEPALIVE void Viewer_destroySwapChain(TViewer *viewer, TSwapChain* swapChain);
EMSCRIPTEN_KEEPALIVE bool Viewer_render( EMSCRIPTEN_KEEPALIVE void Viewer_render(
TViewer *viewer, TViewer *viewer);
TSwapChain *swapChain,
uint64_t frameTimeInNanos,
void *pixelBuffer,
void (*callback)(void *buf, size_t size, void *data),
void *data);
EMSCRIPTEN_KEEPALIVE void Viewer_capture( EMSCRIPTEN_KEEPALIVE void Viewer_capture(
TViewer *viewer, TViewer *viewer,
TView *view, TView *view,
@@ -85,7 +81,7 @@ extern "C"
EMSCRIPTEN_KEEPALIVE TView* Viewer_getViewAt(TViewer *viewer, int index); EMSCRIPTEN_KEEPALIVE TView* Viewer_getViewAt(TViewer *viewer, int index);
EMSCRIPTEN_KEEPALIVE void Viewer_setMainCamera(TViewer *tViewer, TView *tView); EMSCRIPTEN_KEEPALIVE void Viewer_setMainCamera(TViewer *tViewer, TView *tView);
EMSCRIPTEN_KEEPALIVE TSwapChain* Viewer_getSwapChainAt(TViewer *tViewer, int index); EMSCRIPTEN_KEEPALIVE TSwapChain* Viewer_getSwapChainAt(TViewer *tViewer, int index);
EMSCRIPTEN_KEEPALIVE void Viewer_markViewRenderable(TViewer *viewer, TView* view, bool renderable); EMSCRIPTEN_KEEPALIVE void Viewer_setViewRenderable(TViewer *viewer, TSwapChain *swapChain, TView* view, bool renderable);
// Engine // Engine
EMSCRIPTEN_KEEPALIVE TEngine *Viewer_getEngine(TViewer* viewer); EMSCRIPTEN_KEEPALIVE TEngine *Viewer_getEngine(TViewer* viewer);

View File

@@ -17,7 +17,7 @@ extern "C"
typedef int32_t EntityId; typedef int32_t EntityId;
typedef void (*FilamentRenderCallback)(void *const owner); typedef void (*FilamentRenderCallback)(void *const owner);
EMSCRIPTEN_KEEPALIVE void create_filament_viewer_render_thread( EMSCRIPTEN_KEEPALIVE void Viewer_createOnRenderThread(
void *const context, void *const context,
void *const platform, void *const platform,
const char *uberArchivePath, const char *uberArchivePath,
@@ -25,12 +25,13 @@ extern "C"
void (*renderCallback)(void *const renderCallbackOwner), void (*renderCallback)(void *const renderCallbackOwner),
void *const renderCallbackOwner, void *const renderCallbackOwner,
void (*callback)(TViewer *viewer)); void (*callback)(TViewer *viewer));
EMSCRIPTEN_KEEPALIVE void Viewer_createSwapChainRenderThread(TViewer *viewer, void *const surface, uint32_t width, uint32_t height, void (*onComplete)(TSwapChain*)); EMSCRIPTEN_KEEPALIVE void Viewer_createSwapChainRenderThread(TViewer *viewer, void *const surface, void (*onComplete)(TSwapChain*));
EMSCRIPTEN_KEEPALIVE void Viewer_createHeadlessSwapChainRenderThread(TViewer *viewer, uint32_t width, uint32_t height, void (*onComplete)(TSwapChain*));
EMSCRIPTEN_KEEPALIVE void Viewer_destroySwapChainRenderThread(TViewer *viewer, TSwapChain* swapChain, void (*onComplete)()); EMSCRIPTEN_KEEPALIVE void Viewer_destroySwapChainRenderThread(TViewer *viewer, TSwapChain* swapChain, void (*onComplete)());
EMSCRIPTEN_KEEPALIVE void Viewer_renderRenderThread(TViewer *viewer, TView* view, TSwapChain* swapChain); EMSCRIPTEN_KEEPALIVE void Viewer_renderRenderThread(TViewer *viewer, TView* view, TSwapChain* swapChain);
EMSCRIPTEN_KEEPALIVE void Viewer_captureRenderThread(TViewer *viewer, TView* view, TSwapChain* swapChain, uint8_t* out, void (*onComplete)()); EMSCRIPTEN_KEEPALIVE void Viewer_captureRenderThread(TViewer *viewer, TView* view, TSwapChain* swapChain, uint8_t* out, void (*onComplete)());
EMSCRIPTEN_KEEPALIVE void Viewer_captureRenderTargetRenderThread(TViewer *viewer, TView* view, TSwapChain* swapChain, TRenderTarget* renderTarget, uint8_t* out, void (*onComplete)()); EMSCRIPTEN_KEEPALIVE void Viewer_captureRenderTargetRenderThread(TViewer *viewer, TView* view, TSwapChain* swapChain, TRenderTarget* renderTarget, uint8_t* out, void (*onComplete)());
EMSCRIPTEN_KEEPALIVE void Viewer_requestFrameRenderThread(TViewer *viewer, TSwapChain* tSwapChain, void(*onComplete)()); EMSCRIPTEN_KEEPALIVE void Viewer_requestFrameRenderThread(TViewer *viewer, void(*onComplete)());
EMSCRIPTEN_KEEPALIVE void destroy_filament_viewer_render_thread(TViewer *viewer); EMSCRIPTEN_KEEPALIVE void destroy_filament_viewer_render_thread(TViewer *viewer);

View File

@@ -127,7 +127,16 @@ namespace thermion
FilamentViewer::FilamentViewer(const void *sharedContext, const ResourceLoaderWrapperImpl *const resourceLoader, void *const platform, const char *uberArchivePath) FilamentViewer::FilamentViewer(const void *sharedContext, const ResourceLoaderWrapperImpl *const resourceLoader, void *const platform, const char *uberArchivePath)
: _resourceLoaderWrapper(resourceLoader) : _resourceLoaderWrapper(resourceLoader)
{ {
_context = (void *)sharedContext; _context = (void *)sharedContext;
if(!_context) {
Log("Creating, no shared context");
} else {
Log("Creating with shared context");
}
ASSERT_POSTCONDITION(_resourceLoaderWrapper != nullptr, "Resource loader must be non-null"); ASSERT_POSTCONDITION(_resourceLoaderWrapper != nullptr, "Resource loader must be non-null");
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
@@ -166,8 +175,6 @@ namespace thermion
createView(); createView();
setRenderable(_views[0], true);
const float aperture = _mainCamera->getAperture(); const float aperture = _mainCamera->getAperture();
const float shutterSpeed = _mainCamera->getShutterSpeed(); const float shutterSpeed = _mainCamera->getShutterSpeed();
const float sens = _mainCamera->getSensitivity(); const float sens = _mainCamera->getSensitivity();
@@ -665,20 +672,19 @@ namespace thermion
Renderer *FilamentViewer::getRenderer() { return _renderer; } Renderer *FilamentViewer::getRenderer() { return _renderer; }
SwapChain *FilamentViewer::createSwapChain(const void *window, uint32_t width, uint32_t height) SwapChain *FilamentViewer::createSwapChain(const void *window)
{
std::lock_guard lock(_renderMutex);
SwapChain *swapChain = _engine->createSwapChain((void *)window, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE | filament::SwapChain::CONFIG_HAS_STENCIL_BUFFER);
_swapChains.push_back(swapChain);
return swapChain;
}
SwapChain *FilamentViewer::createSwapChain(uint32_t width, uint32_t height)
{ {
std::lock_guard lock(_renderMutex); std::lock_guard lock(_renderMutex);
SwapChain *swapChain; SwapChain *swapChain;
if (window) swapChain = _engine->createSwapChain(width, height, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE | filament::SwapChain::CONFIG_HAS_STENCIL_BUFFER);
{
swapChain = _engine->createSwapChain((void *)window, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE);
Log("Created window swapchain.");
}
else
{
Log("Created headless swapchain %dx%d.", width, height);
swapChain = _engine->createSwapChain(width, height, filament::backend::SWAP_CHAIN_CONFIG_TRANSPARENT | filament::backend::SWAP_CHAIN_CONFIG_READABLE | filament::SwapChain::CONFIG_HAS_STENCIL_BUFFER);
}
_swapChains.push_back(swapChain); _swapChains.push_back(swapChain);
return swapChain; return swapChain;
} }
@@ -734,13 +740,13 @@ namespace thermion
void FilamentViewer::destroySwapChain(SwapChain *swapChain) void FilamentViewer::destroySwapChain(SwapChain *swapChain)
{ {
std::lock_guard lock(_renderMutex); std::lock_guard lock(_renderMutex);
_renderable[swapChain].clear();
auto it = std::find(_swapChains.begin(), _swapChains.end(), swapChain); auto it = std::find(_swapChains.begin(), _swapChains.end(), swapChain);
if (it != _swapChains.end()) if (it != _swapChains.end())
{ {
_swapChains.erase(it); _swapChains.erase(it);
} }
_engine->destroy(swapChain); _engine->destroy(swapChain);
Log("Swapchain destroyed.");
#ifdef __EMSCRIPTEN__ #ifdef __EMSCRIPTEN__
_engine->execute(); _engine->execute();
#else #else
@@ -1016,53 +1022,49 @@ namespace thermion
} }
} }
bool FilamentViewer::render( void FilamentViewer::setRenderable(View* view, SwapChain* swapChain, bool renderable) {
uint64_t frameTimeInNanos,
SwapChain *swapChain, std::lock_guard lock(_renderMutex);
void *pixelBuffer,
void (*callback)(void *buf, size_t size, void *data), auto views = _renderable[swapChain];
void *data)
auto it = std::find(views.begin(), views.end(), view);
if(renderable) {
if(it == views.end()) {
views.push_back(view);
}
} else {
if(it != views.end()) {
views.erase(it);
}
}
_renderable[swapChain] = views;
}
void FilamentViewer::render(
uint64_t frameTimeInNanos)
{ {
if (!swapChain)
{
return false;
}
auto now = std::chrono::high_resolution_clock::now();
auto secsSinceLastFpsCheck = float(std::chrono::duration_cast<std::chrono::seconds>(now - _fpsCounterStartTime).count());
if (secsSinceLastFpsCheck >= 1)
{
auto fps = _frameCount / secsSinceLastFpsCheck;
_frameCount = 0;
_skippedFrames = 0;
_fpsCounterStartTime = now;
}
Timer tmr;
_sceneManager->updateTransforms(); _sceneManager->updateTransforms();
_sceneManager->updateAnimations(); _sceneManager->updateAnimations();
_cumulativeAnimationUpdateTime += tmr.elapsed(); for(auto swapChain : _swapChains) {
auto views = _renderable[swapChain];
// Render the scene, unless the renderer wants to skip the frame. if(views.size() > 0) {
bool beginFrame = _renderer->beginFrame(swapChain, frameTimeInNanos); bool beginFrame = _renderer->beginFrame(swapChain, frameTimeInNanos);
if (!beginFrame) if (beginFrame) {
{ for(auto view : views) {
_skippedFrames++; _renderer->render(view);
} else { }
for(auto *view : _renderable) { }
_renderer->render(view);
} }
_frameCount++;
_renderer->endFrame(); _renderer->endFrame();
} }
#ifdef __EMSCRIPTEN__ #ifdef __EMSCRIPTEN__
_engine->execute(); _engine->execute();
#endif #endif
return beginFrame;
} }
class CaptureCallbackHandler : public filament::backend::CallbackHandler class CaptureCallbackHandler : public filament::backend::CallbackHandler

View File

@@ -44,8 +44,9 @@ extern "C"
filament::math::float4{float(d_mat.col4[0]), float(d_mat.col4[1]), float(d_mat.col4[2]), float(d_mat.col4[3])}}; filament::math::float4{float(d_mat.col4[0]), float(d_mat.col4[1]), float(d_mat.col4[2]), float(d_mat.col4[3])}};
} }
EMSCRIPTEN_KEEPALIVE TViewer *create_filament_viewer(const void *context, const void *const loader, void *const platform, const char *uberArchivePath) EMSCRIPTEN_KEEPALIVE TViewer *Viewer_create(const void *context, const void *const loader, void *const platform, const char *uberArchivePath)
{ {
Log("CREATE");
const auto *loaderImpl = new ResourceLoaderWrapperImpl((ResourceLoaderWrapper *)loader); const auto *loaderImpl = new ResourceLoaderWrapperImpl((ResourceLoaderWrapper *)loader);
auto viewer = new FilamentViewer(context, loaderImpl, platform, uberArchivePath); auto viewer = new FilamentViewer(context, loaderImpl, platform, uberArchivePath);
return reinterpret_cast<TViewer *>(viewer); return reinterpret_cast<TViewer *>(viewer);
@@ -339,29 +340,18 @@ extern "C"
cam->setModelMatrix(mat); cam->setModelMatrix(mat);
} }
EMSCRIPTEN_KEEPALIVE bool Viewer_render( EMSCRIPTEN_KEEPALIVE void Viewer_render(
TViewer *tViewer, TViewer *tViewer)
TSwapChain *tSwapChain,
uint64_t frameTimeInNanos,
void *pixelBuffer,
void (*callback)(void *buf, size_t size, void *data),
void *data)
{ {
auto viewer = reinterpret_cast<FilamentViewer *>(tViewer); auto viewer = reinterpret_cast<FilamentViewer *>(tViewer);
auto swapChain = reinterpret_cast<SwapChain *>(tSwapChain); viewer->render(0);
if(!swapChain) {
swapChain = viewer->getSwapChainAt(0);
}
return viewer->render(frameTimeInNanos, swapChain, pixelBuffer, callback, data);
} }
EMSCRIPTEN_KEEPALIVE void Viewer_markViewRenderable(TViewer *tViewer, TView* tView, bool renderable) { EMSCRIPTEN_KEEPALIVE void Viewer_setViewRenderable(TViewer *tViewer, TSwapChain *tSwapChain, TView *tView, bool renderable) {
auto viewer = reinterpret_cast<FilamentViewer *>(tViewer); auto viewer = reinterpret_cast<FilamentViewer *>(tViewer);
auto swapChain = reinterpret_cast<SwapChain*>(tSwapChain);
auto *view = reinterpret_cast<View*>(tView); auto *view = reinterpret_cast<View*>(tView);
viewer->setRenderable(view, renderable); viewer->setRenderable(view, swapChain, renderable);
} }
EMSCRIPTEN_KEEPALIVE void Viewer_capture( EMSCRIPTEN_KEEPALIVE void Viewer_capture(
@@ -416,10 +406,17 @@ extern "C"
viewer->destroySwapChain(swapChain); viewer->destroySwapChain(swapChain);
} }
EMSCRIPTEN_KEEPALIVE TSwapChain *Viewer_createSwapChain(TViewer *tViewer, const void *const window, uint32_t width, uint32_t height) EMSCRIPTEN_KEEPALIVE TSwapChain *Viewer_createHeadlessSwapChain(TViewer *tViewer, uint32_t width, uint32_t height)
{ {
auto viewer = reinterpret_cast<FilamentViewer *>(tViewer); auto viewer = reinterpret_cast<FilamentViewer *>(tViewer);
auto swapChain = viewer->createSwapChain(window, width, height); auto swapChain = viewer->createSwapChain(width, height);
return reinterpret_cast<TSwapChain *>(swapChain);
}
EMSCRIPTEN_KEEPALIVE TSwapChain *Viewer_createSwapChain(TViewer *tViewer, const void *const window)
{
auto viewer = reinterpret_cast<FilamentViewer *>(tViewer);
auto swapChain = viewer->createSwapChain(window);
return reinterpret_cast<TSwapChain *>(swapChain); return reinterpret_cast<TSwapChain *>(swapChain);
} }
@@ -721,7 +718,8 @@ extern "C"
EMSCRIPTEN_KEEPALIVE void SceneManager_queueTransformUpdates(TSceneManager *tSceneManager, EntityId *entities, const double *const transforms, int numEntities) EMSCRIPTEN_KEEPALIVE void SceneManager_queueTransformUpdates(TSceneManager *tSceneManager, EntityId *entities, const double *const transforms, int numEntities)
{ {
auto *sceneManager = reinterpret_cast<SceneManager *>(tSceneManager); auto *sceneManager = reinterpret_cast<SceneManager *>(tSceneManager);
math::mat4 matrices[numEntities]; math::mat4 matrices[
numEntities];
for (int i = 0; i < numEntities; i++) for (int i = 0; i < numEntities; i++)
{ {
matrices[i] = math::mat4( matrices[i] = math::mat4(

View File

@@ -82,10 +82,9 @@ public:
} }
} }
void requestFrame(TSwapChain* tSwapChain, void (*callback)()) void requestFrame(void (*callback)())
{ {
std::unique_lock<std::mutex> lock(_mutex); std::unique_lock<std::mutex> lock(_mutex);
this->swapChain = tSwapChain;
this->_requestFrameRenderCallback = callback; this->_requestFrameRenderCallback = callback;
} }
@@ -93,12 +92,12 @@ public:
{ {
{ {
std::unique_lock<std::mutex> lock(_mutex); std::unique_lock<std::mutex> lock(_mutex);
if (swapChain) if (_requestFrameRenderCallback)
{ {
doRender(swapChain); doRender();
lock.unlock(); lock.unlock();
this->_requestFrameRenderCallback(); this->_requestFrameRenderCallback();
swapChain = nullptr; this->_requestFrameRenderCallback = nullptr;
// Calculate and print FPS // Calculate and print FPS
auto currentTime = std::chrono::high_resolution_clock::now(); auto currentTime = std::chrono::high_resolution_clock::now();
@@ -158,10 +157,13 @@ public:
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
// emscripten_webgl_commit_frame(); // emscripten_webgl_commit_frame();
_viewer = (FilamentViewer *)create_filament_viewer((void *const)_context, loader, platform, uberArchivePath); _viewer = (FilamentViewer *)Viewer_create((void *const)_context, loader, platform, uberArchivePath);
MAIN_THREAD_EM_ASM({ moduleArg.dartFilamentResolveCallback($0, $1); }, callback, _viewer); MAIN_THREAD_EM_ASM({ moduleArg.dartFilamentResolveCallback($0, $1); }, callback, _viewer);
#else #else
auto viewer = (FilamentViewer *)create_filament_viewer(context, loader, platform, uberArchivePath); Log("CREATING1");
auto viewer = (FilamentViewer *)Viewer_create(context, loader, platform, uberArchivePath);
Log("CREATING2");
_viewer = reinterpret_cast<TViewer*>(viewer); _viewer = reinterpret_cast<TViewer*>(viewer);
callback(_viewer); callback(_viewer);
#endif #endif
@@ -180,26 +182,13 @@ public:
fut.wait(); fut.wait();
} }
bool doRender(TSwapChain *tSwapChain) void doRender()
{ {
#ifdef __EMSCRIPTEN__ Viewer_render(_viewer);
if (emscripten_is_webgl_context_lost(_context) == EM_TRUE)
{
Log("Context lost");
auto sleepFor = std::chrono::seconds(1);
std::this_thread::sleep_for(sleepFor);
return;
}
#endif
bool rendered = Viewer_render(_viewer, tSwapChain, 0, nullptr, nullptr, nullptr);
if (_renderCallback) if (_renderCallback)
{ {
_renderCallback(_renderCallbackOwner); _renderCallback(_renderCallbackOwner);
} }
return rendered;
#ifdef __EMSCRIPTEN__
// emscripten_webgl_commit_frame();
#endif
} }
void setFrameIntervalInMilliseconds(float frameIntervalInMilliseconds) void setFrameIntervalInMilliseconds(float frameIntervalInMilliseconds)
@@ -251,7 +240,7 @@ extern "C"
static RenderLoop *_rl; static RenderLoop *_rl;
EMSCRIPTEN_KEEPALIVE void create_filament_viewer_render_thread( EMSCRIPTEN_KEEPALIVE void Viewer_createOnRenderThread(
void *const context, void *const platform, const char *uberArchivePath, void *const context, void *const platform, const char *uberArchivePath,
const void *const loader, const void *const loader,
void (*renderCallback)(void *const renderCallbackOwner), void (*renderCallback)(void *const renderCallbackOwner),
@@ -274,8 +263,7 @@ extern "C"
_rl = nullptr; _rl = nullptr;
} }
EMSCRIPTEN_KEEPALIVE void Viewer_createSwapChainRenderThread(TViewer *viewer, EMSCRIPTEN_KEEPALIVE void Viewer_createHeadlessSwapChainRenderThread(TViewer *viewer,
void *const surface,
uint32_t width, uint32_t width,
uint32_t height, uint32_t height,
void (*onComplete)(TSwapChain*)) void (*onComplete)(TSwapChain*))
@@ -283,12 +271,21 @@ extern "C"
std::packaged_task<void()> lambda( std::packaged_task<void()> lambda(
[=]() mutable [=]() mutable
{ {
auto *swapChain = Viewer_createSwapChain(viewer, surface, width, height); auto *swapChain = Viewer_createHeadlessSwapChain(viewer, width, height);
#ifdef __EMSCRIPTEN__ onComplete(swapChain);
MAIN_THREAD_EM_ASM({ moduleArg.dartFilamentResolveCallback($0); }, onComplete); });
#else auto fut = _rl->add_task(lambda);
}
EMSCRIPTEN_KEEPALIVE void Viewer_createSwapChainRenderThread(TViewer *viewer,
void *const surface,
void (*onComplete)(TSwapChain*))
{
std::packaged_task<void()> lambda(
[=]() mutable
{
auto *swapChain = Viewer_createSwapChain(viewer, surface);
onComplete(swapChain); onComplete(swapChain);
#endif
}); });
auto fut = _rl->add_task(lambda); auto fut = _rl->add_task(lambda);
} }
@@ -309,7 +306,7 @@ extern "C"
} }
EMSCRIPTEN_KEEPALIVE void Viewer_requestFrameRenderThread(TViewer *viewer, TSwapChain* tSwapChain, void(*onComplete)()) EMSCRIPTEN_KEEPALIVE void Viewer_requestFrameRenderThread(TViewer *viewer, void(*onComplete)())
{ {
if (!_rl) if (!_rl)
{ {
@@ -317,7 +314,7 @@ extern "C"
} }
else else
{ {
_rl->requestFrame(tSwapChain, onComplete); _rl->requestFrame(onComplete);
} }
} }
@@ -334,7 +331,7 @@ extern "C"
{ {
std::packaged_task<void()> lambda([=]() mutable std::packaged_task<void()> lambda([=]() mutable
{ {
_rl->doRender(tSwapChain); _rl->doRender();
}); });
auto fut = _rl->add_task(lambda); auto fut = _rl->add_task(lambda);
} }

View File

@@ -15,6 +15,13 @@ extern "C" {
return window; return window;
} }
// int ThermionAndroid_createGLTexture(jobject )
// int ASurfaceTexture_attachToGLContext(
// ASurfaceTexture *st,
// uint32_t texName
// )
ResourceLoaderWrapper* make_resource_loader_wrapper_android(LoadFilamentResourceFromOwner loadFn, FreeFilamentResourceFromOwner freeFn, void* owner) { ResourceLoaderWrapper* make_resource_loader_wrapper_android(LoadFilamentResourceFromOwner loadFn, FreeFilamentResourceFromOwner freeFn, void* owner) {
ResourceLoaderWrapper *rlw = (ResourceLoaderWrapper *)malloc(sizeof(ResourceLoaderWrapper)); ResourceLoaderWrapper *rlw = (ResourceLoaderWrapper *)malloc(sizeof(ResourceLoaderWrapper));
rlw->loadToOut = nullptr; rlw->loadToOut = nullptr;

View File

@@ -25,6 +25,8 @@ import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.view.TextureRegistry.SurfaceTextureEntry import io.flutter.view.TextureRegistry.SurfaceTextureEntry
import java.io.File import java.io.File
import java.util.* import java.util.*
import android.opengl.GLES20;
class LoadFilamentResourceFromOwnerImpl(plugin:ThermionFlutterPlugin) : LoadFilamentResourceFromOwner { class LoadFilamentResourceFromOwnerImpl(plugin:ThermionFlutterPlugin) : LoadFilamentResourceFromOwner {
var plugin = plugin var plugin = plugin
@@ -66,10 +68,18 @@ class ThermionFlutterPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, Lo
private var lifecycle: Lifecycle? = null private var lifecycle: Lifecycle? = null
private lateinit var _lib : FilamentInterop private lateinit var _lib : FilamentInterop
private data class TextureEntry(
val surfaceTextureEntry: SurfaceTextureEntry,
val surfaceTexture: SurfaceTexture,
val surface: Surface
)
var _surfaceTexture: SurfaceTexture? = null var _surfaceTexture: SurfaceTexture? = null
private var _surfaceTextureEntry: SurfaceTextureEntry? = null private var _surfaceTextureEntry: SurfaceTextureEntry? = null
var _surface: Surface? = null var _surface: Surface? = null
private val textures: MutableMap<Long, TextureEntry> = mutableMapOf()
private lateinit var activity:Activity private lateinit var activity:Activity
@@ -153,35 +163,55 @@ class ThermionFlutterPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, Lo
@RequiresApi(Build.VERSION_CODES.M) @RequiresApi(Build.VERSION_CODES.M)
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
Log.e("thermion_flutter", call.method, null)
when (call.method) { when (call.method) {
"createTexture" -> { "createTexture" -> {
if(_surfaceTextureEntry != null) { val args = call.arguments as List<*>
result.error("TEXTURE_EXISTS", "Texture already exist. Make sure you call destroyTexture first", null) val width = args[0] as Int
return val height = args[1] as Int
} if (width < 1 || height < 1) {
val args = call.arguments as List<*> result.error("DIMENSION_MISMATCH", "Both dimensions must be greater than zero (you provided $width x $height)", null)
val width = args[0] as Int return
val height = args[1] as Int }
if(width <1 || height < 1) { Log.i("thermion_flutter", "Creating SurfaceTexture ${width}x${height}")
result.error("DIMENSION_MISMATCH","Both dimensions must be greater than zero (you provided $width x $height)", null);
return; val surfaceTextureEntry = flutterPluginBinding.textureRegistry.createSurfaceTexture()
} val surfaceTexture = surfaceTextureEntry.surfaceTexture()
Log.i("thermion_flutter", "Creating SurfaceTexture ${width}x${height}"); surfaceTexture.setDefaultBufferSize(width, height)
_surfaceTextureEntry = flutterPluginBinding.textureRegistry.createSurfaceTexture()
_surfaceTexture = _surfaceTextureEntry!!.surfaceTexture();
_surfaceTexture!!.setDefaultBufferSize(width, height)
_surface = Surface(_surfaceTexture) val surface = Surface(surfaceTexture)
if(!_surface!!.isValid) { if (!surface.isValid) {
result.error("SURFACE_INVALID", "Failed to create valid surface", null) result.error("SURFACE_INVALID", "Failed to create valid surface", null)
} else { } else {
val nativeWindow = _lib.get_native_window_from_surface(_surface!! as Object, JNIEnv.CURRENT) val flutterTextureId = surfaceTextureEntry.id()
result.success(listOf(_surfaceTextureEntry!!.id(), null, Pointer.nativeValue(nativeWindow))) textures[flutterTextureId] = TextureEntry(surfaceTextureEntry, surfaceTexture, surface)
} val nativeWindow = _lib.get_native_window_from_surface(surface as Object, JNIEnv.CURRENT)
} result.success(listOf(flutterTextureId, flutterTextureId, Pointer.nativeValue(nativeWindow)))
}
}
"destroyTexture" -> {
val args = call.arguments as List<*>
val textureId = (args[0] as Int).toLong()
val textureEntry = textures[textureId]
if (textureEntry != null) {
textureEntry.surface.release()
textureEntry.surfaceTextureEntry.release()
textures.remove(textureId)
result.success(true)
} else {
result.error("TEXTURE_NOT_FOUND", "Texture with id $textureId not found", null)
}
}
"markTextureFrameAvailable" -> {
val textureId = (call.arguments as Int).toLong()
val textureEntry = textures[textureId]
if (textureEntry != null) {
//textureEntry.surfaceTexture.updateTexImage()
result.success(null)
} else {
result.error("TEXTURE_NOT_FOUND", "Texture with id $textureId not found", null)
}
}
"getResourceLoaderWrapper" -> { "getResourceLoaderWrapper" -> {
val resourceLoader = _lib.make_resource_loader_wrapper_android(loadResourceWrapper, freeResourceWrapper, Pointer(0)) val resourceLoader = _lib.make_resource_loader_wrapper_android(loadResourceWrapper, freeResourceWrapper, Pointer(0))
result.success(Pointer.nativeValue(resourceLoader)) result.success(Pointer.nativeValue(resourceLoader))
@@ -196,13 +226,6 @@ class ThermionFlutterPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, Lo
val renderCallbackFnPointer = _lib.make_render_callback_fn_pointer(RenderCallbackImpl(this)) val renderCallbackFnPointer = _lib.make_render_callback_fn_pointer(RenderCallbackImpl(this))
result.success(listOf(Pointer.nativeValue(renderCallbackFnPointer), 0)) result.success(listOf(Pointer.nativeValue(renderCallbackFnPointer), 0))
} }
"destroyTexture" -> {
_surface!!.release();
_surfaceTextureEntry!!.release();
_surface = null
_surfaceTextureEntry = null
result.success(true)
}
else -> { else -> {
result.notImplemented() result.notImplemented()
} }
@@ -210,7 +233,13 @@ class ThermionFlutterPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, Lo
} }
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null) channel.setMethodCallHandler(null)
// Release all textures
for ((_, textureEntry) in textures) {
textureEntry.surface.release()
textureEntry.surfaceTextureEntry.release()
}
textures.clear()
} }

View File

@@ -50,13 +50,8 @@ class _ThermionTextureWidgetState extends State<ThermionTextureWidget> {
var width = (size.width * dpr).ceil(); var width = (size.width * dpr).ceil();
var height = (size.height * dpr).ceil(); var height = (size.height * dpr).ceil();
_texture = _texture = await ThermionFlutterPlatform.instance
await ThermionFlutterPlatform.instance.createTexture(width, height); .createTexture(widget.view, width, height);
_renderTarget = await widget.viewer.createRenderTarget(
_texture!.width, _texture!.height, _texture!.hardwareId);
await widget.view.setRenderTarget(_renderTarget!);
await widget.view.updateViewport(_texture!.width, _texture!.height); await widget.view.updateViewport(_texture!.width, _texture!.height);
var camera = await widget.view.getCamera(); var camera = await widget.view.getCamera();
@@ -74,13 +69,7 @@ class _ThermionTextureWidgetState extends State<ThermionTextureWidget> {
if (mounted) { if (mounted) {
setState(() {}); setState(() {});
} }
if (texture != null) { await texture?.destroy();
_renderTarget = await widget.viewer.createRenderTarget(
texture.width, texture.height, texture.flutterId);
await widget.view.setRenderTarget(null);
await _renderTarget!.destroy();
texture.destroy();
}
_views.clear(); _views.clear();
}); });
}); });
@@ -100,7 +89,7 @@ class _ThermionTextureWidgetState extends State<ThermionTextureWidget> {
WidgetsBinding.instance.scheduleFrameCallback((d) async { WidgetsBinding.instance.scheduleFrameCallback((d) async {
if (widget.viewer.rendering && !_rendering) { if (widget.viewer.rendering && !_rendering) {
_rendering = true; _rendering = true;
if (_callbackId == _primaryCallback) { if (_callbackId == _primaryCallback && _texture != null) {
await widget.viewer.requestFrame(); await widget.viewer.requestFrame();
lastRender = d.inMilliseconds; lastRender = d.inMilliseconds;
} }
@@ -140,8 +129,6 @@ class _ThermionTextureWidgetState extends State<ThermionTextureWidget> {
var newWidth = newSize.width.ceil(); var newWidth = newSize.width.ceil();
var newHeight = newSize.height.ceil(); var newHeight = newSize.height.ceil();
var lastTextureId = _texture?.hardwareId;
await _texture?.resize( await _texture?.resize(
newWidth, newWidth,
newHeight, newHeight,
@@ -149,12 +136,6 @@ class _ThermionTextureWidgetState extends State<ThermionTextureWidget> {
0, 0,
); );
if (_texture?.hardwareId != lastTextureId) {
await _renderTarget?.destroy();
_renderTarget = await widget.viewer.createRenderTarget(
_texture!.width, _texture!.height, _texture!.hardwareId);
await widget.view.setRenderTarget(_renderTarget!);
}
await widget.view.updateViewport(_texture!.width, _texture!.height); await widget.view.updateViewport(_texture!.width, _texture!.height);
var camera = await widget.view.getCamera(); var camera = await widget.view.getCamera();

View File

@@ -0,0 +1,97 @@
import 'package:logging/logging.dart';
import 'package:thermion_dart/thermion_dart.dart';
import 'thermion_flutter_method_channel_interface.dart';
class FlutterPlatformTexture extends MethodChannelFlutterTexture {
final _logger = Logger("ThermionFlutterTexture");
final ThermionViewer viewer;
final View view;
int flutterId = -1;
int _lastFlutterId = -1;
int _lastHardwareId = -1;
int hardwareId = -1;
int width = -1;
int height = -1;
SwapChain? swapChain;
RenderTarget? _renderTarget;
late bool destroySwapChainOnResize;
FlutterPlatformTexture(
super.channel, this.viewer, this.view, this.swapChain) {
if (swapChain == null) {
destroySwapChainOnResize = true;
} else {
destroySwapChainOnResize = false;
}
}
@override
Future<void> resize(
int newWidth, int newHeight, int newLeft, int newTop) async {
if (newWidth == this.width &&
newHeight == this.height &&
newLeft == 0 &&
newTop == 0) {
return;
}
this.width = newWidth;
this.height = newHeight;
var result =
await channel.invokeMethod("createTexture", [width, height, 0, 0]);
if (result == null || (result[0] == -1)) {
throw Exception("Failed to create texture");
}
_lastFlutterId = flutterId;
_lastHardwareId = hardwareId;
flutterId = result[0] as int;
hardwareId = result[1] as int;
if (destroySwapChainOnResize) {
await swapChain?.destroy();
swapChain = await viewer.createSwapChain(result[2]);
}
_logger.info(
"Created new texture: flutter id $flutterId, hardware id $hardwareId");
if (destroySwapChainOnResize) {
await view.setRenderable(true, swapChain!);
} else if (hardwareId != _lastHardwareId) {
await _renderTarget?.destroy();
_renderTarget =
await viewer.createRenderTarget(width, height, hardwareId);
await view.setRenderTarget(_renderTarget!);
await view.setRenderable(true, swapChain!);
await _destroyTexture(_lastFlutterId, _lastHardwareId);
}
}
Future<void> _destroyTexture(int flutterId, int? hardwareId) async {
try {
await channel.invokeMethod("destroyTexture", [flutterId, hardwareId]);
_logger.info(
"Destroyed old texture: flutter id $flutterId, hardware id $hardwareId");
} catch (e) {
_logger.severe("Failed to destroy texture: $e");
}
}
Future destroy() async {
await view.setRenderTarget(null);
await _renderTarget?.destroy();
await swapChain?.destroy();
await channel.invokeMethod("destroyTexture", hardwareId);
}
Future markFrameAvailable() async {
await channel.invokeMethod("markTextureFrameAvailable", this.flutterId);
}
}

View File

@@ -0,0 +1,11 @@
class TextureCacheEntry {
final int flutterId;
final int hardwareId;
final DateTime creationTime;
DateTime? removalTime;
bool inUse;
TextureCacheEntry(this.flutterId, this.hardwareId,
{this.removalTime, this.inUse = true})
: creationTime = DateTime.now();
}

View File

@@ -1,130 +0,0 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:thermion_dart/thermion_dart.dart';
import 'package:thermion_dart/src/viewer/src/ffi/thermion_viewer_ffi.dart';
import 'package:thermion_flutter_ffi/thermion_flutter_method_channel_interface.dart';
import 'package:thermion_flutter_platform_interface/thermion_flutter_platform_interface.dart';
import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart';
import 'package:logging/logging.dart';
///
/// An implementation of [ThermionFlutterPlatform] that uses
/// Flutter platform channels to create a rendering context,
/// resource loaders, and surface/render target(s).
///
class ThermionFlutterAndroid
extends ThermionFlutterMethodChannelInterface {
final _channel = const MethodChannel("dev.thermion.flutter/event");
final _logger = Logger("ThermionFlutterFFI");
ThermionViewerFFI? _viewer;
ThermionFlutterAndroid._() {}
RenderTarget? _renderTarget;
SwapChain? _swapChain;
static void registerWith() {
ThermionFlutterPlatform.instance = ThermionFlutterAndroid._();
}
final _textures = <ThermionFlutterTexture>{};
bool _creatingTexture = false;
bool _destroyingTexture = false;
bool _resizing = false;
///
/// Create a rendering surface.
///
/// This is internal; unless you are [thermion_*] package developer, don't
/// call this yourself.
///
/// The name here is slightly misleading because we only create
/// a texture render target on macOS and iOS; on Android, we render into
/// a native window derived from a Surface, and on Windows we render into
/// a HWND.
///
/// Currently, this only supports a single "texture" (aka rendering surface)
/// at any given time. If a [ThermionWidget] is disposed, it will call
/// [destroyTexture]; if it is resized, it will call [resizeTexture].
///
/// In future, we probably want to be able to create multiple distinct
/// textures/render targets. This would make it possible to have multiple
/// Flutter Texture widgets, each with its own Filament View attached.
/// The current design doesn't accommodate this (for example, it seems we can
/// only create a single native window from a Surface at any one time).
///
Future<ThermionFlutterTexture?> createTexture(int width, int height) async {
throw Exception("TODO");
// note that when [ThermionWidget] is disposed, we don't destroy the
// texture; instead, we keep it around in case a subsequent call requests
// a texture of the same size.
// if (_textures.length > 1) {
// throw Exception("Multiple textures not yet supported");
// } else if (_textures.length == 1) {
// if (_textures.first.height == physicalHeight &&
// _textures.first.width == physicalWidth) {
// return _textures.first;
// } else {
// await _viewer!.setRendering(false);
// await _swapChain?.destroy();
// await destroyTexture(_textures.first);
// _textures.clear();
// }
// }
// _creatingTexture = true;
// var result = await _channel.invokeMethod("createTexture",
// [physicalWidth, physicalHeight, offsetLeft, offsetLeft]);
// if (result == null || (result[0] == -1)) {
// throw Exception("Failed to create texture");
// }
// final flutterTextureId = result[0] as int?;
// final hardwareTextureId = result[1] as int?;
// final surfaceAddress = result[2] as int?;
// _logger.info(
// "Created texture with flutter texture id ${flutterTextureId}, hardwareTextureId $hardwareTextureId and surfaceAddress $surfaceAddress");
// final texture = ThermionFlutterTexture(flutterTextureId, hardwareTextureId,
// physicalWidth, physicalHeight, surfaceAddress);
// await _viewer?.createSwapChain(physicalWidth, physicalHeight,
// surface: texture.surfaceAddress == null
// ? nullptr
// : Pointer<Void>.fromAddress(texture.surfaceAddress!));
// if (texture.hardwareTextureId != null) {
// if (_renderTarget != null) {
// await _renderTarget!.destroy();
// }
// // ignore: unused_local_variable
// _renderTarget = await _viewer?.createRenderTarget(
// physicalWidth, physicalHeight, texture.hardwareTextureId!);
// }
// await _viewer?.updateViewportAndCameraProjection(
// physicalWidth.toDouble(), physicalHeight.toDouble());
// _creatingTexture = false;
// _textures.add(texture);
// return texture;
}
///
/// Called by [ThermionWidget] to resize a texture. Don't call this yourself.
///
@override
Future resizeWindow(
int width,
int height,
int offsetLeft,
int offsetTop,
) async {
throw Exception("Not supported on iOS");
}
}

View File

@@ -1,3 +1,2 @@
export 'thermion_flutter_android.dart';
export 'thermion_flutter_windows.dart'; export 'thermion_flutter_windows.dart';
export 'thermion_flutter_texture_backed_platform.dart'; export 'thermion_flutter_texture_backed_platform.dart';

View File

@@ -14,11 +14,11 @@ import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dar
/// ///
abstract class ThermionFlutterMethodChannelInterface abstract class ThermionFlutterMethodChannelInterface
extends ThermionFlutterPlatform { extends ThermionFlutterPlatform {
final _channel = const MethodChannel("dev.thermion.flutter/event");
final channel = const MethodChannel("dev.thermion.flutter/event");
final _logger = Logger("ThermionFlutterMethodChannelInterface"); final _logger = Logger("ThermionFlutterMethodChannelInterface");
ThermionViewerFFI? viewer; ThermionViewerFFI? viewer;
SwapChain? _swapChain;
Future<ThermionViewer> createViewer({ThermionFlutterOptions? options}) async { Future<ThermionViewer> createViewer({ThermionFlutterOptions? options}) async {
if (viewer != null) { if (viewer != null) {
@@ -27,25 +27,18 @@ abstract class ThermionFlutterMethodChannelInterface
} }
var resourceLoader = Pointer<Void>.fromAddress( var resourceLoader = Pointer<Void>.fromAddress(
await _channel.invokeMethod("getResourceLoaderWrapper")); await channel.invokeMethod("getResourceLoaderWrapper"));
if (resourceLoader == nullptr) { if (resourceLoader == nullptr) {
throw Exception("Failed to get resource loader"); throw Exception("Failed to get resource loader");
} }
// var renderCallbackResult = await _channel.invokeMethod("getRenderCallback"); var driverPlatform = await channel.invokeMethod("getDriverPlatform");
var renderCallback = nullptr;
// Pointer<NativeFunction<Void Function(Pointer<Void>)>>.fromAddress(
// renderCallbackResult[0]);
var renderCallbackOwner = nullptr;
// Pointer<Void>.fromAddress(renderCallbackResult[1]);
var driverPlatform = await _channel.invokeMethod("getDriverPlatform");
var driverPtr = driverPlatform == null var driverPtr = driverPlatform == null
? nullptr ? nullptr
: Pointer<Void>.fromAddress(driverPlatform); : Pointer<Void>.fromAddress(driverPlatform);
var sharedContext = await _channel.invokeMethod("getSharedContext"); var sharedContext = await channel.invokeMethod("getSharedContext");
var sharedContextPtr = sharedContext == null var sharedContextPtr = sharedContext == null
? nullptr ? nullptr
@@ -53,8 +46,6 @@ abstract class ThermionFlutterMethodChannelInterface
viewer = ThermionViewerFFI( viewer = ThermionViewerFFI(
resourceLoader: resourceLoader, resourceLoader: resourceLoader,
renderCallback: renderCallback,
renderCallbackOwner: renderCallbackOwner,
driver: driverPtr, driver: driverPtr,
sharedContext: sharedContextPtr, sharedContext: sharedContextPtr,
uberArchivePath: options?.uberarchivePath); uberArchivePath: options?.uberarchivePath);
@@ -69,9 +60,7 @@ abstract class MethodChannelFlutterTexture extends ThermionFlutterTexture {
MethodChannelFlutterTexture(this.channel); MethodChannelFlutterTexture(this.channel);
Future destroy() async {
await channel.invokeMethod("destroyTexture", hardwareId);
}
@override @override
int get flutterId; int get flutterId;
@@ -85,7 +74,5 @@ abstract class MethodChannelFlutterTexture extends ThermionFlutterTexture {
@override @override
int get width; int get width;
Future markFrameAvailable() async {
await channel.invokeMethod("markTextureFrameAvailable", this.flutterId);
}
} }

View File

@@ -1,18 +1,22 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/services.dart'; import 'dart:io';
import 'package:thermion_dart/thermion_dart.dart'; import 'package:thermion_dart/thermion_dart.dart';
import 'package:thermion_dart/thermion_dart.dart' as t;
import 'package:thermion_flutter_ffi/thermion_flutter_method_channel_interface.dart'; import 'package:thermion_flutter_ffi/thermion_flutter_method_channel_interface.dart';
import 'package:thermion_flutter_platform_interface/thermion_flutter_platform_interface.dart'; import 'package:thermion_flutter_platform_interface/thermion_flutter_platform_interface.dart';
import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart';
import 'platform_texture.dart';
/// ///
/// An implementation of [ThermionFlutterPlatform] that uses /// An implementation of [ThermionFlutterPlatform] that uses
/// Flutter platform channels to create a rendering context, /// Flutter platform channels to create a rendering context,
/// resource loaders, and surface/render target(s). /// resource loaders, and surface/render target(s).
/// ///
class ThermionFlutterTextureBackedPlatform extends ThermionFlutterMethodChannelInterface { class ThermionFlutterTextureBackedPlatform
final _channel = const MethodChannel("dev.thermion.flutter/event"); extends ThermionFlutterMethodChannelInterface {
final _logger = Logger("ThermionFlutterTextureBackedPlatform"); final _logger = Logger("ThermionFlutterTextureBackedPlatform");
static SwapChain? _swapChain; static SwapChain? _swapChain;
@@ -32,16 +36,19 @@ class ThermionFlutterTextureBackedPlatform extends ThermionFlutterMethodChannelI
if (_swapChain != null) { if (_swapChain != null) {
throw Exception("Only a single swapchain can be created"); throw Exception("Only a single swapchain can be created");
} }
// this implementation renders directly into a texture/render target // this implementation renders directly into a texture/render target
// we still need to create a (headless) swapchain, but the actual dimensions // we still need to create a (headless) swapchain, but the actual dimensions
// don't matter // don't matter
_swapChain = await viewer.createSwapChain(1, 1); if (Platform.isMacOS || Platform.isIOS) {
_swapChain = await viewer.createHeadlessSwapChain(1, 1);
}
return viewer; return viewer;
} }
// On desktop platforms, textures are always created // On desktop platforms, textures are always created
Future<ThermionFlutterTexture?> createTexture(int width, int height) async { Future<ThermionFlutterTexture?> createTexture(t.View view, int width, int height) async {
var texture = ThermionFlutterTexture(_channel); var texture = FlutterPlatformTexture(channel, viewer!, view, (Platform.isMacOS || Platform.isIOS)? _swapChain : null);
await texture.resize(width, height, 0, 0); await texture.resize(width, height, 0, 0);
return texture; return texture;
} }
@@ -54,119 +61,3 @@ class ThermionFlutterTextureBackedPlatform extends ThermionFlutterMethodChannelI
} }
} }
class TextureCacheEntry {
final int flutterId;
final int hardwareId;
final DateTime creationTime;
DateTime? removalTime;
bool inUse;
TextureCacheEntry(this.flutterId, this.hardwareId, {this.removalTime, this.inUse = true})
: creationTime = DateTime.now();
}
class ThermionFlutterTexture extends MethodChannelFlutterTexture {
final _logger = Logger("ThermionFlutterTexture");
int flutterId = -1;
int hardwareId = -1;
int width = -1;
int height = -1;
static final Map<String, List<TextureCacheEntry>> _textureCache = {};
ThermionFlutterTexture(super.channel);
@override
Future<void> resize(
int newWidth, int newHeight, int newLeft, int newTop) async {
if (newWidth == this.width &&
newHeight == this.height &&
newLeft == 0 &&
newTop == 0) {
return;
}
this.width = newWidth;
this.height = newHeight;
// Clean up old textures
await _cleanupOldTextures();
final cacheKey = '${width}x$height';
final availableTextures =
_textureCache[cacheKey]?.where((entry) => !entry.inUse) ?? [];
if (availableTextures.isNotEmpty) {
final cachedTexture = availableTextures.first;
flutterId = cachedTexture.flutterId;
hardwareId = cachedTexture.hardwareId;
cachedTexture.inUse = true;
_logger.info(
"Using cached texture: flutter id $flutterId, hardware id $hardwareId");
} else {
var result =
await channel.invokeMethod("createTexture", [width, height, 0, 0]);
if (result == null || (result[0] == -1)) {
throw Exception("Failed to create texture");
}
flutterId = result[0] as int;
hardwareId = result[1] as int;
final newEntry = TextureCacheEntry(flutterId, hardwareId, inUse: true);
_textureCache.putIfAbsent(cacheKey, () => []).add(newEntry);
_logger.info(
"Created new texture: flutter id $flutterId, hardware id $hardwareId");
}
// Mark old texture as not in use
if (this.width != -1 && this.height != -1) {
final oldCacheKey = '${this.width}x${this.height}';
final oldEntry = _textureCache[oldCacheKey]?.firstWhere(
(entry) => entry.flutterId == this.flutterId,
orElse: () => TextureCacheEntry(-1, -1),
);
if (oldEntry != null && oldEntry.flutterId != -1) {
oldEntry.inUse = false;
oldEntry.removalTime = DateTime.now();
}
}
}
Future _cleanupOldTextures() async {
final now = DateTime.now();
final entriesToRemove = <String, List<TextureCacheEntry>>{};
for (var entry in _textureCache.entries) {
final expiredTextures = entry.value.where((texture) {
return !texture.inUse &&
texture.removalTime != null &&
now.difference(texture.removalTime!).inSeconds > 5;
}).toList();
if (expiredTextures.isNotEmpty) {
entriesToRemove[entry.key] = expiredTextures;
}
}
for (var entry in entriesToRemove.entries) {
for (var texture in entry.value) {
await _destroyTexture(texture.flutterId, texture.hardwareId);
_logger.info("Destroying texture: ${texture.flutterId}");
_textureCache[entry.key]?.remove(texture);
}
if (_textureCache[entry.key]?.isEmpty ?? false) {
_textureCache.remove(entry.key);
}
}
}
Future<void> _destroyTexture(int flutterId, int? hardwareId) async {
try {
await channel.invokeMethod("destroyTexture", [flutterId, hardwareId]);
_logger.info("Destroyed old texture: flutter id $flutterId, hardware id $hardwareId");
} catch (e) {
_logger.severe("Failed to destroy texture: $e");
}
}
}

View File

@@ -32,8 +32,9 @@ class ThermionFlutterWindows
/// ///
/// Not supported on Windows. Throws an exception. /// Not supported on Windows. Throws an exception.
/// ///
Future<ThermionFlutterTexture?> createTexture(int width, int height) async { @override
throw Exception("Texture not supported on Windows"); Future<ThermionFlutterTexture?> createTexture(View view, int width, int height) {
throw UnimplementedError();
} }
bool _resizing = false; bool _resizing = false;
@@ -94,4 +95,6 @@ class ThermionFlutterWindows
// _resizing = false; // _resizing = false;
// return newTexture; // return newTexture;
} }
} }

View File

@@ -13,7 +13,7 @@ flutter:
ios: ios:
dartPluginClass: ThermionFlutterTextureBackedPlatform dartPluginClass: ThermionFlutterTextureBackedPlatform
android: android:
dartPluginClass: ThermionFlutterAndroid dartPluginClass: ThermionFlutterTextureBackedPlatform
macos: macos:
dartPluginClass: ThermionFlutterTextureBackedPlatform dartPluginClass: ThermionFlutterTextureBackedPlatform
windows: windows:

View File

@@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:thermion_dart/thermion_dart.dart' as t;
import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'package:thermion_dart/thermion_dart.dart'; import 'package:thermion_dart/thermion_dart.dart';
import 'thermion_flutter_texture.dart'; import 'thermion_flutter_texture.dart';
@@ -36,11 +37,11 @@ abstract class ThermionFlutterPlatform extends PlatformInterface {
/// This is internal; unless you are [thermion_*] package developer, don't /// This is internal; unless you are [thermion_*] package developer, don't
/// call this yourself. May not be supported on all platforms. /// call this yourself. May not be supported on all platforms.
/// ///
Future<ThermionFlutterTexture?> createTexture(int width, int height); Future<ThermionFlutterTexture?> createTexture(
t.View view, int width, int height);
/// ///
/// ///
/// ///
Future resizeWindow( Future resizeWindow(int width, int height, int offsetTop, int offsetRight);
int width, int height, int offsetTop, int offsetRight);
} }

View File

@@ -1,18 +1,5 @@
// class ThermionFlutterTextureImpl {
// final int width;
// final int height;
// final int? flutterTextureId;
// final int? hardwareTextureId;
// final int? surfaceAddress;
// bool get usesBackingWindow => flutterTextureId == null;
// ThermionFlutterTexture(this.flutterTextureId, this.hardwareTextureId,
// this.width, this.height, this.surfaceAddress) {
// }
// }
abstract class ThermionFlutterTexture { abstract class ThermionFlutterTexture {
int get width; int get width;
int get height; int get height;