前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >分享一个在 dotnet 里使用 D2D 配合 AOT 开发小而美的应用开发经验

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

作者头像
林德熙
发布于 2024-02-19 01:22:18
发布于 2024-02-19 01:22:18
16300
代码可运行
举报
文章被收录于专栏:林德熙的博客林德熙的博客
运行总次数:0
代码可运行

本文将分享我在 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 的结构体,用于存放每个圆形的坐标和大小等信息

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
readonly record struct DrawingInfo(System.Numerics.Vector2 Offset, Size Size, D2D.ID2D1SolidColorBrush Brush);

先在绘制的循环外对 DrawingInfo 进行随机设置值

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
            var ellipseInfoList = new List<DrawingInfo>();
            for (int i = 0; i < 3000; i++)
            {
                // 随意创建颜色
                var color = new Color4((byte) Random.Shared.Next(255), (byte) Random.Shared.Next(255), (byte) Random.Shared.Next(255));
                D2D.ID2D1SolidColorBrush brush = renderTarget.CreateSolidColorBrush(color);
                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));
            }

进入循环之后,再每次修改 Offset 的值,这样就可以让每次绘制的圆形动起来

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
            while (true)
            {
                // 开始绘制逻辑
                renderTarget.BeginDraw();

                // 清空画布
                renderTarget.Clear(new Color4(0xFF, 0xFF, 0xFF));

                // 在下面绘制漂亮的界面

                for (var i = 0; i < ellipseInfoList.Count; i++)
                {
                    var drawingInfo = ellipseInfoList[i];
                    var vector2 = drawingInfo.Offset;
                    vector2.X += Random.Shared.Next(200) - 100;
                    vector2.Y += Random.Shared.Next(200) - 100;

                    while (vector2.X < 100 || vector2.X > clientSize.Width - 100)
                    {
                        vector2.X = Random.Shared.Next(clientSize.Width);
                    }

                    while (vector2.Y < 100 || vector2.Y > clientSize.Height - 100)
                    {
                        vector2.Y = Random.Shared.Next(clientSize.Height);
                    }

                    ellipseInfoList[i] = drawingInfo with { Offset = vector2 };
                    
                    // 忽略其他代码
                }

                // 忽略其他代码
            }

以上的修改坐标代码只是为了让圆形每次都在其附近移动

附带就在里层循环将每个圆形绘制,代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
                // 在下面绘制漂亮的界面

                for (var i = 0; i < ellipseInfoList.Count; i++)
                {
                    // 忽略其他代码
                    renderTarget.FillEllipse(new D2D.Ellipse(vector2, drawingInfo.Size.Width, drawingInfo.Size.Height), drawingInfo.Brush);
                }

大概的改动如此,接下来咱需要改造一下 csproj 项目文件,让此项目可以构建出 AOT 版本的应用

先修改 TargetFramework 为 net8.0 使用 .NET 8 可以更好构建 AOT 应用

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>

接着为了减少不断提示的平台警告,添加以下代码忽略 CA1416 警告

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <NoWarn>CA1416</NoWarn>
  </PropertyGroup>

接着再添加 PublishAot 属性,这样调用发布命令之后,就可以自动创建 AOT 应用的文件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <NoWarn>CA1416</NoWarn>
    <PublishAot>true</PublishAot>
  </PropertyGroup>

此时运行起来将不会成功,将会提示大概如下的错误

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Unhandled Exception: System.MissingMethodException: No parameterless constructor defined for type 'Vortice.DXGI.IDXGIFactory2'.
   at System.ActivatorImplementation.CreateInstance(Type, BindingFlags, Binder, Object[], CultureInfo, Object[]) + 0x348
   at SharpGen.Runtime.MarshallingHelpers.FromPointer[T](IntPtr) + 0x8c
   at Vortice.DXGI.DXGI.CreateDXGIFactory1[T]() + 0x55
   at Program.<<Main>$>g__CreateD2D|0_2(Program.<>c__DisplayClass0_0&) + 0x90
   at Program.<Main>$(String[] args) + 0x23e
   at CedageawhakairnerewhalNaibiferenagifee!<BaseAddress>+0x17a3c0

或者是如下的错误

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Unhandled Exception: System.MissingMethodException: No parameterless constructor defined for type 'Vortice.Direct3D11.ID3D11Device1'.
   at System.ActivatorImplementation.CreateInstance(Type, BindingFlags, Binder, Object[], CultureInfo, Object[]) + 0x348
   at SharpGen.Runtime.MarshallingHelpers.FromPointer[T](IntPtr) + 0x8c
   at SharpGen.Runtime.ComObject.QueryInterface[T]() + 0x64
   at Program.<<Main>$>g__CreateD2D|0_2(Program.<>c__DisplayClass0_0&) + 0x1c7
   at Program.<Main>$(String[] args) + 0x23e
   at CedageawhakairnerewhalNaibiferenagifee!<BaseAddress>+0x335cf0

这是因为这些引用的库里面的类型在 AOT 的裁剪过程被丢掉

修复的方法很简单,那就是将 Vortice 添加到 TrimmerRootAssembly 里面,防止在 AOT 过程被裁剪

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  <ItemGroup>
    <TrimmerRootAssembly Include="Vortice.Win32"/>
    <TrimmerRootAssembly Include="Vortice.DXGI"/>
    <TrimmerRootAssembly Include="Vortice.Direct3D11"/>
    <TrimmerRootAssembly Include="Vortice.Direct2D1"/>
    <TrimmerRootAssembly Include="Vortice.D3DCompiler"/>
    <TrimmerRootAssembly Include="Vortice.DirectX"/>
    <TrimmerRootAssembly Include="Vortice.Mathematics"/>
  </ItemGroup>

修改之后的 csproj 代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <PublishAot>true</PublishAot>
    <NoWarn>CA1416</NoWarn>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Vortice.Direct2D1" Version="2.1.32" />
    <PackageReference Include="Vortice.Direct3D11" Version="2.1.32" />
    <PackageReference Include="Vortice.DirectX" Version="2.1.32" />
    <PackageReference Include="Vortice.D3DCompiler" Version="2.1.32" />
    <PackageReference Include="Vortice.Win32" Version="1.6.2" />
    <PackageReference Include="Microsoft.Windows.CsWin32" PrivateAssets="all" Version="0.2.63-beta" />
  </ItemGroup>
  <ItemGroup>
    <TrimmerRootAssembly Include="Vortice.Win32"/>
    <TrimmerRootAssembly Include="Vortice.DXGI"/>
    <TrimmerRootAssembly Include="Vortice.Direct3D11"/>
    <TrimmerRootAssembly Include="Vortice.Direct2D1"/>
    <TrimmerRootAssembly Include="Vortice.D3DCompiler"/>
    <TrimmerRootAssembly Include="Vortice.DirectX"/>
    <TrimmerRootAssembly Include="Vortice.Mathematics"/>
  </ItemGroup>
</Project>

完成以上配置之后,即可使用命令行 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 绘制的界面去当安装包的界面,那估计安装包行业会卷起来

以下是所有的代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
using D3D = Vortice.Direct3D;
using D3D11 = Vortice.Direct3D11;
using DXGI = Vortice.DXGI;
using D2D = Vortice.Direct2D1;

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
using static Windows.Win32.PInvoke;
using static Windows.Win32.UI.WindowsAndMessaging.PEEK_MESSAGE_REMOVE_TYPE;
using static Windows.Win32.UI.WindowsAndMessaging.WNDCLASS_STYLES;
using static Windows.Win32.UI.WindowsAndMessaging.WINDOW_STYLE;
using static Windows.Win32.UI.WindowsAndMessaging.WINDOW_EX_STYLE;
using static Windows.Win32.UI.WindowsAndMessaging.SYSTEM_METRICS_INDEX;
using static Windows.Win32.UI.WindowsAndMessaging.SHOW_WINDOW_CMD;
using Vortice.DCommon;
using Vortice.Mathematics;
using AlphaMode = Vortice.DXGI.AlphaMode;
using System.Diagnostics;

unsafe
{
    SizeI clientSize = new SizeI(1000, 1000);

    // 窗口标题
    var title = "lindexi D2D AOT";
    var windowClassName = title;

    WINDOW_STYLE style = WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_CLIPSIBLINGS | WS_BORDER | WS_DLGFRAME | WS_THICKFRAME | WS_GROUP | WS_TABSTOP | WS_SIZEBOX;

    var rect = new RECT
    {
        right = clientSize.Width,
        bottom = clientSize.Height
    };

    AdjustWindowRectEx(&rect, style, false, WS_EX_APPWINDOW);

    int x = 0;
    int y = 0;
    int windowWidth = rect.right - rect.left;
    int windowHeight = rect.bottom - rect.top;

    // 随便,放在屏幕中间好了。多个显示器?忽略
    int screenWidth = GetSystemMetrics(SM_CXSCREEN);
    int screenHeight = GetSystemMetrics(SM_CYSCREEN);

    x = (screenWidth - windowWidth) / 2;
    y = (screenHeight - windowHeight) / 2;

    var hInstance = GetModuleHandle((string) null);

    fixed (char* lpszClassName = windowClassName)
    {
        PCWSTR szCursorName = new((char*) IDC_ARROW);

        var wndClassEx = new WNDCLASSEXW
        {
            cbSize = (uint) Unsafe.SizeOf<WNDCLASSEXW>(),
            style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC,
            // 核心逻辑,设置消息循环
            lpfnWndProc = new WNDPROC(WndProc),
            hInstance = (HINSTANCE) hInstance.DangerousGetHandle(),
            hCursor = LoadCursor((HINSTANCE) IntPtr.Zero, szCursorName),
            hbrBackground = (Windows.Win32.Graphics.Gdi.HBRUSH) IntPtr.Zero,
            hIcon = (HICON) IntPtr.Zero,
            lpszClassName = lpszClassName
        };

        ushort atom = RegisterClassEx(wndClassEx);

        if (atom == 0)
        {
            throw new InvalidOperationException($"Failed to register window class. Error: {Marshal.GetLastWin32Error()}");
        }
    }

    // 创建窗口
    var hWnd = CreateWindowEx
    (
        WS_EX_APPWINDOW,
        windowClassName,
        title,
        style,
        x,
        y,
        windowWidth,
        windowHeight,
        hWndParent: default,
        hMenu: default,
        hInstance: default,
        lpParam: null
    );

    // 创建完成,那就显示
    ShowWindow(hWnd, SW_NORMAL);

    CreateD2D();

    // 开个消息循环等待
    Windows.Win32.UI.WindowsAndMessaging.MSG msg;
    while (true)
    {
        if (GetMessage(out msg, hWnd, 0, 0) != false)
        {
            _ = TranslateMessage(&msg);
            _ = DispatchMessage(&msg);

            if (msg.message is WM_QUIT or WM_CLOSE or 0)
            {
                return;
            }
        }
    }

    void CreateD2D()
    {
        RECT windowRect;
        GetClientRect(hWnd, &windowRect);
        clientSize = new SizeI(windowRect.right - windowRect.left, windowRect.bottom - windowRect.top);

        // 开始创建工厂创建 D3D 的逻辑
        var dxgiFactory2 = DXGI.DXGI.CreateDXGIFactory1<DXGI.IDXGIFactory2>();

        var hardwareAdapter = GetHardwareAdapter(dxgiFactory2)
            // 这里 ToList 只是想列出所有的 IDXGIAdapter1 方便调试而已。在实际代码里,大部分都是获取第一个
            .ToList().FirstOrDefault();

        if (hardwareAdapter == null)
        {
            throw new InvalidOperationException("Cannot detect D3D11 adapter");
        }

        // 功能等级
        // [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)
        D3D.FeatureLevel[] featureLevels = new[]
        {
            D3D.FeatureLevel.Level_11_1,
            D3D.FeatureLevel.Level_11_0,
            D3D.FeatureLevel.Level_10_1,
            D3D.FeatureLevel.Level_10_0,
            D3D.FeatureLevel.Level_9_3,
            D3D.FeatureLevel.Level_9_2,
            D3D.FeatureLevel.Level_9_1,
        };

        DXGI.IDXGIAdapter1 adapter = hardwareAdapter;
        D3D11.DeviceCreationFlags creationFlags = D3D11.DeviceCreationFlags.BgraSupport;
        var result = D3D11.D3D11.D3D11CreateDevice
        (
            adapter,
            D3D.DriverType.Unknown,
            creationFlags,
            featureLevels,
            out D3D11.ID3D11Device d3D11Device, out D3D.FeatureLevel featureLevel,
            out D3D11.ID3D11DeviceContext d3D11DeviceContext
        );

        if (result.Failure)
        {
            // 如果失败了,那就不指定显卡,走 WARP 的方式
            // http://go.microsoft.com/fwlink/?LinkId=286690
            result = D3D11.D3D11.D3D11CreateDevice(
                IntPtr.Zero,
                D3D.DriverType.Warp,
                creationFlags,
                featureLevels,
                out d3D11Device, out featureLevel, out d3D11DeviceContext);

            // 如果失败,就不能继续
            result.CheckError();
        }

        // 大部分情况下,用的是 ID3D11Device1 和 ID3D11DeviceContext1 类型
        // 从 ID3D11Device 转换为 ID3D11Device1 类型
        var d3D11Device1 = d3D11Device.QueryInterface<D3D11.ID3D11Device1>();
        var d3D11DeviceContext1 = d3D11DeviceContext.QueryInterface<D3D11.ID3D11DeviceContext1>();

        // 转换完成,可以减少对 ID3D11Device1 的引用计数
        // 调用 Dispose 不会释放掉刚才申请的 D3D 资源,只是减少引用计数
        d3D11Device.Dispose();
        d3D11DeviceContext.Dispose();

        // 创建设备,接下来就是关联窗口和交换链
        DXGI.Format colorFormat = DXGI.Format.B8G8R8A8_UNorm;

        const int FrameCount = 2;

        DXGI.SwapChainDescription1 swapChainDescription = new()
        {
            Width = clientSize.Width,
            Height = clientSize.Height,
            Format = colorFormat,
            BufferCount = FrameCount,
            BufferUsage = DXGI.Usage.RenderTargetOutput,
            SampleDescription = DXGI.SampleDescription.Default,
            Scaling = DXGI.Scaling.Stretch,
            SwapEffect = DXGI.SwapEffect.FlipDiscard,
            AlphaMode = AlphaMode.Ignore,
        };

        // 设置是否全屏
        DXGI.SwapChainFullscreenDescription fullscreenDescription = new DXGI.SwapChainFullscreenDescription
        {
            Windowed = true
        };

        // 给创建出来的窗口挂上交换链
        DXGI.IDXGISwapChain1 swapChain = 
            dxgiFactory2.CreateSwapChainForHwnd(d3D11Device1, hWnd, swapChainDescription, fullscreenDescription);

        // 不要被按下 alt+enter 进入全屏
        dxgiFactory2.MakeWindowAssociation(hWnd, DXGI.WindowAssociationFlags.IgnoreAltEnter);

        D3D11.ID3D11Texture2D backBufferTexture = swapChain.GetBuffer<D3D11.ID3D11Texture2D>(0);

        // 获取到 dxgi 的平面,这个平面就约等于窗口渲染内容
        DXGI.IDXGISurface dxgiSurface = backBufferTexture.QueryInterface<DXGI.IDXGISurface>();

        // 对接 D2D 需要创建工厂
        D2D.ID2D1Factory1 d2DFactory = D2D.D2D1.D2D1CreateFactory<D2D.ID2D1Factory1>();

        // 方法1:
        //var renderTargetProperties = new D2D.RenderTargetProperties(PixelFormat.Premultiplied);

        //// 在窗口的 dxgi 的平面上创建 D2D 的画布,如此即可让 D2D 绘制到窗口上
        //D2D.ID2D1RenderTarget d2D1RenderTarget =
        //    d2DFactory.CreateDxgiSurfaceRenderTarget(dxgiSurface, renderTargetProperties);
        //var renderTarget = d2D1RenderTarget;

        // 方法2:
        // 创建 D2D 设备,通过设置 ID2D1DeviceContext 的 Target 输出为 dxgiSurface 从而让 ID2D1DeviceContext 渲染内容渲染到窗口上
        // 如 https://learn.microsoft.com/en-us/windows/win32/direct2d/images/devicecontextdiagram.png 图
        // 获取 DXGI 设备,用来创建 D2D 设备
        DXGI.IDXGIDevice dxgiDevice = d3D11Device1.QueryInterface<DXGI.IDXGIDevice>();
        D2D.ID2D1Device d2dDevice = d2DFactory.CreateDevice(dxgiDevice);
        D2D.ID2D1DeviceContext d2dDeviceContext = d2dDevice.CreateDeviceContext();

        D2D.ID2D1Bitmap1 d2dBitmap = d2dDeviceContext.CreateBitmapFromDxgiSurface(dxgiSurface);
        d2dDeviceContext.Target = d2dBitmap;

        var renderTarget = d2dDeviceContext;

        // 开启后台渲染线程,无限刷新

        var stopwatch = Stopwatch.StartNew();
        var count = 0;

        Task.Factory.StartNew(() =>
        {
            var ellipseInfoList = new List<DrawingInfo>();
            for (int i = 0; i < 100; i++)
            {
                // 随意创建颜色
                var color = new Color4((byte) Random.Shared.Next(255), (byte) Random.Shared.Next(255), (byte) Random.Shared.Next(255));
                D2D.ID2D1SolidColorBrush brush = renderTarget.CreateSolidColorBrush(color);
                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));
            }
            while (true)
            {
                // 开始绘制逻辑
                renderTarget.BeginDraw();

                // 清空画布
                renderTarget.Clear(new Color4(0xFF, 0xFF, 0xFF));

                // 在下面绘制漂亮的界面

                for (var i = 0; i < ellipseInfoList.Count; i++)
                {
                    var drawingInfo = ellipseInfoList[i];
                    var vector2 = drawingInfo.Offset;
                    vector2.X += Random.Shared.Next(200) - 100;
                    vector2.Y += Random.Shared.Next(200) - 100;

                    while (vector2.X < 100 || vector2.X > clientSize.Width - 100)
                    {
                        vector2.X = Random.Shared.Next(clientSize.Width);
                    }

                    while (vector2.Y < 100 || vector2.Y > clientSize.Height - 100)
                    {
                        vector2.Y = Random.Shared.Next(clientSize.Height);
                    }

                    ellipseInfoList[i] = drawingInfo with { Offset = vector2 };
                    renderTarget.FillEllipse(new D2D.Ellipse(vector2, drawingInfo.Size.Width, drawingInfo.Size.Height), drawingInfo.Brush);
                }

                renderTarget.EndDraw();

                swapChain.Present(1, DXGI.PresentFlags.None);
                // 等待刷新
                d3D11DeviceContext1.Flush();

                // 统计刷新率
                count++;
                if (stopwatch.Elapsed >= TimeSpan.FromSeconds(1))
                {
                    Console.WriteLine($"FPS: {count / stopwatch.Elapsed.TotalSeconds}");
                    stopwatch.Restart();
                    count = 0;
                }
            }
        }, TaskCreationOptions.LongRunning);
    }
}

static IEnumerable<DXGI.IDXGIAdapter1> GetHardwareAdapter(DXGI.IDXGIFactory2 factory)
{
    DXGI.IDXGIFactory6? factory6 = factory.QueryInterfaceOrNull<DXGI.IDXGIFactory6>();
    if (factory6 != null)
    {
        // 先告诉系统,要高性能的显卡
        for (int adapterIndex = 0;
             factory6.EnumAdapterByGpuPreference(adapterIndex, DXGI.GpuPreference.HighPerformance,
                 out DXGI.IDXGIAdapter1? adapter).Success;
             adapterIndex++)
        {
            if (adapter == null)
            {
                continue;
            }

            DXGI.AdapterDescription1 desc = adapter.Description1;

            if ((desc.Flags & DXGI.AdapterFlags.Software) != DXGI.AdapterFlags.None)
            {
                // Don't select the Basic Render Driver adapter.
                adapter.Dispose();
                continue;
            }

            //factory6.Dispose();

            Console.WriteLine($"枚举到 {adapter.Description1.Description} 显卡");
            yield return adapter;
        }

        factory6.Dispose();
    }

    // 如果枚举不到,那系统返回啥都可以
    for (int adapterIndex = 0;
         factory.EnumAdapters1(adapterIndex, out DXGI.IDXGIAdapter1? adapter).Success;
         adapterIndex++)
    {
        DXGI.AdapterDescription1 desc = adapter.Description1;

        if ((desc.Flags & DXGI.AdapterFlags.Software) != DXGI.AdapterFlags.None)
        {
            // Don't select the Basic Render Driver adapter.
            adapter.Dispose();

            continue;
        }

        Console.WriteLine($"枚举到 {adapter.Description1.Description} 显卡");
        yield return adapter;
    }
}

static LRESULT WndProc(HWND hWnd, uint message, WPARAM wParam, LPARAM lParam)
{
    return DefWindowProc(hWnd, message, wParam, lParam);
}

readonly record struct DrawingInfo(System.Numerics.Vector2 Offset, Size Size, D2D.ID2D1SolidColorBrush Brush);

本文以上代码放在githubgitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 66f9fe05baba8ad30495069aebd447b160484215

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 66f9fe05baba8ad30495069aebd447b160484215

获取代码之后,进入 CedageawhakairnerewhalNaibiferenagifee 文件夹

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
级别太高,架构师面试题,谈谈你对IaaS、PaaS、SaaS的理解
有位工作6年的小伙伴,去面试架构师岗位,被问到这样一道题,说谈你对IaaS、PaaS、SaaS的理解。今天,我给大家分享一下我的理解。
Tom弹架构
2022/12/19
1.6K0
级别太高,架构师面试题,谈谈你对IaaS、PaaS、SaaS的理解
如何快速区分IaaS、PaaS 、SaaS?
云是一个非常广泛的概念,它涵盖了所有可能的在线服务,但是当企业参考云采购时,通常会考虑三种云服务模式,即基础架构即服务(laaS),平台即服务(PaaS)和软件即服(SaaS)。由于云系统自身的复杂性和混合模式的组合需要大量理论技巧,在不同应用场景下,也会存在各种具体的使用模式。
齿轮易创说互联网
2018/09/14
13.2K0
如何快速区分IaaS、PaaS 、SaaS?
IaaS、PaaS和SaaS的区别
任何一个在互联网上提供其服务的公司都可以叫做云计算公司。其实云计算分几层的,分别是Infrastructure(基础设施)-as-a-Service,Platform(平台)-as-a-Service,Software(软件)-as-a-Service。基础设施在最下端,平台在中间,软件在顶端。别的一些“软”的层可以在这些层上面添加。
兮动人
2021/06/11
2.1K0
IaaS、PaaS和SaaS的区别
浅谈云计算发展演变出的三种服务模式:IaaS,PaaS和SaaS
云计算指的是通过网络“云”将巨大的数据计算处理程序分解成无数个小程序,然后,通过多部服务器组成的系统进行处理和分析这些小程序得到结果并返回给用户,因此任何一个在互联网上提供服务的公司,都可以称之为云计算公司。然而,并不是所有的云计算公司的服务模式都是一样的,虽然云计算的服务模式在不断变化,在业界根据公司的提供的服务方式,划分为三类:
青年码农
2021/02/01
4.6K0
浅谈云计算发展演变出的三种服务模式:IaaS,PaaS和SaaS
IaaS、PaaS、SaaS 是什么?一文看懂云计算的三种服务模式
我们初步了解了“云计算”这一技术是如何通过互联网将计算资源以服务的形式提供给用户的。今天,我们进一步深入云计算的核心:服务模式。
思维云_飞机@ACT888
2025/03/26
2.5K0
有哪些通俗易懂的例子可以解释 IAAS、SAAS、PAAS 的区别?
你一定听说过云计算中的三个“高大上”的你一定听说过云计算中的三个“高大上”的概念:IaaS、PaaS和SaaS,这几个术语并不好理解。不过,如果你是个吃货,还喜欢披萨,这个问题就好解决了!好吧,其实你根本不是一个吃货,之所以自我标榜为吃货,其实是为了收获赞叹式的夸奖,“吃货还这么瘦,好羡慕啊!”或者,总得给伦家的微丰找个像样的理由。
孙杰
2019/10/29
1.3K0
有哪些通俗易懂的例子可以解释 IAAS、SAAS、PAAS 的区别?
如何理解云计算中IaaS、PaaS和SaaS?很简单,就像吃货想吃披萨了…
你一定听说过云计算中的三个“高大上”的概念:IaaS、PaaS和SaaS,这几个术语并不好理解。不过,如果你是个吃货,还喜欢披萨,这个问题就好解决了!好吧,其实你根本不是一个吃货,之所以自我标榜为吃货
小小科
2018/05/03
1.1K0
如何理解云计算中IaaS、PaaS和SaaS?很简单,就像吃货想吃披萨了…
一文看懂LaaS、PaaS、SaaS
导读 互联网的普及,让信息实现了实时传递,缩短了人与人之间的举例。而互联网的高级玩法云计算的落地,让更多具有场景的服务得以实现。于是乎,“云服务”成为当下众多公司快速布局业务的不二之选。 云服务也是分层的。任何一个在互联网上提供其服务的公司都可以叫做云计算公司。其实云计算分几层的,分别是Infrastructure(基础设施)-as-a- Service,Platform(平台)-as-a-Service,Software(软件)-as-a-Service。基础设施在最下端,平台在中间,软件在顶端。 此前,
企鹅号小编
2018/01/31
15.2K3
一文看懂LaaS、PaaS、SaaS
通俗了解IaaS,PaaS,SaaS
【IT168 评论】云服务已经被大众所熟知,但对于刚接触云计算的朋友来说,仍然是云里雾里的绕着,今天小编就为你解读一下云计算的几种服务模式,IaaS,PaaS,SaaS到底是什么…区别有哪些? 字正腔
企鹅号小编
2018/02/05
4.1K0
通俗了解IaaS,PaaS,SaaS
IaaS, PaaS和SaaS公司都做些什么
云服务”现在已经快成了一个家喻户晓的词了。如果你不知道PaaS, IaaS 和SaaS的区别,那么也没啥,因为很多人确实不知道。
臭豆腐
2019/04/16
1.8K0
IaaS, PaaS和SaaS公司都做些什么
IaaS、PaaS、SaaS到底是什么鬼?
IBM 的软件架构师 Albert Barron 曾经使用披萨作为比喻,解释这个问题。David Ng 进一步,让它变得更准确易懂。
Bug开发工程师
2019/05/05
2.8K0
IaaS、PaaS、SaaS到底是什么鬼?
云服务模型解析:IaaS、PaaS和SaaS以及构建所需的关键技术
在当今数字化时代,云计算为企业提供了灵活性和可扩展性的解决方案。其中,IaaS(Infrastructure as a Service)、PaaS(Platform as a Service)和SaaS(Software as a Service)是三种主要的云服务模型,每个模型都满足不同层次的业务需求。本文将深入解释这三个模型,并探讨构建这些云平台所需的关键技术。
GeekLiHua
2025/01/21
2370
如何区分 PaaS、IaaS 、SaaS呢?
一、基础设施即服务 (IaaS : Infrastructure as a Service)
用户1880875
2021/07/02
1.3K0
如何区分 PaaS、IaaS 、SaaS呢?
云计算三类巨头:IaaS、PaaS、SaaS,分别是什么意思,应用场景是什么?
随着云环境的日益普及,越来越多的企业正在摆脱本地 IT 解决方案,并逐步依赖云环境,在选择云服务时,我们经常会看到IaaS、PaaS、SaaS等术语,那么这三个术语分别是什么意思?如何选择?三者有什么区别呢?
网络技术联盟站
2023/03/05
10.5K0
云计算三类巨头:IaaS、PaaS、SaaS,分别是什么意思,应用场景是什么?
LAAS、PAAS、SAAS是什么意思
SAAS是“Software as a Service”的缩写,中文意思是“软件即服务”。这是一种软件分发模型,其中软件应用由第三方提供商托管,并通过互联网向客户提供。用户通常通过订阅的方式获得软件服务,而不是购买和安装在本地计算机上。SAAS模式允许用户根据需求访问和使用软件,而无需担心软件的维护、更新和支持等后端任务,这些都由服务提供商负责。
JaneYork
2024/05/25
9990
IT知识百科:三大云计算模型IAAS、PAAS、SAAS
云计算已经成为现代IT架构的核心组成部分,而云服务模型是构建和交付云计算服务的关键概念。在云服务模型中,IAAS、PAAS和SAAS是最常见的三种模型。
网络技术联盟站
2023/05/22
15.8K0
IT知识百科:三大云计算模型IAAS、PAAS、SAAS
到底什么是IaaS、PaaS、SaaS?
服务: 指履行职务,为他人做事,并使他人从中受益的一种有偿或无偿的活动,不以实物形式而以提供劳动的形式满足他人某种特殊需要。
奋飛
2021/08/31
5.5K0
到底什么是IaaS、PaaS、SaaS?
SaaS、PaaS、IaaS
PaaS是Platform-as-a-Service的缩写,意思是平台即服务。把服务器平台作为一种服务提供的商业模式。通过网络进行程序提供的服务称之为SaaS(Software as a Service),而云计算时代相应的服务器平台或者开发环境作为服务进行提供就成为了PaaS(Platform as a Service)。
PM吃瓜
2023/03/02
2.6K0
SaaS、PaaS、IaaS
漫画:大厂总体SaaS化部署,到底什么是IaaS、PaaS和SaaS?
在之前的文章《如何给女朋友解释什么是云计算?》中,我们介绍了云计算,在文章中我们提到虽然都是使用云计算服务,但是不同的用户需要的服务是不一样的,有些公司可能只需要租借服务器、有些公司可能需要整套服务,而有些个人客户可能希望直接可以通过付费得到一个网站等。
Java3y
2020/02/21
3.6K0
漫画:大厂总体SaaS化部署,到底什么是IaaS、PaaS和SaaS?
[入门]一道东北菜讲述什么是IaaS、PaaS和SaaS
每次和从事信息化或IT专业人士聊天,总会从他们的嘴里蹦出如:云计算、大数据、雾计算、IaaS、PaaS和SaaS等相关名词。而你呢,只能“嗯嗯嗯、对、听说过,要不就说:那个以前我做过,但是没做完,所以不太懂”或者对方说一句你重复一句,装作很懂得样子。
木禾wen
2019/11/15
1.2K0
[入门]一道东北菜讲述什么是IaaS、PaaS和SaaS
推荐阅读
相关推荐
级别太高,架构师面试题,谈谈你对IaaS、PaaS、SaaS的理解
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档