Skip to main content

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}