diff --git a/CHANGELOG.md b/CHANGELOG.md index b52ef77..61069d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ 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.25.0] - 2026-08-09 + +### Added + +- **Damage tracking interfaces** (ADR-065) — multi-renderer damage aggregation for shared surfaces. When multiple renderers (gg, g3d, video, compose) share a single GPU surface, each registers as a damage source and reports per-frame damage rectangles. The compositor unions all sources at present time. + - `DamageReporter` interface — 2-method frozen contract (`ReportDamage`, `ReportDamageWithReason`) + - `DamageCategory` enum — 6 categories (`Content`, `Layout`, `Animation`, `Resize`, `Full`, `External`) + - `DamageReason` struct — typed category + human-readable detail string + - `DamageSourceSnapshot` struct — per-source frame snapshot (name, color, rects, reason) + - `DamageOverlayRenderer` interface — custom overlay rendering for libraries with text capability + - `DamageOverlayInfo` struct — structured per-source data passed to overlay renderer + - Design follows Chromium `cc/DamageTracker` pattern — adapted for explicit registration model + +- **Pluggable debug overlay system** (ADR-066) — GTK4 Inspector-inspired overlay architecture. Multiple debug overlays register with the compositor and draw in registration order after content, before present. + - `DebugOverlay` interface — 2-method frozen contract (`Name`, `Draw`) + - `DebugOverlayContext` struct — GPU resources and frame metadata for overlay rendering + - Self-sustaining render loop: `Draw()` returns true → compositor calls `RequestRedraw()` + - Env var activation: `GOGPU_DEBUG_DAMAGE=overlay`, `GOGPU_DEBUG_FPS=overlay`, `GOGPU_DEBUG_DIRTY=overlay` + ## [0.24.0] - 2026-08-01 ### Added diff --git a/damage.go b/damage.go new file mode 100644 index 0000000..8121ad9 --- /dev/null +++ b/damage.go @@ -0,0 +1,242 @@ +// Copyright 2026 The gogpu Authors +// SPDX-License-Identifier: MIT + +package gpucontext + +import ( + "image" + "image/color" +) + +// Damage reporting types and interfaces for the multi-renderer damage tracking +// system (ADR-065). When multiple renderers (gg, g3d, video, compose) share a +// single surface, each registers as a damage source and reports per-frame +// damage rectangles. The compositor (gogpu) unions all sources at present time. +// +// This design follows Chromium's cc DamageTracker pattern where all layers +// contribute damage and the compositor unions them — adapted for our explicit +// registration model where independent libraries register themselves rather +// than being discovered via a layer tree. + +// DamageCategory classifies WHY damage occurred. +// +// Used for programmatic filtering, statistics, and overlay color +// differentiation (ADR-066). Chromium uses DamageReasonSet (bitfield of +// internal layer reasons). We use a category enum because our sources are +// independent libraries with diverse domain-specific reasons — a bitfield +// cannot cover all domains cleanly. +// +// Zero value is DamageCategoryContent, the most common category. +type DamageCategory uint8 + +const ( + // DamageCategoryContent means content changed (text, image, path, mesh). + // This is the default category when no specific reason is provided. + DamageCategoryContent DamageCategory = iota + + // DamageCategoryLayout means layout or position changed (widget moved, resized). + DamageCategoryLayout + + // DamageCategoryAnimation means an animation tick occurred (spinner, transition, camera). + DamageCategoryAnimation + + // DamageCategoryResize means the surface or window was resized. + DamageCategoryResize + + // DamageCategoryFull means a full redraw is required (initial render, theme change). + DamageCategoryFull + + // DamageCategoryExternal means damage from an external source (video frame, compose child). + DamageCategoryExternal +) + +// String returns the damage category name for debugging. +func (c DamageCategory) String() string { + switch c { + case DamageCategoryContent: + return "Content" + case DamageCategoryLayout: + return "Layout" + case DamageCategoryAnimation: + return "Animation" + case DamageCategoryResize: + return "Resize" + case DamageCategoryFull: + return "Full" + case DamageCategoryExternal: + return "External" + default: + return "Unknown" + } +} + +// DamageReason combines a typed category (for filtering and optimization) with +// a human-readable detail string (for overlay labels and structured logging). +// +// Category is always meaningful — the zero value DamageCategoryContent is the +// most common case. Detail is optional — empty string means no additional +// context beyond the category. +// +// Examples: +// +// DamageReason{} // content changed, no detail +// DamageReason{Category: DamageCategoryAnimation, Detail: "camera rotation"} // animation with detail +// DamageReason{Category: DamageCategoryFull, Detail: "theme change"} // full redraw with detail +type DamageReason struct { + // Category classifies the damage type for programmatic use. + // Zero value (DamageCategoryContent) is the default for content changes. + Category DamageCategory + + // Detail is a human-readable string for overlay labels and logging. + // Empty string is valid and means no additional context beyond the category. + // Examples: "camera rotation", "spinner tick", "theme change", "scene loaded". + Detail string +} + +// DamageReporter reports damage rectangles for a registered damage source. +// +// Each frame, the source calls ReportDamage or ReportDamageWithReason with the +// rectangles that changed. The compositor (gogpu) unions damage from all +// registered sources at present time to determine the final damage region +// sent to the Wayland compositor or other presentation backend. +// +// Calling ReportDamage with no rectangles signals full-surface damage for this +// source — the compositor will present the entire surface. +// +// Both methods reset after present — the source must report damage every frame +// that content changes. +// +// This interface is frozen at 2 methods for v1.0+ stability. Future metadata +// (per-rect labels, opaque regions, damage subtraction) goes through concrete +// type methods and DamageSourceSnapshot struct fields — both are non-breaking +// additions in Go. +// +// Implementations: +// - gogpu.DamageSource implements DamageReporter (registered via Context.RegisterDamageSource) +// +// Example usage: +// +// // gg (via ggcanvas) — reports partial damage: +// ggSource.ReportDamage(dirtyRect1, dirtyRect2) +// +// // g3d — reports full viewport with reason: +// g3dSource.ReportDamageWithReason( +// gpucontext.DamageReason{Category: gpucontext.DamageCategoryAnimation, Detail: "camera rotation"}, +// viewportRect, +// ) +// +// // Full surface damage (no rects): +// source.ReportDamage() +type DamageReporter interface { + // ReportDamage reports damage rectangles for this frame. + // No rects signals full-surface damage for this source. + // Rects are in physical pixels (surface coordinates). + // Damage is reset after present — report again next frame if content changes. + ReportDamage(rects ...image.Rectangle) + + // ReportDamageWithReason reports damage with a typed reason for debug + // overlay labels, structured logging, and future optimization heuristics. + // No rects signals full-surface damage. Reason is reset after present. + // + // Use this method when the damage source can provide meaningful context + // (e.g., "camera rotation", "spinner tick"). For common content changes + // where no detail is needed, use ReportDamage instead. + ReportDamageWithReason(reason DamageReason, rects ...image.Rectangle) +} + +// DamageOverlayRenderer provides custom rendering for the damage debug overlay. +// +// The default overlay in gogpu renders flat-color quads (no text). Libraries +// with text rendering capability (gg) can register a custom renderer that adds +// anti-aliased borders, text labels per source, and richer visuals. +// +// Registration: gogpu.Context.SetDamageOverlayRenderer(renderer) +// +// The renderer receives a DamageOverlayInfo snapshot each frame when the +// damage overlay is active (GOGPU_DEBUG_DAMAGE=overlay). The renderer draws +// on top of content using the provided encoder and surface view. +// +// Implementations: +// - gg/integration/ggcanvas provides a text-enhanced overlay renderer +// +// Example usage: +// +// type ggDamageOverlay struct { /* ... */ } +// +// func (o *ggDamageOverlay) RenderDamageOverlay(info gpucontext.DamageOverlayInfo) { +// for _, src := range info.Sources { +// // Draw colored rects with text labels using gg.Context +// } +// } +type DamageOverlayRenderer interface { + // RenderDamageOverlay draws the damage debug overlay for the current frame. + // Called by the compositor after all content renderers have finished and + // before present, when GOGPU_DEBUG_DAMAGE=overlay is active. + // + // The renderer should use info.Encoder and info.SurfaceView to submit GPU + // commands. Fade tracking and flash state are the renderer's responsibility. + RenderDamageOverlay(info DamageOverlayInfo) +} + +// DamageOverlayInfo provides structured per-source damage data to the overlay +// renderer. The compositor (gogpu) constructs this from its internal damage +// sources — the renderer receives ready-to-use snapshots without needing to +// aggregate or group data. +// +// All fields are populated by gogpu before calling RenderDamageOverlay. +type DamageOverlayInfo struct { + // Sources contains per-source snapshots for the current frame. + // Each source corresponds to a registered damage source (gg, g3d, video, etc.). + // Order matches registration order. + Sources []DamageSourceSnapshot + + // FrameNumber is the monotonic frame counter, useful for logging + // (e.g., "frame=1234") and frame-based statistics. + FrameNumber uint64 + + // SurfaceWidth is the surface width in physical pixels. + SurfaceWidth uint32 + + // SurfaceHeight is the surface height in physical pixels. + SurfaceHeight uint32 + + // Encoder is the GPU command encoder for the current frame. + // The renderer uses this to begin render passes and submit draw commands. + Encoder CommandEncoder + + // SurfaceView is the surface texture view for the current frame. + // The renderer uses this as the render pass color attachment. + SurfaceView TextureView +} + +// DamageSourceSnapshot is a per-source damage snapshot for one frame. +// +// Name and Color are stable across frames — set once at RegisterDamageSource. +// Rects, Full, and Reason change every frame based on ReportDamage calls and +// are reset after present. +// +// Zero value: no damage reported (empty Rects, Full=false, zero Reason). +type DamageSourceSnapshot struct { + // Name identifies the damage source, set at registration time. + // Examples: "gg", "g3d", "video", "compose". + Name string + + // Color is the overlay color assigned by the compositor from a fixed palette + // at registration time. The color uses the stdlib image/color.RGBA type to + // avoid adding dependencies to gpucontext. + Color color.RGBA + + // Rects contains the damage rectangles reported this frame, in physical + // pixels (surface coordinates). Empty when Full is true or no damage was + // reported. + Rects []image.Rectangle + + // Full is true when ReportDamage was called with no arguments, indicating + // the entire surface is damaged for this source. + Full bool + + // Reason is the damage reason from ReportDamageWithReason. Zero value + // (DamageReason{}) means ReportDamage was used without a reason, which + // is the common case for content changes. + Reason DamageReason +} diff --git a/debug_overlay.go b/debug_overlay.go new file mode 100644 index 0000000..fe6a030 --- /dev/null +++ b/debug_overlay.go @@ -0,0 +1,96 @@ +// Copyright 2026 The gogpu Authors +// SPDX-License-Identifier: MIT + +package gpucontext + +// Pluggable debug overlay system (ADR-066), inspired by GTK4's Inspector +// overlay architecture. Multiple debug overlays register with the compositor +// and are drawn in registration order after all content renderers, before +// present. +// +// GTK4 defines GtkInspectorOverlay with snapshot + queue_draw, supporting +// 8 concrete overlay types (updates, fps, layout, focus, a11y, etc.). +// Chromium uses a monolithic HeadsUpDisplayLayerImpl. We follow GTK4's +// pluggable model because it scales to N overlay types without monolith growth. +// +// Env vars control activation: GOGPU_DEBUG_DAMAGE=overlay, GOGPU_DEBUG_FPS=overlay, +// GOGPU_DEBUG_DIRTY=overlay. Each overlay is independent — any combination +// is valid simultaneously. + +// DebugOverlay is a pluggable debug visualization layer. +// +// Multiple overlays register with the compositor via +// Context.RegisterDebugOverlay. They are drawn in registration order after +// all content renderers have finished, before present. Later overlays render +// on top of earlier ones (e.g., FPS counter on top of damage rects). +// +// This interface is frozen at 2 methods for v1.0+ stability. Future overlay +// capabilities go through DebugOverlayContext struct fields (non-breaking). +// +// Concrete overlays shipped with the ecosystem: +// - Damage rects overlay (gogpu built-in + gg text-enhanced override) +// - FPS counter overlay (gogpu built-in + gg text-enhanced override) +// - Dirty widget overlay (ui, registers with gogpu) +// +// Example implementation: +// +// type fpsOverlay struct { +// history [120]float64 +// idx int +// } +// +// func (f *fpsOverlay) Name() string { return "fps" } +// +// func (f *fpsOverlay) Draw(ctx gpucontext.DebugOverlayContext) bool { +// // Render FPS counter using ctx.Encoder and ctx.SurfaceView +// return true // needs another frame for continuous update +// } +// +// Registration: +// +// dc.RegisterDebugOverlay(&fpsOverlay{}) +type DebugOverlay interface { + // Name identifies the overlay for logging, env var filtering, and + // removal via RemoveDebugOverlay. Must be unique among registered + // overlays. Examples: "damage", "fps", "dirty_widgets". + Name() string + + // Draw renders the overlay on top of content. + // + // Returns true if the overlay needs another frame to complete its + // visualization (e.g., fade animation in progress, FPS counter + // updating). The compositor calls RequestRedraw when any overlay + // returns true, creating a self-sustaining render loop that + // automatically stops when all overlays return false. + // + // The overlay should use ctx.Encoder and ctx.SurfaceView to submit + // GPU commands. All overlays use alpha blending (SrcAlpha / + // OneMinusSrcAlpha) with LoadOp::Load to composite on top of content. + Draw(ctx DebugOverlayContext) bool +} + +// DebugOverlayContext provides GPU resources and frame metadata to a debug +// overlay's Draw method. The compositor (gogpu) populates this struct each +// frame before calling Draw on registered overlays. +// +// All fields are read-only from the overlay's perspective — the compositor +// owns the encoder and surface view lifetime. +type DebugOverlayContext struct { + // SurfaceWidth is the surface width in physical pixels. + SurfaceWidth uint32 + + // SurfaceHeight is the surface height in physical pixels. + SurfaceHeight uint32 + + // Encoder is the GPU command encoder for the current frame. + // The overlay uses this to begin render passes and submit draw commands. + Encoder CommandEncoder + + // SurfaceView is the surface texture view for the current frame. + // The overlay uses this as the render pass color attachment. + SurfaceView TextureView + + // FrameNumber is the monotonic frame counter, useful for logging and + // frame-based statistics (e.g., rolling average FPS calculation). + FrameNumber uint64 +}