diff options
| author | Nathan Reiner <nathan@nathanreiner.xyz> | 2025-02-12 12:48:11 +0100 |
|---|---|---|
| committer | Nathan Reiner <nathan@nathanreiner.xyz> | 2025-02-12 12:48:11 +0100 |
| commit | 9fd81c0b38b2b843c24fb61bf8cb5b7873deaa72 (patch) | |
| tree | a2d4d76a4fcc1334d83c5538e684061913be24d3 | |
| parent | dae5bc02b1c934075e95694953b4330676e21611 (diff) | |
graphics: add line
| -rw-r--r-- | build.zig | 106 | ||||
| -rw-r--r-- | src/estd/graphics/box.zig | 101 | ||||
| -rw-r--r-- | src/estd/graphics/canvas.zig | 13 | ||||
| -rw-r--r-- | src/estd/graphics/color.zig | 23 | ||||
| -rw-r--r-- | src/estd/graphics/curve.zig | 36 | ||||
| -rw-r--r-- | src/estd/graphics/line.zig | 76 | ||||
| -rw-r--r-- | src/estd/graphics/root.zig | 202 | ||||
| -rw-r--r-- | src/screen/drm/buffer/raw.zig | 2 | ||||
| -rw-r--r-- | src/screen/drm/buffer/root.zig | 2 | ||||
| -rw-r--r-- | src/screen/drm/pixel.zig | 18 | ||||
| -rw-r--r-- | src/screen/drm/request/frame-buffer.zig | 13 | ||||
| -rw-r--r-- | src/screen/drm/root.zig | 1 | ||||
| -rw-r--r-- | src/screen/main.zig | 47 |
13 files changed, 347 insertions, 293 deletions
@@ -1,72 +1,72 @@ const std = @import("std"); const Program = struct { - name: []const u8, - qemu: bool, + name: []const u8, + qemu: bool, }; const programs = [_]Program { - .{ .name = "shell", .qemu = false }, - .{ .name = "screen", .qemu = true }, + .{ .name = "shell", .qemu = false }, + .{ .name = "screen", .qemu = true }, }; pub fn build(b: *std.Build) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); - const estd = b.createModule(.{ - .root_source_file = b.path("src/estd/root.zig"), - }); + const estd = b.createModule(.{ + .root_source_file = b.path("src/estd/root.zig"), + }); - const init_exe = b.addExecutable(.{ - .name = "init", - .root_source_file = b.path("src/init/main.zig"), - .target = target, - .optimize = optimize, - }); - init_exe.root_module.addImport("estd", estd); + const init_exe = b.addExecutable(.{ + .name = "init", + .root_source_file = b.path("src/init/main.zig"), + .target = target, + .optimize = optimize, + }); + init_exe.root_module.addImport("estd", estd); - const test_step = b.step("test", "Run tests"); + const test_step = b.step("test", "Run tests"); - inline for (programs) |program| { - const path = b.path("src/" ++ program.name ++ "/main.zig"); + inline for (programs) |program| { + const path = b.path("src/" ++ program.name ++ "/main.zig"); - const exe = b.addExecutable(.{ - .name = program.name, - .root_source_file = path, - .target = target, - .optimize = optimize - }); - exe.root_module.addImport("estd", estd); - b.installArtifact(exe); + const exe = b.addExecutable(.{ + .name = program.name, + .root_source_file = path, + .target = target, + .optimize = optimize, + }); + exe.root_module.addImport("estd", estd); + b.installArtifact(exe); - const run = b.step("run-" ++ program.name, "Run " ++ program.name); + const run = b.step("run-" ++ program.name, "Run " ++ program.name); - if (program.qemu) { - const run_qemu = b.addSystemCommand(&.{ "./build/run-qemu" }); - run_qemu.addFileArg(init_exe.getEmittedBin()); - run_qemu.addFileArg(exe.getEmittedBin()); - run_qemu.step.dependOn(&exe.step); - run_qemu.step.dependOn(&init_exe.step); - run.dependOn(&run_qemu.step); - } else { - const run_artifact = b.addRunArtifact(exe); - run.dependOn(&run_artifact.step); - } + if (program.qemu) { + const run_qemu = b.addSystemCommand(&.{ "./build/run-qemu" }); + run_qemu.addFileArg(init_exe.getEmittedBin()); + run_qemu.addFileArg(exe.getEmittedBin()); + run_qemu.step.dependOn(&exe.step); + run_qemu.step.dependOn(&init_exe.step); + run.dependOn(&run_qemu.step); + } else { + const run_artifact = b.addRunArtifact(exe); + run.dependOn(&run_artifact.step); + } - const tests = b.addTest(.{ - .name = program.name, - .root_source_file = path, - }); - tests.root_module.addImport("estd", estd); - const run_tests = b.addRunArtifact(tests); - test_step.dependOn(&run_tests.step); - } + const tests = b.addTest(.{ + .name = program.name, + .root_source_file = path, + }); + tests.root_module.addImport("estd", estd); + const run_tests = b.addRunArtifact(tests); + test_step.dependOn(&run_tests.step); + } - const tests = b.addTest(.{ - .name = "estd", - .root_source_file = b.path("src/estd/root.zig"), - }); - const run_tests = b.addRunArtifact(tests); - test_step.dependOn(&run_tests.step); + const tests = b.addTest(.{ + .name = "estd", + .root_source_file = b.path("src/estd/root.zig"), + }); + const run_tests = b.addRunArtifact(tests); + test_step.dependOn(&run_tests.step); } diff --git a/src/estd/graphics/box.zig b/src/estd/graphics/box.zig new file mode 100644 index 0000000..83868aa --- /dev/null +++ b/src/estd/graphics/box.zig @@ -0,0 +1,101 @@ +const std = @import("std"); +const graphics = @import("root.zig"); + +pub const Box = struct { + const Self = @This(); + + x: u32, + y: u32, + width: u32, + height: u32, + color: graphics.Color, + radius: u32 = 0, + + fn render_rect( + self: *const Self, + x: u32, + y: u32, + width: u32, + height: u32, + canvas: *const graphics.Canvas + ) void { + if (x >= canvas.width or y >= canvas.height) { + return; + } + + const x_end = @min(x + width, canvas.width); + const y_end = @min(y + height, canvas.height); + + for (y..y_end) |yp| { + const offset = yp * canvas.width; + @memset( + canvas.buffer[(offset + x)..(offset + x_end)], + self.color + ); + } + } + + pub fn render(self: *const Self, canvas: *const graphics.Canvas) void { + if (self.radius == 0) { + self.render_rect(self.x, self.y, self.width, self.height, canvas); + return; + } + + const radius = @min(self.radius, self.width / 2, self.height / 2); + const core_width = self.width - 2 * radius; + const core_height = self.height - 2 * radius; + + self.render_rect( + self.x + radius, + self.y, + core_width, + radius, + canvas + ); + + self.render_rect( + self.x + radius, + self.y + radius + core_height, + core_width, + radius, + canvas + ); + + self.render_rect( + self.x, + self.y + radius, + self.width, + core_height, + canvas + ); + + for (0..radius) |yp| { + const width: u32 = @intFromFloat(@round(std.math.sqrt(@as(f64, @floatFromInt(radius * radius - yp * yp))))); + const spacing = radius - width; + + var offset = ((radius - yp) + self.y) * canvas.width + self.x + spacing; + @memset( + canvas.buffer[offset..offset + width], + self.color + ); + + offset = ((radius - yp) + self.y) * canvas.width + self.x + core_width + radius; + @memset( + canvas.buffer[offset..offset + width], + self.color + ); + + offset = (yp + self.y + radius + core_height) * canvas.width + self.x + spacing; + @memset( + canvas.buffer[offset..offset + width], + self.color + ); + + offset = (yp + self.y + radius + core_height) * canvas.width + self.x + core_width + radius; + @memset( + canvas.buffer[offset..offset + width], + self.color + ); + } + } +}; diff --git a/src/estd/graphics/canvas.zig b/src/estd/graphics/canvas.zig new file mode 100644 index 0000000..35dba4b --- /dev/null +++ b/src/estd/graphics/canvas.zig @@ -0,0 +1,13 @@ +const graphics = @import("root.zig"); + +pub const Canvas = struct { + const Self = @This(); + + width: u32, + height: u32, + buffer: []volatile graphics.Color, + + pub fn fill(self: *const Canvas, color: graphics.Color) void { + @memset(self.buffer, color); + } +}; diff --git a/src/estd/graphics/color.zig b/src/estd/graphics/color.zig new file mode 100644 index 0000000..b7fd97e --- /dev/null +++ b/src/estd/graphics/color.zig @@ -0,0 +1,23 @@ +pub const Color = packed struct(u32) { + const Self = @This(); + + blue: u8, + green: u8, + red: u8, + _padding: u8 = 0x0, + + pub fn mix(self: *const Self, other: *const Self, factor: f64) Self { + const inverse = 1 - factor; + return Self { + .red = @intFromFloat( + @as(f64, @floatFromInt(self.red)) * inverse + @as(f64, @floatFromInt(other.red)) * factor + ), + .green = @intFromFloat( + @as(f64, @floatFromInt(self.green)) * inverse + @as(f64, @floatFromInt(other.green)) * factor + ), + .blue = @intFromFloat( + @as(f64, @floatFromInt(self.blue)) * inverse + @as(f64, @floatFromInt(other.blue)) * factor + ), + }; + } +}; diff --git a/src/estd/graphics/curve.zig b/src/estd/graphics/curve.zig new file mode 100644 index 0000000..d4be1d7 --- /dev/null +++ b/src/estd/graphics/curve.zig @@ -0,0 +1,36 @@ +const std = @import("std"); +const graphics = @import("root.zig"); + +pub const Curve = struct { + const Self = @This(); + const Point = struct { + x: u32, + y: u32, + }; + + start: Point, + middle: Point, + end: Point, + + color: graphics.Color, + + pub fn render(self: *const Self, canvas: *const graphics.Canvas) void { + const start_x: f64 = @floatFromInt(self.start.x); + const start_y: f64 = @floatFromInt(self.start.y); + const middle_x: f64 = @floatFromInt(self.middle.x); + const middle_y: f64 = @floatFromInt(self.middle.y); + const end_x: f64 = @floatFromInt(self.end.x); + const end_y: f64 = @floatFromInt(self.end.y); + + for (0..100) |index| { + const t: f64 = @as(f64, @floatFromInt(index)) / 100; + const x: u32 = @intFromFloat(@round( + (1 - t) * ((1 - t) * start_x + t * middle_x) + t * ((1 - t) * middle_x + t * end_x) + )); + const y: u32 = @intFromFloat(@round( + (1 - t) * ((1 - t) * start_y + t * middle_y) + t * ((1 - t) * middle_y + t * end_y) + )); + canvas.buffer[x + canvas.width * y] = self.color; + } + } +}; diff --git a/src/estd/graphics/line.zig b/src/estd/graphics/line.zig new file mode 100644 index 0000000..93f17ff --- /dev/null +++ b/src/estd/graphics/line.zig @@ -0,0 +1,76 @@ +const std = @import("std"); +const graphics = @import("root.zig"); + +pub const Line = struct { + const Self = @This(); + const Point = struct { + x: u32, + y: u32, + }; + + start: Point, + end: Point, + + color: graphics.Color, + + fn distance(a: u32, b: u32) u32 { + if (a < b) { + return b - a; + } else { + return a - b; + } + } + + pub fn render(self: *const Self, canvas: *const graphics.Canvas) void { + var start = self.start; + var end = self.end; + const steep = distance(start.y, end.y) > distance(start.x, end.x); + + if (steep) { + std.mem.swap(u32, &start.x, &start.y); + std.mem.swap(u32, &end.x, &end.y); + } + + if (start.x > end.x) { + std.mem.swap(Point, &start, &end); + } + + const dx: f64 = @floatFromInt(end.x - start.x); + const dy: f64 = @floatFromInt(@as(i64, @intCast(end.y)) - @as(i64, @intCast(start.y))); + var gradient: f64 = dy / dx; + if (dx == 0) { + gradient = 1; + } + + + var intersect_y: f64 = @floatFromInt(start.y); + + if (steep) { + for (start.x..end.x + 1) |x| { + const y: usize = @intFromFloat(intersect_y); + const factor = intersect_y - @as(f64, @floatFromInt(y)); + + var previous_color = canvas.buffer[canvas.width * x + y]; + canvas.buffer[x * canvas.width + y] = previous_color.mix(&self.color, factor); + + previous_color = canvas.buffer[x * canvas.width + y - 1]; + canvas.buffer[x * canvas.width + y - 1] = self.color.mix(&previous_color, factor); + + intersect_y += gradient; + } + } else { + for (start.x..end.x + 1) |x| { + const y: u32 = @intFromFloat(intersect_y); + const factor = intersect_y - @as(f64, @floatFromInt(y)); + + var previous_color = canvas.buffer[x + canvas.width * y]; + canvas.buffer[x + canvas.width * y] = previous_color.mix(&self.color, factor); + + previous_color = canvas.buffer[x + canvas.width * (y - 1)]; + canvas.buffer[x + canvas.width * (y - 1)] = self.color.mix(&previous_color, factor); + + intersect_y += gradient; + } + } + } +}; diff --git a/src/estd/graphics/root.zig b/src/estd/graphics/root.zig index da09450..533d518 100644 --- a/src/estd/graphics/root.zig +++ b/src/estd/graphics/root.zig @@ -1,197 +1,5 @@ -const std = @import("std"); - -pub const Pixel = packed struct(u32) { - blue: u8, - green: u8, - red: u8, - _padding: u8 = 0x0, -}; - -pub const Canvas = struct { - const Self = @This(); - - width: u32, - height: u32, - buffer: []volatile Pixel, - - pub fn fill(self: *const Canvas, pixel: Pixel) void { - @memset(self.buffer, pixel); - } - - fn gaussian_mean(values: [9]u32) u32 { - return (values[0] + values[1] * 2 + values[2] + - values[3] * 2 + values[4] * 8 + values[5] * 2 + - values[6] + values[7] * 2 + values[8]) / 20; - } - - pub fn set_with_anti_aliasing(self: *const Canvas, x: u32, y: u32) void { - var neighbors_red: [9]u32 = undefined; - var neighbors_green: [9]u32 = undefined; - var neighbors_blue: [9]u32 = undefined; - - for (0..3) |h| { - for (0..3) |w| { - const pixel = self.buffer[(y + 1 - h) * self.width + x + 1 - w]; - neighbors_red[w + 3 * h] = pixel.red; - neighbors_green[w + 3 * h] = pixel.green; - neighbors_blue[w + 3 * h] = pixel.blue; - } - } - - self.buffer[x + (y * self.width)] = .{ - .red = @intCast(Self.gaussian_mean(neighbors_red)), - .green = @intCast(Self.gaussian_mean(neighbors_green)), - .blue = @intCast(Self.gaussian_mean(neighbors_blue)), - }; - } -}; - -pub const Box = struct { - const Self = @This(); - - x: u32, - y: u32, - width: u32, - height: u32, - color: Pixel, - radius: u32 = 0, - - fn render_rect( - self: *const Self, - x: u32, - y: u32, - width: u32, - height: u32, - canvas: *const Canvas - ) void { - if (x >= canvas.width or y >= canvas.height) { - return; - } - - const x_end = @min(x + width, canvas.width); - const y_end = @min(y + height, canvas.height); - - for (y..y_end) |yp| { - const offset = yp * canvas.width; - @memset( - canvas.buffer[(offset + x)..(offset + x_end)], - self.color - ); - } - } - - pub fn render(self: *const Self, canvas: *const Canvas) void { - if (self.radius == 0) { - self.render_rect(self.x, self.y, self.width, self.height, canvas); - return; - } - - const radius = @min(self.radius, self.width / 2, self.height / 2); - const core_width = self.width - 2 * radius; - const core_height = self.height - 2 * radius; - - self.render_rect( - self.x + radius, - self.y, - core_width, - radius, - canvas - ); - - self.render_rect( - self.x + radius, - self.y + radius + core_height, - core_width, - radius, - canvas - ); - - self.render_rect( - self.x, - self.y + radius, - self.width, - core_height, - canvas - ); - - for (0..radius) |yp| { - const width: u32 = @intFromFloat(std.math.round( - @as(f64, @floatFromInt(radius)) * - @cos(std.math.asin( - @as(f64, @floatFromInt(yp)) / @as(f64, @floatFromInt(radius)) - )) - )); - const spacing = radius - width; - - var offset = ((radius - yp) + self.y) * canvas.width + self.x + spacing; - @memset( - canvas.buffer[offset..offset + width], - self.color - ); - - offset = ((radius - yp) + self.y) * canvas.width + self.x + core_width + radius; - @memset( - canvas.buffer[offset..offset + width], - self.color - ); - - offset = (yp + self.y + radius + core_height) * canvas.width + self.x + spacing; - @memset( - canvas.buffer[offset..offset + width], - self.color - ); - - offset = (yp + self.y + radius + core_height) * canvas.width + self.x + core_width + radius; - @memset( - canvas.buffer[offset..offset + width], - self.color - ); - } - - for (0..radius) |yp| { - const width: u32 = @intFromFloat(std.math.round( - @as(f64, @floatFromInt(radius)) * - @cos(std.math.asin( - @as(f64, @floatFromInt(yp)) / @as(f64, @floatFromInt(radius)) - )) - )); - - const spacing = radius - width; - - canvas.set_with_anti_aliasing( - self.x + spacing, - @intCast(self.y + radius - yp), - ); - - canvas.set_with_anti_aliasing( - self.x + spacing - 1, - @intCast(self.y + radius - yp), - ); - - canvas.set_with_anti_aliasing( - self.x + core_width + radius + width, - @intCast(self.y + radius - yp), - ); - - canvas.set_with_anti_aliasing( - self.x + spacing, - @intCast(self.y + yp + radius + core_height), - ); - - canvas.set_with_anti_aliasing( - self.x + core_width + radius + width, - @intCast(self.y + yp + radius + core_height), - ); - } - - for (0..core_width) |w| { - canvas.set_with_anti_aliasing(@intCast(self.x + radius + w), self.y); - canvas.set_with_anti_aliasing(@intCast(self.x + radius + w), self.y + self.height); - } - - for (0..core_height) |h| { - canvas.set_with_anti_aliasing(self.x, @intCast(self.y + radius + h)); - canvas.set_with_anti_aliasing(self.x + self.width, @intCast(self.y + radius + h)); - } - } -}; +pub const Canvas = @import("canvas.zig").Canvas; +pub const Box = @import("box.zig").Box; +pub const Color = @import("color.zig").Color; +pub const Line = @import("line.zig").Line; +pub const Curve = @import("curve.zig").Curve; diff --git a/src/screen/drm/buffer/raw.zig b/src/screen/drm/buffer/raw.zig index c5a04f3..12784de 100644 --- a/src/screen/drm/buffer/raw.zig +++ b/src/screen/drm/buffer/raw.zig @@ -40,7 +40,7 @@ pub const Raw = struct { .id = 0, .width = fb.width, .height = fb.height, - .pixel_format = drm.Pixel.Format.xrgb8888, + .pixel_format = .xrgb8888, .flags = 0, .handles = [_] u32 { fb.handle, 0, 0, 0 }, .pitches = [_] u32 { fb.stride, 0, 0, 0 }, diff --git a/src/screen/drm/buffer/root.zig b/src/screen/drm/buffer/root.zig index f14ed66..30734f2 100644 --- a/src/screen/drm/buffer/root.zig +++ b/src/screen/drm/buffer/root.zig @@ -35,7 +35,7 @@ pub const Buffer = struct { .width = self.current.width, .height = self.current.height, .buffer = @as( - [*]volatile graphics.Pixel, + [*]volatile graphics.Color, @ptrCast(@alignCast(self.current.bytes)) )[0..self.current.bytes.len / 4], }; diff --git a/src/screen/drm/pixel.zig b/src/screen/drm/pixel.zig index 7c99e52..8b13789 100644 --- a/src/screen/drm/pixel.zig +++ b/src/screen/drm/pixel.zig @@ -1,19 +1 @@ -fn pixel_format_code(comptime a: u8, comptime b: u8, comptime c: u8, comptime d: u8) u32 { - return @as(u32, @intCast(a)) | - (@as(u32, @intCast(b)) << 8) | - (@as(u32, @intCast(c)) << 16) | - (@as(u32, @intCast(d)) << 24); -} - -pub const Pixel = packed struct(u32) { - blue: u8, - green: u8, - red: u8, - _padding: u8 = 0x0, - - pub const Format = enum(u32) { - xrgb8888 = pixel_format_code('X', 'R', '2', '4'), - }; -}; - diff --git a/src/screen/drm/request/frame-buffer.zig b/src/screen/drm/request/frame-buffer.zig index 8f93862..c9077e8 100644 --- a/src/screen/drm/request/frame-buffer.zig +++ b/src/screen/drm/request/frame-buffer.zig @@ -10,11 +10,22 @@ pub const CreateDumb = extern struct { size: u32, }; +fn pixel_format_code(comptime a: u8, comptime b: u8, comptime c: u8, comptime d: u8) u32 { + return @as(u32, @intCast(a)) | + (@as(u32, @intCast(b)) << 8) | + (@as(u32, @intCast(c)) << 16) | + (@as(u32, @intCast(d)) << 24); +} + +pub const PixelFormat = enum(u32) { + xrgb8888 = pixel_format_code('X', 'R', '2', '4'), +}; + pub const FrameBufferCmd2 = extern struct { id: u32, width: u32, height: u32, - pixel_format: drm.Pixel.Format, + pixel_format: PixelFormat, flags: u32, handles: [4]u32, pitches: [4]u32, diff --git a/src/screen/drm/root.zig b/src/screen/drm/root.zig index 5012743..2417463 100644 --- a/src/screen/drm/root.zig +++ b/src/screen/drm/root.zig @@ -6,4 +6,3 @@ pub const Event = @import("event.zig").Event; pub const Connector = @import("connector/root.zig").Connector; pub const Crtc = @import("crtc.zig").Crtc; pub const Buffer = @import("buffer/root.zig").Buffer; -pub const Pixel = @import("pixel.zig").Pixel; diff --git a/src/screen/main.zig b/src/screen/main.zig index 7b73450..2cb8c03 100644 --- a/src/screen/main.zig +++ b/src/screen/main.zig @@ -28,39 +28,44 @@ pub fn main() !void { try crtc.attach(&buffer, &connector, mode); - const center_x: i64 = @intCast(buffer.current.width / 2 - 100); - const center_y: i64 = @intCast(buffer.current.height / 2 - 100); + const center_x = (buffer.current.width / 2); + const center_y = (buffer.current.height / 2); - var box = graphics.Box { - .x = @intCast(center_x), - .y = @intCast(center_y), - .width = 200, - .height = 200, - .color = .{ .red = 0xff, .green = 0x05, .blue = 0x07 }, - .radius = 0, + var line = graphics.Line { + .start = .{ .x = center_x, .y = center_y }, + .end = .{ .x = center_x + 110 , .y = center_y + 100 }, + .color = .{ .red = 0xff, .green = 0, .blue = 0 }, }; var last_duration: f64 = 0; - var dir: f64 = 1; var delta: f64 = 0; - var radius: f64 = 0; + var angle: f64 = 0; while (true) { const start = try std.time.Instant.now(); const canvas = buffer.canvas(); + canvas.fill(.{ .red = 0x25, .green = 0x25, .blue = 0x25 }); - box.render(&canvas); - radius += delta * dir; - if (radius > 100) { - std.debug.print("HERE\n", .{}); - radius = 100; - dir = -1; - } else if (radius < 0) { - radius = 0; - dir = 1; + (graphics.Box { + .x = center_x - 100, + .y = center_y - 100, + .width = 200, + .height = 200, + .radius = 100, + .color = .{ .red = 0xff, .green = 0xff, .blue = 0xff }, + }).render(&canvas); + + line.render(&canvas); + const cos = @cos(std.math.degreesToRadians(angle)); + const sin = @sin(std.math.degreesToRadians(angle)); + line.end.x = @intCast(@as(i64, @intCast(center_x)) + @as(i64, @intFromFloat(100 * cos))); + line.end.y = @intCast(@as(i64, @intCast(center_y)) + @as(i64, @intFromFloat(100 * sin))); + + angle += delta; + if (angle > 360) { + angle = 0; } - box.radius = @intFromFloat(radius); const end = try std.time.Instant.now(); const duration: f64 = @floatFromInt(end.since(start)); |