Files
cup_edit/examples/dart/cli_windows/native/thermion_window.cpp
Nick Fisher 436873a455 successfully creating D3D texture with D3D11_RESOURCE_MISC_SHARED_NTHANDLE;
successfully allocating with VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT

working copying vulkan texture

successfully passing D3D texture back to Flutter

chore: Dart/Windows sample project: remove unnnecessary InvalidateRect from update()

chore: Dart/Windows sample project: add generated bindings

successfully blitting from Vulkan swapchain to D3D texture

working Vulkan texture integration with Flutter

refactor to allow disposal of resources in destructors

handle destroyTexture correctly

correctly implement surface resizing/destruction

move Windows engine to Vulkan backend and flush after creating swapchain

add vulkan + vkshaders to Windows libs

update materials with Vulkan

move Vulkan implementation to thermion_Dart

remove extras folder

thermion_flutter plugin updates

update build hook to copy .lib file to output directory and use -vulkan lib zip file

thermion_flutter cleanup

reinstate stereoscopic on Windows

add dxgi and d3d11.lib to windows header pragma

update cli_windows sample project

copy filament/vulkan headers to output directory. This was originally added to facilitate linking on Windows (where thermion_flutter_plugin.cpp needs the Vulkan-related headers), but this doesn't actually solve the problem because there's no way that I've found to get the directory structure correct in the Dart native_assets build directory unless you explicitly address each inidivual file. The current approach is therefore to just keep a permanent copy of the headers in the thermion_filament directory (meaning these will need to be updated manually if the Filament version changes). However, I decided to keep the changes to build.dart because it doesn't have much negative impact and may be helpful in future.

disable stereoscopic on Windows and disable handle use after free checks

use filament headers for thermion_flutter

throw Exception for MSAA on Windows (note that passing msaa:true for setAntiAliasing doesn't actually set MSAA on other platforms, but at least it won't cause the engine to crash)

change header include path for Windows/Vulkan

change header include path for Windows/Vulkan

add filament/vulkan headers for flutter (Windows)

ensure destroyTexture platform methods accept an integer rather than a list

handle Android/Windows swapchain creation separately
2024-11-11 12:49:40 +08:00

390 lines
12 KiB
C++

#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")
#pragma comment(lib, "dxgi.lib")
#include <dxgi.h>
#include <cstdint>
#include <chrono>
#include <thread>
#include <algorithm>
#include <Windows.h>
#include <dwmapi.h>
#include <ShObjIdl.h>
#include <iostream>
#include <Windows.h>
#include "thermion_window.h"
namespace thermion {
void PrintDefaultGPU() {
IDXGIFactory* factory = nullptr;
CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&factory);
IDXGIAdapter* adapter = nullptr;
factory->EnumAdapters(0, &adapter); // 0 is the default adapter
DXGI_ADAPTER_DESC desc;
adapter->GetDesc(&desc);
std::wcout << L"GPU: " << desc.Description << std::endl;
adapter->Release();
factory->Release();
}
///
/// 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<std::chrono::steady_clock> _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<std::chrono::microseconds>(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<std::chrono::milliseconds>(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() {
}
// 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;
static WPARAM last_wm_size_wparam_ = SIZE_RESTORED;
uint64_t last_thread_time_ = 0;
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);
}
}
// 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) {
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<HWND>(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<ThermionWindow*>(
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<HWND>(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);
}
return 0;
}
ThermionWindow::ThermionWindow(int width,
int height,
int left,
int top) : _width(width), _height(height), _left(left), _top(top) {
PrintDefaultGPU();
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);
// 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<LONG_PTR>(this));
// Disable DWM animations
auto disable_window_transitions = TRUE;
DwmSetWindowAttribute(_windowHandle, DWMWA_TRANSITIONS_FORCEDISABLED,
&disable_window_transitions,
sizeof(disable_window_transitions));
::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;
::SetWindowPos(_windowHandle, nullptr, left, top, width, height,
SWP_NOZORDER | SWP_NOACTIVATE);
}
HWND ThermionWindow::GetHandle() { return _windowHandle; }
} // namespace thermion