116 lines
4.2 KiB
Zig
116 lines
4.2 KiB
Zig
const std = @import("std");
|
|
const terminal = @import("terminal.zig");
|
|
|
|
const Cell = @import("cell.zig");
|
|
const Position = @import("size.zig").Position;
|
|
const Size = @import("size.zig").Size;
|
|
|
|
/// Double-buffered intermediate rendering pipeline
|
|
pub const Buffered = struct {
|
|
const log = std.log.scoped(.renderer_buffered);
|
|
// _ = log;
|
|
allocator: std.mem.Allocator,
|
|
created: bool,
|
|
size: Size,
|
|
screen: []Cell,
|
|
virtual_screen: []Cell,
|
|
|
|
pub fn init(allocator: std.mem.Allocator) @This() {
|
|
return .{
|
|
.allocator = allocator,
|
|
.created = false,
|
|
.size = undefined,
|
|
.screen = undefined,
|
|
.virtual_screen = undefined,
|
|
};
|
|
}
|
|
|
|
pub fn deinit(this: *@This()) void {
|
|
if (this.created) {
|
|
this.allocator.free(this.screen);
|
|
this.allocator.free(this.virtual_screen);
|
|
}
|
|
}
|
|
|
|
pub fn resize(this: *@This(), size: Size) !void {
|
|
log.debug("renderer::resize", .{});
|
|
defer this.size = size;
|
|
|
|
if (!this.created) {
|
|
this.screen = this.allocator.alloc(Cell, @as(usize, size.cols) * @as(usize, size.rows)) catch @panic("render.zig: Out of memory.");
|
|
@memset(this.screen, .{});
|
|
this.virtual_screen = this.allocator.alloc(Cell, @as(usize, size.cols) * @as(usize, size.rows)) catch @panic("render.zig: Out of memory.");
|
|
@memset(this.virtual_screen, .{});
|
|
this.created = true;
|
|
} else {
|
|
this.allocator.free(this.screen);
|
|
this.screen = this.allocator.alloc(Cell, @as(usize, size.cols) * @as(usize, size.rows)) catch @panic("render.zig: Out of memory.");
|
|
@memset(this.screen, .{});
|
|
this.allocator.free(this.virtual_screen);
|
|
this.virtual_screen = this.allocator.alloc(Cell, @as(usize, size.cols) * @as(usize, size.rows)) catch @panic("render.zig: Out of memory.");
|
|
@memset(this.virtual_screen, .{});
|
|
}
|
|
try this.clear();
|
|
}
|
|
|
|
/// Clear the entire screen and reset the screen buffer, to force a re-draw with the next `flush` call.
|
|
pub fn clear(this: *@This()) !void {
|
|
log.debug("renderer::clear", .{});
|
|
try terminal.clearScreen();
|
|
@memset(this.screen, .{});
|
|
}
|
|
|
|
/// Render provided cells at size (anchor and dimension) into the *virtual screen*.
|
|
pub fn render(this: *@This(), comptime T: type, container: *T) !void {
|
|
const size: Size = container.size;
|
|
const cells: []const Cell = try container.contents();
|
|
|
|
if (cells.len == 0) return;
|
|
|
|
var idx: usize = 0;
|
|
var vs = this.virtual_screen;
|
|
const anchor: usize = (@as(usize, size.anchor.row) * @as(usize, this.size.cols)) + @as(usize, size.anchor.col);
|
|
|
|
blk: for (0..size.rows) |row| {
|
|
for (0..size.cols) |col| {
|
|
const cell = cells[idx];
|
|
idx += 1;
|
|
|
|
vs[anchor + (row * this.size.cols) + col].style = cell.style;
|
|
vs[anchor + (row * this.size.cols) + col].cp = cell.cp;
|
|
|
|
if (cells.len == idx) break :blk;
|
|
}
|
|
}
|
|
// free immediately
|
|
container.allocator.free(cells);
|
|
|
|
for (container.elements.items) |*element| {
|
|
try this.render(T, element);
|
|
}
|
|
}
|
|
|
|
/// Write *virtual screen* to alternate screen (should be called once and last during each render loop iteration in the main loop).
|
|
pub fn flush(this: *@This()) !void {
|
|
// TODO: measure timings of rendered frames?
|
|
log.debug("renderer::flush", .{});
|
|
const writer = terminal.writer();
|
|
const s = this.screen;
|
|
const vs = this.virtual_screen;
|
|
for (0..this.size.rows) |row| {
|
|
for (0..this.size.cols) |col| {
|
|
const idx = (row * this.size.cols) + col;
|
|
const cs = s[idx];
|
|
const cvs = vs[idx];
|
|
if (cs.eql(cvs)) continue;
|
|
|
|
// render differences found in virtual screen
|
|
try terminal.setCursorPosition(.{ .row = @truncate(row + 1), .col = @truncate(col + 1) });
|
|
try cvs.value(writer);
|
|
// update screen to be the virtual screen for the next frame
|
|
s[idx] = vs[idx];
|
|
}
|
|
}
|
|
}
|
|
};
|