atmos/kernel/draw_clip.rs
1//! 塗りつぶし矩形のクリップ計算(純粋ロジック)。
2//!
3//! ハードウェアにもグローバル状態にも依存しないため、ホスト側の
4//! ユニットテスト(`tests/src/test_draw_clip.rs`)から直接検証できる。
5//!
6//! # 座標の約束
7//!
8//! - 入力 `(x0, y0, x1, y1)`: 呼び出し側の塗り矩形。`x1`/`y1` は
9//! [`crate::kernel::draw::Screen::boxfill`] の従来仕様どおり**両端を含む**。
10//! - `clip`: `(kx0, ky0, kx1, ky1)` で、`kx1`/`ky1` は**右下を含まない**
11//! (`draw_pixel` が `x >= cx1` で捨てているのと同じ約束)。
12//! - 戻り値: 両端を含む `(x0, y0, x1, y1)`。1 ピクセルも描かないなら `None`。
13//!
14//! # 【2026-09-08 カーネルパニックの原因】
15//!
16//! 以前は `boxfill` 内に直接、
17//!
18//! ```text
19//! if cx1 >= kx1 { cx1 = kx1 - 1; }
20//! ```
21//!
22//! と書かれていた。`kx1 == 0`(幅ゼロのクリップ矩形)のとき `0 - 1` が
23//! u32 で **4294967295 へラップ**し、続く `cx0 > cx1` の空判定も素通りする。
24//! 結果 `rect_w` が約 43 億となり、`dma_fill_rect` が RAM 全域を塗り色で
25//! 埋めてヒープのフリーリストを破壊していた。
26//!
27//! 実際のパニックログでは `Tlsf::allocate` が `0xffc7c8daffc7c8da` を
28//! ポインタとして辿って落ちており、この `0xffc7c8da` は
29//! ARGB(255,199,200,218) という**塗り色そのもの**だった。
30//! 別のフレームでは `boxfill` がフレームバッファを走り抜けて
31//! ペリフェラル領域 `0x3f007008` へ書き込み、外部アボートで落ちている。
32//!
33//! `kx1 == 0` は絵空事ではなく、`render.rs` の
34//! `push_clip(x0, y0, x1.saturating_sub(18), y1)`(`<select>` の
35//! ドロップダウン矢印ぶんを削る処理)が、幅 18px 未満の要素で
36//! そのまま 0 を渡す。`push_clip` の交差計算 `cx1.min(x1)` からも 0 は出る。
37
38/// 塗り矩形を描画先サイズとクリップ矩形で切り詰める。
39///
40/// 空クリップ・反転クリップ・画面外を**すべて `None`**(何も描かない)に落とす。
41/// u32 の減算は一切行わないので、どんな入力でもラップアラウンドしない。
42pub fn clip_fill_rect(
43 x0: u32,
44 y0: u32,
45 x1: u32,
46 y1: u32,
47 target_w: u32,
48 target_h: u32,
49 clip: Option<(u32, u32, u32, u32)>,
50) -> Option<(u32, u32, u32, u32)> {
51 // 描画先が無い(0×0)なら書ける場所が無い。
52 if target_w == 0 || target_h == 0 {
53 return None;
54 }
55 // 左上が画面外なら捨てる。負値を u32 へキャストして渡された場合も
56 // 巨大な値になるためここで落ちる。
57 if x0 >= target_w || y0 >= target_h {
58 return None;
59 }
60
61 let mut cx0 = x0;
62 let mut cy0 = y0;
63 let mut cx1 = x1.min(target_w - 1);
64 let mut cy1 = y1.min(target_h - 1);
65
66 if let Some((kx0, ky0, kx1, ky1)) = clip {
67 // 幅ゼロ・高さゼロ・反転したクリップ矩形は「描画領域なし」。
68 // ここで返さないと下の `kx1 - 1` がラップする。
69 if kx0 >= kx1 || ky0 >= ky1 {
70 return None;
71 }
72 cx0 = cx0.max(kx0);
73 cy0 = cy0.max(ky0);
74 cx1 = cx1.min(kx1 - 1);
75 cy1 = cy1.min(ky1 - 1);
76 }
77
78 if cx0 > cx1 || cy0 > cy1 {
79 return None;
80 }
81 Some((cx0, cy0, cx1, cy1))
82}