Microkernel thing OS experiment (Zig ⚡)
1const std = @import("std");
2const builtin = @import("builtin");
3pub const psf2 = @import("psf2.zig");
4const are_we_le = builtin.cpu.arch.endian() == .little;
5
6const fontdata_embed = @embedFile("fonts/spleen-12x24.psf");
7const fontdata: [fontdata_embed.len]u8 align(@alignOf(u32)) = fontdata_embed.*;
8
9/// Basic framebuffer container
10pub const Framebuffer = struct {
11 const Self = @This();
12 address: [*]u32,
13 width: u64,
14 height: u64,
15 pitch: u64,
16 bypp: u16,
17 red_mask_size: u8,
18 red_mask_shift: u8,
19 green_mask_size: u8,
20 green_mask_shift: u8,
21 blue_mask_size: u8,
22 blue_mask_shift: u8,
23};
24
25/// Framebuffer based console
26pub const Console = struct {
27 fb: Framebuffer,
28 canvas: [*]u8,
29 font: psf2.Font,
30 x_pos: usize = 0,
31 y_pos: usize = 0,
32 x_chrs_max: usize,
33 y_chrs_max: usize,
34 fg_color: u32 = 0xFFFFFFFF,
35 bg_color: u32 = 0,
36
37 /// Create an instance given a framebuffer
38 /// Canvas must be exactly fb.width * fb.height * fb.bypp bytes
39 pub fn init(fb: Framebuffer, canvas: [*]u8) Console {
40 const font = psf2.Font.new(&fontdata);
41 return init_with_font(fb, canvas, font);
42 }
43
44 /// Create an instance given a framebuffer and font
45 pub fn init_with_font(fb: Framebuffer, canvas: [*]u8, font: psf2.Font) Console {
46 const font_hdr = font.getHdr();
47 return .{
48 .fb = fb,
49 .font = font,
50 .canvas = canvas,
51 // TODO: implement spacing between chars?
52 .x_chrs_max = fb.width / font_hdr.width,
53 .y_chrs_max = fb.height / font_hdr.height,
54 };
55 }
56
57 /// Write a string to the console
58 pub fn puts(self: *Console, msg: []const u8) usize {
59 var written: usize = 0;
60
61 const start_line, const num_lines = blk: {
62 const start_line = self.y_pos;
63 var scrolled: bool = false;
64 for (msg) |ch| {
65 // TODO: handle characters that failed to print
66 scrolled |= self.putc(ch) catch false;
67 written += 1;
68 }
69 if (scrolled) break :blk .{ 0, self.y_chrs_max };
70 break :blk .{ start_line, self.y_pos - start_line + 1 };
71 };
72 self.renderCanvas(start_line, num_lines);
73 return written;
74 }
75
76 // Copy in the given lines from the canvas to the framebuffer
77 fn renderCanvas(
78 self: *Console,
79 start_line: usize,
80 num_lines: usize,
81 ) void {
82 const glyph_height: usize = @intCast(self.font.getHdr().height);
83 // Not necessarily fb pitch!
84 const canvas_pitch = self.fb.width * self.fb.bypp;
85 const byte_fb: [*]u8 = @ptrCast(self.fb.address);
86
87 const src_buf = self.canvas[canvas_pitch * glyph_height * start_line ..][0 .. canvas_pitch * glyph_height * num_lines];
88 const dst_buf = byte_fb[self.fb.pitch * glyph_height * start_line ..][0 .. self.fb.pitch * glyph_height * num_lines];
89 @memcpy(dst_buf, src_buf);
90 }
91
92 /// Write a character to the console, return true if scrolled
93 /// If putchar failed we did not scroll for sure
94 fn putc(self: *Console, ch: u8) !bool {
95 var scrolled: bool = false;
96 // Handle newlines
97 if (ch == '\r') return scrolled;
98 if (ch == '\n') {
99 // Reset to the beginning of the next line
100 self.x_pos = 0;
101 self.y_pos += 1;
102 // If we've overrun, scroll the entire framebuffer up one
103 // and then reset to the last line
104 if (self.y_pos >= self.y_chrs_max) {
105 self.scrollUp();
106 scrolled = true;
107 }
108 return scrolled;
109 }
110 // TODO: color palette and escape codes?
111 try self.putchar(ch, self.x_pos, self.y_pos, self.fg_color, self.bg_color);
112 self.x_pos += 1;
113
114 // If our x is too far, go down a line
115 if (self.x_pos < self.x_chrs_max) return scrolled;
116 self.x_pos = 0;
117 self.y_pos += 1;
118 if (self.y_pos >= self.y_chrs_max) {
119 self.scrollUp();
120 scrolled = true;
121 }
122 return scrolled;
123 }
124
125 fn putchar(self: *const Console, ch: u8, x_pos: usize, y_pos: usize, fg_val: u32, bg_val: u32) !void {
126 const raw_color_choice: [2]u32 = [2]u32{
127 if (are_we_le) bg_val else @byteSwap(bg_val),
128 if (are_we_le) fg_val else @byteSwap(fg_val),
129 };
130
131 const font = self.font;
132 const hdr = font.getHdr();
133
134 const bytes_per_line = hdr.bytesPerLine();
135 const mask_shamt: u5 = @truncate(bytes_per_line * 8 - 1);
136 const mask_initial: u32 = @as(u32, 1) << mask_shamt;
137 const glyph = try font.getGlyph(ch);
138
139 // Offset into framebuffer of the beginning of the character
140 const canvas_pitch: usize = self.fb.width * self.fb.bypp;
141 var offset: usize = (y_pos * @as(usize, hdr.height) * canvas_pitch) + (x_pos * @as(usize, hdr.width) * self.fb.bypp);
142 // run for every line
143 var glyph_y: u32 = 0;
144 var mask: u32 = 0;
145 while (glyph_y < hdr.height) : (glyph_y += 1) {
146 // initialize the mask and current line
147 mask = mask_initial;
148 // TODO: endian
149 const line_value: u32 = std.mem.readVarInt(u32, glyph[glyph_y * bytes_per_line ..][0..bytes_per_line], .big);
150 // offset into the fb of the current line
151 var line_offset: usize = offset;
152 var glyph_x: u32 = 0;
153 while (glyph_x < hdr.width) : (glyph_x += 1) {
154 // Write the fb or bg color
155 const color: [4]u8 = @bitCast(raw_color_choice[@intFromBool(line_value & mask != 0)]);
156 @memcpy(self.canvas[line_offset..][0..self.fb.bypp], color[0..]);
157 // Move right a pixel
158 line_offset += self.fb.bypp;
159 mask >>= 1;
160 }
161 // Move down a line
162 offset += canvas_pitch;
163 }
164 }
165
166 // Set the fg and bg color
167 pub fn setColor(self: *Console, new_fg: u32, new_bg: u32) void {
168 self.fg_color = self.convertColor(new_fg);
169 self.bg_color = self.convertColor(new_bg);
170 }
171
172 // Convert a normal _RGB u32 to the actual framebuffer format
173 fn convertColor(self: *const Console, color: u32) u32 {
174 // The color value also needs to be scaled. For example,
175 // if it's 10 bits per color and we're starting from 8 bits,
176 // Full bright will only be 0xFF/0x3FF or about 25% brightness.
177 // To fix this hypothetical, shift left by (10 - 8). This isn't
178 // perfectly accurate but close enough.
179 const red_left_shift: u5 = @truncate(self.fb.red_mask_size);
180 const green_left_shift: u5 = @truncate(self.fb.red_mask_size);
181 const blue_left_shift: u5 = @truncate(self.fb.red_mask_size);
182
183 // Get our source RGB 888
184 const right_shift = 8;
185 const red_src: u32 = (color >> 16) & 0xFF;
186 const green_src: u32 = (color >> 8) & 0xFF;
187 const blue_src: u32 = color & 0xFF;
188
189 // These shifts are the offsets to place each color.
190 const red_dest_shift: u5 = @truncate(self.fb.red_mask_shift);
191 const green_dest_shift: u5 = @truncate(self.fb.green_mask_shift);
192 const blue_dest_shift: u5 = @truncate(self.fb.blue_mask_shift);
193
194 // Do the calculations
195 const red_dst = switch (std.math.order(red_left_shift, right_shift)) {
196 .gt => red_src << (red_left_shift - right_shift),
197 .lt => red_src >> (right_shift - red_left_shift),
198 .eq => red_src,
199 } << red_dest_shift;
200
201 const green_dst = switch (std.math.order(green_left_shift, right_shift)) {
202 .gt => green_src << (green_left_shift - right_shift),
203 .lt => green_src >> (right_shift - green_left_shift),
204 .eq => green_src,
205 } << green_dest_shift;
206
207 const blue_dst = switch (std.math.order(blue_left_shift, right_shift)) {
208 .gt => blue_src << (blue_left_shift - right_shift),
209 .lt => blue_src >> (right_shift - blue_left_shift),
210 .eq => blue_src,
211 } << blue_dest_shift;
212
213 return red_dst | green_dst | blue_dst;
214 }
215
216 // Make sure to set damage to the entire screen!
217 fn scrollUp(self: *Console) void {
218 const glyph_height: usize = @intCast(self.font.getHdr().height);
219 const canvas_pitch: usize = self.fb.width * self.fb.bypp;
220
221 // Copy 1:n line up to 0:n-1 with memmove
222 const dst_buf = self.canvas[0 .. canvas_pitch * glyph_height * (self.y_chrs_max - 1)];
223 const src_buf = self.canvas[canvas_pitch * glyph_height ..][0 .. canvas_pitch * glyph_height * (self.y_chrs_max - 1)];
224 @memmove(dst_buf, src_buf);
225
226 // Clear last line
227 const last_line = self.canvas[canvas_pitch * glyph_height * (self.y_chrs_max - 1) ..][0 .. canvas_pitch * glyph_height];
228 @memset(last_line, 0);
229 self.x_pos = 0;
230 self.y_pos = self.y_chrs_max - 1;
231 }
232
233 // Get a writer with optional buffering
234 pub fn writer(self: *Console, buffer: []u8) Writer {
235 return Writer.init(self, buffer);
236 }
237
238 // Writer with the new std.Io.Writer interface
239 pub const Writer = struct {
240 console: *Console,
241 interface: std.Io.Writer,
242
243 pub fn init(console: *Console, buffer: []u8) Writer {
244 return .{
245 .console = console,
246 .interface = .{
247 .buffer = buffer,
248 .vtable = &.{ .drain = Writer.drain },
249 },
250 };
251 }
252
253 pub fn drain(w: *std.Io.Writer, data: []const []const u8, splat: usize) !usize {
254 // fieldParentPtr is so cool
255 const self: *Writer = @fieldParentPtr("interface", w);
256 var written: usize = 0;
257 // First, consume the buffer
258 if (w.end < w.buffer.len) {
259 const n = self.console.puts(w.buffer[w.end..]);
260 written += n;
261 w.end += n;
262 }
263
264 // Iterate over all the provided slices
265 for (data, 0..) |slice, i| {
266 // If we are the last slice, splat by the amount
267 if (i == data.len - 1) {
268 for (0..splat) |_| {
269 written += self.console.puts(slice);
270 }
271 break;
272 }
273 written += self.console.puts(slice);
274 }
275 return written;
276 }
277 };
278};