From 4d97126ef6e566fabc2543f04cf16995b5b0443f Mon Sep 17 00:00:00 2001 From: Nick Fisher Date: Sat, 2 Nov 2024 17:52:17 +0800 Subject: [PATCH] chore: update cli_windows project --- .../dart/cli_windows/bin/cli_windows.dart | 36 +- .../cli_windows/native/thermion_window.cpp | 317 ++++++++++++------ .../dart/cli_windows/native/thermion_window.h | 58 +--- examples/dart/cli_windows/pubspec.yaml | 6 +- 4 files changed, 257 insertions(+), 160 deletions(-) diff --git a/examples/dart/cli_windows/bin/cli_windows.dart b/examples/dart/cli_windows/bin/cli_windows.dart index 4a1e5cc6..cbeac69a 100644 --- a/examples/dart/cli_windows/bin/cli_windows.dart +++ b/examples/dart/cli_windows/bin/cli_windows.dart @@ -1,19 +1,22 @@ import 'dart:ffi'; import 'dart:io'; +import 'dart:math'; import 'package:ffi/ffi.dart'; // import 'package:thermion_dart/thermion_dart/thermion_viewer_ffi.dart'; import 'package:thermion_dart/src/utils/src/dart_resources.dart'; import 'package:thermion_dart/src/viewer/src/ffi/src/thermion_viewer_ffi.dart'; import 'package:thermion_dart/src/viewer/src/ffi/src/thermion_dart.g.dart'; +import 'package:thermion_dart/thermion_dart.dart'; +import 'package:vector_math/vector_math_64.dart'; +import 'package:cli_windows/thermion_window.g.dart'; void main(List arguments) async { - var lib = DynamicLibrary.open("thermion_windows.dll"); - var createWindow = lib.lookupFunction("create_thermion_window"); - var update = lib.lookupFunction("update"); - var hwnd = createWindow(500, 500, 0, 0); + var hwnd = create_thermion_window(500, 500, 0, 0); update(); + print("VISIBLE"); + final resourceLoader = calloc(1); var loadToOut = NativeCallable< @@ -25,26 +28,41 @@ void main(List arguments) async { DartResourceLoader.freeResource); resourceLoader.ref.freeResource = freeResource.nativeFunction; - var viewer = ThermionViewerFFI( - + var viewer = ThermionViewerFFI( resourceLoader: resourceLoader.cast()); await viewer.initialized; var swapChain = await viewer.createSwapChain(hwnd); var view = await viewer.getViewAt(0); await view.updateViewport(500, 500); + var camera = await viewer.getMainCamera(); + await camera.setLensProjection(); await view.setRenderable(true, swapChain); await viewer.setBackgroundColor(1.0, 0.0, 0.0, 1.0); var skyboxPath = File("..\\..\\assets\\default_env_skybox.ktx").absolute; - await viewer.loadSkybox("file://${skyboxPath.uri.toFilePath(windows: true)}"); - while(true) { + + final cube = await viewer.createGeometry(GeometryHelper.cube()); + + var stopwatch = Stopwatch(); + stopwatch.start(); + + var last = 0; + + await viewer.setCameraPosition(0, 0, 10); + + while(true) { + var angle = (stopwatch.elapsedMilliseconds / 1000) * 2 * pi; + var rotation = Quaternion.axisAngle(Vector3(0,1,0), angle); + var position = Vector3(10 * sin(angle), 0, 10 * cos(angle)); + var modelMatrix = Matrix4.compose(position, rotation, Vector3.all(1)); + await viewer.setCameraModelMatrix4(modelMatrix); await viewer.render(); - update(); await Future.delayed(Duration(milliseconds: 16)); + update(); } diff --git a/examples/dart/cli_windows/native/thermion_window.cpp b/examples/dart/cli_windows/native/thermion_window.cpp index d189f6bc..64b236c4 100644 --- a/examples/dart/cli_windows/native/thermion_window.cpp +++ b/examples/dart/cli_windows/native/thermion_window.cpp @@ -1,19 +1,132 @@ -#include "thermion_window.h" +#pragma comment(lib, "dwmapi.lib") +#pragma comment(lib, "comctl32.lib") +#pragma comment(lib, "Shlwapi.lib") +#pragma comment(lib, "opengl32.lib") +#pragma comment(lib, "Gdi32.lib") +#pragma comment(lib, "User32.lib") #include -#include #include #include - +#include #include #include #include -#pragma comment(lib, "dwmapi.lib") -#pragma comment(lib, "comctl32.lib") +#include +#include + +#include "thermion_window.h" namespace thermion { + /// +/// Instantiating a ThermionWindow creates a HWND that can be passed +/// to Filament to create a swapchain. +/// +/// +class ThermionWindow { + public: + ThermionWindow( + int width, + int height, + int left, + int top); + HWND GetHandle(); + void Resize(int width, int height, int left, int top); + uint32_t _width = 0; + uint32_t _height = 0; + uint32_t _left = 0; + uint32_t _top = 0; + private: + HWND _windowHandle; + +}; + +static ThermionWindow* _window; + + +static bool _running = false; +static std::thread _renderThread; + +// Add these for timing and stats +static int _frameCount = 0; +static std::chrono::time_point _lastFpsLog; + +static void RenderLoop() { + _lastFpsLog = std::chrono::steady_clock::now(); + auto lastFrame = std::chrono::steady_clock::now(); + + while (_running) { + auto now = std::chrono::steady_clock::now(); + auto frameDuration = std::chrono::duration_cast(now - lastFrame).count(); + + // Force a redraw + InvalidateRect(_window->GetHandle(), NULL, FALSE); + + // Process any pending messages + MSG msg; + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + if (msg.message == WM_QUIT) { + _running = false; + break; + } + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + // Wait for vsync + DwmFlush(); + + // Update timing stats + lastFrame = now; + _frameCount++; + + // Log FPS every second + auto timeSinceLastLog = std::chrono::duration_cast(now - _lastFpsLog).count(); + if (timeSinceLastLog >= 1000) { // Every second + float fps = (_frameCount * 1000.0f) / timeSinceLastLog; + float avgFrameTime = timeSinceLastLog / (float)_frameCount; + std::cout << "FPS: " << fps << " Frame Time: " << avgFrameTime << "ms" + << " Last Frame: " << frameDuration / 1000.0f << "ms" << std::endl; + + _frameCount = 0; + _lastFpsLog = now; + } + } +} + +extern "C" { + + EMSCRIPTEN_KEEPALIVE intptr_t create_thermion_window(int width, int height, int left, int top) { + _window = new ThermionWindow(width, height, left, top); + + // Start the render thread + _running = true; + _renderThread = std::thread(RenderLoop); + + return (intptr_t)_window->GetHandle(); + } + + // Update function can now be simplified or removed since rendering happens in the thread + EMSCRIPTEN_KEEPALIVE void update() { + // This could be used to trigger specific updates if needed + InvalidateRect(_window->GetHandle(), NULL, FALSE); + } + + // Add a cleanup function + EMSCRIPTEN_KEEPALIVE void cleanup() { + _running = false; + if (_renderThread.joinable()) { + _renderThread.join(); + } + if (_window) { + delete _window; + _window = nullptr; + } + } +} + static constexpr auto kClassName = L"THERMION_WINDOW"; static constexpr auto kWindowName = L"thermion_window"; static bool was_window_hidden_due_to_minimize_ = false; @@ -131,114 +244,126 @@ void SetWindowComposition(HWND window, int32_t accent_state, } +// Add tracking for drag state +static bool isDragging = false; +static POINT dragStart = {0, 0}; +static POINT windowStart = {0, 0}; + + LRESULT CALLBACK FilamentWindowProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { - switch (message) { - std::cout << message <(user_data); - ::SetForegroundWindow(flutterRootWindow); - LONG ex_style = ::GetWindowLong(flutterRootWindow, GWL_EXSTYLE); - ex_style &= ~(WS_EX_TRANSPARENT | WS_EX_LAYERED); - ::SetWindowLong(flutterRootWindow, GWL_EXSTYLE, ex_style); + switch (message) { + case WM_DESTROY: { + PostQuitMessage(0); + return 0; + } + case WM_NCHITTEST: { + POINT pt = { LOWORD(lparam), HIWORD(lparam) }; + ScreenToClient(window, &pt); + return HTCAPTION; + } + + case WM_MOUSEMOVE: { + TRACKMOUSEEVENT event; + event.cbSize = sizeof(event); + event.hwndTrack = window; + event.dwFlags = TME_HOVER; + event.dwHoverTime = 200; + auto user_data = ::GetWindowLongPtr(window, GWLP_USERDATA); + if (user_data) { + HWND flutterRootWindow = reinterpret_cast(user_data); + LONG ex_style = ::GetWindowLong(flutterRootWindow, GWL_EXSTYLE); + ex_style &= ~(WS_EX_TRANSPARENT | WS_EX_LAYERED); + ::SetWindowLong(flutterRootWindow, GWL_EXSTYLE, ex_style); + } + break; + } + + case WM_ERASEBKGND: { + HDC hdc = (HDC)wparam; + RECT rect; + GetClientRect(window, &rect); + + ThermionWindow* thermionWindow = reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); + + if (thermionWindow) { + HBRUSH brush = CreateSolidBrush(RGB(0, 0, 255)); + FillRect(hdc, &rect, brush); + DeleteObject(brush); + } + return TRUE; + } + + case WM_SIZE: + case WM_MOVE: + case WM_MOVING: + case WM_WINDOWPOSCHANGED: { + auto user_data = ::GetWindowLongPtr(window, GWLP_USERDATA); + if (user_data) { + HWND flutterRootWindow = reinterpret_cast(user_data); + LONG ex_style = ::GetWindowLong(flutterRootWindow, GWL_EXSTYLE); + ex_style &= ~(WS_EX_TRANSPARENT | WS_EX_LAYERED); + ::SetWindowLong(flutterRootWindow, GWL_EXSTYLE, ex_style); + } + break; + } + + default: + return ::DefWindowProc(window, message, wparam, lparam); } - break; - } - case WM_ERASEBKGND: { - HDC hdc = (HDC)wparam; - RECT rect; - GetClientRect(window, &rect); - - // Get the ThermionWindow instance associated with this window - ThermionWindow* thermionWindow = reinterpret_cast( - GetWindowLongPtr(window, GWLP_USERDATA)); - - if (thermionWindow) { - HBRUSH brush = CreateSolidBrush(RGB(0, 0, 255)); - FillRect(hdc, &rect, brush); - DeleteObject(brush); - } - - break; - } - case WM_SIZE: - break; - case WM_MOVE: - case WM_MOVING: - case WM_ACTIVATE: - case WM_WINDOWPOSCHANGED: { - // NativeViewCore::GetInstance()->SetHitTestBehavior(0); - auto user_data = ::GetWindowLongPtr(window, GWLP_USERDATA); - if (user_data) { - 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); - } - break; - } - default: - break; - } - return ::DefWindowProc(window, message, wparam, lparam); + return 0; } ThermionWindow::ThermionWindow(int width, int height, int left, int top) : _width(width), _height(height), _left(left), _top(top) { - // 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, _width, _height, nullptr, - nullptr, GetModuleHandle(nullptr), nullptr); + auto window_class = WNDCLASSEX{}; + ::SecureZeroMemory(&window_class, sizeof(window_class)); + window_class.cbSize = sizeof(window_class); + window_class.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + window_class.lpfnWndProc = FilamentWindowProc; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.lpszClassName = L"THERMION_WINDOW"; + window_class.hCursor = ::LoadCursorW(nullptr, IDC_ARROW); + window_class.hbrBackground = ::CreateSolidBrush(RGB(0,255,0)); + ::RegisterClassExW(&window_class); - // Disable DWM animations - auto disable_window_transitions = TRUE; - DwmSetWindowAttribute(_windowHandle, DWMWA_TRANSITIONS_FORCEDISABLED, + // Create a normal popup window without forcing it to be topmost + _windowHandle = ::CreateWindowW( + L"THERMION_WINDOW", + L"thermion_window", + WS_OVERLAPPEDWINDOW, + left, top, width, height, + nullptr, nullptr, + GetModuleHandle(nullptr), + nullptr + ); + + // Store the this pointer for use in window procedure + ::SetWindowLongPtr(_windowHandle, GWLP_USERDATA, reinterpret_cast(this)); + + // Disable DWM animations + auto disable_window_transitions = TRUE; + DwmSetWindowAttribute(_windowHandle, DWMWA_TRANSITIONS_FORCEDISABLED, &disable_window_transitions, sizeof(disable_window_transitions)); - auto style = ::GetWindowLong(_windowHandle, GWL_STYLE); - style &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | - WS_EX_APPWINDOW); - ::SetWindowLong(_windowHandle, GWL_STYLE, style); - - // // 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); - UpdateWindow(_windowHandle); + ::ShowWindow(_windowHandle, SW_SHOW); + UpdateWindow(_windowHandle); } void ThermionWindow::Resize(int width, int height, int left, int top) { - _width = width; - _height = height; - _left = left; - _top = top; + _width = width; + _height = height; + _left = left; + _top = top; + ::SetWindowPos(_windowHandle, nullptr, left, top, width, height, + SWP_NOZORDER | SWP_NOACTIVATE); } HWND ThermionWindow::GetHandle() { return _windowHandle; } -} // namespace thermion_flutter + +} // namespace thermion \ No newline at end of file diff --git a/examples/dart/cli_windows/native/thermion_window.h b/examples/dart/cli_windows/native/thermion_window.h index d7738200..85fcfe15 100644 --- a/examples/dart/cli_windows/native/thermion_window.h +++ b/examples/dart/cli_windows/native/thermion_window.h @@ -1,64 +1,14 @@ #pragma once -#pragma comment(lib, "Shlwapi.lib") -#pragma comment(lib, "opengl32.lib") -#pragma comment(lib, "dwmapi.lib") -#pragma comment(lib, "comctl32.lib") -#pragma comment(lib, "Gdi32.lib") -#pragma comment(lib, "User32.lib") - -#ifdef _WIN32 #ifdef IS_DLL #define EMSCRIPTEN_KEEPALIVE __declspec(dllimport) #else #define EMSCRIPTEN_KEEPALIVE __declspec(dllexport) #endif -#include -#include -#include +extern "C" { -namespace thermion { +intptr_t create_thermion_window(int width, int height, int left, int top); +void update(); -/// -/// Instantiating a ThermionWindow creates a HWND that can be passed -/// to Filament to create a swapchain. -/// -/// -class ThermionWindow { - public: - ThermionWindow( - int width, - int height, - int left, - int top); - HWND GetHandle(); - void Resize(int width, int height, int left, int top); - private: - HWND _windowHandle; - uint32_t _width = 0; - uint32_t _height = 0; - uint32_t _left = 0; - uint32_t _top = 0; -}; - -static ThermionWindow* _window; - -extern "C" { - EMSCRIPTEN_KEEPALIVE intptr_t create_thermion_window(int width, int height, int left, int top) { - _window = new ThermionWindow(width, height, left, top); - return (intptr_t)_window->GetHandle(); - } - - EMSCRIPTEN_KEEPALIVE void update() { - MSG msg; - if(GetMessage(&msg, NULL, 0, 0)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } -} - -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/examples/dart/cli_windows/pubspec.yaml b/examples/dart/cli_windows/pubspec.yaml index ee9d29e6..1e4440b7 100644 --- a/examples/dart/cli_windows/pubspec.yaml +++ b/examples/dart/cli_windows/pubspec.yaml @@ -28,8 +28,12 @@ ffigen: include-directives: - 'native/thermion_window.h' ffi-native: - assetId: package:thermion_window/thermion_window.dart + assetId: package:cli_windows/thermion_window.dart ignore-source-errors: true + llvm-path: + - E:\clang+llvm-19.1.3-x86_64-pc-windows-msvc\bin + - E:\clang+llvm-19.1.3-x86_64-pc-windows-msvc\ + - E:\clang+llvm-19.1.3-x86_64-pc-windows-msvc\lib functions: leaf: include: