Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,11 @@ linters:
- revive
- funlen
- gocyclo
- cyclop
- gocognit
- unparam
- nestif
- maintidx

issues:
max-issues-per-linter: 0
Expand Down
48 changes: 48 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,54 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.19] — 2026-05-10

### Added

- **Per-boundary GPU textures** (ADR-007 Phase 7) — each RepaintBoundary rendered into own offscreen GPU texture. Clean boundaries reuse previous texture (0 GPU work). Compositor blits via non-MSAA path. No full widget tree traversal per frame.
- **0% GPU idle** — frame skip in `desktop.draw`: early return when no boundary is dirty and no widget needs redraw. Previous frame's GPU output reused. Verified 0% GPU on all 6 examples.
- **Offscreen boundary culling** — `isBoundaryVisible()` checks CompositorClip intersection before recording. Offscreen spinner → Draw never runs → ScheduleAnimationFrame not called → animation pumper stops → 0% GPU.
- **34 integration tests** for render loop pipeline — multi-frame spinner lifecycle, data ticker isolation, recording order, ScreenBounds accuracy, clean state early return, visibility matrix (14 subtests).
- **DrawChild skip pattern** (Flutter `paintChild`) — child boundaries are SKIPPED during parent recording. Each child boundary gets its own GPU texture, composed separately. Parent scene contains only non-boundary children. When a child boundary is dirty, the root re-records cheaply (child content skipped), then child re-renders its own texture.
- **Compositor scissor clipping** — ScrollView viewport clipping applied via GPU scissor rect during texture composition. Items outside the viewport are clipped at the GPU level, not during scene recording.
- **AnimationScheduler** (Flutter `scheduleFrame` pattern) — deferred animation frame requests at 30fps. Separates animation-driven from interaction-driven invalidation.
- **RepaintBoundary as WidgetBase property** (ADR-024) — `SetRepaintBoundary(true)` on any widget. Flutter pattern replaces wrapper-based approach. ListView items auto-boundary.
- **CrossAxisAlignment** for VBox/HBox — `CrossAxisCenter`, `CrossAxisStart`, `CrossAxisEnd`, `CrossAxisStretch`. Flutter `CrossAxisAlignment` equivalent.
- **TextModeController** optional interface — `widget.TextMode` enum (Auto/MSDF/Vector/Bitmap/GlyphMask) for explicit text rendering mode control during zoom (issue #94).
- **SVG icons in SceneCanvas** — `SVGRenderer` + `SVGFiller` interfaces on SceneCanvas. CPU rasterization via `RasterizerAnalytic` (bypasses GPU queueing on temp context).
- **2-level IconCache** (enterprise pattern) — Level 1: parsed `svg.Document` by pointer. Level 2: rasterized `*scene.Image` by (ptr, w, h, color) with LRU eviction (256 max). Before: 7.5ms/frame (50 icons). After: <1µs (cache hit).
- **DPI-aware icon rendering** (ADR-026) — render SVG icons at `ceil(logicalSize × deviceScale)` physical pixels. Qt6/Chromium/IntelliJ enterprise pattern. `DeviceScaler` interface propagates scale.
- **Damage rects passthrough** — dirty boundary rects → gg `SetPresentDamage()` → OS compositor partial present.
- **Debug overlays** (ADR-023) — `GOGPU_DEBUG_DIRTY=1` cyan flash on dirty widgets, `GOGPU_DEBUG_DAMAGE=1` green flash on gg damage regions.
- **Dirty tracking** — per-item `InvalidateRect` for ListView, `StampScreenOrigin` for correct screen-space positions, viewport clip in dirty collector.
- **Hover E2E tests** — 3 tests: button hover → boundary dirty propagation, deep nesting, full Window.HandleEvent chain.
- **36 IconCache tests** — 99%+ coverage on cache logic.
- **28 DPI-aware rendering tests** — scale 1x/2x, cache key separation, edge cases.

### Fixed

- **Double rendering of boundary items** (#94, #91) — `renderBoundaryTextures` used `depth > 1` threshold. ListView items (depth 1) rendered into BOTH root texture (inline) AND own textures (overlay blit). Alpha-blended overlap = ghost text artifacts. Fix: `depth > 0` — only root gets offscreen texture.
- **Inline child boundary hover** — dirty child boundaries didn't trigger root scene re-recording. Root texture stayed stale on hover/state changes. Fix: `paintBoundaryWithDepth` re-records parent when inline child dirty.
- **ListView hover background** — hover on ListView items now triggers root re-recording with DrawChild skip. Child boundaries are skipped during parent recording, so root re-records cheaply while items retain their own textures.
- **Force root re-recording** — `NeedsRedrawInTree` check in `desktop.draw` ensures root scene re-records when any descendant widget is dirty, even when the root boundary itself is clean.
- **ScreenOriginBase in recordBoundary** — `ScreenOriginBase` set from boundary widget's screen position before recording. Nested boundaries get correct screen-space origins for compositor texture placement.
- **Scrollbar track repeat timing** — Qt6-inspired timing: 500ms initial delay, 50ms repeat interval (QScrollBar pattern). Prevents root re-recording flood from polling-based repeat.
- **SVG icons missing** — temp `gg.NewContext()` with GPU accelerator active queued shapes instead of CPU pixmap rendering. `dc.Image()` returned empty. Fix: `SetRasterizerMode(RasterizerAnalytic)`.
- **TextField/Slider/LineChart width** — hardcoded preferred widths (100px, 200px, 308px). Now fill `MaxWidth` from layout constraints.
- **Nested boundary clip** — `DrawChild` for nested boundaries during BoundaryRecording draws directly (preserves parent PushClip).
- **ScreenOrigin positioning** — depth-based nesting, `ScreenOrigin()` for compositor texture placement.
- **Spinner intrinsic layout** — 48×48 ignores parent MinWidth.
- **Damage rect screen coords** — `onBoundaryDirty` callback now uses `ScreenOrigin + Bounds` for screen-space damage rect (was local bounds at 0,0).
- **CollectDirtyRegions ordering** — moved after `PaintBoundaryLayers` so `ScreenOrigin` is fresh from root recording. Fixes debug overlay showing damage at (0,0).
- **Pumper isolation** — suppress `onBoundaryDirty` when `desktop.draw` forces root `InvalidateScene`. Data tickers (1/sec) no longer restart 30fps animation pumper.
- **Viewport culling removed from BoxWidget** — compositor-level culling handles visibility (Flutter/Chrome/Qt6 pattern). Fixes spinner "floating" when viewport culling skipped `StampScreenOrigin`.

### Changed (Dependencies)

- **gg** v0.44.1 → **v0.46.4** (LCD ClearType glyph mask ADR-024, TagText scene text ADR-022, atlas zoom resilience, deferred ortho projection, blit scissor groups)
- **gogpu** v0.31.0 → **v0.34.0** (LCD ClearType, SubpixelLayout, three-mode D2 render loop, EventSource fix)
- **gpucontext** v0.16.0 → **v0.18.0** (SubpixelLayout API, AdapterInfo)

## [0.1.18] — 2026-05-01

### Changed (Dependencies)
Expand Down
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func main() {
| `geometry` | Point, Size, Rect, Constraints, Insets | 98.8% |
| `event` | MouseEvent, KeyEvent, WheelEvent, FocusEvent, Modifiers | 100% |
| `widget` | Widget, WidgetBase, Context, Canvas, Lifecycle (mount/unmount), SchedulerRef | 100% |
| `internal/render` | Canvas, SceneCanvas (tile-parallel), Renderer using gogpu/gg | 96.5% |
| `internal/render` | Canvas, SceneCanvas, IconCache (2-level LRU), DPI-aware SVG | 96.5% |
| `internal/layout` | Flex, Stack, Grid layout engines | 89.9% |

### MVP (Phase 1)
Expand Down Expand Up @@ -199,14 +199,16 @@ func main() {
| `theme/fluent` | Microsoft Fluent Design: 9 painters, accent colors, inner focus ring, light/dark | 96%+ |
| `theme/cupertino` | Apple HIG: 9 painters, iOS toggle switch, segmented control, pill buttons | 96%+ |
| `theme/font` | Font Registry: CSS weight matching (W3C spec), Weight 100-900, Style, Family/Face | 97.7% |
| `icon` | SVG icons (JetBrains expui), vector path icons, De Casteljau bezier, gg/svg renderer | 97%+ |
| `icon` | SVG icons (JetBrains expui), 2-level cache, DPI-aware rasterization, gg/svg renderer | 97%+ |
| `i18n` | Internationalization: Locale, Bundle, Translator, CLDR plural rules, RTL, LocaleSignal | 97.9% |
| `dnd` | Drag and drop: DragSource/DropTarget interfaces, Manager, 5px threshold, Escape cancel | 99.3% |
| `offscreen` | Headless widget rendering: CPU-only `*image.RGBA` output, no GPU/window/app required | 100% |
| `uitest` | Testing utilities: MockCanvas, MockContext, event factories, widget helpers, assertions | 93.1% |
| `internal/dirty` | Dirty region tracking: Collector, Tracker, merge algorithm, partial repaints | 100% |

**Total: ~171,000 lines of code | 55+ packages | ~6,800 tests | ~97% average coverage**
| `compositor` | Layer Tree: OffsetLayer, PictureLayer, ClipRectLayer, OpacityLayer | 95%+ |

**Total: ~170,000+ lines of code | 56+ packages | ~6,800+ tests | 97%+ average coverage**

---

Expand Down Expand Up @@ -238,7 +240,8 @@ func main() {
├─────────────────────────────────────────────────────────────┤
│ app/ + FocusManager │ focus/ │ overlay/ │ render/ │
├─────────────────────────────────────────────────────────────┤
│ desktop/ (scene composition compositor, ADR-007) │
│ desktop/ (Layer Tree Compositor, ADR-007) │
│ compositor/ (OffsetLayer, PictureLayer, Compositor)│
│ offscreen/ (headless widget → *image.RGBA) │
├─────────────────────────────────────────────────────────────┤
│ layout/ │ state/ │ a11y/ │
Expand All @@ -252,7 +255,7 @@ func main() {
├─────────────────────────────────────────────────────────────┤
│ internal/render │ internal/layout│ internal/focus │
│ Canvas, Scene, │ Flex, Grid │ Manager, Ring │
ImageCache (LRU) │ internal/dirty │ Tracker, Collector │
IconCache (LRU) │ internal/dirty │ Tracker, Collector │
├─────────────────────────────────────────────────────────────┤
│ gogpu/gg │ gpucontext │ coregx/signals │
│ 2D Graphics │ Shared Ifaces │ State Management │
Expand Down Expand Up @@ -696,7 +699,7 @@ go get github.com/gogpu/gg@latest
| [gogpu/wgpu](https://github.com/gogpu/wgpu) | Pure Go WebGPU — Vulkan, Metal, GLES, Software |
| [gogpu/naga](https://github.com/gogpu/naga) | Shader compiler — WGSL to SPIR-V, MSL, GLSL |

**Total ecosystem: 300K+ lines of Pure Go** — no CGO, no Rust, no C.
**Total ecosystem: 800K+ lines of Pure Go** — no CGO, no Rust, no C.

---

Expand Down
Loading
Loading