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 在他自己的主线程渲染。

本文是一个系列,希望大家从第一篇开始看

更多请看 WPF 使用 SharpDx 渲染博客导航

虽然上一篇告诉大家如何使用封装的 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 底层渲染

特别感谢

Direct2D - 随笔分类 - 万仓一黍 - 博客园

Direct2D - 随笔分类 - 万一 - 博客园

Direct2D - 随笔分类 - zdd - 博客园

2019-10-23-WPF-使用-SharpDx-异步渲染的更多相关文章

  1. WPF 使用 SharpDx 异步渲染

    本文告诉大家如何通过 SharpDx 进行异步渲染,但是因为在 WPF 是需要使用 D3DImage 画出来,所以渲染只是画出图片,最后的显示还是需要 WPF 在他自己的主线程渲染 本文是一个系列,希 ...

  2. WPF 使用 SharpDX 在 D3DImage 显示

    原文:WPF 使用 SharpDX 在 D3DImage 显示 版权声明:博客已迁移到 http://lindexi.gitee.io 欢迎访问.如果当前博客图片看不到,请到 http://linde ...

  3. WPF 使用 SharpDX

    原文:WPF 使用 SharpDX 版权声明:博客已迁移到 http://lindexi.gitee.io 欢迎访问.如果当前博客图片看不到,请到 http://lindexi.gitee.io 访问 ...

  4. Beta冲刺(2/7)——2019.5.23

    所属课程 软件工程1916|W(福州大学) 作业要求 Beta冲刺(2/7)--2019.5.23 团队名称 待就业六人组 1.团队信息 团队名称:待就业六人组 团队描述:同舟共济扬帆起,乘风破浪万里 ...

  5. 背水一战 Windows 10 (23) - MVVM: 通过 Binding 或 x:Bind 结合 Command 实现,通过 ButtonBase 触发命令

    [源码下载] 背水一战 Windows 10 (23) - MVVM: 通过 Binding 或 x:Bind 结合 Command 实现,通过 ButtonBase 触发命令 作者:webabcd ...

  6. 异步渲染页面怎么点击checkbox获取value值

    前后端分离时 后端向前端传递json数据  前端根据需要进行页面渲染 因为是异步渲染 想要获取获取渲染数据里面的值时获取不到的 介绍两个方法: 1,设置全局变量 即渲染时在html页面设置全局变量 如 ...

  7. 10.23 正睿停课训练 Day7

    目录 2018.10.23 正睿停课训练 Day7 A 矩形(组合) B 翻转(思路) C 求和(思路 三元环计数) 考试代码 B1 B2 C 2018.10.23 正睿停课训练 Day7 期望得分: ...

  8. Daily Scrum 10.23

    (写于10.22周四) 说下现在的人员情况: 康家华请假至下周一,刘彦熙至周五18:00,张启东至周六中午. 其他人正常工作. 然后是现在的进度情况: 已经完成服务器数据库搭建,以及基础的注册登陆功能 ...

  9. 从父子组件的mounted钩子的同步执行与页面的异步渲染看nextTick的用法

    最近复习vue的时候遇到了一个很奇怪的问题,我们直接从实例中看: <div id="app"> <child ref="child">& ...

  10. 2019.10 搜索引擎最新排名,Elasticsearch遥遥领先

    大数据的搜索平台已经成为了众多企业的标配,Elasticsearch.Splunk(商业上市公司).Solr(Apache开源项目)是其中最为优秀和流行的选择.在2019.10 最新搜索引擎排名中,E ...

随机推荐

  1. python实现统计字符类型

    #字符串处理统计类型def my_func(*str): list_num = [] list_big = [] list_small = [] list_qt = [] str = input('请 ...

  2. 会话技术之Session(购物车加入、查看和清空)

    会话技术之Session session:服务器端会话技术 当我们第一次访问的服务器的时候,服务器获取id, 能获取id 要拿着这个id去服务器中查找有无此session 若查找到了:直接拿过来将数据 ...

  3. 【机器学习PAI实战】—— 玩转人工智能之商品价格预测

    摘要: 我们经常思考机器学习,深度学习,以至于人工智能给我们带来什么?在数据相对充足,足够真实的情况下,好的学习模型可以发现事件本身的内在规则,内在联系.我们去除冗余的信息,可以通过最少的特征构建最简 ...

  4. PLUTO平台是由美林数据技术股份有限公司下属西安交大美林数据挖掘研究中心自主研发的一款基于云计算技术架构的数据挖掘产品,产品设计严格遵循国际数据挖掘标准CRISP-DM(跨行业数据挖掘过程标准),具备完备的数据准备、模型构建、模型评估、模型管理、海量数据处理和高纬数据可视化分析能力。

    http://www.meritdata.com.cn/article/90 PLUTO平台是由美林数据技术股份有限公司下属西安交大美林数据挖掘研究中心自主研发的一款基于云计算技术架构的数据挖掘产品, ...

  5. linux中的用户、群组和权限

     linux中的用户.群组和权限   新建用户natasha,uid为1000,gid为555,备注信息为“master”   groupadd -g 555 natasha useradd -u 1 ...

  6. LocalDateTime计算时间差

    LocalDateTime 为java8的新特性之一 LocalDateTime.now() 获得当前时间 java.time.Duration duration = java.time.Durati ...

  7. QT QSettings读写配置文件

    QT QSettings读写配置文件 需要用一个配置文件来保存程序的初始值,同时也需要做保存修改后的值. 那么借助于QSetting 就可以达到目的. 注意,生成的是 ini 文件! //1.创建和读 ...

  8. SpringMVC的简单传值总结

    之前学习SpringMVC时感觉他的传值很神奇:简便,快捷,高效. 今天写几个简单的传值与大家分享,希望能对大家有帮助. 一. 从后往前传: (1) @Controller @RequestMappi ...

  9. 在oracle中操作数据——使用特点的格式插入日期 sql函数的使用——日期函数

    日期函数用于处理date类型的数据,默认情况下的日期格式是dd-mm-yy即12-7月-78 (1)sysdate:该函数返回系统时间 (2)add_months(d,n) (3)last_day(d ...

  10. 【uml】之用例图中的关系 标签: uml图形 2014-11-23 11:10 1422人阅读 评论(29)

    用例图显示谁是相关的用户,用户希望系统提供什么样的服务(用例),用例之间的关系图,用例图主要的作用是获取需求.指导测试.所以,用例图是站在用户的角度来画的图,应该体现的是用户想要的功能,并不需要体现如 ...