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
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
242 changes: 242 additions & 0 deletions damage.go
Original file line number Diff line number Diff line change
@@ -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
}
96 changes: 96 additions & 0 deletions debug_overlay.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading