翼度科技»论坛 编程开发 .net 查看内容

分享一个在 dotnet 里使用 D2D 配合 AOT 开发小而美的应用开发经验

8

主题

8

帖子

24

积分

新手上路

Rank: 1

积分
24
本文将分享我在 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

来源:https://www.cnblogs.com/lindexi/p/18019833
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具