要素ごとの描画時間を出したところ、単独で突出した要素があった:
[PERF][EL] element draw took 179 ticks. is_img=false, text_len=0, w=924, h=44
[PERF][EL] element draw took 205 ticks. is_img=false, text_len=0, w=924, h=44
[PERF][EL] element draw took 478 ticks. is_img=false, text_len=0, w=924, h=44
[PERF][EL] element draw took 98 ticks. is_img=false, text_len=0, w=924, h=529
924×44(4 万ピクセル)が 924×529(49 万ピクセル)より数倍重い。 面積比例ではない処理が入っている。
正体は backdrop-filter(グラスモーフィズム)。
sugi-lab.net のナビゲーションバーが使っている。
これが paint の未特定分(約 53%)の正体。
blur_screen_rect は素朴な 2 次元ボックスブラー:
for y in 0..h {
for x in 0..w {
for dy in -r..=r {
for dx in -r..=r { /* 範囲内なら加算 */ }
}
}
}O(W×H×(2r+1)²)。r は 1〜8
にクランプされるので最大 17×17 = 289。 924×44 なら 1170
万回の加算。毎フレーム。
ボックスブラーは各ピクセルについて クリップされた窓の総和 ÷ 窓内の画素数(整数除算・切り捨て)である。
窓は矩形なので、x 方向の範囲は y
に依らず、y 方向の範囲は x に依らない。
したがって画素数は n = nx(x) × ny(y) と分解できる。
2 次元の総和も
S(x,y) = Σ_dy Σ_dx src = Σ_dy H(x, y+dy)
と分解できる(H は行方向の窓和)。
総和のまま持ち回れば除算は最後の 1 回だけになり、
現行実装とビット単位で同一の出力が得られる。
さらに H は行ごとの累積和から O(1) で引けるので、 全体は
O(W×H)(半径に依存しない)。
r=8 のとき 1 ピクセルあたり 289 サンプル → 定数回。
radius <= 0 や幅・高さが 0
の場合は何もしない(現行どおり)web_engine/blur_box.rs(純粋・no_std・グローバル状態なし)
/// 窓の範囲(両端含む)を矩形内へクリップして返す。
pub fn window_bounds(i: usize, r: usize, n: usize) -> (usize, usize);
/// パック済み RGB(上位 8bit は無視)に対するボックスブラー。
/// 出力は素朴な 2 次元実装とビット単位で一致する(B-1)。
pub fn box_blur_rgb(src: &[u32], w: usize, h: usize, radius: usize) -> alloc::vec::Vec<u32>; ナビバー要素 paint 全体
修正前 179〜478 tick 243〜305 tick
修正後 18〜 24 tick 122〜151 tick
make test 765 件パス(test_blur_box 21
件を新規追加。 うち 6
件は素朴実装との総当たり突き合わせ)。
paint=122 bgimg=31 grad=53 fill=19 pixbg=17 text=1
→ 合計 121
未特定分が 53% → ほぼ 0% になった。
paint の 53%
を占めていた「どのプリミティブにも計上されない時間」は、 すべて
backdrop-filter のブラーだった。
[PERF][EL] element draw took 98〜134 ticks. w=924, h=529
ヒーロー領域(背景画像+グラデーション)。 これは面積相応(49 万ピクセル)なので、 ブラーのような異常な定数倍ではない。