当前位置: 华文问答 > 数码

Windows 和 macOS 在高 DPI 支持上的差距,真的只是对生态的把控力问题吗?

2017-06-19数码

说到API设计,程序员就要show the code,自行对比(>_<)


macOS(Per-Monitor DPI from OS X 10.8)

AppKit:APIs for Supporting High Resolution

Carbon:已死亡,从OS X 10.8开始API全线已经Deprecated,也没有Retina支持,估计下个macOS版本二进制直接就没法跑了吧,毕竟是比MFC还古老的技术啊...

Java(比如说Swing之类):在JDK 8已修复,对开发者透明,JDK-8000629 [macosx] Blurry rendering with Java 7 on Retina display


Windows(Firstly Windows 8, Per-monitor DPI from Windows 8.1)

UWP:Introduction to Universal Windows Platform (UWP) app design (Windows apps)

WPF:Developing a Per-Monitor DPI-Aware WPF Application

WinForm:Online Documentation - Developer Express Inc.

Win32:Writing DPI-Aware Desktop and Win32 Applications

Qt:High DPI Displays


科普

DPI字面上的意思就是每英寸的显示出来的点数,这个是和肉眼看到的物理尺寸有关,常见于印刷制品。

macOS上,实际上用的Retina的概念(来自iPhone)更为简单,对所有UI的绘制不以像素(pixel)为基准,而是叫做Point,在Retina 2x设备上就是1point=2pixel,3x设备就是3pixel(现如今Mac还没有,只有iPhone Plus系列是),而且由于软硬件一体,Retina只会有整数倍数,类比200% DPI,不会出现类似1.5x这样的问题。

Retina和分辨率:The Ultimate Guide To iPhone Resolutions

Retina详细解释:High Resolution Explained: Features and Benefits


Windows上就比较麻烦了,DPI缩放可以设置成100% 125% 150% 200%等等,而且XP时代,8.1时代,10时代还有各自不同的处理方式,整体看起来明显就相对麻烦了,具体可以看看DPI DIP Effective Pixels(UWP时代)这三者说明。

DPI和DIP:DPI and Device-Independent Pixels

Effective Pixels:Sceen sizes and break points for responsive design


看法

大概扫了一眼各种GUI框架简介,如果你用的是新的API(AppKit UWP WPF),程序员不用有任何额外处理,单独让设计师对所有标量图切出不同大小的图,对AppKit是*[email protected](Retina 2x) *[email protected](Retina 3x,Mac还没有,对应iPhone Plus系列)。对UWP WPF这种使用XAML的就是*.scale-150.png(150% DPI) *.scale-200(200% DPI)的图,全部拖到工程目录里面放到本身那个png旁边,剩下的啥事没有,图片自动根据设备DPI选择匹配的那个,UI控件都是GUI库提供的,根据DPI绘制,字体也会根据DPI显示,完美。

但是如果用的旧的那些API,就得手动处理一些Dirty Work,封装一下接口,比如说自己搞定标量图选择呀,自己转换像素值,绘制对应DPI下的文字,然后还得升级用到的第三方UI组件,成本是有的。所以还得看第三方软件厂商......天知道他们用了什么GUI框架......

另外,GUI这样的东西,除了标准那些组件(类似什么Label TextView ImageView这些),还有一些是直接绘制的(简单点说就是描点、画线、填充这些底层API)的UI,Mac上是基于Core Graphics绘制,在OS X 10.8之后(也就是第一款Retina MacBook 发布时候)就已经全部考虑到DPI,Deprecated了老接口。对于Core Text文本渲染也是对开发者透明的情况下支持了Retina。在Windows上,对应的绘制API应该是GDI(之后有了GDI+和Direct 2D),但GDI是直接基于像素的,而不是像Core Graphics那样基于Point的概念,因此开发者得搞一些工作支持。字体渲染的话早期也是GDI,这个坑已经说了。不过现在另外有一个DirectWrtie用来渲染字体,对DPI支持的非常不错。不过现在主要是UWP用,Win32用的软件不是非常多(比如Chrome Steam for Windows是用的)。

当然,假如你直接用OpenGL或者DirectX这种级别的底层渲染API(虽然很少见用这种东西写软件,大部分是做游戏的,而游戏通常从不考虑DPI,只考虑像素和分辨率),在哪个操作系统上都没有DPI支持的,全看自己...


关于多屏幕DPI适配

提问者补充追加里面说到了Per-monitor DPI的问题,这个macOS和Windows都有各自的解决策略,当然是不是方便易用,可以参考两边的文档。


macOS:

OS X 10.8全面支持Retina后,就已经想到这个问题,对AppKit所有上层的坐标系统接口进行了梳理,提出了一个叫做Backing Store的坐标系统,你不管有多少外接屏幕,所有的计算是在一个单独的坐标系统中计算,渲染的时候把这个Backing Store的坐标映射到当前屏幕上。具体原理在上文的 High Resolution Explained: Features and Benefits 已经介绍过了。开发者本身不需要额外处理什么消息机制,重绘策略,一切透明通过这个坐标系统处理(当然,对动态生成的标量图,默认行为是直接拉伸。但是比如图片应用,你可以手动接收一下通知来处理,因为此时两个屏幕scale不同,可以在两个屏幕展示不同尺寸的标量图)。

另外,为了避免开发者再手动处理这些问题,把所有旧的API全部标记为deprecated并要求迁移到新的API上。如果你使用了新API,那就是可以完全免费得到Per-monitor DPI。可以参考这个接口:NSView | Apple Developer Documentation


Windows:

Windows 8.1之后才提供了Per-monitor DPI的接口,Windows 8及之前是没有官方接口的。对于UWP应用,本身使用了DPI以外的叫做Effective Pixel的概念,直接映射到另一套坐标系统上,应该比AppKit还简单一些,毕竟开发者不用操任何心。

但是对于WPF和Win32,虽然我不搞WPF,不过看了那个官方教程文档Developing a Per-Monitor DPI-Aware WPF Application,还需要一定工作量(取决于你的代码复杂度)。首先,需要继承一个子类PerMonitorDPIWindow,然后在接受到DPI更改的消息时候重新对所有child view重绘(那个教程里面直接加上了一个Transform,但是实际上UI肯定不会这么写,会导致锯齿等,又不是写3D游戏。需要真实重新设置scale之类),估计又得搞一堆代码。总之不是对开发者透明的,意味着有一定开发成本。还有一些问题,比如只有Main window有通知,菜单对话框等系统提供的Win32组件本身不支持这个情况……

在Windows 10 Creator Update之后,新加入了一个Per-monitor DPI Awareness V2(已经2017年了),支持了child window的通知,还有一系列Win32控件的自动处理,相对开发者好用多了。具体可以参考:High-DPI Scaling Improvements for Desktop Applications in the Windows 10 Creators Update


旧软件兼容性

macOS:

macOS对于旧软件,使用了全局的缩放(由macOS的窗口管理程序WindowServer处理),效果应该也就是简单的插值过滤罢了,模糊点但是布局正常。不过开发者或者高级用户,可以直接在应用.app包中的info.plist,声明NSHighResolutionCapable为True,可以开启强制Retina渲染,也是直接对旧版API(主要是Core Graphics画的)也强制按Retina渲染(具体原理没查到,看表现应该是直接把所有像素值当作pt给你渲染了一次...),有一些应用(比如说公司用的某Qt写的VPN软件)就非常完美,也有的比如当年的Word 2011就会挂......所以说也不是绝对,需要尝试,有个小软件能方便普通用户使用这种方式设置强制开启Retina,参考:http://retinizer.mikelpr.com


Windows:

Windows其实对旧版兼容性好得多,这里有一个DPI Virtualization的概念(参见上面的Win32文档),意思是说,如果应用不支持DPI Awareness,那么和Mac类似,使用暴力全局缩放(由Windows的窗口管理程序DWM处理),看起来模糊但是至少是正确的UI。如果是声明了DPI Awareness,那么GDI绘制时会返回伪造的像素值(比如你200% DPI的10像素,GDI就绘制20像素),这样有一些软件看上去就会锐利无比,但有些也会挂。有意思的是,普通用户也可以在exe右键打开"在高分辨率下禁用缩放"来强制声明DPI Awareness,一些软件看着就舒服多啦。不过实际上自己试过有些Win32软件开始后会有字体模糊,UI正常,不知为何(难道不是用GDI渲染的字体?)……

另外,Windows 10 Creator Update里面提供了一个对传统Win32程序的增强的GDI HiDPI支持,开启后效果更加,前文已提到:Improving the high-DPI experience in GDI based Desktop Apps


总结

个人观点,还是苹果对兼容性比较大胆,在第一款Mac Retina设备发布时(搭载OS X 10.8),就直接把旧的API全部Deprecated,让你没法用。而且GUI框架生态上来说,基本上是绝对数量的AppKit,很少的Java应用和Qt应用,由于软硬件一体,Retina的概念比起DPI更好处理(毕竟没有非整数倍),当年广大Retina MacBook用户拿到设备后,集体邮件轰击软件商支持,加之Mac App Store审核也是要求Retina支持。因此大波厂商迅速更新支持Retina吸引用户(不然就淘汰)。

Windows这边,虽然最早在Windows 7就有识别DPI的API,但是配套GUI框架没跟上,而且各路GUI框架争雄(8.1时代应该有Metro(UWP前身) WPF WinForm Win32 MFC Qt好几个...),开发者用的GUI框架也许更新就不够及时。而且不是软硬件一体,OEM硬件厂商不给力,2K 4K屏幕推的太慢,到了Windows 10才大力推广,API也加上来,结果造成生态没有及时跟上...不过感觉现如今好多了(比起当年8.0/8.1时代),而且不是要把UWP推上正位吗,原生支持高分屏和Per-monitor DPI,如果厂商多提供UWP应用,软件多高分屏的支持肯定越来越好(虽然感觉VS这种应用,不太可能用UWP写……)


---更新---


关于编程语言

经过评论区提示,编程语言和接口设计的关系也比较大。在macOS上,AppKit本身提供的是Objective-C的接口,而Objective-C是一门严格的C超集的动态语言,在ABI上使用Runtime和动态派发,避免了静态面向对象语言的Fragile Binary Interface问题(参考Wiki:Fragile binary interface problem)问题,因此可以随便添加成员变量,方法,甚至覆盖,而不会导致的ABI兼容问题。同时,Core Graphics是纯C的接口,也不会有面向对象语言特有的这个问题。

但是Windows上的Win32 API和GDI+,提供的是C++接口,大部分C++程序员都是知道C++的ABI兼容是多么难做,一不小心(比如没有通过PIMPL等方式而是直接暴露成员变量的话),添加一个新的变量可能就你的旧的二进制程序链接系统动态库时崩溃,因此得非常慎重。GDI是C接口,不过设计时间比起Core Graphics要早十年(DOS时代……),那时候的C编译器产生出来的二进制想要ABI兼容也是大问题。

另外,Win32 API一般不太会及时Deprecated老的API,除非到已经维护不下去为止,不像Apple,一旦不满足最新的需要(比如AppKit的那个NSView的例子)立即Deprecated。用户依旧能继续运行旧应用,但可以大大鼓励开发者尽快迁移新接口。这点也间接影响了软件商,让Windows很多软件一直保留旧的API,随时间推移更加难迁移,直到最后「重构」(重写)。