# Double Buffer Architecture Research v8
## Focus: v1/v2 失敗根因深入分析

---

## 重現 v1/v2 的修改

### v1 WndProc 做了什麼
1. 建立 g_hSharedBuf（新的 memory DC + bitmap）
2. g_hPaintDC = g_hSharedBuf
3. HideCaret(hWnd)
4. CallWindowProcW → PaintText 用 g_hPaintDC 畫 ASCII
5. ScanAndDrawUda 用 g_hPaintDC 畫 UDA
6. hdcScr = GetDC(hWnd)
7. BitBlt(hdcScr ← g_hSharedBuf, 全螢幕)
8. ReleaseDC
9. Caret 重建
10. g_hPaintDC = NULL

### v3 WndProc 做了什麼
1. g_bSharedPaint = 1
2. CallWindowProcW → PaintText 用自己的 s_hdcPaintBuf
3. ScanAndDrawUda 用 g_hPaintBuf（= s_hdcPaintBuf）
4. g_bSharedPaint = 0

## 差異分析

| 操作 | v1/v2 | v3/v4 |
|------|-------|-------|
| Buffer 建立者 | WndProc | PaintText（自己的 s_hdcPaintBuf） |
| PaintText 畫到 | WndProc 的新 buffer | 自己的 buffer |
| 螢幕 BitBlt 執行者 | WndProc（步驟 7） | ScanAndDrawUda |
| HideCaret | WndProc（步驟 3） | ScanAndDrawUda |
| Caret 重建 | WndProc（步驟 9） | ScanAndDrawUda |
| GetDC for screen write | WndProc（步驟 6） | ScanAndDrawUda（原有） |

## 為什麼 v1/v2 「無法輸入」

### 假說 A：GetDC/BitBlt 在 WndProc 內造成新 WM_PAINT
- WndProc WM_PAINT handler 內呼叫 GetDC + BitBlt 寫螢幕
- 寫螢幕本身不觸發 InvalidateRect
- **排除**

### 假說 B：BeginPaint/EndPaint 與 g_hPaintDC 衝突
- PaintText 內 BeginPaint 驗證 update region → ps.rcPaint
- PaintText 用 g_hPaintDC 畫（不是 ps.hdc）
- EndPaint 清除 update region
- 之後 WndProc 的 BitBlt 寫螢幕 → 新內容
- **應該不衝突**
- **排除（可能）**

### 假說 C：PaintText 的 SelectObject 失敗
- PaintText: SelectObject(hdc, pWVT->hFont) 其中 hdc = g_hPaintDC
- g_hPaintDC 是 WndProc 建的新 DC
- SelectObject 應該成功（Compatible DC 接受任何 font）
- **排除**

### 假說 D：WndProc 的 Caret 操作阻塞訊息
- DestroyCaret + CreateCaret + ShowCaret 在 WndProc WM_PAINT handler 內
- ShowCaret 可能觸發 caret 繪製（InvalidateRect for caret area）
- 如果 InvalidateRect 立即觸發新 WM_PAINT...
- 新 WM_PAINT 進入 UsrFontWndProc
- s_bInsidePaint=0（已清除？）→ 走完整路徑
- **等等 — v1 的 s_bInsidePaint 在什麼時候清除的？**

```
v1 code:
  g_hPaintDC = NULL;        // 步驟 10
  s_bInsidePaint = 0;       // 步驟 11
  return result;            // 步驟 12
```

- ShowCaret（步驟 9）在 s_bInsidePaint=1 時執行
- ShowCaret 觸發的 InvalidateRect 排入佇列
- s_bInsidePaint = 0（步驟 11）
- return（步驟 12）→ DispatchMessage 返回
- 下一個 PeekMessage 取得 WM_PAINT → 正常處理
- **應該不會死循環**

### 假說 E：PaintText 的 margin 填充寫到新 buffer
- PaintText line 3199-3240: margin 填充（FillRect）
- 使用 hdc = g_hPaintDC（v1 的新 buffer）
- 填充整個 margin 區域
- **如果 margin > 0（最大化）**：大面積 FillRect 到新 buffer
- **如果 margin = 0（正常）**：不填充
- **這不應該造成問題**

### 假說 F：★★★ PaintText 的 EndPaint 清除 update region，但 WndProc 的 BitBlt 仍需要重繪 ★★★
- PaintText EndPaint 清除 update region → Windows 認為「已處理」
- 但 WndProc 還在做 BitBlt + Caret → 更多螢幕操作
- 這些操作不在 BeginPaint/EndPaint 保護下
- **如果 Windows 在 DispatchMessage 返回前檢查 update region 並發現新的 dirty area...**
- **可能觸發另一個 WM_PAINT？**
- 加上 ShowCaret 的 InvalidateRect → 確實會有新 dirty area

### 新假說 G：★★★ 無限 WM_PAINT 循環 ★★★
```
WM_PAINT #1:
  s_bInsidePaint = 1
  BeginPaint (clears update)
  EndPaint (validates)
  → WndProc BitBlt (writes screen)
  → ShowCaret → InvalidateRect (caret area)
  s_bInsidePaint = 0
  return → DispatchMessage returns

PeekMessage → WM_PAINT #2 (from ShowCaret InvalidateRect)
  s_bInsidePaint = 1
  BeginPaint (clears update)
  EndPaint (validates)
  → WndProc BitBlt (writes screen)
  → ShowCaret → InvalidateRect (caret area)
  s_bInsidePaint = 0
  return → DispatchMessage returns

PeekMessage → WM_PAINT #3...
```

**每次 ShowCaret 觸發 InvalidateRect → 新 WM_PAINT → 又 ShowCaret → 又 InvalidateRect...**

**這就是死循環！**

## 結論

**v1/v2 失敗根因：WndProc 的 ShowCaret（在 WM_PAINT handler 末尾）觸發 caret 區域的 InvalidateRect，立即排入新 WM_PAINT。由於 s_bInsidePaint 已清除，新 WM_PAINT 走完整路徑，又 ShowCaret → 無限循環。**

**v3/v4 不受影響：ShowCaret 在 ScanAndDrawUda 內部，ScanAndDrawUda 在 s_bInsidePaint=1 期間執行。ScanAndDrawUda 結束後 WndProc 設 s_bInsidePaint=0。此時沒有更多的 ShowCaret 觸發。**

## 驗證方法

如果這個假說正確，v1/v2 只要把 ShowCaret 移到 return 之前（s_bInsidePaint=0 之前），就能修好。但風險在於 s_bInsidePaint=1 時的 ShowCaret 是否仍然觸發 InvalidateRect。

答案：ShowCaret 總是觸發 InvalidateRect。但 s_bInsidePaint=1 時，nested WM_PAINT 被 hook 的 guard 擋住（line 74），走 fallback 路徑。Fallback 路徑不做 ShowCaret → 不再觸發 → 循環斷開。

**所以 v1/v2 的修正應該是：把所有 Caret 操作放在 s_bInsidePaint=0 之前。**
