WPF 使用 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 底层渲染
特别感谢
我搭建了自己的博客 https://blog.lindexi.com/ 欢迎大家访问,里面有很多新的博客。只有在我看到博客写成熟之后才会放在csdn或博客园,但是一旦发布了就不再更新
如果在博客看到有任何不懂的,欢迎交流,我搭建了 dotnet 职业技术学院 欢迎大家加入
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接:http://blog.csdn.net/lindexi_gd ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。
WPF 使用 SharpDx 异步渲染的更多相关文章
- WPF 使用 SharpDX 在 D3DImage 显示
原文:WPF 使用 SharpDX 在 D3DImage 显示 版权声明:博客已迁移到 http://lindexi.gitee.io 欢迎访问.如果当前博客图片看不到,请到 http://linde ...
- WPF 使用 SharpDX
原文:WPF 使用 SharpDX 版权声明:博客已迁移到 http://lindexi.gitee.io 欢迎访问.如果当前博客图片看不到,请到 http://lindexi.gitee.io 访问 ...
- 异步渲染页面怎么点击checkbox获取value值
前后端分离时 后端向前端传递json数据 前端根据需要进行页面渲染 因为是异步渲染 想要获取获取渲染数据里面的值时获取不到的 介绍两个方法: 1,设置全局变量 即渲染时在html页面设置全局变量 如 ...
- 从父子组件的mounted钩子的同步执行与页面的异步渲染看nextTick的用法
最近复习vue的时候遇到了一个很奇怪的问题,我们直接从实例中看: <div id="app"> <child ref="child">& ...
- vue 异步渲染
<!DOCTYPE html> <html> <head> <title> hello world vue </title> <met ...
- WPF 设置纯软件渲染
最近看到有小伙伴说 WPF 使用硬件渲染,如何让 WPF 不使用硬件渲染,因为他觉得性能太好了.万一这个版本发布了,产品经理说下个版本要提升性能就不好了.于是就找到一个快速的方法,让程序不使用硬件渲染 ...
- Vue为何采用异步渲染
Vue为何采用异步渲染 Vue在更新DOM时是异步执行的,只要侦听到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更,如果同一个watcher被多次触发,只会被推入到队列中一次 ...
- WPF界面的异步后台加载
private void Init() { BackgroundWorker worker = new BackgroundWorker(); ...
- 我是这样使用template.js来异步渲染数据的
总监的代码用的是define+module.exports,为了效率先没去了解那一块,在github上找了一款功能单一的template.js来使用 https://github.com/yanhai ...
随机推荐
- 【JZOJ4782】【NOIP2016提高A组模拟9.15】Math
题目描述 输入 输出 样例输入 3 5 样例输出 -1 数据范围 解法 观察式子,可以得知整个式子与d(i*j)的奇偶性有关. d(n)为奇数当且仅当n是完全平方数. 对于一个i,如果d(i*j) ( ...
- node项目搭建
一:安装 1.简单安装法 下载.msi [编译好的nodejs] -> 点击安装 [系统会自动配置系统变量] 2.复杂安装法(不推荐) 由于nodejs的源码由C++和js组成 同时需要 ...
- blogbeta1
//html <!DOCTYPE html> blog 身高:170 体重:230 座右铭 再给我吃一口 关于我 微信 微博 标签 SM SP 重金求爹 2019/11/16 本人找爹,带 ...
- jmeter进行的接口测试和压力测试
1.接口测试 接口测试的内容我们之前已经讲过,values-key形式和json串传参形式: 包括的协议有http,webservice(soap),jdbc数据库,java请求 2.参数化 定义:把 ...
- win10下Anaconda3配置环境变量
有时候在win10安装好Anaconda3后,使用conda命令时依然会出现: C:\Users\dell\PycharmProjects\pytorch>conda list 'conda' ...
- framework7 上拉加载一些ajax问题
1.请求第一组数据后如果不能产生上拉进度条,则无法进行上拉加载. 解决办法:首次加载的数据量设置合理即可. 2.同一组数据请求多次,原因是异步刷新时间差,请求参数未更新,多次触发了上拉加载. 解决办法 ...
- 洛谷2387 BZOJ3669魔法森林题解
题目链接 BZ链接 这道题被很多人用spfa水了过去,表示很... 其实spfa很好卡,这组数据可以卡掉大多数spfa 链接:密码:rjvk 这里讲一下LCT的做法 我们按照a将边排序,然后依次添加 ...
- BZOJ1878 洛谷1972 HH的项链题解
洛谷链接 BZOJ链接 看到这样不用修改的题目,应该佷容易就联想到了离线来处理. 我们发现若将询问按照r来排序,排完后每次对答案有贡献的仅是每个颜色最后出现的位置 我们用next[i]表示i处颜色之前 ...
- 【Linux】vim缩进配置 (转载)
1.安装vim,终端输入: wanpeng@ubuntu:~$ sudo apt-get install vim-full 2.终端输入,打开配置文件: wanpeng@ubuntu:~$ vim ~ ...
- AtCoder Regular Contest 082 D Derangement
AtCoder Regular Contest 082 D Derangement 与下标相同与下个交换就好了.... Define a sequence of ’o’ and ’x’ of lengt ...