这是我做一个十万点实时刷新的图表控件遇到的问题,做过高性能图表的伙伴大概都知道,此时需要关闭命中测试的功能,无论是控件的还是 Drawing 的,否则计算命中测试的耗时将会让主线程卡住。为了解决此问题,有多个可以选择的方法,在此控件,我选择的是采用 VisualBrush 的方法。将 DrawingVisual 绘制到 VisualBrush 里面,再将 VisualBrush 作为贴图给矩形使用,这样的优势在于可以在命中测试的时候,只处理矩形。矩形命中测试的耗时可以忽略。但是在一些 4k 加百分之 200 的 DPI 缩放设备上,看不到某些 GlyphRun 的内容,本文记录此问题和对应的解决方法

前置要求:

  • 4k 分辨率屏幕
  • 百分之两百 DPI 缩放
  • 使用 GlyphRun 直接或间接
  • 绘制到 VisualBrush 中

在 WPF 的底层文本绘制都是采用 GlyphRun 绘制,因此可以认定为影响为全部文本,以及对应的文本控件

现象:

有某些文本内容不绘制渲染出来,看不见某些文本内容,但是在相同的 DrawingContext 里面的其他绘制内容,如线条或图片等都可以正常绘制出来

以上的现象包括:

  • 在某些设备上,暂时未找到具体影响因素
  • 某些文本内容不可见,而不是全部文本内容
  • 对整个控件进行 RenderTransform 之后可以让某些文本可见
  • 对界面进行刷新,可以让文本可见
  • 对界面进行偶数次刷新,文本不可见

开始之前先回答一下为什么会在图表控件里面,将 DrawingContext 的内容放入到 VisualBrush 中。如上文所述,这是因为 DrawingContext 对象是从 DrawingVisual 里面获取的,而 DrawingVisual 的 RenderOpen 返回的是一个带 RenderData 收集器的 DrawingContext 对象,也就是说此对象还远远不是最终被执行 DirectX 渲染的对象,仅仅是收集绘制内容,放入到 RenderData 里面。后续还有在执行默认命中测试的时候,取 RenderData 里面的内容进行计算渲染边距以及命中测试。总之,如果将 DrawingVisual 加入到视觉树里面,那么将会因为存在命中测试等逻辑导致需要执行很多逻辑而降低性能

为了提升性能,提升性能的其中一个方法是减少 CPU 工作量,也就是减少计算逻辑量。此时将 DrawingVisual 放入到 VisualBrush 中,作为 Brush 给一个矩形做填充,这样的优势在于进行命中测试的时候,默认是无视图层的,只会对矩形进行命中测试。刚好矩形命中测试的耗时是基本可以被忽略的,因此也就能极大提升了性能

需要说明的是,默认是可以无视命中测试给 DrawingVisual 带来的性能损耗,因为计算速度还是非常快的。但是在图表控件里面,架不住点的数量很多,尽管命中测试性能足够高,然而点的数量足够多也可以拖住性能

如下是将 DrawingVisual 绘制到 VisualBrush 上,再将 VisualBrush 贴到矩形上的方法,也就是我的图表控件的核心绘制逻辑

        private DrawingVisual CreateVisual()
{
var dv = new DrawingVisual();
_visualBase = dv;
var drawingContext = dv.RenderOpen(); // 绘制点
DrawPoints(drawingContext); // 绘制线
DrawLine(drawingContext); // 绘制文本
DrawGlyphRun(drawingContext); drawingContext.Close(); var ret = new DrawingVisual(); using (var dw = ret.RenderOpen())
{
var visualBrush = new VisualBrush(dv)
{
Stretch = Stretch.None,
}; dw.DrawRectangle(visualBrush, null, dv.ContentBounds);
_visualBrush = visualBrush;
} return ret; //return dv;
}

将绘制点和绘制线的 DrawingVisual 也就是上文的 dv 创建出来 drawingContext 用来做实际的图表内容绘制收集。而将 dv 作为 VisualBrush 的输入,接着新建一个叫 ret 的 DrawingVisual 对象,在这里面重新绘制出矩形然后用 VisualBrush 做贴图

这样做的优势在于可以利用到 WPF 无视贴图的命中测试的特性,而提升性能

但是带来的问题就是存在某些 GlyphRun 的文本不绘制,在相同的 drawingContext 绘制的点和线是可见的,只有文本看不到

其中最优解决方法是干掉 VisualBrush 而是换成 DrawingBrush 作为贴图,更改之后代码如下

        private DrawingVisual CreateTextVisual()
{
// var dv = new DrawingVisual();
var dv = new DrawingGroup();
_visualBase = dv;
var drawingContext = dv.Open(); // 绘制点
DrawPoints(drawingContext); // 绘制线
DrawLine(drawingContext); // 绘制文本
DrawGlyphRun(drawingContext); drawingContext.Close(); var ret = new DrawingVisual(); using (var dw = ret.RenderOpen())
{
// var visualBrush = new VisualBrush(dv)
_drawingBrush = new DrawingBrush(dv); dw.DrawRectangle(_drawingBrush, null, dv.Bounds);
} return ret; //return dv;
}

如上面代码,将 dv 的类型从 DrawingVisual 换成 DrawingGroup 类型,将后续的贴图从 VisualBrush 换成 DrawingBrush 类型。这样就能修复某些文本不显示的问题

为什么 VisualBrush 会让某些文本不更新脏就不显示?和 VisualBrush 的机制有关,在 VisualBrush 里面,要求先将内容渲染为 Bitmap 位图再作为某个元素的贴图层,执行顺序上需要有些复杂。而为什么如此复杂的逻辑会挖坑?表示我追踪了代码也没有发现更本质的问题,而且此问题只有在我的此图表控件才有偶尔复现,在能复现的设备上,每次都能用相同的图表数据进行复现。在能复现的设备上,如果变更了图表的内容,也许就又不复现了

如果将我的图表控件放在 demo 上跑,那也不会有啥锅。我也不知道是不是我的应用层挖的坑。因为我的应用层也充满了各个逗比加诡异的逻辑,因此我也不好说是不是某个有趣的逻辑的锅。此问题只有在使用特定的图表内容(很复杂)再加上放入到我的某个特定的应用里面才能复现,要调试 WPF 层的话,必须加入到我的应用层才能开始调试此问题。因此预计我也不会继续往底层调试,告诉大家具体的原因

WPF 使用 VisualBrush 在 4k 加 200 DPI 设备上某些文本不渲染看不见问题的更多相关文章

  1. [WPF 自定义控件]让Form在加载后自动获得焦点

    1. 需求 加载后让第一个输入框或者焦点是个很基本的功能,典型的如"登录"对话框.一般来说"登录"对话框加载后"用户名"应该马上获得焦点,用 ...

  2. WPF 的 VisualBrush 只刷新显示的视觉效果,不刷新布局范围

    原文:WPF 的 VisualBrush 只刷新显示的视觉效果,不刷新布局范围 WPF 的 VisualBrush 可以帮助我们在一个控件中显示另一个控件的外观.这是非常妙的功能. 但是本文需要说其中 ...

  3. WPF中下拉框即可以选择项也可以作为只读文本框使用

    1.需求 当前在开发的系统需要一个这样的控件. (1)可以选择已有的选择项,类似于ComboBox选择: (2)可以通过其他按钮点击,选择一个文件,选择后,把文件路径显示到控件上,并且处于只读状态,行 ...

  4. Linux加载DTS设备节点的过程(以高通8974平台为例)

    DTS是Device Tree Source的缩写,用来描述设备的硬件细节.在过去的ARM Linux中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代码, ...

  5. 【jquery】 在异步加载的元素上绑定事件

    最近因为工作关系又重新回归到了jquery的怀抱,发现很多jquery的一些细节处理的部分都忘记了.这里记录一下最近在做项目时频繁遇到的一个问题:给异步加载的元素添加事件绑定. 问题发生的前提是项目前 ...

  6. html加载顺序以及影响页面二次渲染额的因素

    浏览器请求发往服务器以后,返回HTML页面,页面内容开始渲染,具体的执行顺序为: 1. 浏览器开始载入html代码,发现<head>标签内有一个<link>标签引用外部CSS文 ...

  7. xcode6-添加真机设备

    xcode6-添加真机设备 第一:添加真机设备 1:到苹果开发者中心,中得iOS-APPs,在列表中得Devices中,选择All-点击右侧的"+",添加真机设备. 会打开下面的页 ...

  8. 一张图搞定OAuth2.0 在Office应用中打开WPF窗体并且让子窗体显示在Office应用上 彻底关闭Excle进程的几个方法 (七)Net Core项目使用Controller之二

    一张图搞定OAuth2.0   目录 1.引言 2.OAuth2.0是什么 3.OAuth2.0怎么写 回到顶部 1.引言 本篇文章是介绍OAuth2.0中最经典最常用的一种授权模式:授权码模式 非常 ...

  9. Android加载手机磁盘上的资源---decodeFile方法的使用

    一般在写Android程序时,通常会将图片资源放在/res/drawable/文件夹下,读取时,通过R.drawable.imageId即可读取图片内容,但用户在使用时,一般会想要读取存放在存储卡上的 ...

  10. 如何重新加载 Spring Boot 上的更改,而无需重新启动服务器?

    这可以使用 DEV 工具来实现.通过这种依赖关系,您可以节省任何更改,嵌入式 tomcat将重新启动.Spring Boot 有一个开发工具(DevTools)模块,它有助于提高开发人员的生产力.Ja ...

随机推荐

  1. .NET开源免费的Windows快速文件搜索和应用程序启动器

    前言 今天大姚给大家分享一款.NET开源(MIT License).免费.功能强大的Windows快速文件搜索和应用程序启动器:Flow Launcher. 工具介绍 Flow Launcher 是一 ...

  2. Cesium之CustomShader

    1. 引言 Cesium自1.87.1版本,开始支持3DTileset使用CustomShader: Added CustomShader class for styling Cesium3DTile ...

  3. HDFS Balancer负载均衡器

    目录 1.背景 2.什么是平衡 2.1 每个DataNode的利用率计算 2.2 集群的利用率 2.3 平衡 3.hdfs balancer语法 4.运行一个简单的balance案例 4.1 设置平衡 ...

  4. 舒服了,学习了,踩到一个 Lombok 的坑!

    你好呀,我是歪歪. 踩坑了啊,最近踩了一个 lombok 的坑,有点意思,给你分享一波. 我之前写过一个公共的服务接口,这个接口已经有好几个系统对接并稳定运行了很长一段时间了,长到这个接口都已经交接给 ...

  5. LOTO任意波形发生器SIG82模拟输出继电器吸合断开的信号波形用于算法调试

    LOTO任意波形发生器SIG82模拟输出继电器吸合断开的信号波形用于算法调试 继电器吸合的电流变化过程是如图这样的波形,0到2的时间大约为17毫秒,2到3的时间大约38毫秒. 批量继电器产品吸合是否满 ...

  6. AXI-自定义IP-PS设计

    基于AXI4的自定义IP核的设计 1.实验目标 利用vivado中内置的AXI总线初始化的IP核设计工具,将自己设计的流水灯的PL代码打包成IP核外设,挂在PS上,通过PS上的按钮控制流水灯的开始和结 ...

  7. 基于IDEA javaweb项目目录结构

    https://www.pianshen.com/article/62631355687/

  8. #博弈论,贪心#AT2376 [AGC014D] Black and White Tree

    题目传送门 分析 考虑到先手放一个白点后手必将在相邻位置放一个黑点, 如果没有合适的位置放黑点先手必胜,也就是问是否存在完美匹配, 直接从叶子节点到根贪心匹配即可 代码 #include <cs ...

  9. #线段树,倒序#CF356A Knight Tournament

    题目 分析 要求覆盖必须是第一个覆盖的, 考虑最后一个覆盖的很简单做线段树区间赋值, 那么倒序区间赋值就可以了 代码 #include <cstdio> #include <ccty ...

  10. jcmd:JDK14中的调试神器

    目录 简介 jcmd的语法 列出运行的JVM 打印stack信息 打印heap info 打印heap dump 统计heap使用情况 JFR功能 总结 简介 jcmd是JDK自带的调试工具,具有非常 ...