using Newtonsoft.Json.Linq; using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Media.Media3D; using System.Windows.Navigation; using System.Windows.Shapes; namespace Veldrid.Wpf { public sealed class VeldridContent { static VeldridContent() { } private VeldridContent() { } private const string WindowClass = "HwndWrapper"; public IntPtr Hwnd { get; private set; } [AllowNull] public Veldrid.Common.VeldridContent Content { get; private set; } public static Veldrid.Wpf.VeldridContent CreateContent() { VeldridContent content = new VeldridContent(); var wndClass = new NativeMethods.WndClassEx(); wndClass.cbSize = (uint)Marshal.SizeOf(wndClass); wndClass.hInstance = NativeMethods.GetModuleHandle(null); wndClass.lpfnWndProc = NativeMethods.DefaultWindowProc; wndClass.lpszClassName = WindowClass; wndClass.hCursor = NativeMethods.LoadCursor(IntPtr.Zero, NativeMethods.IDC_ARROW); NativeMethods.RegisterClassEx(ref wndClass); content.Hwnd = NativeMethods.CreateWindowEx( 0, WindowClass, "", NativeMethods.WS_VISIBLE, 0, 0, 200, 200, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 0); NativeMethods.ShowWindow(content.Hwnd, 0); Module mainModule = typeof(VeldridView).Module; IntPtr hinstance = Marshal.GetHINSTANCE(mainModule); SwapchainSource win32Source = SwapchainSource.CreateWin32(content.Hwnd, hinstance); SwapchainDescription scDesc = new SwapchainDescription(win32Source, 200, 200, PixelFormat.R32_Float, true); GraphicsDevice device = GraphicsDevice.CreateD3D11(new GraphicsDeviceOptions(false, null, true, ResourceBindingModel.Default, true, true, true), scDesc); content.Content = new Common.VeldridContent(device); return content; } } public sealed class VeldridView : HwndHost { [AllowNull] private VeldridContent _Content; private IntPtr Hwnd; public bool HwndInitialized { get; private set; } public VeldridView(VeldridContent content) { _Content = content; Hwnd = _Content.Hwnd; base.Loaded += OnLoaded; base.Unloaded += OnUnloaded; } public GraphicsBackend Backend { get; set; } = GraphicsBackend.Direct3D11; private void OnLoaded(object sender, RoutedEventArgs routedEventArgs) { Initialize(); HwndInitialized = true; CreateGraphicsDevice(); } private void CreateGraphicsDevice() { double dpiScale = CompositionScaleX; uint width = (uint)(ActualWidth < 0 ? 0 : Math.Ceiling(ActualWidth * dpiScale)); uint height = (uint)(ActualHeight < 0 ? 0 : Math.Ceiling(ActualHeight * dpiScale)); width = (uint)ActualWidth; height = (uint)ActualHeight; NativeMethods.SetWindowLong(Hwnd, -16, 0x40000000L); //NativeMethods.SetWindowLong(Hwnd, -20, 0x08000000); NativeMethods.SetParent(_Content.Hwnd, parentptr); NativeMethods.ShowWindow(Hwnd, 1); NativeMethods.SetWindowPos(Hwnd, 0, 0, 0, (int)width, (int)height, 0); if (ActualWidth > 0 && ActualHeight > 0) { _Content.Content.Resize(width,height); } GraphicsDeviceCreated?.Invoke(); Thread.Sleep(20); } private void DestroyGraphicsDevice() { if (_Content != null) { GraphicsDeviceDestroyed?.Invoke(); _Content = null; } } [AllowNull] public Action GraphicsDeviceDestroyed; [AllowNull] public Action GraphicsDeviceCreated; private void OnUnloaded(object sender, RoutedEventArgs routedEventArgs) { Uninitialize(); HwndInitialized = false; DestroyGraphicsDevice(); Dispose(); } public IntPtr NativeHwnd => this.Hwnd; [AllowNull] public new Action SizeChanged; [AllowNull] public new Action Loaded; [AllowNull] public new Action Unloaded; private void Initialize() { Loaded?.Invoke(); } private void Resized() { if (_Content != null) { if (ActualWidth > 0 && ActualHeight > 0) { _Content.Content.Resize((uint)(ActualWidth * CompositionScaleX), (uint)(ActualHeight * CompositionScaleY)); } } SizeChanged?.Invoke(); } public double CompositionScaleX => GetDpiScale(); public double CompositionScaleY => GetDpiScale(); private double GetDpiScale() { PresentationSource source = PresentationSource.FromVisual(this); if (source != null) return source.CompositionTarget.TransformToDevice.M11; else return 1; } private void Uninitialize() { Unloaded?.Invoke(); } protected override HandleRef BuildWindowCore(HandleRef hwndParent) { parentptr = hwndParent.Handle; NativeMethods.SetWindowLong(Hwnd, -16, 0x40000000L); //NativeMethods.SetWindowLong(Hwnd, -20, 0x80000000L); NativeMethods.SetParent(_Content.Hwnd, parentptr); if (ActualWidth > 0 && ActualHeight > 0) { _Content.Content.Resize((uint)(ActualWidth*CompositionScaleX), (uint)(ActualHeight*CompositionScaleY)); } NativeMethods.ShowWindow(Hwnd, 1); return new HandleRef(this, Hwnd); } private IntPtr parentptr; protected override void DestroyWindowCore(HandleRef hwnd) { NativeMethods.ShowWindow(Hwnd, 0); var result = NativeMethods.GetWindowLong(Hwnd, -16); result &= ~0x40000000L; NativeMethods.SetWindowLong(Hwnd, -16, result); NativeMethods.SetParent(Hwnd, IntPtr.Zero); Hwnd = IntPtr.Zero; _Content = null; } protected unsafe override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { if (_Content != null) { tagRECT rect = new tagRECT(); GetWindowRect(hwnd, ref rect); switch (msg) { case NativeMethods.WM_LBUTTONDOWN: _Content.Content.WindowScale = new Vector2((float)ActualWidth / (rect.right - rect.left), (float)ActualHeight / (rect.bottom - rect.top)); int val = (int)lParam.ToInt64(); _Content.Content.MouseLeftDown(Unsafe.As(ref val)); break; case NativeMethods.WM_LBUTTONUP: _Content.Content.WindowScale = new Vector2((float)ActualWidth / (rect.right - rect.left), (float)ActualHeight / (rect.bottom - rect.top)); val = (int)lParam.ToInt64(); _Content.Content.MouseLeftUp(Unsafe.As(ref val)); break; case NativeMethods.WM_RBUTTONDOWN: _Content.Content.WindowScale = new Vector2((float)ActualWidth / (rect.right - rect.left), (float)ActualHeight / (rect.bottom - rect.top)); val = (int)lParam.ToInt64(); _Content.Content.MouseRightDown(Unsafe.As(ref val)); break; case NativeMethods.WM_RBUTTONUP: _Content.Content.WindowScale = new Vector2((float)ActualWidth / (rect.right - rect.left), (float)ActualHeight / (rect.bottom - rect.top)); val = (int)lParam.ToInt64(); _Content.Content.MouseRightUp(Unsafe.As(ref val)); break; case NativeMethods.WM_MBUTTONDOWN: _Content.Content.WindowScale = new Vector2((float)ActualWidth / (rect.right - rect.left), (float)ActualHeight / (rect.bottom - rect.top)); val = (int)lParam.ToInt64(); _Content.Content.MouseMiddleDown(Unsafe.As(ref val) ); break; case NativeMethods.WM_MBUTTONUP: _Content.Content.WindowScale = new Vector2((float)ActualWidth / (rect.right - rect.left), (float)ActualHeight / (rect.bottom - rect.top)); val = (int)lParam.ToInt64(); _Content.Content.MouseMiddleUp(Unsafe.As(ref val) ); break; case NativeMethods.WM_MOUSEMOVE: _Content.Content.WindowScale = new Vector2((float)ActualWidth / (rect.right - rect.left), (float)ActualHeight / (rect.bottom - rect.top)); val = (int)lParam.ToInt64(); _Content.Content.MouseMove(Unsafe.As(ref val)); break; case NativeMethods.WM_MOUSEWHEEL: _Content.Content.WindowScale = new Vector2((float)ActualWidth / (rect.right - rect.left), (float)ActualHeight / (rect.bottom - rect.top)); val = (int)lParam.ToInt64(); _Content.Content.MouseWheel(Unsafe.As(ref val) , (((Int32)wParam.ToInt64()) >> 16) / Mouse.MouseWheelDeltaForOneLine); break; case NativeMethods.WM_MOUSELEAVE: _Content.Content.MouseLeave(); break; } } return base.WndProc(hwnd, msg, wParam, lParam, ref handled); } [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] private struct tagRECT { /// LONG->int public int left; /// LONG->int public int top; /// LONG->int public int right; /// LONG->int public int bottom; } [System.Runtime.InteropServices.DllImportAttribute("user32.dll", EntryPoint = "GetWindowRect")] [return: System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)] private static extern bool GetWindowRect(System.IntPtr hWnd, ref tagRECT lpRect); [StructLayout(LayoutKind.Sequential)] private struct MSLLHOOKSTRUCT { public Point pt; public int mouseData; public int flags; public int time; public long dwExtraInfo; } private void RaiseMouseEvent(System.Windows.Input.MouseButton button, RoutedEvent @event) { RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, (int)DateTime.Now.Ticks, button) { RoutedEvent = @event, Source = this, }); } protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) { UpdateWindowPos(); base.OnRenderSizeChanged(sizeInfo); if (HwndInitialized) Resized(); } } internal class NativeMethods { // ReSharper disable InconsistentNaming public const int WS_CHILD = 0x40000000; public const int WS_VISIBLE = 0x10000000; public const int WM_PAINT = 0x0f; public const int WM_LBUTTONDOWN = 0x0201; public const int WM_LBUTTONUP = 0x0202; public const int WM_RBUTTONDOWN = 0x0204; public const int WM_RBUTTONUP = 0x0205; public const int WM_NCCALCSIZE = 0x0083; public const int WM_MOUSEWHEEL = 0x020A; public const int WM_MBUTTONDOWN = 0x0207; public const int WM_MBUTTONUP = 0x0208; public const int WM_MOUSEMOVE = 0x0200; public const int WM_MOUSELEAVE = 0x02A3; public const int IDC_ARROW = 32512; [StructLayout(LayoutKind.Sequential)] public struct WndClassEx { public uint cbSize; public uint style; [MarshalAs(UnmanagedType.FunctionPtr)] public WndProc lpfnWndProc; public int cbClsExtra; public int cbWndExtra; public IntPtr hInstance; public IntPtr hIcon; public IntPtr hCursor; public IntPtr hbrBackground; public string lpszMenuName; public string lpszClassName; public IntPtr hIconSm; } [DllImport("user32.dll")] public static extern IntPtr DefWindowProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam); public delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); public static readonly WndProc DefaultWindowProc = DefWindowProc; [DllImport("user32.dll", EntryPoint = "CreateWindowEx", CharSet = CharSet.Auto)] public static extern IntPtr CreateWindowEx( int exStyle, string className, string windowName, int style, int x, int y, int width, int height, IntPtr hwndParent, IntPtr hMenu, IntPtr hInstance, [MarshalAs(UnmanagedType.AsAny)] object pvParam); [DllImport("user32.dll", EntryPoint = "DestroyWindow", CharSet = CharSet.Auto)] public static extern bool DestroyWindow(IntPtr hwnd); [DllImport("kernel32.dll")] public static extern IntPtr GetModuleHandle(string module); [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.U2)] public static extern short RegisterClassEx([In] ref WndClassEx lpwcx); [DllImport("user32.dll")] public static extern IntPtr LoadCursor(IntPtr hInstance, int lpCursorName); [System.Runtime.InteropServices.DllImportAttribute("user32.dll", EntryPoint = "SetParent")] public static extern System.IntPtr SetParent(System.IntPtr hWndChild, System.IntPtr hWndNewParent); [System.Runtime.InteropServices.DllImportAttribute("user32.dll", EntryPoint = "ShowWindow")] [return: System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)] public static extern bool ShowWindow(System.IntPtr hWnd, int nCmdShow); [System.Runtime.InteropServices.DllImportAttribute("user32.dll", EntryPoint = "SetWindowPos")] [return: System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)] public static extern bool SetWindowPos(System.IntPtr hWnd, System.IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); [System.Runtime.InteropServices.DllImportAttribute("user32.dll", EntryPoint = "SetWindowLong")] public static extern long SetWindowLong(System.IntPtr hWnd, int nIndex, long dwNewLong); [System.Runtime.InteropServices.DllImportAttribute("user32.dll", EntryPoint = "GetWindowLong")] public static extern long GetWindowLong(System.IntPtr hWnd, int nIndex); } }