diff --git a/android/src/main/kotlin/app/polyvox/filament/PolyvoxFilamentPlugin.kt b/android/src/main/kotlin/app/polyvox/filament/PolyvoxFilamentPlugin.kt index 3b205445..f36e6391 100644 --- a/android/src/main/kotlin/app/polyvox/filament/PolyvoxFilamentPlugin.kt +++ b/android/src/main/kotlin/app/polyvox/filament/PolyvoxFilamentPlugin.kt @@ -155,9 +155,6 @@ class PolyvoxFilamentPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, Lo override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { Log.e("polyvox_filament", call.method, null) when (call.method) { - "getSharedContext" -> { - result.success(null) - } "createTexture" -> { if(_surfaceTextureEntry != null) { result.error("TEXTURE_EXISTS", "Texture already exist. Make sure you call destroyTexture first", null) @@ -184,7 +181,7 @@ class PolyvoxFilamentPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, Lo val nativeWindow = _lib.get_native_window_from_surface(_surface!! as Object, JNIEnv.CURRENT) - val resultList = listOf(_surfaceTextureEntry!!.id(), Pointer.nativeValue(nativeWindow), null ) + val resultList = listOf(_surfaceTextureEntry!!.id(), Pointer.nativeValue(nativeWindow), null, null ) result.success(resultList) } diff --git a/ios/Classes/SwiftPolyvoxFilamentPlugin.swift b/ios/Classes/SwiftPolyvoxFilamentPlugin.swift index ef7e7218..6d4a35c4 100644 --- a/ios/Classes/SwiftPolyvoxFilamentPlugin.swift +++ b/ios/Classes/SwiftPolyvoxFilamentPlugin.swift @@ -166,8 +166,6 @@ public class SwiftPolyvoxFilamentPlugin: NSObject, FlutterPlugin, FlutterTexture public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { let methodName = call.method; switch methodName { - case "getSharedContext": - result(nil) case "getResourceLoaderWrapper": let resourceLoaderWrapper = make_resource_loader(loadResource, freeResource, Unmanaged.passUnretained(self).toOpaque()) result(unsafeBitCast(resourceLoaderWrapper, to:Int64.self)) @@ -180,7 +178,7 @@ public class SwiftPolyvoxFilamentPlugin: NSObject, FlutterPlugin, FlutterTexture createPixelBuffer(width:Int(args[0]), height:Int(args[1])) let pixelBufferPtr = unsafeBitCast(pixelBuffer!, to:UnsafeRawPointer.self) let pixelBufferAddress = Int(bitPattern:pixelBufferPtr); - result([self.flutterTextureId, pixelBufferAddress, nil]) + result([self.flutterTextureId, pixelBufferAddress, nil, nil]) case "destroyTexture": if(self.flutterTextureId != nil) { self.registry.unregisterTexture(self.flutterTextureId!) diff --git a/ios/src/AssetManager.cpp b/ios/src/AssetManager.cpp index 0825fb63..5e3b8825 100644 --- a/ios/src/AssetManager.cpp +++ b/ios/src/AssetManager.cpp @@ -257,15 +257,17 @@ FilamentAsset* AssetManager::getAssetByEntityId(EntityId entityId) { void AssetManager::updateAnimations() { - auto now = high_resolution_clock::now(); RenderableManager &rm = _engine->getRenderableManager(); for (auto& asset : _assets) { + std::vector completed; int index = 0; for(auto& anim : asset.mAnimations) { + + auto now = high_resolution_clock::now(); auto elapsed = float(std::chrono::duration_cast(now - anim.mStart).count()) / 1000.0f; diff --git a/ios/src/FilamentViewer.cpp b/ios/src/FilamentViewer.cpp index 6908f210..876a4b99 100644 --- a/ios/src/FilamentViewer.cpp +++ b/ios/src/FilamentViewer.cpp @@ -991,22 +991,22 @@ namespace polyvox cam.lookAt(eye, target, upward); } - // TODO - this was an experiment but probably useful to keep for debugging - // if pixelBuffer is provided, we will copy the framebuffer into the pixelBuffer. - if (pixelBuffer) - { - auto pbd = Texture::PixelBufferDescriptor( - pixelBuffer, size_t(1024 * 768 * 4), - Texture::Format::RGBA, - Texture::Type::BYTE, nullptr, callback, data); + // // TODO - this was an experiment but probably useful to keep for debugging + // // if pixelBuffer is provided, we will copy the framebuffer into the pixelBuffer. + // if (pixelBuffer) + // { + // auto pbd = Texture::PixelBufferDescriptor( + // pixelBuffer, size_t(1024 * 768 * 4), + // Texture::Format::RGBA, + // Texture::Type::BYTE, nullptr, callback, data); - _renderer->beginFrame(_swapChain, 0); - _renderer->render(_view); - _renderer->readPixels(0, 0, 1024, 768, std::move(pbd)); - _renderer->endFrame(); - } - else - { + // _renderer->beginFrame(_swapChain, 0); + // _renderer->render(_view); + // _renderer->readPixels(0, 0, 1024, 768, std::move(pbd)); + // _renderer->endFrame(); + // } + // else + // { // Render the scene, unless the renderer wants to skip the frame. if (_renderer->beginFrame(_swapChain, frameTimeInNanos)) { @@ -1015,9 +1015,10 @@ namespace polyvox } else { + // std::cout << "Skipped" << std::endl; // skipped frame } - } + // } } void FilamentViewer::updateViewportAndCameraProjection( diff --git a/lib/filament_controller.dart b/lib/filament_controller.dart index b2bd4390..a7d0d557 100644 --- a/lib/filament_controller.dart +++ b/lib/filament_controller.dart @@ -105,13 +105,13 @@ abstract class FilamentController { /// 5) The FilamentWidget will replace the empty Container with a Texture widget /// If you need to wait until a FilamentViewer has been created, listen to the [viewer] stream. /// - Future createViewer(int width, int height); + Future createViewer(Rect rect); /// /// Resize the viewport & backing texture. /// This is called by FilamentWidget; you shouldn't need to invoke this manually. /// - Future resize(int width, int height, {double scaleFactor = 1.0}); + Future resize(Rect rect); /// /// Set the background image to [path] (which should have a file extension .png, .jpg, or .ktx). diff --git a/lib/filament_controller_ffi.dart b/lib/filament_controller_ffi.dart index 0e37ced3..d3cf90d9 100644 --- a/lib/filament_controller_ffi.dart +++ b/lib/filament_controller_ffi.dart @@ -9,16 +9,17 @@ import 'package:polyvox_filament/filament_controller.dart'; import 'package:polyvox_filament/animations/animation_data.dart'; import 'package:polyvox_filament/generated_bindings.dart'; +import 'package:polyvox_filament/rendering_surface.dart'; // ignore: constant_identifier_names const FilamentEntity _FILAMENT_ASSET_ERROR = 0; class FilamentControllerFFI extends FilamentController { - final _channel = const MethodChannel("app.polyvox.filament/event"); + bool _usesBackingWindow = false; @override - bool get requiresTextureWidget => !Platform.isWindows; + bool get requiresTextureWidget => !_usesBackingWindow; double _pixelRatio = 1.0; @@ -48,17 +49,22 @@ class FilamentControllerFFI extends FilamentController { /// Setting up the context/texture (since this is platform-specific) and the render ticker are platform-specific; all other methods are passed through by the platform channel to the methods specified in PolyvoxFilamentApi.h. /// FilamentControllerFFI({this.uberArchivePath}) { + // on some platforms, we ignore the resize event raised by the Flutter RenderObserver + // in favour of a window-level event passed via the method channel. + // (this is because there is no apparent way to exactly synchronize resizing a Flutter widget and resizing a pixel buffer, so we need + // to handle the latter first and rebuild the swapchain appropriately). _channel.setMethodCallHandler((call) async { - if(call.arguments[0] == _resizingWidth && call.arguments[1] == _resizingHeight) { + if (call.arguments[0] == _resizingWidth && + call.arguments[1] == _resizingHeight) { return; } _resizeTimer?.cancel(); _resizingWidth = call.arguments[0]; _resizingHeight = call.arguments[1]; _resizeTimer = Timer(const Duration(milliseconds: 500), () async { - await resize(_resizingWidth!, _resizingHeight!); + await resize(Offset.zero & + ui.Size(_resizingWidth!.toDouble(), _resizingHeight!.toDouble())); }); - }); late DynamicLibrary dl; if (Platform.isIOS || Platform.isMacOS || Platform.isWindows) { @@ -67,6 +73,12 @@ class FilamentControllerFFI extends FilamentController { dl = DynamicLibrary.open("libpolyvox_filament_android.so"); } _lib = NativeLibrary(dl); + if(Platform.isWindows) { + _channel.invokeMethod("usesBackingWindow").then((result) { + _usesBackingWindow = result; + }); + } + } bool _rendering = false; @@ -123,19 +135,21 @@ class FilamentControllerFFI extends FilamentController { @override Future destroyTexture() async { if (textureDetails.value != null) { - await _channel.invokeMethod("destroyTexture", textureDetails.value!.textureId); + await _channel.invokeMethod( + "destroyTexture", textureDetails.value!.textureId); } print("Texture destroyed"); } Pointer _driver = nullptr.cast(); - /// /// Called by `FilamentWidget`. You do not need to call this yourself. /// @override - Future createViewer(int width, int height) async { + Future createViewer(Rect rect) async { + double width = rect.width; + double height = rect.height; if (_viewer != null) { throw Exception( "Viewer already exists, make sure you call destroyViewer first"); @@ -155,18 +169,6 @@ class FilamentControllerFFI extends FilamentController { print("Creating viewer with size $size"); - var textures = - await _channel.invokeMethod("createTexture", [size.width, size.height]); - var flutterTextureId = textures[0]; - - // void* on iOS (pointer to pixel buffer), Android (pointer to native window), null on macOS/Windows - var surfaceAddress = textures[1] as int? ?? 0; - - // null on iOS/Android, void* on MacOS (pointer to metal texture), GLuid on Windows/Linux - var nativeTexture = textures[2] as int? ?? 0; - - print("Using flutterTextureId $flutterTextureId, surface $surfaceAddress and nativeTexture $nativeTexture"); - if (Platform.isWindows && requiresTextureWidget) { _driver = Pointer.fromAddress( await _channel.invokeMethod("getDriverPlatform")); @@ -179,11 +181,10 @@ class FilamentControllerFFI extends FilamentController { var renderCallbackOwner = Pointer.fromAddress(renderCallbackResult[1]); - var sharedContext = await _channel.invokeMethod("getSharedContext"); - print("Got shared context : $sharedContext"); + var renderingSurface = await _createRenderingSurface(rect); _viewer = _lib.create_filament_viewer_ffi( - Pointer.fromAddress(sharedContext ?? 0), + Pointer.fromAddress(renderingSurface.sharedContext ?? 0), _driver, uberArchivePath?.toNativeUtf8().cast() ?? nullptr, loader, @@ -194,50 +195,57 @@ class FilamentControllerFFI extends FilamentController { throw Exception("Failed to create viewer. Check logs for details"); } - _lib.create_swap_chain_ffi( - _viewer!, - Pointer.fromAddress(surfaceAddress), - size.width.toInt(), - size.height.toInt()); - if (nativeTexture != 0) { - assert(surfaceAddress == 0); - print("Creating render target from native texture $nativeTexture"); - _lib.create_render_target_ffi( - _viewer!, nativeTexture, size.width.toInt(), size.height.toInt()); - } - - _lib.update_viewport_and_camera_projection_ffi( - _viewer!, size.width.toInt(), size.height.toInt(), 1.0); - _assetManager = _lib.get_asset_manager(_viewer!); + _lib.create_swap_chain_ffi(_viewer!, renderingSurface.surface, + rect.width.toInt(), rect.height.toInt()); + if (renderingSurface.textureHandle != 0) { + print( + "Creating render target from native texture ${renderingSurface.textureHandle}"); + _lib.create_render_target_ffi(_viewer!, renderingSurface.textureHandle, + rect.width.toInt(), rect.height.toInt()); + } + textureDetails.value = TextureDetails( - textureId: flutterTextureId!, width: width, height: height); + textureId: renderingSurface.flutterTextureId!, + width: rect.width.toInt(), + height: rect.height.toInt()); + + _lib.update_viewport_and_camera_projection_ffi( + _viewer!, rect.width.toInt(), rect.height.toInt(), 1.0); + _hasViewerController.add(true); } + Future _createRenderingSurface(Rect rect) async { + return RenderingSurface.from(await _channel.invokeMethod( + "createTexture", + [rect.width, rect.height, _pixelRatio, rect.left, rect.top])); + } + /// - /// When a FilamentWidget is resized, it will call [resize]. This method will tear down/recreate the swapchain and propagate a new texture ID back to the FilamentWidget. - /// For "once-off" resizes, this is fine. - /// However, this can be problematic for consecutive resizes (e.g. dragging to expand/contract the parent window on desktop, or animating the size of the FilamentWidget itself). + /// When a FilamentWidget is resized, it will call the [resize] method below, which will tear down/recreate the swapchain. + /// For "once-off" resizes, this is fine; however, this can be problematic for consecutive resizes + /// (e.g. dragging to expand/contract the parent window on desktop, or animating the size of the FilamentWidget itself). /// It is too expensive to recreate the swapchain multiple times per second. - /// We therefore add a timer to FilamentWidget so that the call to [resize] is delayed (e.g. 50ms). + /// We therefore add a timer to FilamentWidget so that the call to [resize] is delayed (e.g. 500ms). /// Any subsequent resizes before the delay window elapses will cancel the earlier call. /// - /// The overall process looks like this: + /// The overall process looks like this: /// 1) the window is resized - /// 2) (Windows only) PixelBufferTexture is requested to provide a new pixel buffer with a new size, and we return an empty texture + /// 2) (Windows only) the Flutter engine requests PixelBufferTexture to provide a new pixel buffer with a new size (we return an empty texture, blanking the Texture widget) /// 3) After Xms, [resize] is invoked /// 4) the viewer is instructed to stop rendering (synchronous) /// 5) the existing Filament swapchain is destroyed (synchronous) - /// 6) the Flutter texture is unregistered + /// 6) (where a Texture widget is used), the Flutter texture is unregistered /// a) this is asynchronous, but /// b) *** SEE NOTE BELOW ON WINDOWS *** by passing the method channel result through to the callback, we make this synchronous from the Flutter side, - // c) in this async callback, the glTexture is destroyed - /// 7) a new Flutter/OpenGL texture is created (synchronous) + /// c) in this async callback, the glTexture is destroyed + /// 7) (where a backing window is used), the window is resized + /// 7) (where a Texture widget is used), a new Flutter/OpenGL texture is created (synchronous) /// 8) a new swapchain is created (synchronous) /// 9) if the viewer was rendering prior to the resize, the viewer is instructed to recommence rendering - /// 10) the new texture ID is pushed to the FilamentWidget + /// 10) (where a Texture widget is used) the new texture ID is pushed to the FilamentWidget /// 11) the FilamentWidget updates the Texture widget with the new texture. /// /// #### (Windows-only) ############################################################ @@ -279,60 +287,68 @@ class FilamentControllerFFI extends FilamentController { /// # Given we don't do this on other platforms, I'm OK to stick with the existing solution for the time being. /// ############################################################################ /// - + bool _resizing = false; @override - Future resize(int width, int height, {double scaleFactor = 1.0}) async { + Future resize(Rect rect) async { - if(Platform.isWindows) { - return; + if (_viewer == null) { + throw Exception("Cannot resize without active viewer"); } - // we defer to the FilamentWidget to ensure that every call to [resize] is synchronized - // so this exception should never be thrown (right?) - if (textureDetails.value == null) { + + if (_resizing) { throw Exception("Resize currently underway, ignoring"); } + _resizing = true; + _lib.set_rendering_ffi(_viewer!, false); - if (textureDetails.value != null) { - if (_viewer != null) { - _lib.destroy_swap_chain_ffi(_viewer!); + if(!_usesBackingWindow) { + _lib.destroy_swap_chain_ffi(_viewer!); + } + + if (requiresTextureWidget) { + if(textureDetails.value != null) { + await _channel.invokeMethod( + "destroyTexture", textureDetails.value!.textureId); } - await _channel.invokeMethod("destroyTexture", textureDetails.value!.textureId); - print("Destroyed texture ${textureDetails.value!.textureId}"); + } else if(Platform.isWindows) { + print("Resizing window with rect $rect"); + await _channel.invokeMethod( + "resizeWindow", [rect.width, rect.height, _pixelRatio, rect.left, rect.top]); } - var newSize = ui.Size(width * _pixelRatio, height * _pixelRatio); + var renderingSurface = await _createRenderingSurface(rect); - print("Size after pixel ratio : $width x $height "); - - var textures = await _channel - .invokeMethod("createTexture", [newSize.width, newSize.height]); - - // void* on iOS (pointer to pixel buffer), void* on Android (pointer to native window), null on Windows/macOS - var surfaceAddress = textures[1] as int? ?? 0; - - // null on iOS/Android, void* on MacOS (pointer to metal texture), GLuid on Windows/Linux - var nativeTexture = textures[2] as int? ?? 0; - - _lib.create_swap_chain_ffi( - _viewer!, - Pointer.fromAddress(surfaceAddress), - newSize.width.toInt(), - newSize.height.toInt()); - if (nativeTexture != 0) { - assert(surfaceAddress == 0); - print("Creating render target from native texture $nativeTexture"); - _lib.create_render_target_ffi(_viewer!, nativeTexture, - newSize.width.toInt(), newSize.height.toInt()); + if (_viewer!.address == 0) { + throw Exception("Failed to create viewer. Check logs for details"); } + _assetManager = _lib.get_asset_manager(_viewer!); + + if(!_usesBackingWindow) { + _lib.create_swap_chain_ffi(_viewer!, renderingSurface.surface, + rect.width.toInt(), rect.height.toInt()); + } + + if (renderingSurface.textureHandle != 0) { + print( + "Creating render target from native texture ${renderingSurface.textureHandle}"); + _lib.create_render_target_ffi(_viewer!, renderingSurface.textureHandle, + rect.width.toInt(), rect.height.toInt()); + } + + textureDetails.value = TextureDetails( + textureId: renderingSurface.flutterTextureId!, + width: rect.width.toInt(), + height: rect.height.toInt()); + _lib.update_viewport_and_camera_projection_ffi( - _viewer!, newSize.width.toInt(), newSize.height.toInt(), 1.0); + _viewer!, rect.width.toInt(), rect.height.toInt(), 1.0); await setRendering(_rendering); - textureDetails.value = - TextureDetails(textureId: textures[0]!, width: width, height: height); + + _resizing = false; } @override diff --git a/lib/rendering_surface.dart b/lib/rendering_surface.dart new file mode 100644 index 00000000..ccc138b8 --- /dev/null +++ b/lib/rendering_surface.dart @@ -0,0 +1,37 @@ +import 'dart:ffi'; + +class RenderingSurface { + final int flutterTextureId; + final Pointer surface; + final int textureHandle; + final int sharedContext; + + factory RenderingSurface.from(dynamic platformMessage) { + var flutterTextureId = platformMessage[0]; + + // void* on iOS (pointer to pixel buffer), Android (pointer to native window), null on macOS/Windows + var surfaceAddress = platformMessage[1] as int? ?? 0; + + // null on iOS/Android, void* on MacOS (pointer to metal texture), GLuid on Windows/Linux + var nativeTexture = platformMessage[2] as int? ?? 0; + + if(nativeTexture != 0) { + assert(surfaceAddress == 0); + } + + var sharedContext = platformMessage[3] as int? ?? 0; + + print( + "Using flutterTextureId $flutterTextureId, surface $surfaceAddress nativeTexture $nativeTexture and sharedContext $sharedContext"); + return RenderingSurface( + sharedContext: sharedContext, + flutterTextureId: flutterTextureId, + surface: Pointer.fromAddress(surfaceAddress), + textureHandle: nativeTexture); + } + + RenderingSurface({required this.sharedContext, + required this.flutterTextureId, + required this.surface, + required this.textureHandle}); +} diff --git a/lib/widgets/filament_widget.dart b/lib/widgets/filament_widget.dart index 4073dfc5..6bab7b95 100644 --- a/lib/widgets/filament_widget.dart +++ b/lib/widgets/filament_widget.dart @@ -87,9 +87,6 @@ class _FilamentWidgetState extends State { return ResizeObserver( onResized: (newSize) { - if (!Platform.isWindows) { - return; - } WidgetsBinding.instance.addPostFrameCallback((timeStamp) { setState(() { _width = newSize.width.ceil(); @@ -129,6 +126,13 @@ class _SizedFilamentWidgetState extends State<_SizedFilamentWidget> { late final AppLifecycleListener _appLifecycleListener; + Rect get _rect { + final renderBox =(context.findRenderObject()) as RenderBox; + final size = renderBox.size; + final translation = renderBox.getTransformTo(null).getTranslation(); + return Rect.fromLTWH(translation.x, translation.y, size.width, size.height); + } + @override void initState() { _appLifecycleListener = AppLifecycleListener( @@ -137,7 +141,7 @@ class _SizedFilamentWidgetState extends State<_SizedFilamentWidget> { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { try { - await widget.controller.createViewer(widget.width, widget.height); + await widget.controller.createViewer(_rect); } catch (err) { _error = err.toString(); } @@ -151,6 +155,7 @@ class _SizedFilamentWidgetState extends State<_SizedFilamentWidget> { bool _resizing = false; Future _resize() { + print("Resizing widget"); final completer = Completer(); // resizing the window can be sluggish (particular in debug mode), exacerbated when simultaneously recreating the swapchain and resize the window. // to address this, whenever the widget is resized, we set a timer for Xms in the future. @@ -164,14 +169,13 @@ class _SizedFilamentWidgetState extends State<_SizedFilamentWidget> { if (!mounted) { return; } - var size = ((context.findRenderObject()) as RenderBox).size; - var width = size.width.ceil(); - var height = size.height.ceil(); while (_resizing) { await Future.delayed(const Duration(milliseconds: 20)); } + _resizing = true; - await widget.controller.resize(width, height); + + await widget.controller.resize(_rect); _resizeTimer = null; setState(() {}); _resizing = false; diff --git a/macos/Classes/SwiftPolyvoxFilamentPlugin.swift b/macos/Classes/SwiftPolyvoxFilamentPlugin.swift index 9fd0bd2f..a211a07d 100644 --- a/macos/Classes/SwiftPolyvoxFilamentPlugin.swift +++ b/macos/Classes/SwiftPolyvoxFilamentPlugin.swift @@ -111,8 +111,6 @@ public class SwiftPolyvoxFilamentPlugin: NSObject, FlutterPlugin, FlutterTexture public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { let methodName = call.method; switch methodName { - case "getSharedContext": - result(nil) case "getResourceLoaderWrapper": let resourceLoaderWrapper = make_resource_loader(loadResource, freeResource, Unmanaged.passUnretained(self).toOpaque()) result(unsafeBitCast(resourceLoaderWrapper, to:Int64.self)) @@ -154,7 +152,7 @@ public class SwiftPolyvoxFilamentPlugin: NSObject, FlutterPlugin, FlutterTexture let metalTexturePtr = Unmanaged.passUnretained(metalTexture!).toOpaque() let metalTextureAddress = Int(bitPattern:metalTexturePtr) - result([self.flutterTextureId as Any, nil, metalTextureAddress]) + result([self.flutterTextureId as Any, nil, metalTextureAddress, nil]) case "destroyTexture": if(self.flutterTextureId != nil) { self.registry.unregisterTexture(self.flutterTextureId!) diff --git a/windows/backing_window.cpp b/windows/backing_window.cpp index b7a814c0..afc75be2 100644 --- a/windows/backing_window.cpp +++ b/windows/backing_window.cpp @@ -132,35 +132,11 @@ void SetWindowComposition(HWND window, int32_t accent_state, } -LRESULT NativeViewSubclassProc(HWND window, UINT message, WPARAM wparam, - LPARAM lparam, UINT_PTR subclass_id, - DWORD_PTR ref_data) noexcept { - switch (message) { - case WM_ERASEBKGND: { - // Prevent erasing of |window| when it is unfocused and minimized or - // moved out of screen etc. - return 1; - break; - } - case WM_SIZE: { - // Prevent unnecessary maxmize, minimize or restore messages for |window|. - return 1; - break; - } - default: - break; - } - return ::DefSubclassProc(window, message, wparam, lparam); -} - LRESULT CALLBACK FilamentWindowProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { - // std::cout << "FILAMENT WINDOW EVENT " << message << std::endl; - switch (message) { case WM_MOUSEMOVE: { - std::cout << "FILAMENT MOUSE MOVE" << std::endl; TRACKMOUSEEVENT event; event.cbSize = sizeof(event); event.hwndTrack = window; @@ -168,10 +144,8 @@ LRESULT CALLBACK FilamentWindowProc(HWND const window, UINT const message, event.dwHoverTime = 200; auto user_data = ::GetWindowLongPtr(window, GWLP_USERDATA); if (user_data) { - std::cout << "setting foreground in filamentwindwoproc" << std::endl; HWND flutterRootWindow = reinterpret_cast(user_data); ::SetForegroundWindow(flutterRootWindow); - // NativeViewCore::GetInstance()->SetHitTestBehavior(0); LONG ex_style = ::GetWindowLong(flutterRootWindow, GWL_EXSTYLE); ex_style &= ~(WS_EX_TRANSPARENT | WS_EX_LAYERED); ::SetWindowLong(flutterRootWindow, GWL_EXSTYLE, ex_style); @@ -179,7 +153,6 @@ LRESULT CALLBACK FilamentWindowProc(HWND const window, UINT const message, break; } case WM_ERASEBKGND: { - std::cout << "FILAMENT ERASE BKGND" << std::endl; // Prevent erasing of |window| when it is unfocused and minimized or // moved out of screen etc. break; @@ -189,11 +162,9 @@ LRESULT CALLBACK FilamentWindowProc(HWND const window, UINT const message, case WM_MOVING: case WM_ACTIVATE: case WM_WINDOWPOSCHANGED: { - // std::cout << "FILAMENT POS CHANGED" << std::endl; // NativeViewCore::GetInstance()->SetHitTestBehavior(0); auto user_data = ::GetWindowLongPtr(window, GWLP_USERDATA); if (user_data) { - std::cout << "setting foreground in filamentwindwoproc" << std::endl; HWND flutterRootWindow = reinterpret_cast(user_data); ::SetForegroundWindow(flutterRootWindow); // NativeViewCore::GetInstance()->SetHitTestBehavior(0); @@ -210,7 +181,10 @@ LRESULT CALLBACK FilamentWindowProc(HWND const window, UINT const message, } BackingWindow::BackingWindow(flutter::PluginRegistrarWindows *pluginRegistrar, - int initialWidth, int initialHeight) { + int width, + int height, + int left, + int top) : _width(width), _height(height), _left(left), _top(top) { // a Flutter application actually has two windows - the innner window contains the FlutterView. // although we will use the outer window for various events, we always position things relative to the inner window. _flutterViewWindow = pluginRegistrar->GetView()->GetNativeWindow(); @@ -218,9 +192,6 @@ BackingWindow::BackingWindow(flutter::PluginRegistrarWindows *pluginRegistrar, RECT flutterChildRect; ::GetWindowRect(_flutterViewWindow, &flutterChildRect); - // ::GetClientRect(flutterWindow, &flutterChildRect); - - std::cout << "child rect " << flutterChildRect.left << " " << flutterChildRect.top << " " << flutterChildRect.right << " " << flutterChildRect.bottom << std::endl; // set composition to allow transparency SetWindowComposition(_flutterRootWindow, 6, 0); @@ -230,26 +201,21 @@ BackingWindow::BackingWindow(flutter::PluginRegistrarWindows *pluginRegistrar, UINT message, WPARAM wparam, LPARAM lparam) { - // std::cout << "TOP LEVEL EVENT " << message << std::endl; switch (message) { case WM_MOUSEMOVE: { - // std::cout << "FLUTTER MOUSE MOVE" << std::endl; break; } case WM_ACTIVATE: { - std::cout << "WM_ACTIVATE" << std::endl; RECT rootWindowRect; ::GetWindowRect(_flutterViewWindow, &rootWindowRect); // Position |native_view| such that it's z order is behind |window_| & // redraw aswell. - ::SetWindowPos(_windowHandle, _flutterRootWindow, rootWindowRect.left, - rootWindowRect.top, rootWindowRect.right - rootWindowRect.left, - rootWindowRect.bottom - rootWindowRect.top, SWP_NOACTIVATE); + ::SetWindowPos(_windowHandle, _flutterRootWindow, rootWindowRect.left + _left, + rootWindowRect.top + _top, _width, + _height, SWP_NOACTIVATE); break; } case WM_SIZE: { - std::cout << "WM_SIZE" << std::endl; - // Handle Windows's minimize & maximize animations properly. // Since |SetWindowPos| & other Win32 APIs on |native_view_container_| // do not re-produce the same DWM animations like actual user @@ -289,6 +255,12 @@ BackingWindow::BackingWindow(flutter::PluginRegistrarWindows *pluginRegistrar, if (wparam != SIZE_MINIMIZED) { ::ShowWindow(_windowHandle, SW_SHOWNOACTIVATE); } + + RECT flutterViewRect; + ::GetWindowRect(_flutterViewWindow, &flutterViewRect); + ::SetWindowPos(_windowHandle, _flutterRootWindow, flutterViewRect.left + _left, + flutterViewRect.top + _top, _width, _height, + SWP_NOACTIVATE); }, last_thread_time_) .detach(); @@ -302,12 +274,11 @@ BackingWindow::BackingWindow(flutter::PluginRegistrarWindows *pluginRegistrar, case WM_WINDOWPOSCHANGED: { RECT rootWindowRect; ::GetWindowRect(_flutterViewWindow, &rootWindowRect); - // std::cout << "FLUTTER WINDOWPOSCHANGED TO " << rootWindowRect.left << " " << rootWindowRect.top << " " << rootWindowRect.right << " " << rootWindowRect.bottom << std::endl; if (rootWindowRect.right - rootWindowRect.left > 0 && rootWindowRect.bottom - rootWindowRect.top > 0) { - ::SetWindowPos(_windowHandle, _flutterRootWindow, rootWindowRect.left, - rootWindowRect.top, rootWindowRect.right - rootWindowRect.left, - rootWindowRect.bottom - rootWindowRect.top, SWP_NOACTIVATE); + ::SetWindowPos(_windowHandle, _flutterRootWindow, rootWindowRect.left + _left, + rootWindowRect.top + _top, _width, + _height, SWP_NOACTIVATE); // |window_| is minimized. if (rootWindowRect.left < 0 && rootWindowRect.top < 0 && rootWindowRect.right < 0 && rootWindowRect.bottom < 0) { @@ -343,7 +314,7 @@ BackingWindow::BackingWindow(flutter::PluginRegistrarWindows *pluginRegistrar, window_class.hbrBackground = ::CreateSolidBrush(0); ::RegisterClassExW(&window_class); _windowHandle = ::CreateWindow(kClassName, kWindowName, WS_OVERLAPPEDWINDOW, - 0, 0, initialWidth, initialHeight, nullptr, + 0, 0, _width, _height, nullptr, nullptr, GetModuleHandle(nullptr), nullptr); // Disable DWM animations @@ -352,22 +323,19 @@ BackingWindow::BackingWindow(flutter::PluginRegistrarWindows *pluginRegistrar, &disable_window_transitions, sizeof(disable_window_transitions)); - ::SetWindowSubclass(_windowHandle, NativeViewSubclassProc, 69420, - NULL); // what does this do? - - auto style = ::GetWindowLongPtr(_windowHandle, GWL_STYLE); + auto style = ::GetWindowLong(_windowHandle, GWL_STYLE); style &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_EX_APPWINDOW); - ::SetWindowLongPtr(_windowHandle, GWL_STYLE, style); - - RECT flutterViewRect; - ::GetClientRect(_flutterViewWindow, &flutterViewRect); - + ::SetWindowLong(_windowHandle, GWL_STYLE, style); + ::SetWindowLongPtr(_windowHandle, GWLP_USERDATA, reinterpret_cast(_flutterRootWindow)); - ::SetWindowPos(_windowHandle, _flutterRootWindow, flutterViewRect.left, - flutterViewRect.top, flutterViewRect.right - flutterViewRect.left, flutterViewRect.bottom - flutterViewRect.top, + RECT flutterViewRect; + ::GetWindowRect(_flutterViewWindow, &flutterViewRect); + + ::SetWindowPos(_windowHandle, _flutterRootWindow, flutterViewRect.left + _left, + flutterViewRect.top + _top, _width, _height, SWP_NOACTIVATE); // remove taskbar entry for the window we created @@ -378,8 +346,28 @@ BackingWindow::BackingWindow(flutter::PluginRegistrarWindows *pluginRegistrar, taskbar->Release(); ::ShowWindow(_windowHandle, SW_SHOW); - ::ShowWindow(_flutterRootWindow, SW_SHOW); - ::SetFocus(_flutterRootWindow); + ::ShowWindow(_flutterViewWindow, SW_SHOW); + ::SetForegroundWindow(_flutterViewWindow); + ::SetFocus(_flutterViewWindow); + LONG ex_style = ::GetWindowLong(_flutterRootWindow, GWL_EXSTYLE); + ex_style &= ~(WS_EX_TRANSPARENT | WS_EX_LAYERED); + ::SetWindowLong(_flutterRootWindow, GWL_EXSTYLE, ex_style); +} + +void BackingWindow::Resize(int width, int height, int left, int top) { + _width = width; + _height = height; + _left = left; + _top = top; + RECT flutterViewRect; + ::ShowWindow(_windowHandle, SW_HIDE); + ::GetWindowRect(_flutterViewWindow, &flutterViewRect); + std::cout << "Resizing to " << _width << " x " << _height << " with LT" << _left << " " << _top << " flutter view rect" << flutterViewRect.left << " " << flutterViewRect.top << " " << flutterViewRect.right << " " << flutterViewRect.bottom << std::endl; + + ::SetWindowPos(_windowHandle, _flutterRootWindow, flutterViewRect.left + _left, + flutterViewRect.top + _top, _width, _height, + SWP_NOACTIVATE); + ::ShowWindow(_windowHandle, SW_SHOWNOACTIVATE); } HWND BackingWindow::GetHandle() { return _windowHandle; } diff --git a/windows/backing_window.h b/windows/backing_window.h index 090f0727..d4c6afd5 100644 --- a/windows/backing_window.h +++ b/windows/backing_window.h @@ -11,13 +11,20 @@ class BackingWindow { public: BackingWindow( flutter::PluginRegistrarWindows *pluginRegistrar, - int initialWidth, - int initialHeight); + int width, + int height, + int left, + int top); HWND GetHandle(); + void Resize(int width, int height, int left, int top); private: HWND _windowHandle; HWND _flutterRootWindow; HWND _flutterViewWindow; + uint32_t _width = 0; + uint32_t _height = 0; + uint32_t _left = 0; + uint32_t _top = 0; }; } diff --git a/windows/egl_context.cpp b/windows/egl_context.cpp index e9f2eba7..81764feb 100644 --- a/windows/egl_context.cpp +++ b/windows/egl_context.cpp @@ -137,11 +137,19 @@ EGLContext::EGLContext(flutter::PluginRegistrarWindows* pluginRegistrar, flutter } } -EGLContext::CreateTexture( +EGLContext::CreateRenderingSurface( uint32_t width, uint32_t height, - std::unique_ptr> result) { + std::unique_ptr> result, + uint32_t left, uint32_t top + ) { importGLESExtensionsEntryPoints(); + if(left != 0 || top != 0) { + result->Error("ERROR", + "Rendering with EGL uses a Texture render target/Flutter widget and does not need a window offset."); + return false; + } + if (_active.get()) { result->Error("ERROR", "Texture already exists. You must call destroyTexture before " diff --git a/windows/egl_context.h b/windows/egl_context.h index 4ef550c0..92a76b76 100644 --- a/windows/egl_context.h +++ b/windows/egl_context.h @@ -9,9 +9,6 @@ namespace polyvox_filament { class EGLContext : public FlutterRenderingContext { public: EGLContext(flutter::PluginRegistrarWindows* pluginRegistrar, flutter::TextureRegistrar* textureRegistrar); - void CreateTexture( - uint32_t width, uint32_t height, - std::unique_ptr> result); private: EGLContext _context = NULL; EGLConfig _eglConfig = NULL; diff --git a/windows/flutter_render_context.h b/windows/flutter_render_context.h index b6487bf6..ea1a5356 100644 --- a/windows/flutter_render_context.h +++ b/windows/flutter_render_context.h @@ -12,17 +12,14 @@ namespace polyvox_filament { class FlutterRenderContext { public: + void CreateRenderingSurface(uint32_t width, uint32_t height, std::unique_ptr> result, uint32_t left, uint32_t top ); + void DestroyTexture(std::unique_ptr> result) { if (!_active) { result->Success("Texture has already been detroyed, ignoring"); return; } - // if (_active->flutterTextureId != *flutterTextureId) { - // result->Error("TEXTURE_MISMATCH", "Specified texture ID is not active"); - // return; - // } - auto sh = std::make_shared< std::unique_ptr>>( std::move(result)); diff --git a/windows/polyvox_filament_plugin.cpp b/windows/polyvox_filament_plugin.cpp index 9473fb45..f306a5a8 100644 --- a/windows/polyvox_filament_plugin.cpp +++ b/windows/polyvox_filament_plugin.cpp @@ -167,9 +167,18 @@ void PolyvoxFilamentPlugin::CreateTexture( const auto *args = std::get_if(methodCall.arguments()); - const auto width = (uint32_t)round(*(std::get_if(&(args->at(0))))); - const auto height = (uint32_t)round(*(std::get_if(&(args->at(1))))); + double dWidth = *(std::get_if(&(args->at(0)))); + double dHeight = *(std::get_if(&(args->at(1)))); + double pixelRatio = *(std::get_if(&(args->at(2)))); + double dLeft = *(std::get_if(&(args->at(3)))); + double dTop = *(std::get_if(&(args->at(4)))); + auto width = (uint32_t)round(dWidth * pixelRatio); + auto height = (uint32_t)round(dHeight * pixelRatio); + auto left = (uint32_t)round(dLeft * pixelRatio); + auto top = (uint32_t)round(dTop * pixelRatio); + std::cout << "Using " << width << "x" << height << " (pixel ratio " << pixelRatio << ")" << std::endl; + // create a single shared context for the life of the application // this will be used to create a backing texture and passed to Filament if (!_context) { @@ -179,7 +188,7 @@ void PolyvoxFilamentPlugin::CreateTexture( _context = std::make_unique(_pluginRegistrar, _textureRegistrar); #endif } - _context->CreateTexture(width, height, std::move(result)); + _context->CreateRenderingSurface(width, height, std::move(result), left, top); } void PolyvoxFilamentPlugin::DestroyTexture( @@ -207,7 +216,7 @@ void PolyvoxFilamentPlugin::HandleMethodCall( const flutter::MethodCall &methodCall, std::unique_ptr> result) { - if (methodCall.method_name() == "useBackingWindow") { + if (methodCall.method_name() == "usesBackingWindow") { result->Success(flutter::EncodableValue( #ifdef WGL_USE_BACKING_WINDOW true @@ -215,12 +224,28 @@ void PolyvoxFilamentPlugin::HandleMethodCall( false #endif )); - } else if (methodCall.method_name() == "getSharedContext") { - result->Success(flutter::EncodableValue((int64_t)_context->sharedContext)); } else if (methodCall.method_name() == "getResourceLoaderWrapper") { const ResourceLoaderWrapper *const resourceLoader = new ResourceLoaderWrapper(_loadResource, _freeResource, this); result->Success(flutter::EncodableValue((int64_t)resourceLoader)); + } else if (methodCall.method_name() == "resizeWindow") { + #if WGL_USE_BACKING_WINDOW + const auto *args = + std::get_if(methodCall.arguments()); + double dWidth = *(std::get_if(&(args->at(0)))); + double dHeight = *(std::get_if(&(args->at(1)))); + double pixelRatio = *(std::get_if(&(args->at(2)))); + double dLeft = *(std::get_if(&(args->at(3)))); + double dTop = *(std::get_if(&(args->at(4)))); + auto width = (uint32_t)round(dWidth * pixelRatio); + auto height = (uint32_t)round(dHeight * pixelRatio); + auto left = (uint32_t)round(dLeft * pixelRatio); + auto top = (uint32_t)round(dTop * pixelRatio); + _context->ResizeRenderingSurface(width, height, left, top); + result->Success(); + #else + result->Error("ERROR", "resizeWindow is only available when using a backing window"); + #endif } else if (methodCall.method_name() == "createTexture") { CreateTexture(methodCall, std::move(result)); } else if (methodCall.method_name() == "destroyTexture") { diff --git a/windows/wgl_context.cpp b/windows/wgl_context.cpp index fd3a1ff7..f4e2565c 100644 --- a/windows/wgl_context.cpp +++ b/windows/wgl_context.cpp @@ -96,24 +96,37 @@ WGLContext::WGLContext(flutter::PluginRegistrarWindows *pluginRegistrar, } } -void WGLContext::CreateTexture( +void WGLContext::ResizeRenderingSurface(uint32_t width, uint32_t height, uint32_t left, uint32_t top) { + _backingWindow->Resize(width, height, left, top); +} + +void WGLContext::CreateRenderingSurface( uint32_t width, uint32_t height, - std::unique_ptr> result) { + std::unique_ptr> result, uint32_t left, uint32_t top) { #if WGL_USE_BACKING_WINDOW - _backingWindow = std::make_unique( - _pluginRegistrar, static_cast(width), static_cast(height)); + if(!_backingWindow) { + _backingWindow = std::make_unique( + _pluginRegistrar, static_cast(width), static_cast(height), static_cast(left), static_cast(top)); + } else { + ResizeRenderingSurface(width, height, left, top); + } std::vector resultList; resultList.push_back(flutter::EncodableValue((int64_t) nullptr)); resultList.push_back( flutter::EncodableValue((int64_t)_backingWindow->GetHandle())); resultList.push_back(flutter::EncodableValue((int64_t) nullptr)); + resultList.push_back(flutter::EncodableValue((int64_t)sharedContext)); result->Success(resultList); #else - if (_active.get()) { + if(left != 0 || top != 0) { + result->Error("ERROR", + "When WGL_USE_BACKING_WINDOW is false, rendering with WGL uses a Texture render target/Flutter widget and does not need a window offset."); + } else if (_active.get()) { result->Error("ERROR", "Texture already exists. You must call destroyTexture before " "attempting to create a new one."); + } else { _active = std::make_unique( _pluginRegistrar, _textureRegistrar, std::move(result), width, height, @@ -124,9 +137,10 @@ void WGLContext::CreateTexture( resultList.push_back(flutter::EncodableValue((int64_t) nullptr)); resultList.push_back(flutter::EncodableValue((int64_t) nullptr)); resultList.push_back(flutter::EncodableValue((int64_t) nullptr)); + resultList.push_back(flutter::EncodableValue((int64_t)sharedContext)); result->Success(resultList); } else { - result->Error("FOO", "ERROR", nullptr); + result->Error("NO_FLUTTER_TEXTURE", "Unknown error registering texture with Flutter.", nullptr); } } #endif diff --git a/windows/wgl_context.h b/windows/wgl_context.h index 59450ff9..24dd2972 100644 --- a/windows/wgl_context.h +++ b/windows/wgl_context.h @@ -12,9 +12,15 @@ namespace polyvox_filament { class WGLContext : public FlutterRenderContext { public: WGLContext(flutter::PluginRegistrarWindows* pluginRegistrar, flutter::TextureRegistrar* textureRegistrar); - void CreateTexture(uint32_t width, uint32_t height, std::unique_ptr> result); void* GetSharedContext(); + void CreateRenderingSurface( + uint32_t width, uint32_t height, + std::unique_ptr> result, uint32_t left, uint32_t top); + void ResizeRenderingSurface( + uint32_t width, uint32_t height, uint32_t left, uint32_t top + ); private: + flutter::PluginRegistrarWindows* _pluginRegistrar = nullptr; flutter::TextureRegistrar* _textureRegistrar = nullptr; HGLRC _context = NULL;