Compare commits
2 commits
4b7069b076
...
15b9cf47a7
| Author | SHA1 | Date | |
|---|---|---|---|
| 15b9cf47a7 | |||
| 105ff0063d |
2 changed files with 148 additions and 3 deletions
|
|
@ -48,10 +48,9 @@ pub const Sdl2Backend = struct {
|
||||||
errdefer c.SDL_DestroyWindow(window);
|
errdefer c.SDL_DestroyWindow(window);
|
||||||
|
|
||||||
// Create renderer with hardware acceleration and VSync
|
// Create renderer with hardware acceleration and VSync
|
||||||
// VSync syncs with monitor refresh rate (typically 60Hz) which:
|
// VSync syncs with monitor refresh rate (typically 60Hz):
|
||||||
// - Eliminates screen tearing
|
// - Eliminates screen tearing
|
||||||
// - Reduces CPU usage (no busy-wait needed)
|
// - Reduces CPU/GPU usage
|
||||||
// - Provides consistent frame timing
|
|
||||||
// Falls back to software renderer if GPU not available
|
// Falls back to software renderer if GPU not available
|
||||||
const renderer = c.SDL_CreateRenderer(
|
const renderer = c.SDL_CreateRenderer(
|
||||||
window,
|
window,
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,16 @@ pub const Context = struct {
|
||||||
/// Whether the entire screen needs redraw
|
/// Whether the entire screen needs redraw
|
||||||
full_redraw: bool,
|
full_redraw: bool,
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Dirty Panel System (granularity per named panel)
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/// Registered panel areas by name (e.g., "who_list", "doc_detail")
|
||||||
|
panel_areas: std.StringHashMapUnmanaged(Layout.Rect),
|
||||||
|
|
||||||
|
/// Dirty flags per panel (true = needs redraw)
|
||||||
|
dirty_panels: std.StringHashMapUnmanaged(bool),
|
||||||
|
|
||||||
/// Frame statistics
|
/// Frame statistics
|
||||||
stats: FrameStats,
|
stats: FrameStats,
|
||||||
|
|
||||||
|
|
@ -157,6 +167,8 @@ pub const Context = struct {
|
||||||
.height = height,
|
.height = height,
|
||||||
.dirty_rects = .{},
|
.dirty_rects = .{},
|
||||||
.full_redraw = true,
|
.full_redraw = true,
|
||||||
|
.panel_areas = .{},
|
||||||
|
.dirty_panels = .{},
|
||||||
.stats = .{},
|
.stats = .{},
|
||||||
.focus = FocusSystem.init(),
|
.focus = FocusSystem.init(),
|
||||||
};
|
};
|
||||||
|
|
@ -177,6 +189,8 @@ pub const Context = struct {
|
||||||
.height = height,
|
.height = height,
|
||||||
.dirty_rects = .{},
|
.dirty_rects = .{},
|
||||||
.full_redraw = true,
|
.full_redraw = true,
|
||||||
|
.panel_areas = .{},
|
||||||
|
.dirty_panels = .{},
|
||||||
.stats = .{},
|
.stats = .{},
|
||||||
.focus = FocusSystem.init(),
|
.focus = FocusSystem.init(),
|
||||||
};
|
};
|
||||||
|
|
@ -188,6 +202,8 @@ pub const Context = struct {
|
||||||
self.overlay_commands.deinit(self.allocator);
|
self.overlay_commands.deinit(self.allocator);
|
||||||
self.id_stack.deinit(self.allocator);
|
self.id_stack.deinit(self.allocator);
|
||||||
self.dirty_rects.deinit(self.allocator);
|
self.dirty_rects.deinit(self.allocator);
|
||||||
|
self.panel_areas.deinit(self.allocator);
|
||||||
|
self.dirty_panels.deinit(self.allocator);
|
||||||
self.frame_arena.deinit();
|
self.frame_arena.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -238,6 +254,10 @@ pub const Context = struct {
|
||||||
|
|
||||||
// Reset full_redraw for next frame
|
// Reset full_redraw for next frame
|
||||||
self.full_redraw = false;
|
self.full_redraw = false;
|
||||||
|
|
||||||
|
// Clear dirty panel flags AFTER rendering
|
||||||
|
// (renderer uses getDirtyPanelRects during frame)
|
||||||
|
self.clearDirtyPanels();
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
@ -743,6 +763,132 @@ pub const Context = struct {
|
||||||
self.width = width;
|
self.width = width;
|
||||||
self.height = height;
|
self.height = height;
|
||||||
self.invalidateAll();
|
self.invalidateAll();
|
||||||
|
self.markAllPanelsDirty(); // Resize requires all panels to redraw
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Dirty Panel System (granularity per named panel)
|
||||||
|
//
|
||||||
|
// The application registers panel areas at startup:
|
||||||
|
// ctx.registerPanelArea("who_list", rect);
|
||||||
|
//
|
||||||
|
// When data changes, the application marks panels dirty:
|
||||||
|
// ctx.invalidatePanel("who_list");
|
||||||
|
//
|
||||||
|
// The renderer checks which panels are dirty:
|
||||||
|
// const dirty_rects = ctx.getDirtyPanelRects();
|
||||||
|
// renderer.clearDirtyRegions(dirty_rects);
|
||||||
|
//
|
||||||
|
// At endFrame(), dirty flags are cleared automatically.
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/// Register a named panel area. Call at startup or when layout changes.
|
||||||
|
/// The ID should be descriptive (e.g., "who_list", "doc_detail").
|
||||||
|
pub fn registerPanelArea(self: *Self, id: []const u8, rect: Layout.Rect) void {
|
||||||
|
self.panel_areas.put(self.allocator, id, rect) catch {
|
||||||
|
// If we can't register, fall back to full redraw
|
||||||
|
self.full_redraw = true;
|
||||||
|
};
|
||||||
|
// New panels start dirty (need initial draw)
|
||||||
|
self.dirty_panels.put(self.allocator, id, true) catch {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update a panel's rect (e.g., after window resize).
|
||||||
|
/// Does NOT mark the panel dirty - call invalidatePanel separately if needed.
|
||||||
|
pub fn updatePanelArea(self: *Self, id: []const u8, rect: Layout.Rect) void {
|
||||||
|
if (self.panel_areas.getPtr(id)) |ptr| {
|
||||||
|
ptr.* = rect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark a panel as needing redraw.
|
||||||
|
/// Call this when data changes that affects the panel's content.
|
||||||
|
pub fn invalidatePanel(self: *Self, id: []const u8) void {
|
||||||
|
if (self.dirty_panels.getPtr(id)) |ptr| {
|
||||||
|
ptr.* = true;
|
||||||
|
} else {
|
||||||
|
// Panel not registered - this is probably a bug
|
||||||
|
// For safety, do full redraw
|
||||||
|
self.full_redraw = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a panel is marked dirty.
|
||||||
|
pub fn isPanelDirty(self: *Self, id: []const u8) bool {
|
||||||
|
if (self.full_redraw) return true;
|
||||||
|
return self.dirty_panels.get(id) orelse false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark all registered panels as dirty.
|
||||||
|
/// Use for resize, tab change, or other global invalidation.
|
||||||
|
pub fn markAllPanelsDirty(self: *Self) void {
|
||||||
|
var iter = self.dirty_panels.iterator();
|
||||||
|
while (iter.next()) |entry| {
|
||||||
|
entry.value_ptr.* = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get rectangles of all dirty panels (for renderer).
|
||||||
|
/// Returns slice allocated from frame arena (valid until next beginFrame).
|
||||||
|
pub fn getDirtyPanelRects(self: *Self) []const Layout.Rect {
|
||||||
|
if (self.full_redraw) {
|
||||||
|
// Return single rect covering entire screen
|
||||||
|
const full = Layout.Rect{
|
||||||
|
.x = 0,
|
||||||
|
.y = 0,
|
||||||
|
.w = self.width,
|
||||||
|
.h = self.height,
|
||||||
|
};
|
||||||
|
const result = self.frame_arena.alloc_slice(Layout.Rect, 1) orelse return &.{};
|
||||||
|
result[0] = full;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count dirty panels
|
||||||
|
var dirty_count: usize = 0;
|
||||||
|
var iter = self.dirty_panels.iterator();
|
||||||
|
while (iter.next()) |entry| {
|
||||||
|
if (entry.value_ptr.*) dirty_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dirty_count == 0) return &.{};
|
||||||
|
|
||||||
|
// Allocate result array from frame arena
|
||||||
|
const result = self.frame_arena.alloc_slice(Layout.Rect, dirty_count) orelse return &.{};
|
||||||
|
|
||||||
|
// Fill with dirty panel rects
|
||||||
|
var i: usize = 0;
|
||||||
|
var iter2 = self.dirty_panels.iterator();
|
||||||
|
while (iter2.next()) |entry| {
|
||||||
|
if (entry.value_ptr.*) {
|
||||||
|
if (self.panel_areas.get(entry.key_ptr.*)) |rect| {
|
||||||
|
result[i] = rect;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result[0..i];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if any panel is dirty (useful for skip-redraw optimization).
|
||||||
|
pub fn hasAnyDirtyPanel(self: *Self) bool {
|
||||||
|
if (self.full_redraw) return true;
|
||||||
|
|
||||||
|
var iter = self.dirty_panels.iterator();
|
||||||
|
while (iter.next()) |entry| {
|
||||||
|
if (entry.value_ptr.*) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear all dirty panel flags.
|
||||||
|
/// Called automatically at endFrame(), but can be called manually if needed.
|
||||||
|
pub fn clearDirtyPanels(self: *Self) void {
|
||||||
|
var iter = self.dirty_panels.iterator();
|
||||||
|
while (iter.next()) |entry| {
|
||||||
|
entry.value_ptr.* = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue