通过 Vortice 库可以使用非常底层的方式调用到 Direct2D1 进行渲染,本文将使用 D2D 离屏渲染到 IWICBitmap 上,再使用一点点反射黑科技,直接将此 IWICBitmap 对接到 WPF 框架里。本文提供的这个方法可以实现极高性能且只有很少的转换损耗的离屏渲染方式,唯一的一个缺点是需要进行一点反射调用,适合用来静态画面渲染上

在 WPF 的渲染底层里,对于图片来说,都是采用 WIC Bitmap 参与渲染。而刚好 Direct2D1 可以从一个 IWICBitmap 上使用 CreateWicBitmapRenderTarget 方法创建 ID2D1RenderTarget 对象,在 ID2D1RenderTarget 上执行绘制指导命令,从而实现将画面绘制到 IWICBitmap 上

于是新建出一个 IWICBitmap 对象,接着挂上 D2D 的 ID2D1RenderTarget 进行绘制。完成之后,将 IWICBitmap 封装为一个 BitmapSource 对象,扔给 WPF 层,当成图片接入 WPF 的渲染框架

创建 IWICBitmap 对象和挂上 D2D 以及绘制逻辑的细节,请参阅 dotnet C# 使用 Vortice 支持 Direct2D1 离屏渲染

以下是本文使用的代码,这里就不展开细节

    private static Task<IWICBitmap> OffScreenRenderingWICBitmapAsync()
{
return Task.Run(OffScreenRenderingWICBitmap);
}
private static IWICBitmap OffScreenRenderingWICBitmap()
{
using var wicImagingFactory = new IWICImagingFactory();
IWICBitmap wicBitmap =
wicImagingFactory.CreateBitmap(1000, 1000, Win32.Graphics.Imaging.Apis.GUID_WICPixelFormat32bppPBGRA); using D2D.ID2D1Factory1 d2DFactory = D2D.D2D1.D2D1CreateFactory<D2D.ID2D1Factory1>();
var renderTargetProperties = new D2D.RenderTargetProperties(PixelFormat.Premultiplied);
D2D.ID2D1RenderTarget d2D1RenderTarget =
d2DFactory.CreateWicBitmapRenderTarget(wicBitmap, renderTargetProperties); using var renderTarget = d2D1RenderTarget;
// 开始绘制逻辑
renderTarget.BeginDraw(); Render(renderTarget); renderTarget.EndDraw(); return wicBitmap;
} private static void Render(D2D.ID2D1RenderTarget renderTarget)
{
// 以下是测试代码
// 假装是耗时的渲染
var color = new Color4((byte)Random.Shared.Next(255), (byte)Random.Shared.Next(255),
(byte)Random.Shared.Next(255));
renderTarget.Clear(color); color = new Color4((byte)Random.Shared.Next(255), (byte)Random.Shared.Next(255),
(byte)Random.Shared.Next(255));
using var brush = renderTarget.CreateSolidColorBrush(color); // 10万个圆,无论是啥都顶不住
for (int i = 0; i < 100000; i++)
{
renderTarget.DrawEllipse(new D2D.Ellipse(new Vector2(Random.Shared.Next(900), Random.Shared.Next(900)),Random.Shared.Next(1,5), Random.Shared.Next(1, 5)),brush,Random.Shared.Next(1,2));
}
}

从上面代码可以看到,这是完全在另一个线程执行的逻辑,如此将不会卡住主线程。可以放心在后台线程里,执行复杂的逻辑,绘制一张有趣的画面。例如本文就采用啥都顶不住的画 10 万个圆的方法

完成离屏渲染之后,需要将 IWICBitmap 的结果对接到 WPF 框架,对接方法是封装为一个 BitmapSource 对象。可以 WPF 框架里面没有对外公开的 UnmanagedBitmapWrapper 类型,只是使用没有公开的类型就需要用到一点点反射

    private static BitmapSource WICBitmapToBitmapSource(IWICBitmap wicBitmap)
{
var presentationCoreAssembly = typeof(BitmapSource).Assembly;
var bitmapSourceSafeMILHandleType =
presentationCoreAssembly.GetType("System.Windows.Media.Imaging.BitmapSourceSafeMILHandle", throwOnError: true)!;
var bitmapSourceSafeMILHandleConstructor =
bitmapSourceSafeMILHandleType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance,
new Type[] { typeof(IntPtr) })!; var bitmapSourceSafeMILHandle =
bitmapSourceSafeMILHandleConstructor.Invoke(new object[] { wicBitmap.NativePointer }); var unmanagedBitmapWrapperType =
presentationCoreAssembly.GetType("System.Windows.Media.Imaging.UnmanagedBitmapWrapper")!; var unmanagedBitmapWrapperConstructor =
unmanagedBitmapWrapperType.GetConstructor(BindingFlags.Public | BindingFlags.Instance,
new Type[] { bitmapSourceSafeMILHandleType })!; var unmanagedBitmapWrapper = unmanagedBitmapWrapperConstructor.Invoke(new object[] { bitmapSourceSafeMILHandle });
return (BitmapSource) unmanagedBitmapWrapper;
}

按照 WPF 框架的源代码,可以看到 UnmanagedBitmapWrapper 的定义和构造函数如下

    internal sealed class UnmanagedBitmapWrapper : BitmapSource
{
public UnmanagedBitmapWrapper(BitmapSourceSafeMILHandle bitmapSource) :
base(true)
{
_bitmapInit.BeginInit();
// This constructor is used by BitmapDecoder and BitmapFrameDecode for thumbnails and
// previews. The bitmapSource parameter comes from BitmapSource.CreateCachedBitmap
// which already calculated memory pressure, so there's no need to do it here.
WicSourceHandle = bitmapSource;
_bitmapInit.EndInit();
UpdateCachedSettings();
}
// 忽略代码
}

原本这个 UnmanagedBitmapWrapper 是设计给解码器等使用的,不是公开使用的,但是我看起来这个类型很清真,也许开放出来也是可以的

在拿到继承 BitmapSource 的 UnmanagedBitmapWrapper 对象之后,即可作为某个图片的 Source 使用

            Image.Source = unmanagedBitmapWrapper;

为了方便演示效果,在 WPF 的 MainWindow 放一个 Image 控件,如下面代码

        <Image x:Name="Image"></Image>

接着在 Loaded 事件之后,先异步在后台线程调用 D2D 的渲染,将渲染结果封装为 BitmapSource 再设置给图片

    private async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
try
{
using IWICBitmap wicBitmap = await OffScreenRenderingWICBitmapAsync(); var unmanagedBitmapWrapper = WICBitmapToBitmapSource(wicBitmap); Image.Source = unmanagedBitmapWrapper;
}
catch (Exception)
{
// 这里是 async void 线程的顶层,如果有任何异常,那应用就炸了
// 而采用离屏渲染的 OffScreenRenderingWICBitmapAsync 是预期会有很多奇怪的异常
}
}

试试跑跑上本文的例子,可以从文本末尾获取到项目的全部代码的可构建运行项目,测试一下对 WPF 的影响。预计此方法对 WPF 的影响是非常小的,损耗约等于渲染一张图,而且还是一张不需要解码的图片的损耗。此方法和 D3DImage 对比如何?从代码分析上来说,如果不是静态画面绘制,那是比不过 D3DImage 的。静态画面绘制就是绘制一张静态画面,后续也不需要去更新,去修改画面的内容。在绘制静态画面的时候,性能预计和 D3DImage 持平。只是相对于 D3DImage 这么重的来说,使用 WICBitmap 会更加轻而已,而且由于本身设计上也不是作为动态画面使用,少了很多同步的逻辑

既然这个方式需要这么好用,那再用反射似乎也说不过去,刚好我是 WPF 框架的开发者,我在想着要不要将这个 UnmanagedBitmapWrapper 类型开放好了

本文的代码放在githubgitee 欢迎访问

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

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 1de394636dc15865ab90301230ed7ce37fe01ca0

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

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 1de394636dc15865ab90301230ed7ce37fe01ca0

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

更多 DirectX 和 D2D 以及 Vortice 库的博客,请参阅我的 博客导航

另外,我创建了专门聊 Vortice 的 QQ 群: 622808968 欢迎加入交流技术

WPF 对接 Vortice 调用 D2D 使用 IWICBitmap 离屏渲染的更多相关文章

  1. 【ASP.NET Web API教程】3.3 通过WPF应用程序调用Web API(C#)

    原文:[ASP.NET Web API教程]3.3 通过WPF应用程序调用Web API(C#) 注:本文是[ASP.NET Web API系列教程]的一部分,如果您是第一次看本博客文章,请先看前面的 ...

  2. java接口对接——别人调用我们接口获取数据

    java接口对接——别人调用我们接口获取数据,我们需要在我们系统中开发几个接口,给对方接口规范文档,包括访问我们的接口地址,以及入参名称和格式,还有我们的返回的状态的情况, 接口代码: package ...

  3. WPF 用代码调用dynamic resource动态更改背景 - CSDN博客

    原文:WPF 用代码调用dynamic resource动态更改背景 - CSDN博客 一般dynamic resoource通常在XAML里调用,如下范例: <Button Click=&qu ...

  4. WPF 精修篇 调用Win32Api

    原文:WPF 精修篇 调用Win32Api 栗子是 调用WIn32API 让窗口最前 后台代码 [DllImport("user32.dll")] private static e ...

  5. dotnet 读 WPF 源代码笔记 布局时 Arrange 如何影响元素渲染坐标

    大家是否好奇,在 WPF 里面,对 UIElement 重写 OnRender 方法进行渲染的内容,是如何受到上层容器控件的布局而进行坐标偏移.如有两个放入到 StackPanel 的自定义 UIEl ...

  6. 修改ViewPager调用setCurrentItem时,滑屏的速度

    原文摘自: 修改ViewPager调用setCurrentItem时,滑屏的速度 在使用ViewPager的过程中,有需要直接跳转到某一个页面的情况,这个时候就需要用到ViewPager的setCur ...

  7. 【WPF】Winform调用WPF窗体注意事项

     1.需要添加一些引用 2.调用处使用如下方法进行调用 Window win= new Window(); ElementHost.EnableModelessKeyboardInterop(win) ...

  8. WPF 使用RPC调用其他进程

    如果在 WPF 需要用多进程通信,一个推荐的方法是 WCF ,因为 WCF 是 RPC 计算.先来讲下 RPC (Remote Procedure Call) 远程过程调用,他是通过特定协议,包括 t ...

  9. 系统对接API调用

    在与公司外部系统对接时,API接口一般采用REST风格,对外暴露HTTP服务.只需要将入参封装好,并发起HTTP请求即可.具体请求流程如下图所示: 数据格式 API调用参数分为系统参数和业务参数,请求 ...

  10. WPF中窗体调用窗体

    在WPF中有时候我们需要在一个窗体中去调用另外的一个窗体,下面给出调用方法. 下面实现在MainWindow中通过点击一个按钮调用另外的一个窗口. 首先创建你要调用的另外一个窗口:点击最上面的项目  ...

随机推荐

  1. 基于VS Code的JSDoc的使用指南

    1. 引言 JSDoc是一个用于 JavaScript 的API文档生成器,可以将文档注释直接添加到源代码中,JSDoc 工具将扫描您的源代码并提供一些操作,例如,生成一个 API 文档 JSDoc官 ...

  2. 用Vue3.0 写过组件吗?如果想实现一个 Modal你会怎么设计?

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 一.组件设计 组件就是把图形.非图形的各种逻辑均抽象为一个统一的概念(组件)来实现开发的模式 现在有一个场景,点击新增与编辑都弹框出来进行 ...

  3. 把分钟数转化成几小时几分钟(100 -> 01:40)

    /// <summary> /// 把分钟数转化成几小时几分钟(100 -> 01:40) /// </summary> /// <param name=" ...

  4. KingbaseES使用kbbench计算连接耗时

    前言 本文讨论一下KingbaseES数据库中如何计算数据库连接耗时.有这样一个场景,不借助第三方工具,在数据库服务端计算1000个数据库连接的总耗时,并取得每个连接耗时的平均值.怎样实现呢?我们可以 ...

  5. MySQL 索引失效场景总结

    查询条件有 or 假设在 customer_name 字段设置了普通索引,执行以下 sql: # type: ref, possible_keys: idx_customer_name, key: i ...

  6. IDEA怎么添加类注释和方法注释模板

    IDEA设置自动生成模板类和方法注释 一.模板类注释 在右侧粘贴如下代码: /** *@BelongsProject: ${PROJECT_NAME} *@BelongsPackage: ${PACK ...

  7. 给你的wordpress添加文章内图片鼠标点击放大浏览的功能吧~

    注:笔者已启用WP Githuber MD插件使用Markdown语法进行文章编辑,启用的主题为generatepress. 1.进入你的宝塔面板首页 点击文件选项: 2.分别找到以下几个文件进行修改 ...

  8. 3D Object Detection Essay Reading 2024.04.05

    EMIFF 论文:https://arxiv.org/abs/2303.10975 代码:https://github.com/Bosszhe/EMIFF ​ 本文提出了一种新的基于摄像机的三维检测框 ...

  9. CSP2020-S 游记

    10.11 CSP-S1 自从国庆假期回到学校我申请停课, 从此开始了长达近一个的停课生活. 初赛也是有惊无险地过去了. 一出来发现自己仍旧是大考必败型选手, 对了答案发现我其实错了挺多的, 可能是因 ...

  10. OpenHarmony Meetup成都站招募令

    OpenHarmony Meetup 城市巡回成都站火热招募中!! 日期:2023 年 10 月 27 日 14:00 地点:电子科技大学(沙河校区)学术交流中心一楼 104 会议室 与 OpenHa ...