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 ...
随机推荐
- 浅析倾斜摄影三维模型(3D)几何坐标精度偏差的几个因素
浅析倾斜摄影三维模型(3D)几何坐标精度偏差的几个因素 倾斜摄影是一种通过倾斜角度较大的相机拍摄建筑物.地形等场景,从而生成高精度的三维模型的技术.然而,在进行倾斜摄影操作时,由于多种因素的影响,导致 ...
- ts-对象数组reduce-数组转对象数组
将字符串数组转化成{name:xxx,count:xxx}[]数组的代码 #定义数据类型 interface CartInfo{ name:string, count:number } let raw ...
- 记录--开局一张图,构建神奇的 CSS 效果
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 假设,我们有这样一张 Gif 图: 利用 CSS,我们尝试来搞一些事情. 图片的 Glitch Art 风 在这篇文章中 --CSS 故障 ...
- kali 2018.2镜像安装
本文链接来源 Kali Linux 前身是著名渗透测试系统BackTrack ,是一个基于 Debian 的 Linux 发行版,包含很多安全和取证方面的相关工具.此次通过VMware虚拟机安装201 ...
- 基于VB6的磁性移动窗体 - 开源研究系列文章
这次继续整理代码.这个磁性窗体是以前大学的时候开发的,当时模仿的Winamp的效果进行的编程.当时的时候有Windows API函数能够进行处理,但是XP的年代,那个API只是移动的虚框,而不是移动窗 ...
- KafkaProducerDemo
package com.lxw.kafkademo; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache ...
- 【已解决】org.apache.thrift.transport.TTransportException: Could not create ServerSocket on address 0.0.0.0/0.0.0.0:9083.
杀死这些进程 kill -9 进程号
- HTTP内容协商机制和断点续传
- 【LGR-069】洛谷 2 月月赛 II & EE Round 2
目录 前言 洛谷 6101 [EER2]出言不逊 分析 代码 洛谷 6102 [EER2]谔运算 分析 代码 洛谷 6103 [EER2] 直接自然溢出啥事没有 分析 代码 洛谷 6105 [Ynoi ...
- 在typescript中,Omit是什么意思
在TypeScript中,Omit<Type, Keys> 是一个工具类型(utility type),它用于创建一个新的类型,这个新类型是从现有类型(Type)中排除了某些指定的属性(K ...