WPF 对接 Vortice 调用 D2D 使用 IWICBitmap 离屏渲染
通过 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 类型开放好了
可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 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 离屏渲染的更多相关文章
- 【ASP.NET Web API教程】3.3 通过WPF应用程序调用Web API(C#)
原文:[ASP.NET Web API教程]3.3 通过WPF应用程序调用Web API(C#) 注:本文是[ASP.NET Web API系列教程]的一部分,如果您是第一次看本博客文章,请先看前面的 ...
- java接口对接——别人调用我们接口获取数据
java接口对接——别人调用我们接口获取数据,我们需要在我们系统中开发几个接口,给对方接口规范文档,包括访问我们的接口地址,以及入参名称和格式,还有我们的返回的状态的情况, 接口代码: package ...
- WPF 用代码调用dynamic resource动态更改背景 - CSDN博客
原文:WPF 用代码调用dynamic resource动态更改背景 - CSDN博客 一般dynamic resoource通常在XAML里调用,如下范例: <Button Click=&qu ...
- WPF 精修篇 调用Win32Api
原文:WPF 精修篇 调用Win32Api 栗子是 调用WIn32API 让窗口最前 后台代码 [DllImport("user32.dll")] private static e ...
- dotnet 读 WPF 源代码笔记 布局时 Arrange 如何影响元素渲染坐标
大家是否好奇,在 WPF 里面,对 UIElement 重写 OnRender 方法进行渲染的内容,是如何受到上层容器控件的布局而进行坐标偏移.如有两个放入到 StackPanel 的自定义 UIEl ...
- 修改ViewPager调用setCurrentItem时,滑屏的速度
原文摘自: 修改ViewPager调用setCurrentItem时,滑屏的速度 在使用ViewPager的过程中,有需要直接跳转到某一个页面的情况,这个时候就需要用到ViewPager的setCur ...
- 【WPF】Winform调用WPF窗体注意事项
1.需要添加一些引用 2.调用处使用如下方法进行调用 Window win= new Window(); ElementHost.EnableModelessKeyboardInterop(win) ...
- WPF 使用RPC调用其他进程
如果在 WPF 需要用多进程通信,一个推荐的方法是 WCF ,因为 WCF 是 RPC 计算.先来讲下 RPC (Remote Procedure Call) 远程过程调用,他是通过特定协议,包括 t ...
- 系统对接API调用
在与公司外部系统对接时,API接口一般采用REST风格,对外暴露HTTP服务.只需要将入参封装好,并发起HTTP请求即可.具体请求流程如下图所示: 数据格式 API调用参数分为系统参数和业务参数,请求 ...
- WPF中窗体调用窗体
在WPF中有时候我们需要在一个窗体中去调用另外的一个窗体,下面给出调用方法. 下面实现在MainWindow中通过点击一个按钮调用另外的一个窗口. 首先创建你要调用的另外一个窗口:点击最上面的项目 ...
随机推荐
- ProtoBuf-gRPC实践
目录介绍 01.gRPC学习背景 1.1 为什么要学RPC 1.2 RPC是什么 1.3 网络库收益分析 1.4 学习计划说明 1.5 学习问题思考 02.ProtoBuf的介绍 2.1 ProtoB ...
- debian12 linux 安装xfce4
1.安装显示服务器 sudo apt install xorg 2.安装xfce sudo apt install xfce4 xfce4-goodies 3.安装显示管理器 sudo apt ins ...
- python 处理国家标准行业编码(编码·门类·大类·中类·小类)
进度 今天完整地进行了行业维度的清洗分析,把行业代码根据国家标准清洗出格式为 "编码·门类·大类·中类·小类" 的数据格式 过程 1.先把国家标准编码转化为json数据 2.根 ...
- WPF开发分页控件:实现可定制化分页功能及实现原理解析
概要 本文将详细介绍如何使用WPF(Windows Presentation Foundation)开发一个分页控件,并深入解析其实现原理.我们将通过使用XAML和C#代码相结合的方式构建分页控件,并 ...
- #线段树、构造#A 或位运算
题目 一个长度为\(n\)的非负整数序列, 需要满足\(m\)个区间或值为阈值的限制条件 现在要构造一个这样的序列,不存在输出No 分析 线段树支持区间与,但查询区间或,下传标记,那就很好做了 代码 ...
- Bootstrap实战 - 评论列表
一.介绍 社交媒体网站盛行,人们常常会使用评论表达自己的观点,评论功能已然成为网站的一部分. 二.知识点 2.1 媒体对象 官方解释:这是一个抽象的样式,用以构建不同类型的组件,这些组件都具有在文本内 ...
- 做好PPT,提高沟通效率
本文于2019年7月12日完成,发布在个人博客网站上. 考虑个人博客因某种原因无法修复,于是在博客园安家,之前发布的文章逐步搬迁过来. 只用一套PPT来通杀全部使用场景,这个想法其实很天真. 作为一种 ...
- 解锁OpenHarmony技术日!年度盛会,即将揭幕!
OpenHarmony技术日 即将揭幕!4月25日(星期一)09:00-18:00与你惊喜相约! 扫码直达 共建新技术.开拓新领域 OpenHarmony 工作委员会+7 家单位共同发起 ...
- 战码先锋直播预告丨参与ArkUI,共建OpenHarmony繁荣生态
OpenAtom OpenHarmony(以下简称"OpenHarmony")工作委员会首度发起「OpenHarmony开源贡献者计划」,旨在鼓励开发者参与OpenHarmony开 ...
- WPF 像CSS一样使用 Font Awesome 图标字体
WPF 像CSS一样使用 Font Awesome 图标字体 编写目的 WPF中使用这种图标字体不免会出现可读性差的问题,现阶段网络上有的大部分实现方式都是建立枚举,我感觉这样后续维护起来有些麻烦,需 ...