Skip to content

Commit

Permalink
Add DVD sample
Browse files Browse the repository at this point in the history
  • Loading branch information
hryx committed May 18, 2024
1 parent c21982d commit 9704e87
Show file tree
Hide file tree
Showing 7 changed files with 336 additions and 0 deletions.
1 change: 1 addition & 0 deletions showcase/build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const carts = .{
.{ "metalgear_timer", @import("metalgear_timer") },
.{ "raytracer", @import("raytracer") },
.{ "neopixelpuzzle", @import("neopixelpuzzle") },
.{ "dvd", @import("dvd") },
};

pub fn build(b: *std.Build) void {
Expand Down
1 change: 1 addition & 0 deletions showcase/build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
.metalgear_timer = .{ .path = "carts/metalgear-timer" },
.raytracer = .{ .path = "carts/raytracer" },
.neopixelpuzzle = .{ .path = "carts/neopixelpuzzle" },
.dvd = .{ .path = "carts/dvd" },
},
.paths = .{
"README.md",
Expand Down
Binary file added showcase/carts/dvd/assets/dvd.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 55 additions & 0 deletions showcase/carts/dvd/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
const std = @import("std");
const Build = std.Build;
const sycl_badge = @import("sycl_badge");

pub const author_name = "Stevie Hryciw";
pub const author_handle = "hryx";
pub const cart_title = "dvd";
pub const description = "Bouncing DVD logo screensaver";

pub fn build(b: *Build) void {
const optimize = b.standardOptimizeOption(.{});
const sycl_badge_dep = b.dependency("sycl_badge", .{});

const cart = sycl_badge.add_cart(sycl_badge_dep, b, .{
.name = "dvd",
.optimize = optimize,
.root_source_file = b.path("src/main.zig"),
});
add_dvd_assets_step(b, sycl_badge_dep, cart);
cart.install(b);
}

// Thank you to Fabio for the code generation step.
fn add_dvd_assets_step(
b: *Build,
sycl_badge_dep: *Build.Dependency,
cart: *sycl_badge.Cart,
) void {
const convert = b.addExecutable(.{
.name = "convert_gfx",
.root_source_file = b.path("build/convert_gfx.zig"),
.target = b.host,
.optimize = cart.options.optimize,
.link_libc = true,
});
convert.root_module.addImport("zigimg", b.dependency("zigimg", .{}).module("zigimg"));

const gen_gfx = b.addRunArtifact(convert);
gen_gfx.addArg("-i");
gen_gfx.addFileArg(b.path("assets/dvd.png"));
gen_gfx.addArg(std.fmt.comptimePrint("{}", .{8}));
gen_gfx.addArg(std.fmt.comptimePrint("{}", .{false}));
gen_gfx.addArg("-o");
const gfx_zig = gen_gfx.addOutputFileArg("gfx.zig");

const gfx_mod = b.addModule("gfx", .{
.root_source_file = gfx_zig,
.optimize = cart.options.optimize,
});
gfx_mod.addImport("cart-api", sycl_badge_dep.module("cart-api"));

cart.wasm.step.dependOn(&gen_gfx.step);
cart.wasm.root_module.addImport("gfx", gfx_mod);
cart.cart_lib.root_module.addImport("gfx", gfx_mod);
}
18 changes: 18 additions & 0 deletions showcase/carts/dvd/build.zig.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.{
.name = "dvd",
.version = "0.0.0",
.dependencies = .{
.sycl_badge = .{ .path = "../../.." },
.zigimg = .{
.url = "https://github.com/zigimg/zigimg/archive/637974e2d31dcdbc33f1e9cc8ffb2e46abd2e215.tar.gz",
.hash = "122012026c3a65ff1d4acba3b3fe80785f7cee9c6b4cdaff7ed0fbf23b0a6c803989",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
"build",
"assets",
},
}
114 changes: 114 additions & 0 deletions showcase/carts/dvd/build/convert_gfx.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
const std = @import("std");
const allocator = std.heap.c_allocator;
const Image = @import("zigimg").Image;

const ConvertFile = struct {
path: []const u8,
bits: u4,
transparency: bool,
};

pub fn main() !void {
var args = try std.process.argsWithAllocator(allocator);
defer args.deinit();

_ = args.next();
var in_files = std.ArrayList(ConvertFile).init(allocator);
var out_path: []const u8 = undefined;
while (args.next()) |arg| {
if (std.mem.eql(u8, arg, "-i")) {
const path = args.next() orelse return error.MissingArg;
const bits = args.next() orelse return error.MissingArg;
const transparency = args.next() orelse return error.MissingArg;
try in_files.append(.{ .path = path, .bits = @intCast(bits[0] - '0'), .transparency = transparency[0] == 't' });
} else if (std.mem.eql(u8, arg, "-o")) {
out_path = args.next() orelse return error.MissingArg;
}
}

const out_file = try std.fs.cwd().createFile(out_path, .{});
defer out_file.close();

const writer = out_file.writer();
try writer.writeAll("const PackedIntSlice = @import(\"std\").packed_int_array.PackedIntSlice;\n");
try writer.writeAll("const DisplayColor = @import(\"cart-api\").DisplayColor;\n\n");

for (in_files.items) |in_file| {
try convert(in_file, writer);
}
}

fn convert(args: ConvertFile, writer: std.fs.File.Writer) !void {
const N = 8 / args.bits;

var image = try Image.fromFilePath(allocator, args.path);
defer image.deinit();

var colors = std.ArrayList(Color).init(allocator);
defer colors.deinit();
if (args.transparency) try colors.append(.{ .r = 31, .g = 0, .b = 31 });
var indices = try std.ArrayList(usize).initCapacity(allocator, image.width * image.height);
defer indices.deinit();
var it = image.iterator();
while (it.next()) |pixel| {
const color = Color{
.r = @intFromFloat(31.0 * pixel.r),
.g = @intFromFloat(63.0 * pixel.g),
.b = @intFromFloat(31.0 * pixel.b),
};
const index = try getIndex(&colors, color);
indices.appendAssumeCapacity(index);
}
var packed_data = try allocator.alloc(u8, indices.items.len / N);
defer allocator.free(packed_data);
for (packed_data, 0..) |_, i| {
packed_data[i] = 0;
for (0..N) |n| {
const shift: u3 = @intCast(n * args.bits);
packed_data[i] |= @intCast(indices.items[N * i + n] << shift);
}
}

{
const name = std.fs.path.stem(args.path);
try writer.print("pub const {s} = struct {{\n", .{name});

try writer.print(" pub const width = {};\n", .{image.width});
try writer.print(" pub const height = {};\n", .{image.height});

try writer.writeAll(" pub const colors = [_]DisplayColor{\n");
for (colors.items) |c| {
try writer.print(" .{{ .r = {}, .g = {}, .b = {} }},\n", .{ c.r, c.g, c.b });
}
try writer.writeAll(" };\n");

try writer.print(" pub const indices = PackedIntSlice(u{}).init(@constCast(data[0..]), data.len * {});\n", .{ args.bits, N });
try writer.writeAll(" const data = [_]u8{\n");
for (packed_data, 0..) |index, i| {
if (i % 32 == 0) try writer.writeAll(" ");
try writer.print("{}, ", .{index});
if ((i + 1) % 32 == 0) try writer.writeAll("\n");
}
try writer.writeAll(" };\n");

try writer.writeAll("};\n\n");
}
}

pub const Color = packed struct(u16) {
b: u5,
g: u6,
r: u5,

fn eql(self: Color, other: Color) bool {
return @as(u16, @bitCast(self)) == @as(u16, @bitCast(other));
}
};

fn getIndex(colors: *std.ArrayList(Color), color: Color) !usize {
for (colors.items, 0..) |c, i| {
if (c.eql(color)) return i;
}
try colors.append(color);
return colors.items.len - 1;
}
147 changes: 147 additions & 0 deletions showcase/carts/dvd/src/main.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
const std = @import("std");
const cart = @import("cart-api");
const gfx = @import("gfx");

export fn start() void {
// Clear garbage bytes from framebuffer at init since the whole screen is not cleared otherwise.
for (cart.framebuffer[0..]) |*pos| {
pos.* = .{ .r = 0, .b = 0, .g = 0 };
}
}

var dvd_hue: f32 = 0;
var dvd_x: isize = 0;
var dvd_y: isize = 0;
var dvd_dx: isize = 2;
var dvd_dy: isize = 1;
var odd_frame = false;

// These things are super bright at full strength.
const neopixel_brightness = 10.0;

export fn update() void {
const color = hsv_to_rgb(.{ .h = dvd_hue, .s = 1, .v = 1 });
drawDvd(gfx.dvd, @intCast(dvd_x), @intCast(dvd_y), color);
dvd_hue += 5;
if (dvd_hue >= 360.0) dvd_hue = 0;

// The DVD logo gets stuck hitting the places without a fractional angle.
// Offset the dx every other frame to make it look a tiny bit more interesting.
odd_frame = !odd_frame;
dvd_x += if (odd_frame) dvd_dx else @divFloor((dvd_dx * 3), 2);
dvd_y += dvd_dy;
if (dvd_x < 0) {
dvd_dx *= -1;
dvd_x = 0;
}
if (dvd_y < 0) {
dvd_dy *= -1;
dvd_y = 0;
}
if (dvd_x >= cart.screen_width - gfx.dvd.width) {
dvd_dx *= -1;
dvd_x = cart.screen_width - gfx.dvd.width;
}
if (dvd_y >= cart.screen_height - gfx.dvd.height) {
dvd_dy *= -1;
dvd_y = cart.screen_height - gfx.dvd.height;
}

// Press A to light up the neopixels.
// This was just so we could create a light show in the theater. :>
const np_color: cart.NeopixelColor = if (cart.controls.a) .{
.g = @intFromFloat(color.g * neopixel_brightness),
.r = @intFromFloat(color.r * neopixel_brightness),
.b = @intFromFloat(color.b * neopixel_brightness),
} else .{ .g = 0, .r = 0, .b = 0 };
cart.neopixels.* = .{np_color} ** 5;
}

pub fn drawDvd(sprite: anytype, pos_x: usize, pos_y: usize, color: Rgb) void {
var y: usize = 0;
while (y < sprite.height) : (y += 1) {
var x: usize = 0;
while (x < sprite.width) : (x += 1) {
const dst_x = pos_x + x;
const dst_y = pos_y + y;
const index = y * sprite.width + x;
const src = sprite.colors[sprite.indices.get(index)];
var dst = &cart.framebuffer[dst_y * cart.screen_width + dst_x];
dst.r = @intFromFloat(@as(f32, @floatFromInt(src.r)) * color.r);
dst.g = @intFromFloat(@as(f32, @floatFromInt(src.g)) * color.g);
dst.b = @intFromFloat(@as(f32, @floatFromInt(src.b)) * color.b);
cart.framebuffer[dst_y * cart.screen_width + dst_x] = dst.*;
}
}
}

const Hsv = struct {
h: f32,
s: f32,
v: f32,
};

const Rgb = struct {
r: f32,
g: f32,
b: f32,
};

fn hsv_to_rgb(in: Hsv) Rgb {
var hh: f32 = undefined;
var p: f32 = undefined;
var q: f32 = undefined;
var t: f32 = undefined;
var ff: f32 = undefined;
var i: i32 = undefined;
var out: Rgb = undefined;

if (in.s <= 0.0) {
out.r = in.v;
out.g = in.v;
out.b = in.v;
return out;
}
hh = in.h;
if (hh >= 360.0) hh = 0.0;
hh /= 60.0;
i = @intFromFloat(hh);
ff = hh - @as(f32, @floatFromInt(i));
p = in.v * (1.0 - in.s);
q = in.v * (1.0 - (in.s * ff));
t = in.v * (1.0 - (in.s * (1.0 - ff)));

switch (i) {
0 => {
out.r = in.v;
out.g = t;
out.b = p;
},
1 => {
out.r = q;
out.g = in.v;
out.b = p;
},
2 => {
out.r = p;
out.g = in.v;
out.b = t;
},
3 => {
out.r = p;
out.g = q;
out.b = in.v;
},
4 => {
out.r = t;
out.g = p;
out.b = in.v;
},
else => {
out.r = in.v;
out.g = p;
out.b = q;
},
}
return out;
}

0 comments on commit 9704e87

Please sign in to comment.