rendering correctly with backing window but some issues re pixel density, scroll & foregrounding on start

This commit is contained in:
Nick Fisher
2023-10-25 17:52:37 +11:00
parent 8cea106b30
commit 0928d9d273
13 changed files with 404 additions and 376 deletions

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:polyvox_filament/animations/animation_data.dart'; import 'package:polyvox_filament/animations/animation_data.dart';
@@ -14,7 +15,7 @@ import 'package:path_provider/path_provider.dart';
import 'package:polyvox_filament/widgets/filament_gesture_detector.dart'; import 'package:polyvox_filament/widgets/filament_gesture_detector.dart';
import 'package:polyvox_filament/widgets/filament_widget.dart'; import 'package:polyvox_filament/widgets/filament_widget.dart';
void main() { void main() async {
runApp(const MyApp()); runApp(const MyApp());
} }
@@ -30,8 +31,10 @@ class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
// showPerformanceOverlay: true, // showPerformanceOverlay: true,
color: Colors.white, home: Scaffold(
home: Scaffold(backgroundColor: Colors.white, body: ExampleWidget())); body:
ExampleWidget()
));
} }
} }

View File

@@ -77,7 +77,9 @@ public:
void doRender() { void doRender() {
render(_viewer, 0, nullptr, nullptr, nullptr); render(_viewer, 0, nullptr, nullptr, nullptr);
_renderCallback(_renderCallbackOwner); if(_renderCallback) {
_renderCallback(_renderCallbackOwner);
}
} }
void setFrameIntervalInMilliseconds(float frameIntervalInMilliseconds) { void setFrameIntervalInMilliseconds(float frameIntervalInMilliseconds) {

View File

@@ -19,6 +19,14 @@ class TextureDetails {
} }
abstract class FilamentController { abstract class FilamentController {
///
/// Whether a Flutter Texture widget should be inserted into the widget hierarchy.
/// This will be false on certain platforms where we use a transparent window underlay.
/// Used internally by [FilamentWidget]; you probably don't need to access this property directly.
///
bool get requiresTextureWidget;
/// ///
/// The Flutter texture ID and dimensions for current texture in use. /// The Flutter texture ID and dimensions for current texture in use.
/// This is only used by [FilamentWidget]; you shouldn't need to access directly yourself. /// This is only used by [FilamentWidget]; you shouldn't need to access directly yourself.

View File

@@ -4,16 +4,21 @@ import 'dart:io';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:ffi/ffi.dart'; import 'package:ffi/ffi.dart';
import 'package:flutter/widgets.dart';
import 'package:polyvox_filament/filament_controller.dart'; import 'package:polyvox_filament/filament_controller.dart';
import 'package:polyvox_filament/animations/animation_data.dart'; import 'package:polyvox_filament/animations/animation_data.dart';
import 'package:polyvox_filament/generated_bindings.dart'; import 'package:polyvox_filament/generated_bindings.dart';
// ignore: constant_identifier_names
const FilamentEntity _FILAMENT_ASSET_ERROR = 0; const FilamentEntity _FILAMENT_ASSET_ERROR = 0;
class FilamentControllerFFI extends FilamentController { class FilamentControllerFFI extends FilamentController {
late MethodChannel _channel = MethodChannel("app.polyvox.filament/event");
final _channel = const MethodChannel("app.polyvox.filament/event");
@override
bool get requiresTextureWidget => !Platform.isWindows;
double _pixelRatio = 1.0; double _pixelRatio = 1.0;
@@ -25,9 +30,11 @@ class FilamentControllerFFI extends FilamentController {
final String? uberArchivePath; final String? uberArchivePath;
@override
Stream<bool> get hasViewer => _hasViewerController.stream; Stream<bool> get hasViewer => _hasViewerController.stream;
final _hasViewerController = StreamController<bool>(); final _hasViewerController = StreamController<bool>();
@override
Stream<FilamentEntity> get pickResult => _pickResultController.stream; Stream<FilamentEntity> get pickResult => _pickResultController.stream;
final _pickResultController = StreamController<FilamentEntity>.broadcast(); final _pickResultController = StreamController<FilamentEntity>.broadcast();
@@ -48,7 +55,7 @@ class FilamentControllerFFI extends FilamentController {
_resizeTimer?.cancel(); _resizeTimer?.cancel();
_resizingWidth = call.arguments[0]; _resizingWidth = call.arguments[0];
_resizingHeight = call.arguments[1]; _resizingHeight = call.arguments[1];
_resizeTimer = Timer(Duration(milliseconds: 500), () async { _resizeTimer = Timer(const Duration(milliseconds: 500), () async {
await resize(_resizingWidth!, _resizingHeight!); await resize(_resizingWidth!, _resizingHeight!);
}); });
@@ -160,7 +167,7 @@ class FilamentControllerFFI extends FilamentController {
print("Using flutterTextureId $flutterTextureId, surface $surfaceAddress and nativeTexture $nativeTexture"); print("Using flutterTextureId $flutterTextureId, surface $surfaceAddress and nativeTexture $nativeTexture");
if (Platform.isWindows) { if (Platform.isWindows && requiresTextureWidget) {
_driver = Pointer<Void>.fromAddress( _driver = Pointer<Void>.fromAddress(
await _channel.invokeMethod("getDriverPlatform")); await _channel.invokeMethod("getDriverPlatform"));
} }
@@ -275,6 +282,10 @@ class FilamentControllerFFI extends FilamentController {
@override @override
Future resize(int width, int height, {double scaleFactor = 1.0}) async { Future resize(int width, int height, {double scaleFactor = 1.0}) async {
if(Platform.isWindows) {
return;
}
// we defer to the FilamentWidget to ensure that every call to [resize] is synchronized // we defer to the FilamentWidget to ensure that every call to [resize] is synchronized
// so this exception should never be thrown (right?) // so this exception should never be thrown (right?)
if (textureDetails.value == null) { if (textureDetails.value == null) {

View File

@@ -42,7 +42,7 @@ class _RenderResizeObserver extends RenderProxyBox {
void performLayout() async { void performLayout() async {
super.performLayout(); super.performLayout();
if (size.width != _oldSize.width || size.height != _oldSize.height) { if (size.width != _oldSize.width || size.height != _oldSize.height) {
onLayoutChangedCallback(size); onLayoutChangedCallback(size);
_oldSize = Size(size.width, size.height); _oldSize = Size(size.width, size.height);
} }
} }
@@ -87,7 +87,7 @@ class _FilamentWidgetState extends State<FilamentWidget> {
return ResizeObserver( return ResizeObserver(
onResized: (newSize) { onResized: (newSize) {
if(!Platform.isWindows) { if (!Platform.isWindows) {
return; return;
} }
WidgetsBinding.instance.addPostFrameCallback((timeStamp) { WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
@@ -124,10 +124,10 @@ class _SizedFilamentWidget extends StatefulWidget {
} }
class _SizedFilamentWidgetState extends State<_SizedFilamentWidget> { class _SizedFilamentWidgetState extends State<_SizedFilamentWidget> {
String? _error; String? _error;
late final AppLifecycleListener _appLifecycleListener; late final AppLifecycleListener _appLifecycleListener;
AppLifecycleState? _lastState;
@override @override
void initState() { void initState() {
@@ -136,9 +136,6 @@ class _SizedFilamentWidgetState extends State<_SizedFilamentWidget> {
); );
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
if (!kReleaseMode) {
await Future.delayed(Duration(seconds: 2));
}
try { try {
await widget.controller.createViewer(widget.width, widget.height); await widget.controller.createViewer(widget.width, widget.height);
} catch (err) { } catch (err) {
@@ -171,7 +168,7 @@ class _SizedFilamentWidgetState extends State<_SizedFilamentWidget> {
var width = size.width.ceil(); var width = size.width.ceil();
var height = size.height.ceil(); var height = size.height.ceil();
while (_resizing) { while (_resizing) {
await Future.delayed(Duration(milliseconds: 20)); await Future.delayed(const Duration(milliseconds: 20));
} }
_resizing = true; _resizing = true;
await widget.controller.resize(width, height); await widget.controller.resize(width, height);
@@ -237,7 +234,6 @@ class _SizedFilamentWidgetState extends State<_SizedFilamentWidget> {
await widget.controller.setRendering(_wasRenderingOnInactive); await widget.controller.setRendering(_wasRenderingOnInactive);
break; break;
} }
_lastState = state;
} }
@override @override
@@ -252,31 +248,55 @@ class _SizedFilamentWidgetState extends State<_SizedFilamentWidget> {
])); ]));
} }
return ListenableBuilder(listenable: widget.controller.textureDetails, builder: (BuildContext ctx, Widget? wdgt) { if (!widget.controller.requiresTextureWidget) {
if (widget.controller.textureDetails.value == null) {
return Stack(children: [
Positioned.fill(child: widget.initial ?? Container(color: Colors.red))
]);
}
// see [FilamentControllerFFI.resize] for an explanation of how we deal with resizing
var texture = Texture(
key: ObjectKey("texture_${widget.controller.textureDetails.value!.textureId}"),
textureId: widget.controller.textureDetails.value!.textureId,
filterQuality: FilterQuality.none,
freeze: false,
);
return Stack(children: [ return Stack(children: [
Positioned.fill( Positioned.fill(child: CustomPaint(painter: TransparencyPainter()))
child: Platform.isLinux || Platform.isWindows
? Transform(
alignment: Alignment.center,
transform: Matrix4.rotationX(
pi), // TODO - this rotation is due to OpenGL texture coordinate working in a different space from Flutter, can we move this to the C++ side somewhere?
child: texture)
: texture)
]); ]);
}); }
return ListenableBuilder(
listenable: widget.controller.textureDetails,
builder: (BuildContext ctx, Widget? wdgt) {
if (widget.controller.textureDetails.value == null) {
return Stack(children: [
Positioned.fill(
child: widget.initial ?? Container(color: Colors.red))
]);
}
// see [FilamentControllerFFI.resize] for an explanation of how we deal with resizing
var texture = Texture(
key: ObjectKey(
"texture_${widget.controller.textureDetails.value!.textureId}"),
textureId: widget.controller.textureDetails.value!.textureId,
filterQuality: FilterQuality.none,
freeze: false,
);
return Stack(children: [
Positioned.fill(
child: Platform.isLinux || Platform.isWindows
? Transform(
alignment: Alignment.center,
transform: Matrix4.rotationX(
pi), // TODO - this rotation is due to OpenGL texture coordinate working in a different space from Flutter, can we move this to the C++ side somewhere?
child: texture)
: texture)
]);
});
} }
} }
class TransparencyPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
canvas.drawRect(
Rect.fromLTWH(0, 0, size.width, size.height),
Paint()
..blendMode = BlendMode.clear
..color = const Color(0x00000000),
);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

View File

@@ -23,21 +23,19 @@ list(APPEND PLUGIN_SOURCES
) )
set(USE_ANGLE FALSE) set(USE_ANGLE FALSE)
if(!USE_ANGLE)
set(WGL_USE_BACKING_WINDOW TRUE) set(WGL_USE_BACKING_WINDOW TRUE)
endif()
if(USE_ANGLE) if(USE_ANGLE)
add_compile_definitions(USE_ANGLE) add_compile_definitions(USE_ANGLE)
list(APPEND PLUGIN_SOURCES "flutter_angle_texture.cpp" "egl_context.cpp" ) list(APPEND PLUGIN_SOURCES "flutter_angle_texture.cpp" "egl_context.cpp" )
else() else()
list(APPEND PLUGIN_SOURCES "wgl_context.cpp") add_compile_definitions(WGL_USE_BACKING_WINDOW)
if(WGL_USE_BACKING_WINDOW) list(APPEND PLUGIN_SOURCES "wgl_context.cpp" "opengl_texture_buffer.cpp" "backing_window.cpp")
list(APPEND PLUGIN_SOURCES "utils.cc" "backing_window.cpp") # if(WGL_USE_BACKING_WINDOW)
else() # list(APPEND PLUGIN_SOURCES )
list(APPEND PLUGIN_SOURCES "opengl_texture_buffer.cpp") # else()
endif() # list(APPEND PLUGIN_SOURCES )
# endif()
endif() endif()
# Define the plugin library target. Its name must not be changed (see comment # Define the plugin library target. Its name must not be changed (see comment

View File

@@ -1,5 +1,18 @@
#include "backing_window.h" #include "backing_window.h"
#include <cstdint>
#include <iostream>
#include <chrono>
#include <thread>
#include <Commctrl.h>
#include <Windows.h>
#include <dwmapi.h>
#include <ShObjIdl.h>
#pragma comment(lib, "dwmapi.lib")
#pragma comment(lib, "comctl32.lib")
namespace polyvox_filament { namespace polyvox_filament {
static constexpr auto kClassName = L"FLUTTER_FILAMENT_WINDOW"; static constexpr auto kClassName = L"FLUTTER_FILAMENT_WINDOW";
@@ -9,6 +22,116 @@ static WPARAM last_wm_size_wparam_ = SIZE_RESTORED;
uint64_t last_thread_time_ = 0; uint64_t last_thread_time_ = 0;
static constexpr auto kNativeViewPositionAndShowDelay = 300; static constexpr auto kNativeViewPositionAndShowDelay = 300;
typedef enum _WINDOWCOMPOSITIONATTRIB {
WCA_UNDEFINED = 0,
WCA_NCRENDERING_ENABLED = 1,
WCA_NCRENDERING_POLICY = 2,
WCA_TRANSITIONS_FORCEDISABLED = 3,
WCA_ALLOW_NCPAINT = 4,
WCA_CAPTION_BUTTON_BOUNDS = 5,
WCA_NONCLIENT_RTL_LAYOUT = 6,
WCA_FORCE_ICONIC_REPRESENTATION = 7,
WCA_EXTENDED_FRAME_BOUNDS = 8,
WCA_HAS_ICONIC_BITMAP = 9,
WCA_THEME_ATTRIBUTES = 10,
WCA_NCRENDERING_EXILED = 11,
WCA_NCADORNMENTINFO = 12,
WCA_EXCLUDED_FROM_LIVEPREVIEW = 13,
WCA_VIDEO_OVERLAY_ACTIVE = 14,
WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15,
WCA_DISALLOW_PEEK = 16,
WCA_CLOAK = 17,
WCA_CLOAKED = 18,
WCA_ACCENT_POLICY = 19,
WCA_FREEZE_REPRESENTATION = 20,
WCA_EVER_UNCLOAKED = 21,
WCA_VISUAL_OWNER = 22,
WCA_HOLOGRAPHIC = 23,
WCA_EXCLUDED_FROM_DDA = 24,
WCA_PASSIVEUPDATEMODE = 25,
WCA_USEDARKMODECOLORS = 26,
WCA_LAST = 27
} WINDOWCOMPOSITIONATTRIB;
typedef struct _WINDOWCOMPOSITIONATTRIBDATA {
WINDOWCOMPOSITIONATTRIB Attrib;
PVOID pvData;
SIZE_T cbData;
} WINDOWCOMPOSITIONATTRIBDATA;
typedef enum _ACCENT_STATE {
ACCENT_DISABLED = 0,
ACCENT_ENABLE_GRADIENT = 1,
ACCENT_ENABLE_TRANSPARENTGRADIENT = 2,
ACCENT_ENABLE_BLURBEHIND = 3,
ACCENT_ENABLE_ACRYLICBLURBEHIND = 4,
ACCENT_ENABLE_HOSTBACKDROP = 5,
ACCENT_INVALID_STATE = 6
} ACCENT_STATE;
typedef struct _ACCENT_POLICY {
ACCENT_STATE AccentState;
DWORD AccentFlags;
DWORD GradientColor;
DWORD AnimationId;
} ACCENT_POLICY;
typedef BOOL(WINAPI* _GetWindowCompositionAttribute)(
HWND, WINDOWCOMPOSITIONATTRIBDATA*);
typedef BOOL(WINAPI* _SetWindowCompositionAttribute)(
HWND, WINDOWCOMPOSITIONATTRIBDATA*);
static _SetWindowCompositionAttribute g_set_window_composition_attribute = NULL;
static bool g_set_window_composition_attribute_initialized = false;
typedef LONG NTSTATUS, *PNTSTATUS;
#define STATUS_SUCCESS (0x00000000)
typedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);
RTL_OSVERSIONINFOW GetWindowsVersion() {
HMODULE hmodule = ::GetModuleHandleW(L"ntdll.dll");
if (hmodule) {
RtlGetVersionPtr rtl_get_version_ptr =
(RtlGetVersionPtr)::GetProcAddress(hmodule, "RtlGetVersion");
if (rtl_get_version_ptr != nullptr) {
RTL_OSVERSIONINFOW rovi = {0};
rovi.dwOSVersionInfoSize = sizeof(rovi);
if (STATUS_SUCCESS == rtl_get_version_ptr(&rovi)) {
return rovi;
}
}
}
RTL_OSVERSIONINFOW rovi = {0};
return rovi;
}
void SetWindowComposition(HWND window, int32_t accent_state,
int32_t gradient_color) {
// TODO: Look for a better available API.
if (GetWindowsVersion().dwBuildNumber >= 18362) {
if (!g_set_window_composition_attribute_initialized) {
auto user32 = ::GetModuleHandleA("user32.dll");
if (user32) {
g_set_window_composition_attribute =
reinterpret_cast<_SetWindowCompositionAttribute>(
::GetProcAddress(user32, "SetWindowCompositionAttribute"));
if (g_set_window_composition_attribute) {
g_set_window_composition_attribute_initialized = true;
}
}
}
ACCENT_POLICY accent = {static_cast<ACCENT_STATE>(accent_state), 2,
static_cast<DWORD>(gradient_color), 0};
WINDOWCOMPOSITIONATTRIBDATA data;
data.Attrib = WCA_ACCENT_POLICY;
data.pvData = &accent;
data.cbData = sizeof(accent);
g_set_window_composition_attribute(window, &data);
}
}
LRESULT NativeViewSubclassProc(HWND window, UINT message, WPARAM wparam, LRESULT NativeViewSubclassProc(HWND window, UINT message, WPARAM wparam,
LPARAM lparam, UINT_PTR subclass_id, LPARAM lparam, UINT_PTR subclass_id,
DWORD_PTR ref_data) noexcept { DWORD_PTR ref_data) noexcept {
@@ -17,11 +140,12 @@ LRESULT NativeViewSubclassProc(HWND window, UINT message, WPARAM wparam,
// Prevent erasing of |window| when it is unfocused and minimized or // Prevent erasing of |window| when it is unfocused and minimized or
// moved out of screen etc. // moved out of screen etc.
return 1; return 1;
break;
} }
case WM_SIZE: { case WM_SIZE: {
// Prevent unnecessary maxmize, minimize or restore messages for |window|. // Prevent unnecessary maxmize, minimize or restore messages for |window|.
// Since it is |SetParent|'ed into native view container.
return 1; return 1;
break;
} }
default: default:
break; break;
@@ -32,6 +156,8 @@ LRESULT NativeViewSubclassProc(HWND window, UINT message, WPARAM wparam,
LRESULT CALLBACK FilamentWindowProc(HWND const window, UINT const message, LRESULT CALLBACK FilamentWindowProc(HWND const window, UINT const message,
WPARAM const wparam, WPARAM const wparam,
LPARAM const lparam) noexcept { LPARAM const lparam) noexcept {
// std::cout << "FILAMENT WINDOW EVENT " << message << std::endl;
switch (message) { switch (message) {
case WM_MOUSEMOVE: { case WM_MOUSEMOVE: {
std::cout << "FILAMENT MOUSE MOVE" << std::endl; std::cout << "FILAMENT MOUSE MOVE" << std::endl;
@@ -56,14 +182,14 @@ LRESULT CALLBACK FilamentWindowProc(HWND const window, UINT const message,
std::cout << "FILAMENT ERASE BKGND" << std::endl; std::cout << "FILAMENT ERASE BKGND" << std::endl;
// Prevent erasing of |window| when it is unfocused and minimized or // Prevent erasing of |window| when it is unfocused and minimized or
// moved out of screen etc. // moved out of screen etc.
return 1; break;
} }
case WM_SIZE: case WM_SIZE:
case WM_MOVE: case WM_MOVE:
case WM_MOVING: case WM_MOVING:
case WM_ACTIVATE: case WM_ACTIVATE:
case WM_WINDOWPOSCHANGED: { case WM_WINDOWPOSCHANGED: {
std::cout << "FILAMENT POS CHANGED" << std::endl; // std::cout << "FILAMENT POS CHANGED" << std::endl;
// NativeViewCore::GetInstance()->SetHitTestBehavior(0); // NativeViewCore::GetInstance()->SetHitTestBehavior(0);
auto user_data = ::GetWindowLongPtr(window, GWLP_USERDATA); auto user_data = ::GetWindowLongPtr(window, GWLP_USERDATA);
if (user_data) { if (user_data) {
@@ -83,168 +209,178 @@ LRESULT CALLBACK FilamentWindowProc(HWND const window, UINT const message,
return ::DefWindowProc(window, message, wparam, lparam); return ::DefWindowProc(window, message, wparam, lparam);
} }
BackingWindow::BackingWindow(flutter::PluginRegistrarWindows *pluginRegistrar,
int initialWidth, int initialHeight) {
// 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();
_flutterRootWindow = ::GetAncestor(_flutterViewWindow, GA_ROOT);
class BackingWindow { RECT flutterChildRect;
::GetWindowRect(_flutterViewWindow, &flutterChildRect);
// ::GetClientRect(flutterWindow, &flutterChildRect);
BackingWindow::BackingWindow( std::cout << "child rect " << flutterChildRect.left << " " << flutterChildRect.top << " " << flutterChildRect.right << " " << flutterChildRect.bottom << std::endl;
flutter::PluginRegistrarWindows *pluginRegistrar,
int initialWidth,
int initialHeight) {
// get the root Flutter window
HWND flutterWindow = pluginRegistrar->GetView()->GetNativeWindow();
_flutterRootWindow = ::GetAncestor(flutterWindow, GA_ROOT);
// set composition to allow transparency // set composition to allow transparency
flutternativeview::SetWindowComposition(_flutterRootWindow, 6, 0); SetWindowComposition(_flutterRootWindow, 6, 0);
// register a top-level WindowProcDelegate to handle window events // register a top-level WindowProcDelegate to handle window events
pluginRegistrar->RegisterTopLevelWindowProcDelegate([=](HWND hwnd, pluginRegistrar->RegisterTopLevelWindowProcDelegate([=](HWND hwnd,
UINT message, UINT message,
WPARAM wparam, WPARAM wparam,
LPARAM lparam) { LPARAM lparam) {
switch (message) { // std::cout << "TOP LEVEL EVENT " << message << std::endl;
case WM_ACTIVATE: { switch (message) {
std::cout << "WM_ACTIVATE" << std::endl; case WM_MOUSEMOVE: {
RECT window_rect; // std::cout << "FLUTTER MOUSE MOVE" << std::endl;
::GetWindowRect(_flutterRootWindow, &window_rect); break;
// Position |native_view| such that it's z order is behind |window_| & }
// redraw aswell. case WM_ACTIVATE: {
::SetWindowPos(_windowHandle, _flutterRootWindow, window_rect.left, std::cout << "WM_ACTIVATE" << std::endl;
window_rect.top, window_rect.right - window_rect.left, RECT rootWindowRect;
window_rect.bottom - window_rect.top, SWP_NOACTIVATE); ::GetWindowRect(_flutterViewWindow, &rootWindowRect);
break; // Position |native_view| such that it's z order is behind |window_| &
} // redraw aswell.
case WM_SIZE: { ::SetWindowPos(_windowHandle, _flutterRootWindow, rootWindowRect.left,
std::cout << "WM_SIZE" << std::endl; rootWindowRect.top, rootWindowRect.right - rootWindowRect.left,
rootWindowRect.bottom - rootWindowRect.top, SWP_NOACTIVATE);
// Handle Windows's minimize & maximize animations properly. break;
// Since |SetWindowPos| & other Win32 APIs on |native_view_container_|
// do not re-produce the same DWM animations like actual user
// interractions on the |window_| do (though both windows are overlapped
// tightly but maximize and minimze animations can't be mimiced for the
// both of them at the same time), the best solution is to make the
// |window_| opaque & hide |native_view_container_| & alter it's position.
// After that, finally make |native_view_container_| visible again &
// |window_| transparent again. This approach is not perfect, but it's the
// best we can do. The minimize & maximize animations on the |window_|
// look good with just a slight glitch on the visible native views. In
// future, maybe replacing the |NativeView| widget (Flutter-side) with
// equivalent window screenshot will result in a totally seamless
// experience.
if (wparam != SIZE_RESTORED || last_wm_size_wparam_ == SIZE_MINIMIZED ||
last_wm_size_wparam_ == SIZE_MAXIMIZED ||
was_window_hidden_due_to_minimize_) {
was_window_hidden_due_to_minimize_ = false;
// Minimize condition is handled separately inside |WM_WINDOWPOSCHANGED|
// case, since we don't want to cause unnecessary redraws (& show/hide)
// when user is resizing the window by dragging the window border.
SetWindowComposition(_flutterRootWindow, 0, 0);
::ShowWindow(_windowHandle, SW_HIDE);
last_thread_time_ =
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
std::thread(
[=](uint64_t time) {
if (time < last_thread_time_) {
return;
}
std::this_thread::sleep_for(
std::chrono::milliseconds(kNativeViewPositionAndShowDelay));
SetWindowComposition(_flutterRootWindow, 6, 0);
// Handling SIZE_MINIMIZED separately.
if (wparam != SIZE_MINIMIZED) {
::ShowWindow(_windowHandle, SW_SHOWNOACTIVATE);
}
},
last_thread_time_)
.detach();
}
last_wm_size_wparam_ = wparam;
break;
}
// Keep |native_view_container_| behind the |window_|.
case WM_MOVE:
case WM_MOVING:
case WM_WINDOWPOSCHANGED: {
std::cout << "FLUTTER WINDOWPOSCHANGED"<< std::endl;
RECT window_rect;
::GetWindowRect(_flutterRootWindow, &window_rect);
if (window_rect.right - window_rect.left > 0 &&
window_rect.bottom - window_rect.top > 0) {
::SetWindowPos(_windowHandle, _flutterRootWindow, window_rect.left,
window_rect.top, window_rect.right - window_rect.left,
window_rect.bottom - window_rect.top, SWP_NOACTIVATE);
// |window_| is minimized.
if (window_rect.left < 0 && window_rect.top < 0 &&
window_rect.right < 0 && window_rect.bottom < 0) {
// Hide |native_view_container_| to prevent showing
// |native_view_container_| before |window_| placement
// i.e when restoring window after clicking the taskbar icon.
SetWindowComposition(_flutterRootWindow, 0, 0);
::ShowWindow(_windowHandle, SW_HIDE);
was_window_hidden_due_to_minimize_ = true;
}
}
break;
}
case WM_CLOSE: {
// close
break;
}
default:
break;
}
return NULL;
});
// create the HWND for Filament
auto window_class = WNDCLASSEX{};
::SecureZeroMemory(&window_class, sizeof(window_class));
window_class.cbSize = sizeof(window_class);
window_class.style = CS_HREDRAW | CS_VREDRAW;
window_class.lpfnWndProc = FilamentWindowProc;
window_class.hInstance = 0;
window_class.lpszClassName = kClassName;
window_class.hCursor = ::LoadCursorW(nullptr, IDC_ARROW);
window_class.hbrBackground = ::CreateSolidBrush(RGB(0, 255, 0));
::RegisterClassExW(&window_class);
_windowHandle =
::CreateWindow(kClassName, kWindowName, WS_OVERLAPPEDWINDOW, 0, 0, initialWidth, initialHeight,
nullptr, nullptr, GetModuleHandle(nullptr), nullptr);
// Disable DWM animations
auto disable_window_transitions = TRUE;
DwmSetWindowAttribute(_windowHandle, DWMWA_TRANSITIONS_FORCEDISABLED,
&disable_window_transitions,
sizeof(disable_window_transitions));
::SetWindowSubclass(_windowHandle, NativeViewSubclassProc, 69420,
NULL); // what does this do?
auto style = ::GetWindowLongPtr(_windowHandle, GWL_STYLE);
style &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX |
WS_EX_APPWINDOW);
::SetWindowLongPtr(_windowHandle, GWL_STYLE, style);
RECT flutterWindowRect;
::GetClientRect(_flutterRootWindow, &flutterWindowRect);
::SetWindowLongPtr(_windowHandle, GWLP_USERDATA,
reinterpret_cast<LONG>(_flutterRootWindow));
::SetWindowPos(_windowHandle, _flutterRootWindow, flutterWindowRect.left,
flutterWindowRect.top, initialWidth, initialHeight, SWP_SHOWWINDOW);
// flutterWindowRect.right - flutterWindowRect.left,
// flutterWindowRect.bottom - flutterWindowRect.top, SWP_SHOWWINDOW);
::ShowWindow(_windowHandle, SW_SHOW);
::ShowWindow(_flutterRootWindow, SW_SHOW);
::SetFocus(_flutterRootWindow);
}
} }
case WM_SIZE: {
std::cout << "WM_SIZE" << std::endl;
BackingWindow::GetHandle() { // Handle Windows's minimize & maximize animations properly.
return _windowHandle; // Since |SetWindowPos| & other Win32 APIs on |native_view_container_|
// do not re-produce the same DWM animations like actual user
// interractions on the |window_| do (though both windows are overlapped
// tightly but maximize and minimze animations can't be mimiced for the
// both of them at the same time), the best solution is to make the
// |window_| opaque & hide |native_view_container_| & alter it's position.
// After that, finally make |native_view_container_| visible again &
// |window_| transparent again. This approach is not perfect, but it's the
// best we can do. The minimize & maximize animations on the |window_|
// look good with just a slight glitch on the visible native views. In
// future, maybe replacing the |NativeView| widget (Flutter-side) with
// equivalent window screenshot will result in a totally seamless
// experience.
if (wparam != SIZE_RESTORED || last_wm_size_wparam_ == SIZE_MINIMIZED ||
last_wm_size_wparam_ == SIZE_MAXIMIZED ||
was_window_hidden_due_to_minimize_) {
was_window_hidden_due_to_minimize_ = false;
// Minimize condition is handled separately inside |WM_WINDOWPOSCHANGED|
// case, since we don't want to cause unnecessary redraws (& show/hide)
// when user is resizing the window by dragging the window border.
SetWindowComposition(_flutterRootWindow, 0, 0);
::ShowWindow(_windowHandle, SW_HIDE);
last_thread_time_ =
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
std::thread(
[=](uint64_t time) {
if (time < last_thread_time_) {
return;
}
std::this_thread::sleep_for(
std::chrono::milliseconds(kNativeViewPositionAndShowDelay));
SetWindowComposition(_flutterRootWindow, 6, 0);
// Handling SIZE_MINIMIZED separately.
if (wparam != SIZE_MINIMIZED) {
::ShowWindow(_windowHandle, SW_SHOWNOACTIVATE);
}
},
last_thread_time_)
.detach();
}
last_wm_size_wparam_ = wparam;
break;
} }
// Keep |native_view_container_| behind the |window_|.
case WM_MOVE:
case WM_MOVING:
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);
// |window_| is minimized.
if (rootWindowRect.left < 0 && rootWindowRect.top < 0 &&
rootWindowRect.right < 0 && rootWindowRect.bottom < 0) {
// Hide |native_view_container_| to prevent showing
// |native_view_container_| before |window_| placement
// i.e when restoring window after clicking the taskbar icon.
SetWindowComposition(_flutterRootWindow, 0, 0);
::ShowWindow(_windowHandle, SW_HIDE);
was_window_hidden_due_to_minimize_ = true;
}
}
break;
}
case WM_CLOSE: {
// close
break;
}
default:
break;
}
return std::nullopt;
});
// create the HWND for Filament
auto window_class = WNDCLASSEX{};
::SecureZeroMemory(&window_class, sizeof(window_class));
window_class.cbSize = sizeof(window_class);
window_class.style = CS_HREDRAW | CS_VREDRAW;
window_class.lpfnWndProc = FilamentWindowProc;
window_class.hInstance = 0;
window_class.lpszClassName = kClassName;
window_class.hCursor = ::LoadCursorW(nullptr, IDC_ARROW);
window_class.hbrBackground = ::CreateSolidBrush(0);
::RegisterClassExW(&window_class);
_windowHandle = ::CreateWindow(kClassName, kWindowName, WS_OVERLAPPEDWINDOW,
0, 0, initialWidth, initialHeight, nullptr,
nullptr, GetModuleHandle(nullptr), nullptr);
// Disable DWM animations
auto disable_window_transitions = TRUE;
DwmSetWindowAttribute(_windowHandle, DWMWA_TRANSITIONS_FORCEDISABLED,
&disable_window_transitions,
sizeof(disable_window_transitions));
::SetWindowSubclass(_windowHandle, NativeViewSubclassProc, 69420,
NULL); // what does this do?
auto style = ::GetWindowLongPtr(_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);
::SetWindowLongPtr(_windowHandle, GWLP_USERDATA,
reinterpret_cast<LONG>(_flutterRootWindow));
::SetWindowPos(_windowHandle, _flutterRootWindow, flutterViewRect.left,
flutterViewRect.top, flutterViewRect.right - flutterViewRect.left, flutterViewRect.bottom - flutterViewRect.top,
SWP_NOACTIVATE);
// remove taskbar entry for the window we created
ITaskbarList3* taskbar = nullptr;
::CoCreateInstance(CLSID_TaskbarList, 0, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&taskbar));
taskbar->DeleteTab(_windowHandle);
taskbar->Release();
::ShowWindow(_windowHandle, SW_SHOW);
::ShowWindow(_flutterRootWindow, SW_SHOW);
::SetFocus(_flutterRootWindow);
} }
HWND BackingWindow::GetHandle() { return _windowHandle; }
} // namespace polyvox_filament

View File

@@ -17,7 +17,8 @@ class BackingWindow {
private: private:
HWND _windowHandle; HWND _windowHandle;
HWND _flutterRootWindow; HWND _flutterRootWindow;
} HWND _flutterViewWindow;
};
} }
#endif #endif

View File

@@ -36,8 +36,6 @@
#include <dwmapi.h> #include <dwmapi.h>
#include <wrl.h> #include <wrl.h>
#include "utils.h"
#include "flutter_render_context.h" #include "flutter_render_context.h"
#if USE_ANGLE #if USE_ANGLE
@@ -181,7 +179,7 @@ void PolyvoxFilamentPlugin::CreateTexture(
_context = std::make_unique<WGLContext>(_pluginRegistrar, _textureRegistrar); _context = std::make_unique<WGLContext>(_pluginRegistrar, _textureRegistrar);
#endif #endif
} }
//_context->CreateTexture(width, height, std::move(result)); _context->CreateTexture(width, height, std::move(result));
} }
void PolyvoxFilamentPlugin::DestroyTexture( void PolyvoxFilamentPlugin::DestroyTexture(
@@ -228,14 +226,15 @@ void PolyvoxFilamentPlugin::HandleMethodCall(
} else if (methodCall.method_name() == "destroyTexture") { } else if (methodCall.method_name() == "destroyTexture") {
DestroyTexture(methodCall, std::move(result)); DestroyTexture(methodCall, std::move(result));
} else if (methodCall.method_name() == "getRenderCallback") { } else if (methodCall.method_name() == "getRenderCallback") {
flutter::EncodableList resultList;
#if !ANGLE && WGL_USE_BACKING_WINDOW #if !ANGLE && WGL_USE_BACKING_WINDOW
result->Success(nullptr); resultList.push_back(flutter::EncodableValue((int64_t)nullptr));
resultList.push_back(flutter::EncodableValue((int64_t)nullptr));
#else #else
flutter::EncodableList resultList;
resultList.push_back(flutter::EncodableValue((int64_t)&render_callback)); resultList.push_back(flutter::EncodableValue((int64_t)&render_callback));
resultList.push_back(flutter::EncodableValue((int64_t)this)); resultList.push_back(flutter::EncodableValue((int64_t)this));
result->Success(resultList);
#endif #endif
result->Success(resultList);
} else if (methodCall.method_name() == "getDriverPlatform") { } else if (methodCall.method_name() == "getDriverPlatform") {
#ifdef USE_ANGLE #ifdef USE_ANGLE
result->Success(flutter::EncodableValue((int64_t)_platform)); result->Success(flutter::EncodableValue((int64_t)_platform));

View File

@@ -1,130 +0,0 @@
// This file is a part of flutter_native_view
// (https://github.com/alexmercerind/flutter_native_view).
//
// Copyright (c) 2022, Hitesh Kumar Saini <saini123hitesh@gmail.com>.
// All rights reserved.
// Use of this source code is governed by MIT license that can be found in the
// LICENSE file.
#include "utils.h"
#include <iostream>
#pragma comment(lib, "dwmapi.lib")
#pragma comment(lib, "comctl32.lib")
namespace flutternativeview {
typedef enum _WINDOWCOMPOSITIONATTRIB {
WCA_UNDEFINED = 0,
WCA_NCRENDERING_ENABLED = 1,
WCA_NCRENDERING_POLICY = 2,
WCA_TRANSITIONS_FORCEDISABLED = 3,
WCA_ALLOW_NCPAINT = 4,
WCA_CAPTION_BUTTON_BOUNDS = 5,
WCA_NONCLIENT_RTL_LAYOUT = 6,
WCA_FORCE_ICONIC_REPRESENTATION = 7,
WCA_EXTENDED_FRAME_BOUNDS = 8,
WCA_HAS_ICONIC_BITMAP = 9,
WCA_THEME_ATTRIBUTES = 10,
WCA_NCRENDERING_EXILED = 11,
WCA_NCADORNMENTINFO = 12,
WCA_EXCLUDED_FROM_LIVEPREVIEW = 13,
WCA_VIDEO_OVERLAY_ACTIVE = 14,
WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15,
WCA_DISALLOW_PEEK = 16,
WCA_CLOAK = 17,
WCA_CLOAKED = 18,
WCA_ACCENT_POLICY = 19,
WCA_FREEZE_REPRESENTATION = 20,
WCA_EVER_UNCLOAKED = 21,
WCA_VISUAL_OWNER = 22,
WCA_HOLOGRAPHIC = 23,
WCA_EXCLUDED_FROM_DDA = 24,
WCA_PASSIVEUPDATEMODE = 25,
WCA_USEDARKMODECOLORS = 26,
WCA_LAST = 27
} WINDOWCOMPOSITIONATTRIB;
typedef struct _WINDOWCOMPOSITIONATTRIBDATA {
WINDOWCOMPOSITIONATTRIB Attrib;
PVOID pvData;
SIZE_T cbData;
} WINDOWCOMPOSITIONATTRIBDATA;
typedef enum _ACCENT_STATE {
ACCENT_DISABLED = 0,
ACCENT_ENABLE_GRADIENT = 1,
ACCENT_ENABLE_TRANSPARENTGRADIENT = 2,
ACCENT_ENABLE_BLURBEHIND = 3,
ACCENT_ENABLE_ACRYLICBLURBEHIND = 4,
ACCENT_ENABLE_HOSTBACKDROP = 5,
ACCENT_INVALID_STATE = 6
} ACCENT_STATE;
typedef struct _ACCENT_POLICY {
ACCENT_STATE AccentState;
DWORD AccentFlags;
DWORD GradientColor;
DWORD AnimationId;
} ACCENT_POLICY;
typedef BOOL(WINAPI* _GetWindowCompositionAttribute)(
HWND, WINDOWCOMPOSITIONATTRIBDATA*);
typedef BOOL(WINAPI* _SetWindowCompositionAttribute)(
HWND, WINDOWCOMPOSITIONATTRIBDATA*);
static _SetWindowCompositionAttribute g_set_window_composition_attribute = NULL;
static bool g_set_window_composition_attribute_initialized = false;
typedef LONG NTSTATUS, *PNTSTATUS;
#define STATUS_SUCCESS (0x00000000)
typedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);
RTL_OSVERSIONINFOW GetWindowsVersion() {
HMODULE hmodule = ::GetModuleHandleW(L"ntdll.dll");
if (hmodule) {
RtlGetVersionPtr rtl_get_version_ptr =
(RtlGetVersionPtr)::GetProcAddress(hmodule, "RtlGetVersion");
if (rtl_get_version_ptr != nullptr) {
RTL_OSVERSIONINFOW rovi = {0};
rovi.dwOSVersionInfoSize = sizeof(rovi);
if (STATUS_SUCCESS == rtl_get_version_ptr(&rovi)) {
return rovi;
}
}
}
RTL_OSVERSIONINFOW rovi = {0};
return rovi;
}
void SetWindowComposition(HWND window, int32_t accent_state,
int32_t gradient_color) {
// TODO: Look for a better available API.
if (GetWindowsVersion().dwBuildNumber >= 18362) {
std::cout << "got win ver" << std::endl;
if (!g_set_window_composition_attribute_initialized) {
std::cout << "not init" << std::endl;
auto user32 = ::GetModuleHandleA("user32.dll");
if (user32) {
std::cout << "user32" << std::endl;
g_set_window_composition_attribute =
reinterpret_cast<_SetWindowCompositionAttribute>(
::GetProcAddress(user32, "SetWindowCompositionAttribute"));
if (g_set_window_composition_attribute) {
std::cout << "set" << std::endl;
g_set_window_composition_attribute_initialized = true;
}
}
}
ACCENT_POLICY accent = {static_cast<ACCENT_STATE>(accent_state), 2,
static_cast<DWORD>(gradient_color), 0};
WINDOWCOMPOSITIONATTRIBDATA data;
data.Attrib = WCA_ACCENT_POLICY;
data.pvData = &accent;
data.cbData = sizeof(accent);
g_set_window_composition_attribute(window, &data);
}
}
} // namespace flutternativeview

View File

@@ -1,20 +0,0 @@
// This file is a part of flutter_native_view
// (https://github.com/alexmercerind/flutter_native_view).
//
// Copyright (c) 2022, Hitesh Kumar Saini <saini123hitesh@gmail.com>.
// All rights reserved.
// Use of this source code is governed by MIT license that can be found in the
// LICENSE file.
#include <dwmapi.h>
#include <cstdint>
namespace flutternativeview {
RTL_OSVERSIONINFOW GetWindowsVersion();
void SetWindowComposition(HWND window, int32_t accent_state,
int32_t gradient_color);
} // namespace flutternativeview

View File

@@ -9,7 +9,8 @@
namespace polyvox_filament { namespace polyvox_filament {
WGLContext::WGLContext(flutter::PluginRegistrarWindows *pluginRegistrar, WGLContext::WGLContext(flutter::PluginRegistrarWindows *pluginRegistrar,
flutter::TextureRegistrar *textureRegistrar) { flutter::TextureRegistrar *textureRegistrar)
: _pluginRegistrar(pluginRegistrar), _textureRegistrar(textureRegistrar) {
auto hwnd = pluginRegistrar->GetView()->GetNativeWindow(); auto hwnd = pluginRegistrar->GetView()->GetNativeWindow();
@@ -99,27 +100,24 @@ void WGLContext::CreateTexture(
uint32_t width, uint32_t height, uint32_t width, uint32_t height,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) { std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
#ifdef WGL_USE_BACKING_WINDOW #if WGL_USE_BACKING_WINDOW
_backingWindow = std::make_unique<BackingWindow>() _backingWindow = std::make_unique<BackingWindow>(
: std::vector<flutter::EncodableValue> resultList; _pluginRegistrar, static_cast<int>(width), static_cast<int>(height));
std::vector<flutter::EncodableValue> resultList;
resultList.push_back(flutter::EncodableValue((int64_t) nullptr)); resultList.push_back(flutter::EncodableValue((int64_t) nullptr));
resultList.push_back( resultList.push_back(
flutter::EncodableValue((int64_t)_backingWindow->GetHandle())); flutter::EncodableValue((int64_t)_backingWindow->GetHandle()));
resultList.push_back(flutter::EncodableValue((int64_t) nullptr)); resultList.push_back(flutter::EncodableValue((int64_t) nullptr));
result->Success(resultList); result->Success(resultList);
}
else {
result->Error("FOO", "ERROR", nullptr);
}
#else #else
if (_active.get()) { if (_active.get()) {
result->Error("ERROR", result->Error("ERROR",
"Texture already exists. You must call destroyTexture before " "Texture already exists. You must call destroyTexture before "
"attempting to create a new one."); "attempting to create a new one.");
} else { } else {
_active = std::make_unique<OpenGLTextureBuffer>(_pluginRegistrar, _textureRegistrar, _active = std::make_unique<OpenGLTextureBuffer>(
std::move(result), width, height, _pluginRegistrar, _textureRegistrar, std::move(result), width, height,
_context); _context);
if (_active->flutterTextureId != -1) { if (_active->flutterTextureId != -1) {
std::vector<flutter::EncodableValue> resultList; std::vector<flutter::EncodableValue> resultList;

View File

@@ -15,6 +15,8 @@ namespace polyvox_filament {
void CreateTexture(uint32_t width, uint32_t height, std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result); void CreateTexture(uint32_t width, uint32_t height, std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
void* GetSharedContext(); void* GetSharedContext();
private: private:
flutter::PluginRegistrarWindows* _pluginRegistrar = nullptr;
flutter::TextureRegistrar* _textureRegistrar = nullptr;
HGLRC _context = NULL; HGLRC _context = NULL;
#if WGL_USE_BACKING_WINDOW #if WGL_USE_BACKING_WINDOW
std::unique_ptr<BackingWindow> _backingWindow = nullptr; std::unique_ptr<BackingWindow> _backingWindow = nullptr;