mod(renderer): initial version of double buffer intermediate renderer
This branch will implement the necessary changes for the widgets and their implementations to use the new renderer correctly.
This commit is contained in:
168
src/render.zig
168
src/render.zig
@@ -10,97 +10,107 @@
|
||||
const std = @import("std");
|
||||
const terminal = @import("terminal.zig");
|
||||
|
||||
const Cells = []const terminal.Cell;
|
||||
const Cell = terminal.Cell;
|
||||
const Position = terminal.Position;
|
||||
const Size = terminal.Size;
|
||||
|
||||
pub fn Direct(comptime fullscreen: bool) type {
|
||||
const log = std.log.scoped(.renderer_direct);
|
||||
_ = log;
|
||||
/// Double-buffered intermediate rendering pipeline
|
||||
pub fn Buffered(comptime fullscreen: bool) type {
|
||||
const log = std.log.scoped(.renderer_buffered);
|
||||
// _ = log;
|
||||
_ = fullscreen;
|
||||
return struct {
|
||||
size: Size = undefined,
|
||||
allocator: std.mem.Allocator,
|
||||
created: bool,
|
||||
size: Size,
|
||||
screen: []Cell,
|
||||
virtual_screen: []Cell,
|
||||
|
||||
pub fn resize(this: *@This(), size: Size) void {
|
||||
this.size = size;
|
||||
pub fn init(allocator: std.mem.Allocator) @This() {
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.created = false,
|
||||
.size = undefined,
|
||||
.screen = undefined,
|
||||
.virtual_screen = undefined,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn clear(this: *@This(), size: Size) !void {
|
||||
_ = this;
|
||||
// NOTE: clear on the entire screen may introduce too much overhead and could instead clear the entire screen instead.
|
||||
// - it could also then try to optimize for further *clear* calls, that result in pretty much a nop? -> how to identify those clear calls?
|
||||
// TODO: this should instead by dynamic and correct of size (terminal could be too large currently)
|
||||
std.debug.assert(1028 > size.cols);
|
||||
var buf: [1028]u8 = undefined;
|
||||
@memset(buf[0..], ' ');
|
||||
for (0..size.rows) |r| {
|
||||
const row: u16 = @truncate(r);
|
||||
try terminal.setCursorPosition(.{
|
||||
.col = size.anchor.col,
|
||||
.row = size.anchor.row + row,
|
||||
});
|
||||
_ = try terminal.write(buf[0..size.cols]);
|
||||
pub fn deinit(this: *@This()) void {
|
||||
if (this.created) {
|
||||
this.allocator.free(this.screen);
|
||||
this.allocator.free(this.virtual_screen);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(this: *@This(), size: Size, cells: Cells) !void {
|
||||
_ = this;
|
||||
try terminal.setCursorPosition(size.anchor);
|
||||
var row: u16 = 0;
|
||||
var remaining_cols = size.cols;
|
||||
pub fn resize(this: *@This(), size: Size) void {
|
||||
this.size = size;
|
||||
if (!this.created) {
|
||||
this.screen = this.allocator.alloc(Cell, size.cols * size.rows) catch @panic("render.zig: Out of memory.");
|
||||
this.virtual_screen = this.allocator.alloc(Cell, size.cols * size.rows) catch @panic("render.zig: Out of memory.");
|
||||
this.created = true;
|
||||
return;
|
||||
}
|
||||
if (this.allocator.resize(this.screen, size.cols * size.rows)) {
|
||||
@panic("render.zig: Could not resize `screen` buffer");
|
||||
}
|
||||
if (this.allocator.resize(this.virtual_screen, size.cols * size.rows)) {
|
||||
@panic("render.zig: Could not resize `virtual screen` buffer.");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(this: *@This(), size: Size) !void {
|
||||
log.debug("renderer::clear", .{});
|
||||
var vs = this.virtual_screen;
|
||||
const anchor = (size.anchor.row * this.size.rows) + size.anchor.col;
|
||||
for (0..size.rows) |row| {
|
||||
for (0..size.cols) |col| {
|
||||
vs[anchor + (row * this.size.cols) + col].reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Render provided cells at size (anchor and dimension) into the *virtual screen*.
|
||||
pub fn render(this: *@This(), size: Size, cells: []const Cell) !void {
|
||||
log.debug("renderer:render: cells: {any}", .{cells});
|
||||
std.debug.assert(cells.len > 0);
|
||||
|
||||
var idx: usize = 0;
|
||||
var vs = this.virtual_screen;
|
||||
const anchor = (size.anchor.row * this.size.rows) + size.anchor.col;
|
||||
|
||||
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].rune = cell.rune;
|
||||
|
||||
if (cells.len == idx) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
log.debug("renderer::flush", .{});
|
||||
const writer = terminal.writer();
|
||||
for (cells) |cell| {
|
||||
var idx: usize = 0;
|
||||
print_cell: while (true) {
|
||||
const cell_len = cell.len(idx);
|
||||
if (cell_len > remaining_cols) {
|
||||
const result = try cell.writeUpToNewline(writer, idx, idx + remaining_cols);
|
||||
row += 1;
|
||||
if (row >= size.rows) {
|
||||
return; // we are done
|
||||
}
|
||||
try terminal.setCursorPosition(.{
|
||||
.col = size.anchor.col,
|
||||
.row = size.anchor.row + row,
|
||||
});
|
||||
remaining_cols = size.cols;
|
||||
idx = result.idx;
|
||||
if (result.newline) {
|
||||
idx += 1; // skip over newline
|
||||
} else {
|
||||
// there is still content to the newline (which will not be printed)
|
||||
for (idx..cell.content.len) |i| {
|
||||
if (cell.content[i] == '\n') {
|
||||
idx = i + 1;
|
||||
continue :print_cell;
|
||||
}
|
||||
}
|
||||
break; // go to next cell (as we went to the end of the cell and do not print on the next line)
|
||||
}
|
||||
} else {
|
||||
// print rest of cell
|
||||
const result = try cell.writeUpToNewline(writer, idx, idx + cell_len);
|
||||
if (result.newline) {
|
||||
row += 1;
|
||||
if (row >= size.rows) {
|
||||
return; // we are done
|
||||
}
|
||||
try terminal.setCursorPosition(.{
|
||||
.col = size.anchor.col,
|
||||
.row = size.anchor.row + row,
|
||||
});
|
||||
remaining_cols = size.cols;
|
||||
idx = result.idx + 1; // skip over newline
|
||||
} else {
|
||||
remaining_cols -= @truncate(cell_len -| idx);
|
||||
idx = 0;
|
||||
break; // go to next cell
|
||||
}
|
||||
}
|
||||
// written all cell contents
|
||||
if (idx >= cell.content.len) {
|
||||
break; // go to next cell
|
||||
}
|
||||
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
|
||||
// TODO: improve the writing speed (many unecessary writes (i.e. the style for every character..))
|
||||
try terminal.setCursorPosition(.{ .row = @truncate(row), .col = @truncate(col) });
|
||||
try cvs.value(writer);
|
||||
// update screen to be the virtual screen for the next frame
|
||||
s[idx] = vs[idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user