feat: support multiple ThermionWidget on Android
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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';
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user