WPF 使用 VisualBrush 在 4k 加 200 DPI 设备上某些文本不渲染看不见问题
这是我做一个十万点实时刷新的图表控件遇到的问题,做过高性能图表的伙伴大概都知道,此时需要关闭命中测试的功能,无论是控件的还是 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 设备上某些文本不渲染看不见问题的更多相关文章
- [WPF 自定义控件]让Form在加载后自动获得焦点
1. 需求 加载后让第一个输入框或者焦点是个很基本的功能,典型的如"登录"对话框.一般来说"登录"对话框加载后"用户名"应该马上获得焦点,用 ...
- WPF 的 VisualBrush 只刷新显示的视觉效果,不刷新布局范围
原文:WPF 的 VisualBrush 只刷新显示的视觉效果,不刷新布局范围 WPF 的 VisualBrush 可以帮助我们在一个控件中显示另一个控件的外观.这是非常妙的功能. 但是本文需要说其中 ...
- WPF中下拉框即可以选择项也可以作为只读文本框使用
1.需求 当前在开发的系统需要一个这样的控件. (1)可以选择已有的选择项,类似于ComboBox选择: (2)可以通过其他按钮点击,选择一个文件,选择后,把文件路径显示到控件上,并且处于只读状态,行 ...
- Linux加载DTS设备节点的过程(以高通8974平台为例)
DTS是Device Tree Source的缩写,用来描述设备的硬件细节.在过去的ARM Linux中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代码, ...
- 【jquery】 在异步加载的元素上绑定事件
最近因为工作关系又重新回归到了jquery的怀抱,发现很多jquery的一些细节处理的部分都忘记了.这里记录一下最近在做项目时频繁遇到的一个问题:给异步加载的元素添加事件绑定. 问题发生的前提是项目前 ...
- html加载顺序以及影响页面二次渲染额的因素
浏览器请求发往服务器以后,返回HTML页面,页面内容开始渲染,具体的执行顺序为: 1. 浏览器开始载入html代码,发现<head>标签内有一个<link>标签引用外部CSS文 ...
- xcode6-添加真机设备
xcode6-添加真机设备 第一:添加真机设备 1:到苹果开发者中心,中得iOS-APPs,在列表中得Devices中,选择All-点击右侧的"+",添加真机设备. 会打开下面的页 ...
- 一张图搞定OAuth2.0 在Office应用中打开WPF窗体并且让子窗体显示在Office应用上 彻底关闭Excle进程的几个方法 (七)Net Core项目使用Controller之二
一张图搞定OAuth2.0 目录 1.引言 2.OAuth2.0是什么 3.OAuth2.0怎么写 回到顶部 1.引言 本篇文章是介绍OAuth2.0中最经典最常用的一种授权模式:授权码模式 非常 ...
- Android加载手机磁盘上的资源---decodeFile方法的使用
一般在写Android程序时,通常会将图片资源放在/res/drawable/文件夹下,读取时,通过R.drawable.imageId即可读取图片内容,但用户在使用时,一般会想要读取存放在存储卡上的 ...
- 如何重新加载 Spring Boot 上的更改,而无需重新启动服务器?
这可以使用 DEV 工具来实现.通过这种依赖关系,您可以节省任何更改,嵌入式 tomcat将重新启动.Spring Boot 有一个开发工具(DevTools)模块,它有助于提高开发人员的生产力.Ja ...
随机推荐
- 01-【HAL库】STM32实现串口打印
一.什么是串口 串口通讯(Serial Communication)是一种设备间非常常用的串行通讯方式,因为它简单便捷,因此大部分电子设备都支持该通讯方式,电子工程师在调试设备时也经常使用该通讯方式输 ...
- LOTO示波器的变长存储深度和分段存储
LOTO示波器的变长存储深度和分段存储 经常有客户咨询和不理解LOTO示波器的存储深度为什么是变长的,也表示对LOTO示波器的分段存储功能不理解,本文对LOTO示波器的存储机制做一次完整的梳理,帮助我 ...
- #阶梯NIM#Poj 1704 Georgia and Bob
题目 有\(n\)个棋子摆放在x轴的正半轴上, 每次将棋子最多向左移动至上一个棋子之后(不能不移动), 不能操作为败,问先手是不是必胜 分析 这个模型可以转换成阶梯NIM的形式, 那么将奇数位置做NI ...
- 网站优化之开启tomcat的gzip压缩传输特性
本文于2015年底完成,发布在个人博客网站上. 考虑个人博客因某种原因无法修复,于是在博客园安家,之前发布的文章逐步搬迁过来. 基于tomcat 8.0.x版本的文档,可以了解到tomcat支持基于g ...
- 【Learning eBPF-3】一个 eBPF 程序的深入剖析
从这一章开始,我们先放下 BCC 框架,来看仅通过 C 语言如何实现一个 eBPF.如此一来,你会更加理解 BCC 所做的底层工作. 在这一章中,我们会讨论一个 eBPF 程序被执行的完整流程,如下图 ...
- 驾考宝典携手HMS Core统一扫码服务,构建复杂场景中的流畅扫码体验
"驾考宝典"是一款颇具人气的互联网综合驾照考试学习应用,通过强大的驾考功能,在手机移动端为学车学员提供从报名.学习到拿本的全方位驾考服务.作为一个专业的驾培平台,"驾考宝 ...
- 【Java面试指北】单例模式
单线程下的单例模式: public class Singleton { private static Singleton instance; private Singleton() {} public ...
- Native Drawing开发指导,实现HarmonyOS基本图形和字体的绘制
场景介绍 Native Drawing模块提供了一系列的接口用于基本图形和字体的绘制.常见的应用场景举例: ● 2D图形绘制. ● 文本绘制. 接口说明 接口名 描述 OH_Drawing_Bit ...
- 及刻周边惠:拥抱HarmonyOS原子化服务
原文链接:https://mp.weixin.qq.com/s/Y75eiRlvDLXzoZWzAiZdeg,点击链接查看更多技术内容: 开发背景 及刻周边惠是梦享网络旗下本地生活服务平台,旨在为消费 ...
- 将 Github Pages 个人博客录入搜索引擎(以 Bing 为例)
目录 关于 Bing Webmaster Tools 步骤一:登录 步骤二:添加网站 步骤三:验证网站 步骤四:添加网站地图 验证 & 总结 相关链接 笔者最近准备将 Gitee Pages ...