Flutter Windows 開始支援 Impeller,還修復了多視窗 bug

因為這段時間一直在使用 Flutter 多視窗的原因,有一個 Windows 多視窗的 bug 一直挺讓人難受的,就是在切換到專案子視窗的時候,會預設啟用主視窗,之後才能再次點擊回到子視窗:

更準確地說,就是啟用一個 child RegularWindow 後,主視窗會重新回到前景並蓋住它。

這個問題的誘因很多,但實際上主要就是程式碼寫得太草率。

原因之一是在 Engine 裡,Engine 收到 Windows 的 WM_ACTIVATE 訊息時,不管視窗是啟用還是失去啟用,都會執行:

kotlin 代碼解讀複製代碼FocusRootViewOf(this);

也就是讓目前視窗裡的 Flutter view 取得焦點,但問題在於,當主視窗正在失去焦點時,它也會執行這句,然後就導致主視窗本來應該退到後面,卻又主動 SetFocus 自己,接著 Windows 又把主視窗重新啟用並拉到最前面。

這個 Bug 就先不說了,主要是 Flutter framework 還把多個視窗的 focus tree 掛錯層級。Flutter 的多視窗裡,每個視窗都有自己的 View,但 Framework 裡子視窗的 FocusScope 居然還會有掛在父視窗的 FocusScope 下面的情況,這樣會造成一個新問題:

子視窗獲得焦點時,主視窗的 focus scope 也被認為「有焦點」,然後主視窗又又又通知 engine:「我有焦點了,把我的 native window 啟用」,然後又又又又會把主視窗拉到前面……

而且整個過程,也沒有判斷是否已經有另一個視窗拿到焦點,這就導致這個 Bug 是由好幾個地方綜合造成,之前很久一直沒能根本修復問題。

一直只能寫兜底程式碼來強制切換。

不過針對這三個問題,最近終於修復了:

  • 如果收到的是 WA_INACTIVE,也就是視窗正在失去啟用,就直接返回,不再搶焦點
  • 每個 View 的 focus subtree 直接掛到全域 root scope 底下
  • 只有在沒有其他視窗申請焦點時,才恢復舊焦點

然後在我合併完這部分程式碼之後,馬上又踩到另一個坑:Flutter Windows 開始啟用 Impeller 了,是的,Flutter 開始在 Windows 上用 OpenGLESSDF 來適配 Impeller 了:

然後問題又出現了,在繪製 canvas 的時候,出現了細線看起來比 Skia 粗約 1.3–1.6 個物理像素,而且更暗的情況

如果疊加 alpha,還會直接失效。這些以前在我專案裡若隱若現的細線,在 Impeller 出來後直接就變成又大又粗:

根據 issue 的計算,目前不同粗細在 Impeller 上的變化倍率也不一樣:

筆畫寬度(logical→physical) Skia 覆蓋度 Impeller 覆蓋度倍數
0.5 → 0.75 px 0.75 2.0×
1.0 → 1.5 px 1.50 2.78×
2.0 → 3.0 px 3.00 4.59×
4.0 → 6.0 px 6.00 7.50×

而且粗細還不是主要問題,主要問題是:

  • 在 canvas transform 縮放下,AA 邊緣也跟著被放大,導致縮放比例越大線條越模糊(zoom=4 時 0.5px 線變成 12px 寬模糊帶)
  • Alpha 值被完全忽略:0xCC0x66 渲染結果像素完全一致
  • 極細線在某些情況下整條消失

這個問題看起來像是 Impeller 的 SDFAlpha 實作有問題,smoothstep 範圍是固定的物理像素寬度,比如 smoothstep(-fade_size, fade_size, sdf) 會在筆畫邊緣兩側分別產生約半個 pixel_size * aa_pixels 的漸變過渡帶,而這個過渡帶是固定寬度(取決於 aa_pixels 參數,程式碼中設定為常數)。

然後對於 Skia 這樣的解析式 AA,覆蓋度 = 筆畫寬度;但是對於 SDF 的 smoothstep,覆蓋度 = 筆畫寬度 + 固定邊緣過渡帶。所以當筆畫只有 0.75 物理像素寬時,這個固定的 ~1.2px 過渡帶就變得很明顯,導致渲染出來的線是「實心寬線 + 兩側暈染」。

另外 float half_stroke = max(stroke_width, base_pixel_size) * 0.5 的最小寬度鉗制沒有補償 alpha,當 stroke_width < 1 像素時,這裡會把寬度強制鉗制到至少 1 個像素(base_pixel_size),但沒有按比例降低 alpha 來補償。

而 Skia 的做法是次像素線寬:把線變成 1 像素寬,但按比例降低 alpha(0.75px 線 ≈ 38% 不透明的 1px 線)。Impeller 的 SDF 實作直接給了滿 alpha 的 1px+ 寬線,所以看起來更暗更粗。

也就是 SDFStroke 根本沒有把 alpha 納入計算,alpha 值到 fragment shader 時已經在別處處理,沒有和寬度鉗制聯動。

目前暫時緩解的辦法有:

  • drawRect 代替 drawLine:用高度等於預期線寬的 filled rect 代替 stroked line,fill 路徑的 UberSDF 覆蓋度是正確的
  • 強制使用 Skia--no-enable-impeller
  • 1px 整像素線寬:使用 1.0 物理像素寬的線

目前看來,Impeller 在 Windows 上估計還是要經歷一次 Android 的問題,不得不說,自繪製的維護成本確實高。不過多視窗在 Windows 上確實也可以用了,自從 bug 解決之後,基本上沒什麼大問題了。

image.png

連結

github.com/flutter/flu…

github.com/flutter/flu…


原文出處:https://juejin.cn/post/7661123972889559046


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝8   💬3   ❤️4
287
🥈
我愛JS
📝3   💬6   ❤️4
177
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登