IT评测·应用市场-qidao123.com

标题: 分享一个在 dotnet 里利用 D2D 配合 AOT 开发小而美的应用开发经验 [打印本页]

作者: 小秦哥    时间: 2024-5-18 11:26
标题: 分享一个在 dotnet 里利用 D2D 配合 AOT 开发小而美的应用开发经验
本文将分享我在 dotnet 里面利用 Direct2D 配合 AOT 开发一个简朴的测试应用的经验。这是我用不到 370 行代码,从零开始控制台创建 Win32 窗口,再挂上交换链,在窗口上利用 D2D 绘制界面内容,末了利用 AOT 方式发布的测试应用。成品文件体积不超过 10MB 且运行内存稳定在 60MB 以内,满帧率运行但 CPU 近乎不动
此测试应用通过 Win32 裸窗口创建方式创建窗口且开启窗口消息循环。利用 Direct2D 进行界面绘制,可以比较方便绘制出复杂且绚丽的界面,整体利用类似于直接利用 WPF 的 DrawingContext 绘制界面内容。整体应用只依赖 D2D 绘制界面以及一点点 Win32 函数用来创建窗口,除此之外没有其他的依赖。这是一个完全彻底的原生应用,且由于直接通过 D2D 绘制渲染,没有中间的框架层,整体的渲染服从不错,可以达成满帧率运行但 CPU 近乎不动的结果。以下是我的制作过程所需的依赖库和框架
整个测试应用接纳了 .NET 8 的框架,用于更好的支持 AOT 发布
利用了 Vortice 系列库用于对 DirectX 的封装,方便让编写调用 DirectX 的代码
利用了 Microsoft.Windows.CsWin32 方便进行 Win32 方法的调用
所有的代码都写在 Program.cs 文件里面,代码长度不到 370 行,更有趣的是,可以强行算是都写在 Main 方法里面。全部代码由 Main 方法以及放在 Main 方法里面的局部方法构成。整体实现非常简朴。我将会在本文末尾告诉大家本文的代码的下载方法
本文仅仅是分享我的开发经验,不包含 DirectX 的前置知识。如果不认识 D2D 和 DirectX 还请以看着玩的心态阅读本文
一开始接纳了 DirectX 利用 Vortice 从零开始控制台创建 Direct2D1 窗口修改颜色dotnet DirectX 通过 Vortice 控制台利用 ID2D1DeviceContext 绘制画面 博客提供的方法搭建了底子的应用框架
为了让界面更加的丰富,我预备在界面添加多个圆形。然后为了让界面动起来,我添加了名为 DrawingInfo 的布局体,用于存放每个圆形的坐标和大小等信息
  1. readonly record struct DrawingInfo(System.Numerics.Vector2 Offset, Size Size, D2D.ID2D1SolidColorBrush Brush);
复制代码
先在绘制的循环外对 DrawingInfo 进行随机设置值
  1.             var ellipseInfoList = new List<DrawingInfo>();
  2.             for (int i = 0; i < 3000; i++)
  3.             {
  4.                 // 随意创建颜色
  5.                 var color = new Color4((byte) Random.Shared.Next(255), (byte) Random.Shared.Next(255), (byte) Random.Shared.Next(255));
  6.                 D2D.ID2D1SolidColorBrush brush = renderTarget.CreateSolidColorBrush(color);
  7.                 ellipseInfoList.Add(new DrawingInfo(new System.Numerics.Vector2(Random.Shared.Next(clientSize.Width), Random.Shared.Next(clientSize.Height)), new Size(Random.Shared.Next(10, 100)), brush));
  8.             }
复制代码
进入循环之后,再每次修改 Offset 的值,这样就可以让每次绘制的圆形动起来
  1.             while (true)
  2.             {
  3.                 // 开始绘制逻辑
  4.                 renderTarget.BeginDraw();
  5.                 // 清空画布
  6.                 renderTarget.Clear(new Color4(0xFF, 0xFF, 0xFF));
  7.                 // 在下面绘制漂亮的界面
  8.                 for (var i = 0; i < ellipseInfoList.Count; i++)
  9.                 {
  10.                     var drawingInfo = ellipseInfoList[i];
  11.                     var vector2 = drawingInfo.Offset;
  12.                     vector2.X += Random.Shared.Next(200) - 100;
  13.                     vector2.Y += Random.Shared.Next(200) - 100;
  14.                     while (vector2.X < 100 || vector2.X > clientSize.Width - 100)
  15.                     {
  16.                         vector2.X = Random.Shared.Next(clientSize.Width);
  17.                     }
  18.                     while (vector2.Y < 100 || vector2.Y > clientSize.Height - 100)
  19.                     {
  20.                         vector2.Y = Random.Shared.Next(clientSize.Height);
  21.                     }
  22.                     ellipseInfoList[i] = drawingInfo with { Offset = vector2 };
  23.                     
  24.                     // 忽略其他代码
  25.                 }
  26.                 // 忽略其他代码
  27.             }
复制代码
以上的修改坐标代码只是为了让圆形每次都在其附近移动
附带就在里层循环将每个圆形绘制,代码如下
  1.                 // 在下面绘制漂亮的界面
  2.                 for (var i = 0; i < ellipseInfoList.Count; i++)
  3.                 {
  4.                     // 忽略其他代码
  5.                     renderTarget.FillEllipse(new D2D.Ellipse(vector2, drawingInfo.Size.Width, drawingInfo.Size.Height), drawingInfo.Brush);
  6.                 }
复制代码
大概的改动云云,接下来咱必要改造一下 csproj 项目文件,让此项目可以构建出 AOT 版本的应用
先修改 TargetFramework 为 net8.0 利用 .NET 8 可以更好构建 AOT 应用
  1.   <PropertyGroup>
  2.     <TargetFramework>net8.0</TargetFramework>
  3.   </PropertyGroup>
复制代码
接着为了减少不断提示的平台警告,添加以下代码忽略 CA1416 警告
  1.   <PropertyGroup>
  2.     <TargetFramework>net8.0</TargetFramework>
  3.   </PropertyGroup>  CA1416  
复制代码
接着再添加 PublishAot 属性,这样调用发布下令之后,就可以自动创建 AOT 应用的文件
  1.   <PropertyGroup>
  2.     <TargetFramework>net8.0</TargetFramework>
  3.   </PropertyGroup>  CA1416    true  
复制代码
此时运行起来将不会成功,将会提示大概如下的错误
  1. Unhandled Exception: System.MissingMethodException: No parameterless constructor defined for type 'Vortice.DXGI.IDXGIFactory2'.
  2.    at System.ActivatorImplementation.CreateInstance(Type, BindingFlags, Binder, Object[], CultureInfo, Object[]) + 0x348
  3.    at SharpGen.Runtime.MarshallingHelpers.FromPointer[T](IntPtr) + 0x8c
  4.    at Vortice.DXGI.DXGI.CreateDXGIFactory1[T]() + 0x55
  5.    at Program.<<Main>$>g__CreateD2D|0_2(Program.<>c__DisplayClass0_0&) + 0x90
  6.    at Program.<Main>$(String[] args) + 0x23e
  7.    at CedageawhakairnerewhalNaibiferenagifee!<BaseAddress>+0x17a3c0
复制代码
大概是如下的错误
  1. Unhandled Exception: System.MissingMethodException: No parameterless constructor defined for type 'Vortice.Direct3D11.ID3D11Device1'.
  2.    at System.ActivatorImplementation.CreateInstance(Type, BindingFlags, Binder, Object[], CultureInfo, Object[]) + 0x348
  3.    at SharpGen.Runtime.MarshallingHelpers.FromPointer[T](IntPtr) + 0x8c
  4.    at SharpGen.Runtime.ComObject.QueryInterface[T]() + 0x64
  5.    at Program.<<Main>$>g__CreateD2D|0_2(Program.<>c__DisplayClass0_0&) + 0x1c7
  6.    at Program.<Main>$(String[] args) + 0x23e
  7.    at CedageawhakairnerewhalNaibiferenagifee!<BaseAddress>+0x335cf0
复制代码
这是由于这些引用的库里面的类型在 AOT 的裁剪过程被丢掉
修复的方法很简朴,那就是将 Vortice 添加到 TrimmerRootAssembly 里面,防止在 AOT 过程被裁剪
  1.   <ItemGroup>
  2.     <TrimmerRootAssembly Include="Vortice.Win32"/>
  3.     <TrimmerRootAssembly Include="Vortice.DXGI"/>
  4.     <TrimmerRootAssembly Include="Vortice.Direct3D11"/>
  5.     <TrimmerRootAssembly Include="Vortice.Direct2D1"/>
  6.     <TrimmerRootAssembly Include="Vortice.D3DCompiler"/>
  7.     <TrimmerRootAssembly Include="Vortice.DirectX"/>
  8.     <TrimmerRootAssembly Include="Vortice.Mathematics"/>
  9.   </ItemGroup>
复制代码
修改之后的 csproj 代码如下
  1.       Exe    net8.0    enable    enable    true    CA1416  <ItemGroup>
  2.     <TrimmerRootAssembly Include="Vortice.Win32"/>
  3.     <TrimmerRootAssembly Include="Vortice.DXGI"/>
  4.     <TrimmerRootAssembly Include="Vortice.Direct3D11"/>
  5.     <TrimmerRootAssembly Include="Vortice.Direct2D1"/>
  6.     <TrimmerRootAssembly Include="Vortice.D3DCompiler"/>
  7.     <TrimmerRootAssembly Include="Vortice.DirectX"/>
  8.     <TrimmerRootAssembly Include="Vortice.Mathematics"/>
  9.   </ItemGroup>                              
复制代码
完成以上设置之后,即可利用下令行 dotnet publish 将项目进行发布,如果在发布的控制台可以看到 Generating native code 输出,那就证明设置精确,正在构建 AOT 文件
完成构建之后,即可在 bin\Release\net8.0\win-x64\publish 文件夹找到构建输出的文件,在我这里看到的输出文件大小大概在 10MB 以下,大家可以尝试利用本文末尾的方法拉取我的代码自己构建一下,试试结果
运行起来的任务管理器所见内存大小大约是 30MB 左右,通过 VMMap 工具检察 WorkingSet 和 Private Bytes 都在 60MB 以内。固然 Committed 的内存高达 300MB 但是绝大部分都是 Image 共享部分占用内存,如显卡驱动等部分的占用,这部分占用大约在 250MB 以上,现实的 Image 的 private 的占用不到 10MB 大小
我认为这个技能可以用来制作一些小而美的工具,甚至是不消考虑 x86 的,只需考虑 x64 的机器上运行的应用的安装包制作程序。要是拿着 D2D 绘制的界面去当安装包的界面,那估计安装包行业会卷起来
以下是所有的代码
  1. using D3D = Vortice.Direct3D;
  2. using D3D11 = Vortice.Direct3D11;
  3. using DXGI = Vortice.DXGI;
  4. using D2D = Vortice.Direct2D1;
  5. using System.Runtime.CompilerServices;
  6. using System.Runtime.InteropServices;
  7. using Windows.Win32.Foundation;
  8. using Windows.Win32.UI.WindowsAndMessaging;
  9. using static Windows.Win32.PInvoke;
  10. using static Windows.Win32.UI.WindowsAndMessaging.PEEK_MESSAGE_REMOVE_TYPE;
  11. using static Windows.Win32.UI.WindowsAndMessaging.WNDCLASS_STYLES;
  12. using static Windows.Win32.UI.WindowsAndMessaging.WINDOW_STYLE;
  13. using static Windows.Win32.UI.WindowsAndMessaging.WINDOW_EX_STYLE;
  14. using static Windows.Win32.UI.WindowsAndMessaging.SYSTEM_METRICS_INDEX;
  15. using static Windows.Win32.UI.WindowsAndMessaging.SHOW_WINDOW_CMD;
  16. using Vortice.DCommon;
  17. using Vortice.Mathematics;
  18. using AlphaMode = Vortice.DXGI.AlphaMode;
  19. using System.Diagnostics;
  20. unsafe
  21. {
  22.     SizeI clientSize = new SizeI(1000, 1000);
  23.     // 窗口标题
  24.     var title = "lindexi D2D AOT";
  25.     var windowClassName = title;
  26.     WINDOW_STYLE style = WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_CLIPSIBLINGS | WS_BORDER | WS_DLGFRAME | WS_THICKFRAME | WS_GROUP | WS_TABSTOP | WS_SIZEBOX;
  27.     var rect = new RECT
  28.     {
  29.         right = clientSize.Width,
  30.         bottom = clientSize.Height
  31.     };
  32.     AdjustWindowRectEx(&rect, style, false, WS_EX_APPWINDOW);
  33.     int x = 0;
  34.     int y = 0;
  35.     int windowWidth = rect.right - rect.left;
  36.     int windowHeight = rect.bottom - rect.top;
  37.     // 随便,放在屏幕中间好了。多个显示器?忽略
  38.     int screenWidth = GetSystemMetrics(SM_CXSCREEN);
  39.     int screenHeight = GetSystemMetrics(SM_CYSCREEN);
  40.     x = (screenWidth - windowWidth) / 2;
  41.     y = (screenHeight - windowHeight) / 2;
  42.     var hInstance = GetModuleHandle((string) null);
  43.     fixed (char* lpszClassName = windowClassName)
  44.     {
  45.         PCWSTR szCursorName = new((char*) IDC_ARROW);
  46.         var wndClassEx = new WNDCLASSEXW
  47.         {
  48.             cbSize = (uint) Unsafe.SizeOf<WNDCLASSEXW>(),
  49.             style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC,
  50.             // 核心逻辑,设置消息循环
  51.             lpfnWndProc = new WNDPROC(WndProc),
  52.             hInstance = (HINSTANCE) hInstance.DangerousGetHandle(),
  53.             hCursor = LoadCursor((HINSTANCE) IntPtr.Zero, szCursorName),
  54.             hbrBackground = (Windows.Win32.Graphics.Gdi.HBRUSH) IntPtr.Zero,
  55.             hIcon = (HICON) IntPtr.Zero,
  56.             lpszClassName = lpszClassName
  57.         };
  58.         ushort atom = RegisterClassEx(wndClassEx);
  59.         if (atom == 0)
  60.         {
  61.             throw new InvalidOperationException($"Failed to register window class. Error: {Marshal.GetLastWin32Error()}");
  62.         }
  63.     }
  64.     // 创建窗口
  65.     var hWnd = CreateWindowEx
  66.     (
  67.         WS_EX_APPWINDOW,
  68.         windowClassName,
  69.         title,
  70.         style,
  71.         x,
  72.         y,
  73.         windowWidth,
  74.         windowHeight,
  75.         hWndParent: default,
  76.         hMenu: default,
  77.         hInstance: default,
  78.         lpParam: null
  79.     );
  80.     // 创建完成,那就显示
  81.     ShowWindow(hWnd, SW_NORMAL);
  82.     CreateD2D();
  83.     // 开个消息循环等待
  84.     Windows.Win32.UI.WindowsAndMessaging.MSG msg;
  85.     while (true)
  86.     {
  87.         if (GetMessage(out msg, hWnd, 0, 0) != false)
  88.         {
  89.             _ = TranslateMessage(&msg);
  90.             _ = DispatchMessage(&msg);
  91.             if (msg.message is WM_QUIT or WM_CLOSE or 0)
  92.             {
  93.                 return;
  94.             }
  95.         }
  96.     }
  97.     void CreateD2D()
  98.     {
  99.         RECT windowRect;
  100.         GetClientRect(hWnd, &windowRect);
  101.         clientSize = new SizeI(windowRect.right - windowRect.left, windowRect.bottom - windowRect.top);
  102.         // 开始创建工厂创建 D3D 的逻辑
  103.         var dxgiFactory2 = DXGI.DXGI.CreateDXGIFactory1<DXGI.IDXGIFactory2>();
  104.         var hardwareAdapter = GetHardwareAdapter(dxgiFactory2)
  105.             // 这里 ToList 只是想列出所有的 IDXGIAdapter1 方便调试而已。在实际代码里,大部分都是获取第一个
  106.             .ToList().FirstOrDefault();
  107.         if (hardwareAdapter == null)
  108.         {
  109.             throw new InvalidOperationException("Cannot detect D3D11 adapter");
  110.         }
  111.         // 功能等级
  112.         // [C# 从零开始写 SharpDx 应用 聊聊功能等级](https://blog.lindexi.com/post/C-%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E5%86%99-SharpDx-%E5%BA%94%E7%94%A8-%E8%81%8A%E8%81%8A%E5%8A%9F%E8%83%BD%E7%AD%89%E7%BA%A7.html)
  113.         D3D.FeatureLevel[] featureLevels = new[]
  114.         {
  115.             D3D.FeatureLevel.Level_11_1,
  116.             D3D.FeatureLevel.Level_11_0,
  117.             D3D.FeatureLevel.Level_10_1,
  118.             D3D.FeatureLevel.Level_10_0,
  119.             D3D.FeatureLevel.Level_9_3,
  120.             D3D.FeatureLevel.Level_9_2,
  121.             D3D.FeatureLevel.Level_9_1,
  122.         };
  123.         DXGI.IDXGIAdapter1 adapter = hardwareAdapter;
  124.         D3D11.DeviceCreationFlags creationFlags = D3D11.DeviceCreationFlags.BgraSupport;
  125.         var result = D3D11.D3D11.D3D11CreateDevice
  126.         (
  127.             adapter,
  128.             D3D.DriverType.Unknown,
  129.             creationFlags,
  130.             featureLevels,
  131.             out D3D11.ID3D11Device d3D11Device, out D3D.FeatureLevel featureLevel,
  132.             out D3D11.ID3D11DeviceContext d3D11DeviceContext
  133.         );
  134.         if (result.Failure)
  135.         {
  136.             // 如果失败了,那就不指定显卡,走 WARP 的方式
  137.             // http://go.microsoft.com/fwlink/?LinkId=286690
  138.             result = D3D11.D3D11.D3D11CreateDevice(
  139.                 IntPtr.Zero,
  140.                 D3D.DriverType.Warp,
  141.                 creationFlags,
  142.                 featureLevels,
  143.                 out d3D11Device, out featureLevel, out d3D11DeviceContext);
  144.             // 如果失败,就不能继续
  145.             result.CheckError();
  146.         }
  147.         // 大部分情况下,用的是 ID3D11Device1 和 ID3D11DeviceContext1 类型
  148.         // 从 ID3D11Device 转换为 ID3D11Device1 类型
  149.         var d3D11Device1 = d3D11Device.QueryInterface<D3D11.ID3D11Device1>();
  150.         var d3D11DeviceContext1 = d3D11DeviceContext.QueryInterface<D3D11.ID3D11DeviceContext1>();
  151.         // 转换完成,可以减少对 ID3D11Device1 的引用计数
  152.         // 调用 Dispose 不会释放掉刚才申请的 D3D 资源,只是减少引用计数
  153.         d3D11Device.Dispose();
  154.         d3D11DeviceContext.Dispose();
  155.         // 创建设备,接下来就是关联窗口和交换链
  156.         DXGI.Format colorFormat = DXGI.Format.B8G8R8A8_UNorm;
  157.         const int FrameCount = 2;
  158.         DXGI.SwapChainDescription1 swapChainDescription = new()
  159.         {
  160.             Width = clientSize.Width,
  161.             Height = clientSize.Height,
  162.             Format = colorFormat,
  163.             BufferCount = FrameCount,
  164.             BufferUsage = DXGI.Usage.RenderTargetOutput,
  165.             SampleDescription = DXGI.SampleDescription.Default,
  166.             Scaling = DXGI.Scaling.Stretch,
  167.             SwapEffect = DXGI.SwapEffect.FlipDiscard,
  168.             AlphaMode = AlphaMode.Ignore,
  169.         };
  170.         // 设置是否全屏
  171.         DXGI.SwapChainFullscreenDescription fullscreenDescription = new DXGI.SwapChainFullscreenDescription
  172.         {
  173.             Windowed = true
  174.         };
  175.         // 给创建出来的窗口挂上交换链
  176.         DXGI.IDXGISwapChain1 swapChain =
  177.             dxgiFactory2.CreateSwapChainForHwnd(d3D11Device1, hWnd, swapChainDescription, fullscreenDescription);
  178.         // 不要被按下 alt+enter 进入全屏
  179.         dxgiFactory2.MakeWindowAssociation(hWnd, DXGI.WindowAssociationFlags.IgnoreAltEnter);
  180.         D3D11.ID3D11Texture2D backBufferTexture = swapChain.GetBuffer<D3D11.ID3D11Texture2D>(0);
  181.         // 获取到 dxgi 的平面,这个平面就约等于窗口渲染内容
  182.         DXGI.IDXGISurface dxgiSurface = backBufferTexture.QueryInterface<DXGI.IDXGISurface>();
  183.         // 对接 D2D 需要创建工厂
  184.         D2D.ID2D1Factory1 d2DFactory = D2D.D2D1.D2D1CreateFactory<D2D.ID2D1Factory1>();
  185.         // 方法1:
  186.         //var renderTargetProperties = new D2D.RenderTargetProperties(PixelFormat.Premultiplied);
  187.         //// 在窗口的 dxgi 的平面上创建 D2D 的画布,如此即可让 D2D 绘制到窗口上
  188.         //D2D.ID2D1RenderTarget d2D1RenderTarget =
  189.         //    d2DFactory.CreateDxgiSurfaceRenderTarget(dxgiSurface, renderTargetProperties);
  190.         //var renderTarget = d2D1RenderTarget;
  191.         // 方法2:
  192.         // 创建 D2D 设备,通过设置 ID2D1DeviceContext 的 Target 输出为 dxgiSurface 从而让 ID2D1DeviceContext 渲染内容渲染到窗口上
  193.         // 如 https://learn.microsoft.com/en-us/windows/win32/direct2d/images/devicecontextdiagram.png 图
  194.         // 获取 DXGI 设备,用来创建 D2D 设备
  195.         DXGI.IDXGIDevice dxgiDevice = d3D11Device1.QueryInterface<DXGI.IDXGIDevice>();
  196.         D2D.ID2D1Device d2dDevice = d2DFactory.CreateDevice(dxgiDevice);
  197.         D2D.ID2D1DeviceContext d2dDeviceContext = d2dDevice.CreateDeviceContext();
  198.         D2D.ID2D1Bitmap1 d2dBitmap = d2dDeviceContext.CreateBitmapFromDxgiSurface(dxgiSurface);
  199.         d2dDeviceContext.Target = d2dBitmap;
  200.         var renderTarget = d2dDeviceContext;
  201.         // 开启后台渲染线程,无限刷新
  202.         var stopwatch = Stopwatch.StartNew();
  203.         var count = 0;
  204.         Task.Factory.StartNew(() =>
  205.         {
  206.             var ellipseInfoList = new List<DrawingInfo>();
  207.             for (int i = 0; i < 100; i++)
  208.             {
  209.                 // 随意创建颜色
  210.                 var color = new Color4((byte) Random.Shared.Next(255), (byte) Random.Shared.Next(255), (byte) Random.Shared.Next(255));
  211.                 D2D.ID2D1SolidColorBrush brush = renderTarget.CreateSolidColorBrush(color);
  212.                 ellipseInfoList.Add(new DrawingInfo(new System.Numerics.Vector2(Random.Shared.Next(clientSize.Width), Random.Shared.Next(clientSize.Height)), new Size(Random.Shared.Next(10, 100)), brush));
  213.             }
  214.             while (true)
  215.             {
  216.                 // 开始绘制逻辑
  217.                 renderTarget.BeginDraw();
  218.                 // 清空画布
  219.                 renderTarget.Clear(new Color4(0xFF, 0xFF, 0xFF));
  220.                 // 在下面绘制漂亮的界面
  221.                 for (var i = 0; i < ellipseInfoList.Count; i++)
  222.                 {
  223.                     var drawingInfo = ellipseInfoList[i];
  224.                     var vector2 = drawingInfo.Offset;
  225.                     vector2.X += Random.Shared.Next(200) - 100;
  226.                     vector2.Y += Random.Shared.Next(200) - 100;
  227.                     while (vector2.X < 100 || vector2.X > clientSize.Width - 100)
  228.                     {
  229.                         vector2.X = Random.Shared.Next(clientSize.Width);
  230.                     }
  231.                     while (vector2.Y < 100 || vector2.Y > clientSize.Height - 100)
  232.                     {
  233.                         vector2.Y = Random.Shared.Next(clientSize.Height);
  234.                     }
  235.                     ellipseInfoList[i] = drawingInfo with { Offset = vector2 };
  236.                     renderTarget.FillEllipse(new D2D.Ellipse(vector2, drawingInfo.Size.Width, drawingInfo.Size.Height), drawingInfo.Brush);
  237.                 }
  238.                 renderTarget.EndDraw();
  239.                 swapChain.Present(1, DXGI.PresentFlags.None);
  240.                 // 等待刷新
  241.                 d3D11DeviceContext1.Flush();
  242.                 // 统计刷新率
  243.                 count++;
  244.                 if (stopwatch.Elapsed >= TimeSpan.FromSeconds(1))
  245.                 {
  246.                     Console.WriteLine($"FPS: {count / stopwatch.Elapsed.TotalSeconds}");
  247.                     stopwatch.Restart();
  248.                     count = 0;
  249.                 }
  250.             }
  251.         }, TaskCreationOptions.LongRunning);
  252.     }
  253. }
  254. static IEnumerable<DXGI.IDXGIAdapter1> GetHardwareAdapter(DXGI.IDXGIFactory2 factory)
  255. {
  256.     DXGI.IDXGIFactory6? factory6 = factory.QueryInterfaceOrNull<DXGI.IDXGIFactory6>();
  257.     if (factory6 != null)
  258.     {
  259.         // 先告诉系统,要高性能的显卡
  260.         for (int adapterIndex = 0;
  261.              factory6.EnumAdapterByGpuPreference(adapterIndex, DXGI.GpuPreference.HighPerformance,
  262.                  out DXGI.IDXGIAdapter1? adapter).Success;
  263.              adapterIndex++)
  264.         {
  265.             if (adapter == null)
  266.             {
  267.                 continue;
  268.             }
  269.             DXGI.AdapterDescription1 desc = adapter.Description1;
  270.             if ((desc.Flags & DXGI.AdapterFlags.Software) != DXGI.AdapterFlags.None)
  271.             {
  272.                 // Don't select the Basic Render Driver adapter.
  273.                 adapter.Dispose();
  274.                 continue;
  275.             }
  276.             //factory6.Dispose();
  277.             Console.WriteLine($"枚举到 {adapter.Description1.Description} 显卡");
  278.             yield return adapter;
  279.         }
  280.         factory6.Dispose();
  281.     }
  282.     // 如果枚举不到,那系统返回啥都可以
  283.     for (int adapterIndex = 0;
  284.          factory.EnumAdapters1(adapterIndex, out DXGI.IDXGIAdapter1? adapter).Success;
  285.          adapterIndex++)
  286.     {
  287.         DXGI.AdapterDescription1 desc = adapter.Description1;
  288.         if ((desc.Flags & DXGI.AdapterFlags.Software) != DXGI.AdapterFlags.None)
  289.         {
  290.             // Don't select the Basic Render Driver adapter.
  291.             adapter.Dispose();
  292.             continue;
  293.         }
  294.         Console.WriteLine($"枚举到 {adapter.Description1.Description} 显卡");
  295.         yield return adapter;
  296.     }
  297. }
  298. static LRESULT WndProc(HWND hWnd, uint message, WPARAM wParam, LPARAM lParam)
  299. {
  300.     return DefWindowProc(hWnd, message, wParam, lParam);
  301. }
  302. readonly record struct DrawingInfo(System.Numerics.Vector2 Offset, Size Size, D2D.ID2D1SolidColorBrush Brush);
复制代码
本文以上代码放在githubgitee 接待访问
可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着利用下令行 cd 下令进入此空文件夹,在下令行里面输入以下代码,即可获取到本文的代码
  1. git init
  2. git remote add origin https://gitee.com/lindexi/lindexi_gd.git
  3. git pull origin 66f9fe05baba8ad30495069aebd447b160484215
复制代码
以上利用的是 gitee 的源,如果 gitee 不能访问,请更换为 github 的源。请在下令行继续输入以下代码
  1. git remote remove origin
  2. git remote add origin https://github.com/lindexi/lindexi_gd.git
  3. git pull origin 66f9fe05baba8ad30495069aebd447b160484215
复制代码
获取代码之后,进入 CedageawhakairnerewhalNaibiferenagifee 文件夹
更多关于 DirectX 和 D2D 相关技能请参阅我的 博客导航
交流 Vortice 技能,接待加群: 622808968

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com (https://dis.qidao123.com/) Powered by Discuz! X3.4