2019-10-23-WPF-使用-SharpDx-异步渲染
title | author | date | CreateTime | categories |
---|---|---|---|---|
WPF 使用 SharpDx 异步渲染
|
lindexi
|
2019-10-23 21:18:38 +0800
|
2018-05-01 20:33:10 +0800
|
WPF D2D DirectX SharpDX 渲染
|
本文告诉大家如何通过 SharpDx 进行异步渲染,但是因为在 WPF 是需要使用 D3DImage 画出来,所以渲染只是画出图片,最后的显示还是需要 WPF 在他自己的主线程渲染。
本文是一个系列,希望大家从第一篇开始看
虽然上一篇告诉大家如何使用封装的 SharpDx 控件,但是大家也看到了核心是使用CompositionTarget
告诉刷新的。
这个方法适合不停变化的控件,如果是很少刷新的控件使用这个方法会降低 WPF 的性能。
因为 CompositionTarget 刷新数太快了,而且每次都需要重复刷新一个图片,显示的性能比不过自带的控件。
使用方法
因为使用 SharpDx 在 WPF 除了使用 D3DImage 还可以使用 D3D11Image 但是这个需要分开 x86 和 x64 。现在使用的方法是把 D3DImage 作为图片画出来,如果使用 D3D11Image 也没有什么性能提升。
所以本文就和WPF 使用封装的 SharpDx 控件使用的基类不同,原来的基类是 Image 现在的基类是 FrameworkElement
。但是如果使用 Image 而且每次刷新的都是比较小的,性能会比使用 FrameworkElement 画出高一些。
这里因为封装没有告诉需要刷新的大小,所以只能每次都全部刷新,这样的性能使用 FrameworkElement 不会降低。
下面创建一个类,继承 SharpDxMaynumaSejair ,这个 SharpDxMaynumaSejair 是继承 FrameworkElement 而不是图片,这个类的代码放在文章最后,使用这个类可以异步渲染。
public abstract class SharpDxMaynumaSejair : FrameworkElement
请随意写一个类继承 SharpDxMaynumaSejair 并且添加重写的 OnRender 函数。
下面是 SharpDxMaynumaSejair 类的 OnRender 方法,通过继承他就可以使用 SharpDx 画出来。
protected abstract void OnRender(SharpDX.Direct2D1.RenderTarget renderTarget);
其他的代码和WPF 使用封装的 SharpDx 控件使用的差不多
直接通过 OnRender 就可以进行渲染,但是 OnRender 是被触发的,触发的方法是调用基类 Rendering
函数,调用了这个函数会进入异步的 SharpDx 渲染,渲染完成再通过 WPF 渲染画出来。
因为不需要使用 CompositionTarget.Rendering 渲染,所以可以提高 WPF 刷新速度。
这个类可以在执行渲染计算复杂使用,假如需要渲染出 10000 个椭圆,而且有很多重叠,而且不需要立刻渲染。那么就可以使用本文的这个类,这个类可以在调用时异步渲染,不会卡 UI 线程,在 SharpDx 渲染完成再通过 WPF 渲染,这时 WPF 渲染也就是画出图片,性能比画出 10000 个椭圆快很多。通过这个方法可以提高渲染性能,提高软件打开的性能。
但是通过这个方法建议软件是 x64 因为需要很多内存。
下面来告诉大家本文这个类的原理。
绑定
如果需要使用 SharpDx 需要把 SharpDX.Direct3D11 和 D3DImage 绑定,调用时不能在这个控件的 Load 前,不然无法拿到大小。
下面这个方法和WPF 使用封装的 SharpDx 控件使用相同,所以我就直接写代码不解释了。
private void CreateAndBindTargets(int actualWidth, int actualHeight)
{
var width = Math.Max(actualWidth, 100);
var height = Math.Max(actualHeight, 100); var renderDesc = new SharpDX.Direct3D11.Texture2DDescription
{
BindFlags = SharpDX.Direct3D11.BindFlags.RenderTarget | SharpDX.Direct3D11.BindFlags.ShaderResource,
Format = SharpDX.DXGI.Format.B8G8R8A8_UNorm,
Width = width,
Height = height,
MipLevels = 1,
SampleDescription = new SharpDX.DXGI.SampleDescription(1, 0),
Usage = SharpDX.Direct3D11.ResourceUsage.Default,
OptionFlags = SharpDX.Direct3D11.ResourceOptionFlags.Shared,
CpuAccessFlags = SharpDX.Direct3D11.CpuAccessFlags.None,
ArraySize = 1
}; var device = new SharpDX.Direct3D11.Device(DriverType.Hardware,
SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport); _device = device; var renderTarget = new SharpDX.Direct3D11.Texture2D(device, renderDesc); var surface = renderTarget.QueryInterface<SharpDX.DXGI.Surface>(); var d2DFactory = new SharpDX.Direct2D1.Factory(); var renderTargetProperties =
new SharpDX.Direct2D1.RenderTargetProperties(
new SharpDX.Direct2D1.PixelFormat(SharpDX.DXGI.Format.Unknown,
SharpDX.Direct2D1.AlphaMode.Premultiplied)); _d2DRenderTarget = new SharpDX.Direct2D1.RenderTarget(d2DFactory, surface, renderTargetProperties); SetRenderTarget(renderTarget); device.ImmediateContext.Rasterizer.SetViewport(0, 0, width, height); CreationProperties creationProperties = new CreationProperties
{
DebugLevel = DebugLevel.Error,
Options = DeviceContextOptions.EnableMultithreadedOptimizations,
ThreadingMode = ThreadingMode.MultiThreaded,
}; _d2dContext = new DeviceContext(surface, creationProperties); _d2DRenderTarget = _d2dContext; InvalidateVisual();
}
如果大家有对比两个函数,会发现有一个属性不相同,原来是 Direct3D11.Device 被我拿出一个字段,这个字段在下面会使用到。
虽然已经写好 D3DImage 但是如何显示?
继承 FrameworkElement 可以重写 OnRender 。通过 OnRender 可以画出图片,而 D3Dimage 就是 ImageSource,虽然可以看到我自己定义的也是 OnRender, 这个函数和自己定义的不相同,虽然我把自己定义的函数也是和他使用相同的命名。看到下面代码也许就知道我说的是什么。
需要注意,如果因为_d3D.PixelWidth
为0抛出异常,那么就可能是绑定的时候在 Load 之前,需要修改一下代码。
protected override void OnRender(DrawingContext drawingContext)
{
drawingContext.DrawImage(_d3D, new Rect(new Size(_d3D.PixelWidth, _d3D.PixelHeight)));
}
渲染为什么空白
现在已经完成了修改继承类,但是原来使用的渲染还是没有修改。如果大家尝试在一个按钮按下时,进行刷新。代码大概是在 按钮按下时调用Rendering
函数,这个函数只执行一次
public void Rendering()
{ _d2dContext.BeginDraw(); OnRender(_d2dContext); _d2dContext.EndDraw(); _d3D.Lock(); _d3D.AddDirtyRect(new Int32Rect(0, 0, _d3D.PixelWidth, _d3D.PixelHeight)); _d3D.Unlock(); base.InvalidateVisual();
}
如果大家运行了代码,会发现现在的界面虽然有执行OnRender
但是显示和希望的不一样。
原因是没有等待 SharpDx 画完,虽然调用了EndDraw
但是只是把渲染命令发给显卡。
那么如何等待 SharpDx 画完
等待画完
如果刚才看到 CreateAndBindTargets 会看到把 Direct3D11.Device 放在字段,因为在 Rendering 就需要使用这个字段等待显卡刷新。
请看下面的代码
public void Rendering()
{
_d2dContext.BeginDraw(); OnRender(_d2dContext); _d2dContext.EndDraw(); _device.ImmediateContext.Flush(); _d3D.Lock(); _d3D.AddDirtyRect(new Int32Rect(0, 0, _d3D.PixelWidth, _d3D.PixelHeight)); _d3D.Unlock(); base.InvalidateVisual();
}
尝试修改代码运行一下,可以如果调用了一次函数就可以刷新一次。
如果是在按钮按下是刷新,但是渲染很多内容,那么主线程会在 Flush 占用很多资源。
在 WPF 的渲染,是把主线程和渲染线程分开,经常说的主线程是没有做渲染的,在 DrawingContext 实际上不是调用了显示,而且通过 Channel 发送到Dx渲染,也就是调用函数只是告诉显卡如何渲染。
在这里也是需要做相同的方法。
异步渲染
大家也可以看到,只需要使用一个新的线程去等待渲染就可以,使用新线程的方法是 Task ,但是不能把 d3dImage 放在另一个线程,他必须在主线程。
修改一下代码
public async void Rendering()
{ await Task.Run(() =>
{
_d2dContext.BeginDraw(); OnRender(_d2dContext); _d2dContext.EndDraw(); _device.ImmediateContext.Flush();
}); _d3D.Lock(); _d3D.AddDirtyRect(new Int32Rect(0, 0, _d3D.PixelWidth, _d3D.PixelHeight)); _d3D.Unlock(); base.InvalidateVisual();
}
现在就可以在另一个线程告诉 SharpDx 如何画,然后在另一个线程等待 SharpDx 画出来。这样可以做到异步渲染。
需要告诉大家,异步渲染不是多线程渲染,原因是渲染还是需要显卡来做,如果显卡的资源有限,那么渲染需要的时间不会降低。
如果需要设置多线程渲染,可以通过在CreationProperties
使用ThreadingMode.MultiThreaded
现在上面的代码已经这样使用。
不过大家不要直接使用这个类,因为上面代码使用Task.Run
,如果在线程池没有资源,那么这个代码可能会等很久,这样的性能比较差。
这个控件可以用在不需要立刻渲染的资源,但是渲染很慢,可以在用户做其他的输入进行渲染。因为默认的渲染都会让用户感觉软件速度有些慢,不过和这个做法相同的是使用 RenderTargetBitmap ,在另一个线程渲染,然后在主线程显示。
和 RenderTargetBitmap 不同的,本文的方法可以在显卡渲染,渲染性能比 RenderTargetBitmap 高。
多线程渲染
下面告诉大家如何使用 RenderTargetBitmap 多线程渲染
首先创建一个字段,在这个字段为空就需要调用函数创建
private RenderTargetBitmap _stouFa;
这个方法适合只有进行很少的渲染,而且很慢的应用。实际上 RenderTargetBitmap 可以 Freeze ,所以在另一个线程渲染是可以。
protected override void OnRender(DrawingContext drawingContext)
{
if (_stouFa == null)
{
TesuFudresel();
}
else
{
drawingContext.DrawImage(_stouFa, new Rect(new Size(ActualWidth, ActualHeight)));
}
}
在方法 TesuFudresel 就是创建渲染
private void TesuFudresel()
{ Task.Run(() =>
{
var renderTargetBitmap = new RenderTargetBitmap(100, 100, 96, 96, new PixelFormat());
var drawingVisual = new DrawingVisual();
var drawingContext = drawingVisual.RenderOpen();
drawingContext.DrawRectangle(Brushes.Chartreuse, new Pen(Brushes.Chartreuse, 2),
new Rect(new Point(10, 10), new Size(100, 100)));
drawingContext.Close();
renderTargetBitmap.Render(drawingVisual);
renderTargetBitmap.Freeze(); _stouFa = renderTargetBitmap;
}); }
代码主要就是使用 drawingVisual 画出来,然后让对象可以跨线程。
本文就告诉大家如何使用 SharpDx 异步渲染,还告诉大家如何使用 WPF 自带的类进行多线程渲染,下面就是本文这个控件的代码
建议大家自己写一个线程调度而不是使用 Task ,因为最近在写 Avalon 所以暂时我也没有把下面的类写的可以在产品使用。请大家参考代码而不是在项目使用这个代码,因为存在 Task 需要等很久和代码没有优化。
public abstract class SharpDxMaynumaSejair : FrameworkElement
{
private D3DImage _d3D = new D3DImage(); /// <inheritdoc />
protected SharpDxMaynumaSejair()
{
Loaded += SharpDxMaynumaSejair_Loaded;
} private void SharpDxMaynumaSejair_Loaded(object sender, RoutedEventArgs e)
{
CreateAndBindTargets((int) ActualWidth, (int) ActualHeight);
} private void CreateAndBindTargets(int actualWidth, int actualHeight)
{
var width = Math.Max(actualWidth, 100);
var height = Math.Max(actualHeight, 100); var renderDesc = new SharpDX.Direct3D11.Texture2DDescription
{
BindFlags = SharpDX.Direct3D11.BindFlags.RenderTarget | SharpDX.Direct3D11.BindFlags.ShaderResource,
Format = SharpDX.DXGI.Format.B8G8R8A8_UNorm,
Width = width,
Height = height,
MipLevels = 1,
SampleDescription = new SharpDX.DXGI.SampleDescription(1, 0),
Usage = SharpDX.Direct3D11.ResourceUsage.Default,
OptionFlags = SharpDX.Direct3D11.ResourceOptionFlags.Shared,
CpuAccessFlags = SharpDX.Direct3D11.CpuAccessFlags.None,
ArraySize = 1
}; var device = new SharpDX.Direct3D11.Device(DriverType.Hardware,
SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport); _device = device; var renderTarget = new SharpDX.Direct3D11.Texture2D(device, renderDesc); var surface = renderTarget.QueryInterface<SharpDX.DXGI.Surface>(); var d2DFactory = new SharpDX.Direct2D1.Factory(); var renderTargetProperties =
new SharpDX.Direct2D1.RenderTargetProperties(
new SharpDX.Direct2D1.PixelFormat(SharpDX.DXGI.Format.Unknown,
SharpDX.Direct2D1.AlphaMode.Premultiplied)); _d2DRenderTarget = new SharpDX.Direct2D1.RenderTarget(d2DFactory, surface, renderTargetProperties); SetRenderTarget(renderTarget); device.ImmediateContext.Rasterizer.SetViewport(0, 0, width, height); CreationProperties creationProperties = new CreationProperties
{
DebugLevel = DebugLevel.Error,
Options = DeviceContextOptions.EnableMultithreadedOptimizations,
ThreadingMode = ThreadingMode.MultiThreaded,
}; _d2dContext = new DeviceContext(surface, creationProperties); _d2DRenderTarget = _d2dContext; InvalidateVisual();
} public new void InvalidateVisual()
{
Rendering();
} /// <inheritdoc />
protected override void OnRender(DrawingContext drawingContext)
{
drawingContext.DrawImage(_d3D, new Rect(new Size(_d3D.PixelWidth, _d3D.PixelHeight)));
} protected abstract void OnRender(SharpDX.Direct2D1.RenderTarget renderTarget); private SharpDX.Direct3D9.Texture _renderTarget;
private SharpDX.Direct2D1.RenderTarget _d2DRenderTarget;
private DeviceContext _d2dContext; private SharpDX.Direct3D11.Device _device; private async void Rendering()
{
await Task.Run(() =>
{
_d2dContext.BeginDraw(); OnRender(_d2dContext); _d2dContext.EndDraw(); _device.ImmediateContext.Flush();
}); _d3D.Lock(); _d3D.AddDirtyRect(new Int32Rect(0, 0, _d3D.PixelWidth, _d3D.PixelHeight)); _d3D.Unlock(); base.InvalidateVisual();
} private void SetRenderTarget(SharpDX.Direct3D11.Texture2D target)
{
var format = TranslateFormat(target);
var handle = GetSharedHandle(target); var presentParams = GetPresentParameters();
var createFlags = SharpDX.Direct3D9.CreateFlags.HardwareVertexProcessing |
SharpDX.Direct3D9.CreateFlags.Multithreaded |
SharpDX.Direct3D9.CreateFlags.FpuPreserve; var d3DContext = new SharpDX.Direct3D9.Direct3DEx();
var d3DDevice = new SharpDX.Direct3D9.DeviceEx(d3DContext, 0, SharpDX.Direct3D9.DeviceType.Hardware,
IntPtr.Zero, createFlags,
presentParams); _renderTarget = new SharpDX.Direct3D9.Texture(d3DDevice, target.Description.Width,
target.Description.Height, 1,
SharpDX.Direct3D9.Usage.RenderTarget, format, SharpDX.Direct3D9.Pool.Default, ref handle); using (var surface = _renderTarget.GetSurfaceLevel(0))
{
_d3D.Lock();
_d3D.SetBackBuffer(D3DResourceType.IDirect3DSurface9, surface.NativePointer);
_d3D.Unlock();
}
} private static SharpDX.Direct3D9.PresentParameters GetPresentParameters()
{
var presentParams = new SharpDX.Direct3D9.PresentParameters(); presentParams.Windowed = true;
presentParams.SwapEffect = SharpDX.Direct3D9.SwapEffect.Discard;
presentParams.DeviceWindowHandle = NativeMethods.GetDesktopWindow();
presentParams.PresentationInterval = SharpDX.Direct3D9.PresentInterval.Default; return presentParams;
} private IntPtr GetSharedHandle(SharpDX.Direct3D11.Texture2D texture)
{
using (var resource = texture.QueryInterface<SharpDX.DXGI.Resource>())
{
return resource.SharedHandle;
}
} private static SharpDX.Direct3D9.Format TranslateFormat(SharpDX.Direct3D11.Texture2D texture)
{
switch (texture.Description.Format)
{
case SharpDX.DXGI.Format.R10G10B10A2_UNorm:
return SharpDX.Direct3D9.Format.A2B10G10R10;
case SharpDX.DXGI.Format.R16G16B16A16_Float:
return SharpDX.Direct3D9.Format.A16B16G16R16F;
case SharpDX.DXGI.Format.B8G8R8A8_UNorm:
return SharpDX.Direct3D9.Format.A8R8G8B8;
default:
return SharpDX.Direct3D9.Format.Unknown;
}
} private static class NativeMethods
{
[DllImport("user32.dll", SetLastError = false)]
public static extern IntPtr GetDesktopWindow();
}
}
更多渲染博客请看 WPF 底层渲染
特别感谢
2019-10-23-WPF-使用-SharpDx-异步渲染的更多相关文章
- WPF 使用 SharpDx 异步渲染
本文告诉大家如何通过 SharpDx 进行异步渲染,但是因为在 WPF 是需要使用 D3DImage 画出来,所以渲染只是画出图片,最后的显示还是需要 WPF 在他自己的主线程渲染 本文是一个系列,希 ...
- WPF 使用 SharpDX 在 D3DImage 显示
原文:WPF 使用 SharpDX 在 D3DImage 显示 版权声明:博客已迁移到 http://lindexi.gitee.io 欢迎访问.如果当前博客图片看不到,请到 http://linde ...
- WPF 使用 SharpDX
原文:WPF 使用 SharpDX 版权声明:博客已迁移到 http://lindexi.gitee.io 欢迎访问.如果当前博客图片看不到,请到 http://lindexi.gitee.io 访问 ...
- Beta冲刺(2/7)——2019.5.23
所属课程 软件工程1916|W(福州大学) 作业要求 Beta冲刺(2/7)--2019.5.23 团队名称 待就业六人组 1.团队信息 团队名称:待就业六人组 团队描述:同舟共济扬帆起,乘风破浪万里 ...
- 背水一战 Windows 10 (23) - MVVM: 通过 Binding 或 x:Bind 结合 Command 实现,通过 ButtonBase 触发命令
[源码下载] 背水一战 Windows 10 (23) - MVVM: 通过 Binding 或 x:Bind 结合 Command 实现,通过 ButtonBase 触发命令 作者:webabcd ...
- 异步渲染页面怎么点击checkbox获取value值
前后端分离时 后端向前端传递json数据 前端根据需要进行页面渲染 因为是异步渲染 想要获取获取渲染数据里面的值时获取不到的 介绍两个方法: 1,设置全局变量 即渲染时在html页面设置全局变量 如 ...
- 10.23 正睿停课训练 Day7
目录 2018.10.23 正睿停课训练 Day7 A 矩形(组合) B 翻转(思路) C 求和(思路 三元环计数) 考试代码 B1 B2 C 2018.10.23 正睿停课训练 Day7 期望得分: ...
- Daily Scrum 10.23
(写于10.22周四) 说下现在的人员情况: 康家华请假至下周一,刘彦熙至周五18:00,张启东至周六中午. 其他人正常工作. 然后是现在的进度情况: 已经完成服务器数据库搭建,以及基础的注册登陆功能 ...
- 从父子组件的mounted钩子的同步执行与页面的异步渲染看nextTick的用法
最近复习vue的时候遇到了一个很奇怪的问题,我们直接从实例中看: <div id="app"> <child ref="child">& ...
- 2019.10 搜索引擎最新排名,Elasticsearch遥遥领先
大数据的搜索平台已经成为了众多企业的标配,Elasticsearch.Splunk(商业上市公司).Solr(Apache开源项目)是其中最为优秀和流行的选择.在2019.10 最新搜索引擎排名中,E ...
随机推荐
- Quarz框架学习
参考博客:https://www.cnblogs.com/zhanghaoliang/p/7886110.html
- 【水滴石穿】React-Redux-Demo
这个项目没有使用什么组件,可以理解就是个redux项目 项目地址为:https://github.com/HuPingKang/React-Redux-Demo 先看效果图 点击颜色字体颜色改变,以及 ...
- Mathcad 是一种工程计算软件,主要运算功能:代数运算、线性代数、微积分、符号计算、2D和3D图表、动画、函数、程序编写、逻辑运算、变量与单位的定义和计算等。
Mathcad软件包Mathcad是由MathSoft公司(2006 年4 月被美国PTC收购)推出的一种交互式数值计算系统. Mathcad 是一种工程计算软件,作为工程计算的全球标准,与专有的计算 ...
- phpinfo空白
<?php phpinfo(); ?> 以上代码放入一个kk.php的文件里,但浏览器显示是空白. 原因: 检查php.ini文件里 disable_functions =后面有没有限制p ...
- pom.xml中若出现jar not found;
pom.xml中若出现jar not found;我们可以直接在view ->tool windows ->Maven Project 中直接install
- FastAdmin 自学教程 - 目录(持续更新)(2019-10-11)
FastAdmin 自学教程 - 目录 本自学教程将不定期更新. 了解 FastAdmin FastAdmin 开发第 1 天:了解 FastAdmin 框架 FastAdmin 开发第 2 天:安装 ...
- MaxCompute问答整理之6月
本文是基于本人对MaxCompute产品的学习进度,再结合开发者社区里面的一些问题,进而整理成文.希望对大家有所帮助,下面咱们正式开始. 1.什么是MaxCompute呢?MaxCompute是由阿里 ...
- @topcoder - SRM766R1 D1L3@ ShortestMissingSubsequences
目录 @description@ @solution@ @accepted code@ @details@ @description@ 给定一个大小为 G 的字符集,并给定一个长度为 N 的字符串 A ...
- 【JZOJ4817】【NOIP2016提高A组五校联考4】square
题目描述 输入 输出 样例输入 3 4 1 1 0 1 0 1 1 0 0 1 1 0 5 1 1 2 3 2 1 3 2 3 2 3 4 1 1 3 4 1 2 3 4 样例输出 1 1 1 2 2 ...
- lower_bounder()和upper_bound()的函数
lower_bound() .upper_bound()都运用于有序区间的二分查找. ForwardIter lower_bound(ForwardIter first, ForwardIter la ...