找回密码
 立即注册
首页 业界区 安全 Vortice 使用 DirectComposition 显示透明窗口

Vortice 使用 DirectComposition 显示透明窗口

端木茵茵 5 天前
本文是渲染相关系列博客中的一篇,该系列博客已按照逻辑顺序编排,方便大家依次阅读。如您对渲染相关感兴趣,可以通过以下链接访问整个系列:渲染相关系列博客导航
在 DirectX 使用 Vortice 从零开始控制台创建 Direct2D1 窗口修改颜色 博客中和大家介绍了最简方式创建了窗口和对接了 DirectX 层。在此基础上,大家也能看到此时创建的窗口是无法应用透明背景效果的
即使强行设置 SwapChainDescription1.AlphaMode 为 AlphaMode.Premultiplied 也会在 IDXGIFactory2.CreateSwapChainForHwnd 报错
传统 Win32 应用可以通过 UpdateLayeredWindow 方法设置窗口透明,然而 UpdateLayeredWindow 是有比较大的性能代价的,详细请参阅 WPF 从最底层源代码了解 AllowsTransparency 性能差的原因
性能较好的透明窗口实现可参阅 WPF 制作支持点击穿透的高性能的透明背景异形窗口
以上是在 WPF 框架里面帮忙封装好的,现在咱只有纯控制台,需要自己手动干一些活
为了方便大家阅读,本文将重新从零控制台开始,先创建好 WS_EX_LAYERED 的窗口,再将 DirectX 对接上去。总代码控制在 500 行左右。额外,为了方便 Win32 方法调用,本文还请出了 CsWin32 库,详细使用方法请参阅
dotnet 使用 CsWin32 库简化 Win32 函数调用逻辑
准备工作

按照 .NET 惯例,先安装一些库。本文的 D2D 基本没有戏份,仅用于绘制一点用于辅助测试的内容,本身此技术就和 D2D 无关
  1.   <ItemGroup>
  2.     <PackageReference Include="Vortice.Direct2D1" Version="3.8.2" />
  3.     <PackageReference Include="Vortice.Direct3D11" Version="3.8.2" />
  4.     <PackageReference Include="Vortice.DirectComposition" Version="3.8.2" />
  5.     <PackageReference Include="Vortice.DXGI" Version="3.8.2" />
  6.     <PackageReference Include="Vortice.Win32" Version="2.3.0" />
  7.     <PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.257">
  8.       <PrivateAssets>all</PrivateAssets>
  9.       <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
  10.     </PackageReference>
  11.   </ItemGroup>
复制代码
安装之后的 csproj 项目文件代码如下
  1.       Exe    net10.0    enable    true    true    <ItemGroup>
  2.     <PackageReference Include="Vortice.Direct2D1" Version="3.8.2" />
  3.     <PackageReference Include="Vortice.Direct3D11" Version="3.8.2" />
  4.     <PackageReference Include="Vortice.DirectComposition" Version="3.8.2" />
  5.     <PackageReference Include="Vortice.DXGI" Version="3.8.2" />
  6.     <PackageReference Include="Vortice.Win32" Version="2.3.0" />
  7.     <PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.257">
  8.       <PrivateAssets>all</PrivateAssets>
  9.       <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
  10.     </PackageReference>
  11.   </ItemGroup>
复制代码
如以上代码所示,本文提供的代码也是 AOT 友好的。在我的测试中,构建的 32 位的程序只需 2.12 MB 的体积。如果不知道本文的项目是如何组织的,可以在本文末尾找到本文全部代码的下载方法,拉取代码了解更多细节
添加 NativeMethods.txt 文件,添加以下内容,让 CsWin32 辅助生成一些代码
  1. EnumDisplayMonitors
  2. GetMonitorInfo
  3. MONITORINFOEXW
  4. EnumDisplaySettings
  5. GetDisplayConfigBufferSizes
  6. QueryDisplayConfig
  7. DisplayConfigGetDeviceInfo
  8. DISPLAYCONFIG_SOURCE_DEVICE_NAME
  9. DISPLAYCONFIG_TARGET_DEVICE_NAME
  10. RegisterClassEx
  11. GetModuleHandle
  12. LoadCursor
  13. IDC_ARROW
  14. WndProc
  15. CreateWindowEx
  16. CW_USEDEFAULT
  17. ShowWindow
  18. SW_SHOW
  19. GetMessage
  20. TranslateMessage
  21. DispatchMessage
  22. DefWindowProc
  23. GetClientRect
  24. WM
  25. WM_PAINT
  26. GetWindowLong
  27. SetWindowLong
  28. DwmIsCompositionEnabled
  29. UpdateLayeredWindow
  30. DwmExtendFrameIntoClientArea
  31. DCompositionCreateDevice
复制代码
以上提供的列表是超过本文所用范围的,多了也没有什么关系,一来这是测试项目,二来发布的时候 AOT 带裁剪
创建窗口

创建窗口的步骤和 上一篇博客 提供的方法十分接近,只是需要配置 WS_EX_LAYERED 样式,核心代码如下
  1.         WINDOW_EX_STYLE exStyle = WINDOW_EX_STYLE.WS_EX_OVERLAPPEDWINDOW
  2.                                   | WINDOW_EX_STYLE.WS_EX_LAYERED; // Layered 是透明窗口的最关键
  3.         var style = WNDCLASS_STYLES.CS_OWNDC | WNDCLASS_STYLES.CS_HREDRAW | WNDCLASS_STYLES.CS_VREDRAW;
  4.         var defaultCursor = LoadCursor(
  5.             new HINSTANCE(IntPtr.Zero), new PCWSTR(IDC_ARROW.Value));
  6.         var className = $"lindexi-{Guid.NewGuid().ToString()}";
  7.         var title = "The Title";
  8.         fixed (char* pClassName = className)
  9.         fixed (char* pTitle = title)
  10.         {
  11.             var wndClassEx = new WNDCLASSEXW
  12.             {
  13.                 cbSize = (uint) Marshal.SizeOf<WNDCLASSEXW>(),
  14.                 style = style,
  15.                 lpfnWndProc = new WNDPROC(WndProc),
  16.                 hInstance = new HINSTANCE(GetModuleHandle(null).DangerousGetHandle()),
  17.                 hCursor = defaultCursor,
  18.                 hbrBackground = new HBRUSH(IntPtr.Zero),
  19.                 lpszClassName = new PCWSTR(pClassName)
  20.             };
  21.             ushort atom = RegisterClassEx(in wndClassEx);
  22.             var dwStyle = WINDOW_STYLE.WS_OVERLAPPEDWINDOW;
  23.             var windowHwnd = CreateWindowEx(
  24.                 exStyle,
  25.                 new PCWSTR((char*) atom),
  26.                 new PCWSTR(pTitle),
  27.                 dwStyle,
  28.                 0, 0, 1900, 1000,
  29.                 HWND.Null, HMENU.Null, HINSTANCE.Null, null);
  30.             return windowHwnd;
  31.         }
复制代码
以上代码放在 CreateWindow 方法中。在开始之前,也先调用 DwmIsCompositionEnabled 方法,判断是否可用
  1.         DwmIsCompositionEnabled(out var compositionEnabled);
  2.         if (!compositionEnabled)
  3.         {
  4.             Console.WriteLine($"无法启用透明窗口效果");
  5.         }
复制代码
预期在 Win10 以上系统都是能使用的,除非系统被魔改
窗口的消息处理代码 WndProc 先不着急写,等待完成渲染部分的逻辑再一起写
完成窗口创建之后,即可将窗口显示出来,代码如下
  1.         var window = CreateWindow();
  2.         ShowWindow(window, SHOW_WINDOW_CMD.SW_NORMAL);
复制代码
随后先开启独立的线程作为渲染线程,再跑起来消息循环
渲染线程相关逻辑,我封装到 RenderManager 类型里面,其代码如下
  1.         var renderManager = new RenderManager(window);
  2.         renderManager.StartRenderThread();
复制代码
跑起来渲染线程之后,使用标准的消息循环跑起来应用
  1.         while (true)
  2.         {
  3.             var msg = new MSG();
  4.             var getMessageResult = GetMessage(&msg, HWND, 0,
  5.                 0);
  6.             if (!getMessageResult)
  7.             {
  8.                 break;
  9.             }
  10.             TranslateMessage(&msg);
  11.             DispatchMessage(&msg);
  12.         }
复制代码
在 RenderManager 里面也提供窗口尺寸变更的方法,可以在消息循环中调用。此时的消息循环的核心代码如下
  1.     private LRESULT WndProc(HWND hwnd, uint message, WPARAM wParam, LPARAM lParam)
  2.     {
  3.         switch ((WindowsMessage)message)
  4.         {
  5.             case WindowsMessage.WM_NCCALCSIZE:
  6.             {
  7.                 return new LRESULT(0);
  8.             }
  9.             case WindowsMessage.WM_SIZE:
  10.             {
  11.                 RenderManager?.ReSize();
  12.                 break;
  13.             }
  14.         }
  15.         return DefWindowProc(hwnd, message, wParam, lParam);
  16.     }
复制代码
以上的 WM_NCCALCSIZE 用于声明客户区,通过直接返回 0 告诉系统整个区域都是客户区
透明窗口的实现在窗口创建过程中,最关键点只有 WS_EX_LAYERED 和 WM_NCCALCSIZE 的逻辑
渲染线程

独立的渲染线程也是 WPF 等 UI 框架所采用的方式,只需要新建一个线程跑起来就可以了,如果有心的话,再设置线程为 STA 的就更好,代码如下
  1. unsafe class RenderManager(HWND hwnd)
  2. {
  3.     public HWND HWND => hwnd;
  4.     private readonly Format _colorFormat = Format.B8G8R8A8_UNorm;
  5.     public void StartRenderThread()
  6.     {
  7.         var thread = new Thread(() => { RenderCore(); })
  8.         {
  9.             IsBackground = true,
  10.             Name = "Render"
  11.         };
  12.         thread.Priority = ThreadPriority.Highest;
  13.         thread.Start();
  14.     }
  15. }
复制代码
以上的 RenderCore 就是核心的渲染方法了
由于渲染线程是独立的,不能在 ReSize 方法直接修改渲染线程相关的逻辑。本文这里简单使用一个字段表示窗口尺寸变更,需要渲染线程修改交换链尺寸
  1. unsafe class RenderManager(HWND hwnd)
  2. {
  3.     public void ReSize()
  4.     {
  5.         _isReSize = true;
  6.     }
  7.     private bool _isReSize;
  8. }
复制代码
在 RenderCore 的一开始就是执行初始化逻辑,初始化为本文的关键,核心就是对接 DirectComposition 实现透明窗口效果
初始化渲染

先获取客户区,即窗口尺寸,此尺寸用于后续交换链的创建
  1.         RECT windowRect;
  2.         GetClientRect(HWND, &windowRect);
  3.         var clientSize = new SizeI(windowRect.right - windowRect.left, windowRect.bottom - windowRect.top);
复制代码
按照  上一篇博客 提供的方法获取显卡信息,代码如下
  1.         var dxgiFactory2 = DXGI.CreateDXGIFactory1<IDXGIFactory2>();
  2.         IDXGIAdapter1? hardwareAdapter = GetHardwareAdapter(dxgiFactory2)
  3.             // 这里 ToList 只是想列出所有的 IDXGIAdapter1 在实际代码里,大部分都是获取第一个
  4.             .ToList().FirstOrDefault();
  5.         if (hardwareAdapter == null)
  6.         {
  7.             throw new InvalidOperationException("Cannot detect D3D11 adapter");
  8.         }
  9.     private static IEnumerable<IDXGIAdapter1> GetHardwareAdapter(IDXGIFactory2 factory)
  10.     {
  11.         using IDXGIFactory6? factory6 = factory.QueryInterfaceOrNull<IDXGIFactory6>();
  12.         if (factory6 != null)
  13.         {
  14.             // 这个系统的 DX 支持 IDXGIFactory6 类型
  15.             // 先告诉系统,要高性能的显卡
  16.             for (uint adapterIndex = 0;
  17.                  factory6.EnumAdapterByGpuPreference(adapterIndex, GpuPreference.HighPerformance,
  18.                      out IDXGIAdapter1? adapter).Success;
  19.                  adapterIndex++)
  20.             {
  21.                 if (adapter == null)
  22.                 {
  23.                     continue;
  24.                 }
  25.                 AdapterDescription1 desc = adapter.Description1;
  26.                 if ((desc.Flags & AdapterFlags.Software) != AdapterFlags.None)
  27.                 {
  28.                     // Don't select the Basic Render Driver adapter.
  29.                     adapter.Dispose();
  30.                     continue;
  31.                 }
  32.                 Console.WriteLine($"枚举到 {adapter.Description1.Description} 显卡");
  33.                 yield return adapter;
  34.             }
  35.         }
  36.         else
  37.         {
  38.             // 不支持就不支持咯,用旧版本的方式获取显示适配器接口
  39.         }
  40.         // 如果枚举不到,那系统返回啥都可以
  41.         for (uint adapterIndex = 0;
  42.              factory.EnumAdapters1(adapterIndex, out IDXGIAdapter1? adapter).Success;
  43.              adapterIndex++)
  44.         {
  45.             AdapterDescription1 desc = adapter.Description1;
  46.             if ((desc.Flags & AdapterFlags.Software) != AdapterFlags.None)
  47.             {
  48.                 // Don't select the Basic Render Driver adapter.
  49.                 adapter.Dispose();
  50.                 continue;
  51.             }
  52.             Console.WriteLine($"枚举到 {adapter.Description1.Description} 显卡");
  53.             yield return adapter;
  54.         }
  55.     }
复制代码
尝试创建 ID3D11Device 设备,代码如下
  1.         FeatureLevel[] featureLevels = new[]
  2.         {
  3.             FeatureLevel.Level_12_2,
  4.             FeatureLevel.Level_12_1,
  5.             FeatureLevel.Level_12_0,
  6.             FeatureLevel.Level_11_1,
  7.             FeatureLevel.Level_11_0,
  8.             FeatureLevel.Level_10_1,
  9.             FeatureLevel.Level_10_0,
  10.             FeatureLevel.Level_9_3,
  11.             FeatureLevel.Level_9_2,
  12.             FeatureLevel.Level_9_1,
  13.         };
  14.         IDXGIAdapter1 adapter = hardwareAdapter;
  15.         DeviceCreationFlags creationFlags = DeviceCreationFlags.BgraSupport;
  16.         var result = D3D11.D3D11CreateDevice
  17.         (
  18.             adapter,
  19.             DriverType.Unknown,
  20.             creationFlags,
  21.             featureLevels,
  22.             out ID3D11Device d3D11Device, out FeatureLevel featureLevel,
  23.             out ID3D11DeviceContext d3D11DeviceContext
  24.         );
复制代码
本文以上的 FeatureLevel[] 中添加了 Level_12_2 等的不合理需求,如果创建失败了,则执行降级逻辑。按照技术原理,只需有 Level_11_1 即可
  1.         if (result.Failure)
  2.         {
  3.             // 降低等级试试
  4.             featureLevels = new[]
  5.             {
  6.                 //FeatureLevel.Level_12_2,
  7.                 //FeatureLevel.Level_12_1,
  8.                 //FeatureLevel.Level_12_0,
  9.                 FeatureLevel.Level_11_1,
  10.                 FeatureLevel.Level_11_0,
  11.                 FeatureLevel.Level_10_1,
  12.                 FeatureLevel.Level_10_0,
  13.                 FeatureLevel.Level_9_3,
  14.                 FeatureLevel.Level_9_2,
  15.                 FeatureLevel.Level_9_1,
  16.             };
  17.             result = D3D11.D3D11CreateDevice
  18.             (
  19.                 adapter,
  20.                 DriverType.Unknown,
  21.                 creationFlags,
  22.                 featureLevels,
  23.                 out d3D11Device, out featureLevel,
  24.                 out d3D11DeviceContext
  25.             );
  26.         }
复制代码
将获取到的 ID3D11Device 当成 ID3D11Device1 设备来用,这一步基本不会遇到出错的,代码如下
  1.         // 大部分情况下,用的是 ID3D11Device1 和 ID3D11DeviceContext1 类型
  2.         // 从 ID3D11Device 转换为 ID3D11Device1 类型
  3.         ID3D11Device1 d3D11Device1 = d3D11Device.QueryInterface<ID3D11Device1>();
  4.         var d3D11DeviceContext1 = d3D11DeviceContext.QueryInterface<ID3D11DeviceContext1>();
  5.         // 获取到了新的两个接口,就可以减少 `d3D11Device` 和 `d3D11DeviceContext` 的引用计数。调用 Dispose 不会释放掉刚才申请的 D3D 资源,只是减少引用计数
  6.         d3D11Device.Dispose();
  7.         d3D11DeviceContext.Dispose();
复制代码
准备交换链参数,代码如下
  1.         // 缓存的数量,包括前缓存。大部分应用来说,至少需要两个缓存,这个玩过游戏的伙伴都知道
  2.         const int FrameCount = 2;
  3.         SwapChainDescription1 swapChainDescription = new()
  4.         {
  5.             Width = (uint) clientSize.Width,
  6.             Height = (uint) clientSize.Height,
  7.             Format = _colorFormat,
  8.             BufferCount = FrameCount,
  9.             BufferUsage = Usage.RenderTargetOutput,
  10.             SampleDescription = SampleDescription.Default,
  11.             Scaling = Scaling.Stretch,
  12.             SwapEffect = SwapEffect.FlipSequential, // 使用 FlipSequential 配合 Composition
  13.             AlphaMode = AlphaMode.Premultiplied,
  14.             Flags = SwapChainFlags.None,
  15.         };
复制代码
交换链中有以下参数必须固定为此搭配:

  • Scaling: Scaling.Stretch
  • SwapEffect: SwapEffect.FlipDiscard 或 SwapEffect.FlipSequential ,正常来说都会采用 FlipSequential 配合 Composition 使用
  • AlphaMode: AlphaMode.Premultiplied 。使用 AlphaMode.Ignore 和 AlphaMode.Unspecified 参数也是合法的,但是如此就丢失了窗口透明了,不是咱的需求。而 AlphaMode.Straight 参数则是不搭的
如果以上参数不搭配,则会在创建交换链时,返回 0x887A0001 错误
上文提到了 AlphaMode.Premultiplied 预乘,简单来说就是最终输出的值里的 RGB 分量都乘以透明度。更多细节请参阅 支持的像素格式和 Alpha 模式 - Win32 apps - Microsoft Learn
判断系统版本,决定能否使用 DirectComposition 功能,代码如下
  1.         // 使用 DirectComposition 才能支持透明窗口
  2.         bool useDirectComposition = true;
  3.         // 使用 DirectComposition 时有系统版本要求
  4.         useDirectComposition = useDirectComposition & OperatingSystem.IsWindowsVersionAtLeast(8, 1);
复制代码
如此可以让代码走两个分支,使用 DirectComposition 的分支的代码如下
  1.         IDXGISwapChain1 swapChain;
  2.         if (useDirectComposition)
  3.         {
  4.             // 使用 CreateSwapChainForComposition 创建支持预乘 Alpha 的 SwapChain
  5.             swapChain =
  6.                 dxgiFactory2.CreateSwapChainForComposition(d3D11Device1, swapChainDescription);
  7.             // 创建 DirectComposition 设备和目标
  8.             IDXGIDevice dxgiDevice = d3D11Device1.QueryInterface<IDXGIDevice>();
  9.             IDCompositionDevice compositionDevice = DComp.DCompositionCreateDevice<IDCompositionDevice>(dxgiDevice);
  10.             compositionDevice.CreateTargetForHwnd(HWND, true, out IDCompositionTarget compositionTarget);
  11.             // 创建视觉对象并设置 SwapChain 作为内容
  12.             IDCompositionVisual compositionVisual = compositionDevice.CreateVisual();
  13.             compositionVisual.SetContent(swapChain);
  14.             compositionTarget.SetRoot(compositionVisual);
  15.             compositionDevice.Commit();
  16.         }
复制代码
从上面代码可见核心步骤是先让 CreateSwapChainForComposition 创建出交换链对象。再将 ID3D11Device1 当成 IDXGIDevice 设备,用于调用 DComp.DCompositionCreateDevice 创建出 IDCompositionDevice 设备
调用 IDCompositionDevice 的 CreateTargetForHwnd 方法即可为当前的窗口挂上 IDCompositionTarget 对象。随后再调用 IDCompositionDevice 设备的 CreateVisual 创建 IDCompositionVisual 视觉对象。将刚才创建出来的交换链作为视觉对象的内容,如此即可完成交换链与内容的绑定
  1.             // 创建视觉对象并设置 SwapChain 作为内容
  2.             IDCompositionVisual compositionVisual = compositionDevice.CreateVisual();
  3.             compositionVisual.SetContent(swapChain);
复制代码
现在交换链所渲染的画面已经能够到 IDCompositionVisual 里了,再将 IDCompositionVisual 作为 IDCompositionTarget 的根,即可让 IDCompositionVisual 参与 DWM 合成
  1.             compositionTarget.SetRoot(compositionVisual);
  2.             compositionDevice.Commit();
复制代码
如果没有 DirectComposition 可用,则依然使用上一篇博客介绍的方法创建交换链,代码如下
  1.         IDXGISwapChain1 swapChain;
  2.         if (useDirectComposition)
  3.         {
  4.             ...
  5.         }
  6.         else
  7.         {
  8.             var fullscreenDescription = new SwapChainFullscreenDescription()
  9.             {
  10.                 Windowed = true,
  11.             };
  12.             swapChainDescription.AlphaMode = AlphaMode.Ignore;
  13.             swapChain = dxgiFactory2.CreateSwapChainForHwnd(d3D11Device1, hwnd, swapChainDescription,
  14.                 fullscreenDescription);
  15.         }
复制代码
以上代码的 DirectComposition 为本文的核心,只需要创建出输出带预乘的交换链,配合 WS_EX_LAYERED 窗口,即可渲染出透明窗口
接下来的逻辑就是和 D2D 对接,尝试渲染透明的界面用于测试
对接渲染

由于 D2D 没有什么戏份,本文就只贴出核心代码
  1.         using D2D.ID2D1Factory1 d2DFactory = D2D.D2D1.D2D1CreateFactory<D2D.ID2D1Factory1>();
  2.                 var d3D11Texture2D = _renderContext.SwapChain.GetBuffer<ID3D11Texture2D>(0);
  3.                 var dxgiSurface = d3D11Texture2D.QueryInterface<IDXGISurface>();
  4.                 var renderTargetProperties = new D2D.RenderTargetProperties()
  5.                 {
  6.                     PixelFormat = new PixelFormat(D2DColorFormat, Vortice.DCommon.AlphaMode.Premultiplied),
  7.                     Type = D2D.RenderTargetType.Hardware,
  8.                 };
  9.                 D2D.ID2D1RenderTarget d2D1RenderTarget =
  10.                     d2DFactory.CreateDxgiSurfaceRenderTarget(dxgiSurface, renderTargetProperties);
  11.         while (!_isDisposed)
  12.         {
  13.                 D2D.ID2D1RenderTarget renderTarget = d2D1RenderTarget;
  14.                 renderTarget.BeginDraw();
  15.                 var color = new Color4(Random.Shared.NextSingle(), Random.Shared.NextSingle(),
  16.                     Random.Shared.NextSingle(), 0.1f);
  17.                 renderTarget.Clear(color);
  18.                 renderTarget.EndDraw();
  19.                 _renderContext.SwapChain.Present(1, PresentFlags.None);
  20.                 _renderContext.D3D11DeviceContext1.Flush();
  21.         }
复制代码
如果准备处理窗口尺寸改变,则需要在循环里面判断 _isReSize 字段,调用交换链的 ResizeBuffers 方法,代码如下
  1.             if (_isReSize)
  2.             {
  3.                 // 处理窗口大小变化
  4.                 _isReSize = false;
  5.                 GetClientRect(HWND, out var pClientRect);
  6.                 var clientSize = new SizeI(pClientRect.right - pClientRect.left, pClientRect.bottom - pClientRect.top);
  7.                 var swapChain = _renderContext.SwapChain;
  8.                 swapChain.ResizeBuffers(2,
  9.                     (uint) (clientSize.Width),
  10.                     (uint) (clientSize.Height),
  11.                     _colorFormat,
  12.                     SwapChainFlags.None
  13.                 );
  14.             }
复制代码
尝试运行代码,可见一个不断闪烁的背景透明的窗口
代码

本文代码放在 github 和 gitee 上,可以使用如下命令行拉取代码。我整个代码仓库比较庞大,使用以下命令行可以进行部分拉取,拉取速度比较快
先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
  1. git init
  2. git remote add origin https://gitee.com/lindexi/lindexi_gd.git
  3. git pull origin 369de6b65c4122cec6a6c9ffbcc0b352a419e83e
复制代码
以上使用的是国内的 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码,将 gitee 源换成 github 源进行拉取代码。如果依然拉取不到代码,可以发邮件向我要代码
  1. git remote remove origin
  2. git remote add origin https://github.com/lindexi/lindexi_gd.git
  3. git pull origin 369de6b65c4122cec6a6c9ffbcc0b352a419e83e
复制代码
获取代码之后,进入 DirectX/D2D/FarjairyakaBurnefuwache 文件夹,即可获取到源代码
更多技术博客,请参阅 博客导航
更多博客

渲染部分,关于 SharpDx 和 Vortice 的使用方法,包括入门级教程,请参阅:

  • 渲染博客导航
  • SharpDX 系列
更多关于我博客请参阅 博客导航

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

3 天前

举报

懂技术并乐意极积无私分享的人越来越少。珍惜
您需要登录后才可以回帖 登录 | 立即注册