當前位置: 華文問答 > 數碼

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,隨時間推移更加難遷移,直到最後「重構」(重寫)。