Compare commits

...

6 commits

16 changed files with 1169 additions and 103 deletions

View file

@ -95,7 +95,7 @@ pub fn build(b: *std.build.Builder) void {
const gen = vkgen.VkGenerateStep.init(b, "etc/vk.xml", "vk.zig");
exe.addPackage(gen.package);
exe.addPackage(pkgs.uuid);
exe.addPackage(pkgs.glfw);
build_glfw.link(b, exe, .{});

View file

@ -6,3 +6,4 @@ git https://github.com/SpexGuy/Zig-ImGui.git 0a2cfca89de2ef1ff5a346c6e2c29e8b334
git https://github.com/SpexGuy/Zig-ImGui.git 0a2cfca89de2ef1ff5a346c6e2c29e8b3347d2e3 zig-imgui/imgui_build.zig 0a2cfca89de2ef1ff5a346c6e2c29e8b3347d2e3
git https://github.com/viviicat/Zig-VMA.git 643c98344cd921cef25c66fd0f35005c53a3e459 vma_build.zig 643c98344cd921cef25c66fd0f35005c53a3e459
git https://github.com/viviicat/Zig-VMA.git 643c98344cd921cef25c66fd0f35005c53a3e459 vma.zig 643c98344cd921cef25c66fd0f35005c53a3e459
git https://github.com/dmgk/zig-uuid.git 9895f72f67d463b6bd616cc63dcfe70d12dc53b9 uuid.zig 9895f72f67d463b6bd616cc63dcfe70d12dc53b9

View file

@ -19,6 +19,11 @@ deps:
url: "https://github.com/viviicat/Zig-VMA.git"
ref: 643c98344cd921cef25c66fd0f35005c53a3e459
root: vma.zig
uuid:
git:
url: "https://github.com/dmgk/zig-uuid.git"
ref: 9895f72f67d463b6bd616cc63dcfe70d12dc53b9
root: uuid.zig
build_deps:
build_vulkan:
git:

View file

@ -1,7 +1,24 @@
- mach-glfw for glfw bindings
- vulkan-zig for vulkan - probably simplifies this part?
- Can always reimplement if desired.
generational structure of arrays, AKA slotmaps
- Important bit is that the components of each type are all in contiguous memory
- Core reference counted mapping, followed by dependent mappings for each
component type
- SpexGuy/Zig-VMA - vulkan memory allocator lib for zig.
- Outdated build.zig .. .gah.
- time to fork...
- In pim, the current component types are:
- Mesh
- Texture -or- TexTable
- Appear to be two separate similar systems.
- First is containers/table.h, used in the non-vulkan renderer
- This has ref counting and is the first level. If no existing item
found, it calls into vkrTexTable which then uses IdAlloc.
- IdAlloc is used for vulkan stuff and also for some
quake-specific stuff?
- IdAlloc does not have reference counting.
- IdAlloc:
- IdAlloc_Alloc to get GenId. the data is passed to this and is allocated
to the correct size.
- Going to work based on the assumption that reference counting won't hurt
to add. Can make it optional and probably will be fine.

20
src/common/uuid.zig Normal file
View file

@ -0,0 +1,20 @@
const std = @import("std");
const ExtUUID = @import("uuid");
pub const zero = ExtUUID.zero;
pub const UUID = struct {
pub const init = ExtUUID.init;
pub const format = ExtUUID.format;
pub const parse = ExtUUID.parse;
pub fn find(names: []const UUID, name: UUID) ?i32 {
for (names) |n, i| {
if (std.mem.eql([16]u8, n.bytes, name.bytes)) {
return i;
}
}
return null;
}
};

134
src/containers/queue.zig Normal file
View file

@ -0,0 +1,134 @@
const std = @import("std");
/// Simple ringbuffer-based queue. Capacity will always be a power of two.
pub fn Queue(comptime T: type) type {
return struct {
const Self = @This();
const initial_capacity: usize = 16;
backing: []T,
reads: usize,
writes: usize,
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) Self {
return Self{
.backing = &[0]T{},
.reads = 0,
.writes = 0,
.allocator = allocator,
};
}
pub fn deinit(self: *Self) void {
self.clearRetainingCapacity();
self.allocator.free(self.backing);
}
pub fn clearRetainingCapacity(self: *Self) void {
self.reads = 0;
self.writes = 0;
}
pub fn size(self: *const Self) usize {
return self.writes - self.reads;
}
pub fn capacity(self: *const Self) usize {
return self.backing.len;
}
pub fn ensureTotalCapacity(self: *Self, new_capacity: usize) !void {
const capacity_sanitized = if (new_capacity > initial_capacity)
new_capacity
else
initial_capacity;
const new_width = std.math.ceilPowerOfTwoAssert(usize, capacity_sanitized);
const old_width = self.backing.len;
if (new_width > old_width) {
const old_slice = self.backing;
const new_slice = try self.allocator.alloc(T, new_width);
const reads = self.reads;
const len = self.writes - self.reads;
const mask = old_width -% 1;
var i: usize = 0;
while (i < len) : (i += 1) {
const j = (reads + i) & mask;
new_slice[i] = old_slice[j];
}
self.allocator.free(old_slice);
self.backing = new_slice;
self.reads = 0;
self.writes = len;
}
}
pub fn ensureUnusedCapacity(self: *Self, additional_count: usize) !void {
return self.ensureTotalCapacity(self.size() + additional_count);
}
pub fn push(self: *Self, val: T) !void {
try self.ensureUnusedCapacity(1);
const mask = self.backing.len - 1;
const dst = self.writes;
self.writes += 1;
self.backing[dst & mask] = val;
}
pub fn popOrNull(self: *Self) ?T {
if (self.size() <= 0) {
return null;
}
// length is assumed to be a power of 2
const mask = self.backing.len - 1;
const src = self.reads;
self.reads += 1;
// (Same as % self.backing.len because power of 2)
return self.backing[src & mask];
}
pub fn pop(self: *Self) T {
return self.popOrNull() orelse unreachable;
}
};
}
test "general queue usage" {
var q = Queue(u32).init(std.testing.allocator);
defer q.deinit();
try std.testing.expectEqual(@as(?u32, null), q.popOrNull());
try q.push(45);
try q.push(42);
try q.push(2);
try q.push(0);
try std.testing.expectEqual(@as(u32, 45), q.pop());
try std.testing.expectEqual(@as(u32, 42), q.pop());
try q.push(56);
var i: u32 = 0;
while (i < 16) : (i += 1) {
try q.push(i);
}
try std.testing.expectEqual(@as(u32, 2), q.popOrNull() orelse unreachable);
try std.testing.expectEqual(@as(u32, 0), q.pop());
try std.testing.expectEqual(@as(u32, 56), q.pop());
i = 0;
while (i < 16) : (i += 1) {
try std.testing.expectEqual(i, q.pop());
}
try std.testing.expectEqual(@as(usize, 32), q.capacity());
try std.testing.expectEqual(@as(?u32, null), q.popOrNull());
try std.testing.expectEqual(@as(?u32, null), q.popOrNull());
}

423
src/containers/table.zig Normal file
View file

@ -0,0 +1,423 @@
const builtin = @import("builtin");
const std = @import("std");
const assert = std.debug.assert;
const Queue = @import("queue.zig").Queue;
/// A generational collection. Items can be quickly looked up by key,
/// and quickly removed. The free indices are stored in a queue and refilled as new
/// items are added. Ids will remain valid for the lifetime of the object, and when
/// the object is destroyed, will no longer be found in the collection.
///
/// Table is stored as a Structure of Arrays, to improve cache locality.
///
/// Note:
/// Currently hashing of the key is done automatically (using the logic of AutoHashMap).
/// In the future maybe support can be added to pass in a custom hash strategy.
pub fn Table(comptime K: type, comptime V: type) type {
return struct {
const Self = @This();
/// The ID for the table (index + generation)
pub const Id = packed struct {
gen: u8,
index: u24,
};
// Collections sharing an index:
/// generations of the items in the table. incremented when an item is added to a previously-used slot.
gens: std.ArrayList(u8),
/// The actual values.
values: std.ArrayList(?V),
/// Keys for the table entries.
keys: std.ArrayList(?K),
// Other fields
/// List of indices which have previously been freed and are available to fill.
free_list: Queue(u24),
/// Used for mapping the key to its index.
lookup: std.AutoHashMap(K, u24),
/// The amount of items in the table (not the allocated size)
len: u24,
pub fn init(allocator: std.mem.Allocator) Self {
return Self{
.gens = std.ArrayList(u8).init(allocator),
.values = std.ArrayList(?V).init(allocator),
.keys = std.ArrayList(?K).init(allocator),
.free_list = Queue(u24).init(allocator),
.lookup = std.AutoHashMap(K, u24).init(allocator),
.len = 0,
};
}
pub fn deinit(self: *Self) void {
self.len = 0;
self.gens.deinit();
self.values.deinit();
self.keys.deinit();
self.free_list.deinit();
self.lookup.deinit();
}
pub fn clear(self: *Self) void {
self.len = 0;
self.gens.clearRetainingCapacity();
self.values.clearRetainingCapacity();
self.keys.clearRetainingCapacity();
self.free_list.clearRetainingCapacity();
self.lookup.clearRetainingCapacity();
}
pub fn size(self: *const Self) usize {
return self.len;
}
pub fn exists(self: *const Self, id: Id) bool {
return id.index < self.values.items.len and id.gen == self.gens.items[@as(usize, id.index)];
}
pub const AddResult = struct {
id: Id,
added: bool,
};
pub fn add(self: *Self, key: K, val: V) !AddResult {
if (self.find(key)) |id| {
return AddResult{
.id = id,
.added = false,
};
}
if (self.free_list.popOrNull()) |index| {
const uindex = @as(usize, index);
const gen = self.gens.items[uindex];
self.keys.items[uindex] = key;
self.values.items[uindex] = val;
try self.lookup.putNoClobber(key, index);
self.len += 1;
return AddResult{
.id = .{
.index = index,
.gen = gen,
},
.added = true,
};
} else {
self.len += 1;
try self.keys.append(key);
try self.values.append(val);
try self.gens.append(0);
assert(self.len == self.keys.items.len);
assert(self.keys.items.len == self.values.items.len);
assert(self.values.items.len == self.gens.items.len);
const index = @intCast(u24, self.keys.items.len - 1);
try self.lookup.putNoClobber(key, index);
return AddResult{
.id = .{
.index = index,
.gen = 0,
},
.added = true,
};
}
}
pub fn remove(self: *Self, id: Id) !V {
assert(self.len > 0);
const uindex = @as(usize, id.index);
const key = self.keys.items[uindex] orelse unreachable;
const removed = self.lookup.remove(key);
assert(removed);
self.keys.items[uindex] = null;
self.gens.items[uindex] += 1;
const val = self.values.items[uindex] orelse unreachable;
self.values.items[uindex] = null;
try self.free_list.push(id.index);
self.len -= 1;
return val;
}
pub fn get(self: *Self, id: Id) ?*V {
return if (self.exists(id)) &(self.values.items[@as(usize, id.index)] orelse unreachable) else null;
}
pub fn find(self: *Self, key: K) ?Id {
if (self.lookup.get(key)) |index| {
const uindex = @as(usize, index);
const gen = self.gens.items[uindex];
return Id{
.index = index,
.gen = gen,
};
} else {
return null;
}
}
pub fn getKey(self: *Self, id: Id) ?K {
return if (self.exists(id)) (self.keys.items[@as(usize, id.index)] orelse unreachable) else null;
}
};
}
const TestVal = struct {
a: u32,
b: u32,
};
test "general table test" {
var table = Table(u32, TestVal).init(std.testing.allocator);
defer table.deinit();
const first_result = try table.add(56, .{ .a = 42, .b = 87 });
try std.testing.expect(first_result.added);
try std.testing.expectEqual(@as(u8, 0), first_result.id.gen);
try std.testing.expectEqual(@as(u24, 0), first_result.id.index);
const second_result = try table.add(62, .{ .a = 1, .b = 12 });
try std.testing.expect(second_result.added);
try std.testing.expectEqual(@as(u8, 0), second_result.id.gen);
try std.testing.expectEqual(@as(u24, 1), second_result.id.index);
var second_id = table.find(62) orelse unreachable;
var second_val = table.get(second_id) orelse unreachable;
try std.testing.expectEqual(@as(u32, 1), second_val.a);
try std.testing.expectEqual(@as(u32, 12), second_val.b);
try std.testing.expectEqual(@as(usize, 2), table.size());
_ = try table.remove(first_result.id);
try std.testing.expect(!table.exists(first_result.id));
try std.testing.expectEqual(@as(usize, 1), table.size());
// Ensure the id does not invalidate after removal of another
second_val = table.get(second_id) orelse unreachable;
try std.testing.expectEqual(@as(u32, 1), second_val.a);
try std.testing.expectEqual(@as(u32, 12), second_val.b);
// Grab the id again and ensure that works too.
second_id = table.find(62) orelse unreachable;
second_val = table.get(second_id) orelse unreachable;
try std.testing.expectEqual(@as(u32, 1), second_val.a);
try std.testing.expectEqual(@as(u32, 12), second_val.b);
}
test "table across generation" {
var table = Table(u32, TestVal).init(std.testing.allocator);
defer table.deinit();
const first_result = try table.add(48, .{ .a = 1, .b = 2 });
_ = try table.add(28, .{ .a = 2, .b = 3 });
// remove first item, then add a new one, then try to access first item!
_ = try table.remove(table.find(48) orelse unreachable);
const second_result = try table.add(99, .{ .a = 2, .b = 3 });
try std.testing.expectEqual(@as(u8, 1), second_result.id.gen);
try std.testing.expect(!table.exists(first_result.id));
try std.testing.expectEqual(@as(?*TestVal, null), table.get(first_result.id));
}
/// Like Table, but values are refcounted. When duplicate items are added or retained, their
/// reference count is incremented. When items are released, their reference count is
/// decremented, and zero-count items are removed and returned. The ownership of the item then
/// transfers to the decrementer, for any memory cleanup that must happen.
pub fn RefTable(comptime K: type, comptime V: type) type {
return struct {
const Self = @This();
const InternalTable = Table(K, V);
pub const Id = InternalTable.Id;
/// Underlying table.
table: InternalTable,
/// Reference counts of the values.
ref_counts: std.ArrayList(usize),
pub fn init(allocator: std.mem.Allocator) Self {
return Self{
.table = Table(K, V).init(allocator),
.ref_counts = std.ArrayList(usize).init(allocator),
};
}
/// Expects that all references have been cleaned up and will assert if
/// dangling references remain. To ignore dangling references, call clear() first.
pub fn deinit(self: *Self) void {
self.table.deinit();
if (builtin.mode == .Debug or builtin.mode == .ReleaseSafe) {
for (self.ref_counts.items) |count| {
assert(count == 0);
}
}
self.ref_counts.deinit();
}
pub fn size(self: *const Self) usize {
return self.table.size();
}
pub fn remove(self: *Self, id: Id) !V {
self.ref_counts.items[@as(usize, id.index)] = 0;
return self.table.remove(id);
}
/// Increment the reference count of the item.
pub fn retain(self: *Self, id: Id) bool {
if (!self.table.exists(id)) {
return false;
}
const ref_count = self.ref_counts.items[id.index];
self.ref_counts.items[id.index] = ref_count + 1;
return true;
}
/// Decrement the reference count of the item. If it reaches zero, the item will be removed
/// from the table and returned. It is the responsibility of the caller to free any
/// dynamic memory the value may point to.
pub fn release(self: *Self, id: Id) !?V {
if (!self.table.exists(id)) {
return null;
}
const index = id.index;
var ref_count = self.ref_counts.items[index];
ref_count -= 1;
self.ref_counts.items[index] = ref_count;
return if (ref_count == 0) try self.remove(id) else null;
}
/// Clear all items from the table. This should only be called if the items
/// do not point to dynamically allocated memory!
pub fn clear(self: *Self) void {
self.table.clear();
self.ref_counts.clearRetainingCapacity();
}
pub fn exists(self: *const Self, id: Id) bool {
return self.table.exists(id);
}
pub const AddResult = InternalTable.AddResult;
/// Add to the table. If the ID already exists, a new reference will be
/// added to the reference count.
pub fn add(self: *Self, key: K, val: V) !AddResult {
if (self.find(key)) |id| {
_ = self.retain(id);
return AddResult{
.id = id,
.added = false,
};
}
const result = try self.table.add(key, val);
assert(result.added);
if (result.id.index >= self.ref_counts.items.len) {
assert(result.id.index == self.ref_counts.items.len);
try self.ref_counts.append(1);
} else {
self.ref_counts.items[@as(usize, result.id.index)] = 1;
}
return result;
}
pub fn get(self: *Self, id: Id) ?*V {
return self.table.get(id);
}
pub fn find(self: *Self, key: K) ?Id {
return self.table.find(key);
}
pub fn getKey(self: *Self, id: Id) ?K {
return self.table.getKey(id);
}
};
}
test "general ref table test" {
var table = RefTable(u32, TestVal).init(std.testing.allocator);
defer table.deinit();
const first_result = try table.add(56, .{ .a = 42, .b = 87 });
try std.testing.expect(first_result.added);
try std.testing.expectEqual(@as(u8, 0), first_result.id.gen);
try std.testing.expectEqual(@as(u24, 0), first_result.id.index);
const second_result = try table.add(62, .{ .a = 1, .b = 12 });
try std.testing.expect(second_result.added);
try std.testing.expectEqual(@as(u8, 0), second_result.id.gen);
try std.testing.expectEqual(@as(u24, 1), second_result.id.index);
var second_id = table.find(62) orelse unreachable;
var second_val = table.get(second_id) orelse unreachable;
try std.testing.expectEqual(@as(u32, 1), second_val.a);
try std.testing.expectEqual(@as(u32, 12), second_val.b);
try std.testing.expectEqual(@as(usize, 2), table.size());
_ = try table.remove(first_result.id);
try std.testing.expect(!table.exists(first_result.id));
try std.testing.expectEqual(@as(usize, 1), table.size());
// Ensure the id does not invalidate after removal of another
second_val = table.get(second_id) orelse unreachable;
try std.testing.expectEqual(@as(u32, 1), second_val.a);
try std.testing.expectEqual(@as(u32, 12), second_val.b);
// Grab the id again and ensure that works too.
second_id = table.find(62) orelse unreachable;
second_val = table.get(second_id) orelse unreachable;
try std.testing.expectEqual(@as(u32, 1), second_val.a);
try std.testing.expectEqual(@as(u32, 12), second_val.b);
table.clear();
}
test "ref counting" {
var table = RefTable(u32, TestVal).init(std.testing.allocator);
defer table.deinit();
var first_result = try table.add(12, .{ .a = 5, .b = 6 });
try std.testing.expectEqual(@as(usize, 1), table.size());
var val = (try table.release(first_result.id)) orelse unreachable;
try std.testing.expectEqual(@as(usize, 0), table.size());
try std.testing.expectEqual(@as(u32, 5), val.a);
try std.testing.expectEqual(@as(u32, 6), val.b);
first_result = try table.add(12, .{ .a = 6, .b = 5 });
try std.testing.expect(first_result.added);
try std.testing.expectEqual(@as(u24, 0), first_result.id.index);
try std.testing.expectEqual(@as(u8, 1), first_result.id.gen);
const second_result = try table.add(12, .{ .a = 1, .b = 2 });
try std.testing.expect(!second_result.added);
try std.testing.expectEqual(@as(?TestVal, null), (try table.release(first_result.id)));
val = (try table.release(first_result.id)) orelse unreachable;
try std.testing.expectEqual(@as(usize, 0), table.size());
try std.testing.expectEqual(@as(u32, 6), val.a);
try std.testing.expectEqual(@as(u32, 5), val.b);
}

104
src/math/box.zig Normal file
View file

@ -0,0 +1,104 @@
const std = @import("std");
const Vec4f = @import("vec.zig").Vec4f;
const Mat4f = @import("vec.zig").Mat4f;
const BoxData = packed struct {};
pub fn Box(comptime Vec: type, comptime Shift: comptime_int) type {
return packed struct {
const Self = @This();
pub const empty = Self{
.lo = Vec.s(1 << Shift),
.hi = Vec.s(-(1 << Shift)),
};
lo: Vec,
hi: Vec,
pub inline fn new(lo: Vec, hi: Vec) Self {
return Self{
.lo = lo,
.hi = hi,
};
}
pub inline fn center(self: Self) Vec {
return self.lo.lerpvs(self.hi, 0.5);
}
pub inline fn size(self: Self) Vec {
return self.hi.sub(self.lo);
}
pub inline fn extents(self: Self) Vec {
return self.size().mulvs(0.5);
}
pub inline fn contains(self: Self, pt: Vec) bool {
return pt.gteq(self.lo).all3() and pt.lteq(self.hi).all3();
}
pub inline fn area(self: Self) f32 {
const sz = self.size();
return sz.x * sz.y * if (Vec.nelem > 2) sz.z else 1;
}
pub inline fn fromPts(pts: []const Vec) Self {
var lo = Vec.s(1 << Shift);
var hi = Vec.s(-(1 << Shift));
for (pts) |pt| {
lo = lo.min(pt);
hi = hi.max(pt);
}
return Self{
.lo = lo,
.hi = hi,
};
}
pub inline fn uni(lhs: Self, rhs: Self) Self {
return Self{
.lo = lhs.lo.min(rhs.lo),
.hi = lhs.hi.max(rhs.hi),
};
}
pub inline fn intersect(lhs: Self, rhs: Self) Self {
var lo = lhs.lo.min(rhs.lo);
var hi = lhs.hi.max(rhs.hi);
const mid = lo.lerpvs(hi, 0.5);
const inverted = lo.gt(hi);
lo = lo.select(mid, inverted);
hi = hi.select(mid, inverted);
return Self{
.lo = lo,
.hi = hi,
};
}
// TODO: genericise
pub inline fn transform(matrix: Mat4f, box: Self) Self {
var ct = box.center();
var exts = box.hi.sub(ct);
ct = matrix.mulPt(ct);
exts = matrix.mulExtents(exts);
return Self{
.lo = ct.sub(exts),
.hi = ct.add(exts),
};
}
};
}
// pub const Box2d = Box(Vec2f); // need to understand the shift magic
pub const Box3d = Box(Vec4f, 20);
test "Box3d" {
const b1 = Box3d.fromPts(&.{ Vec4f.s(1), Vec4f.s(-1) });
try std.testing.expectEqual(@as(f32, 8), b1.area());
const v1 = Vec4f.s(0.5);
try std.testing.expect(b1.contains(v1));
}

81
src/math/mat.zig Normal file
View file

@ -0,0 +1,81 @@
const std = @import("std");
const vec = @import("vec.zig");
const Vec3f = vec.Vec3f;
const Vec4f = vec.Vec4f;
// Intentionally use Vec4f to hopefully improve vectorization.
pub const Mat3f = Mat(3, Vec4f);
pub const Mat4f = Mat(4, Vec4f);
pub fn Mat(comptime nelem: comptime_int, comptime Vec: type) type {
return packed struct {
const Self = @This();
pub const nelem = nelem;
pub const zero = Self{};
pub const id = Self{
.c0 = .{ .x = 1 },
.c1 = .{ .y = 1 },
.c2 = if (nelem > 2 and Vec.nelem > 2) .{ .z = 1 } else {},
.c3 = if (nelem > 3 and Vec.nelem > 3) .{ .w = 1 } else {},
};
c0: Vec = .{},
c1: Vec = .{},
c2: if (nelem > 2) Vec else void = if (nelem > 2) .{} else {},
c3: if (nelem > 3) Vec else void = if (nelem > 3) .{} else {},
pub inline fn mulCol(self: Self, col: Vec) Vec {
const a = self.c0.mulvs(col.x);
const b = self.c1.mulvs(col.y);
const c = if (nelem > 2 and Vec.nelem > 2) self.c2.mulvs(col.z) else {};
const d = if (nelem > 3 and Vec.nelem > 2) self.c0.mulvs(col.w) else {};
return a.add(b).add(if (nelem > 2) if (nelem > 3) c.add(d) else c else Vec.zero);
}
pub inline fn mul(a: Self, b: Self) Self {
return Self{
.c0 = a.mulCol(b.c0),
.c1 = a.mulCol(b.c1),
.c2 = if (nelem > 2) a.mulCol(b.c2) else {},
.c3 = if (nelem > 3) a.mulCol(b.c3) else {},
};
}
// Special methods specifically for Mat4f.
pub usingnamespace if (nelem == 4 and Vec == Vec4f) struct {
pub inline fn lookAt(eye: Vec4f, at: Vec4f, up: Vec4f) Self {
// right-handed (ew)
const f = at.sub(eye).normalize3();
const s = f.cross3(up).normalize3();
const u = s.cross3(f).normalize3();
const tx = -s.dot3(eye);
const ty = -u.dot3(eye);
const tz = f.dot3(eye);
return Self{
.c0 = .{ .x = s.x, .y = u.x, .z = -f.x, .w = 0 },
.c1 = .{ .x = s.y, .y = u.y, .z = -f.y, .w = 0 },
.c2 = .{ .x = s.z, .y = u.z, .z = -f.z, .w = 0 },
.c3 = .{ .x = tx, .y = ty, .z = tz, .w = 1 },
};
}
pub inline fn glPerspective(fov_y: f32, aspect: f32, z_near: f32, z_far: f32) Self {
const t = std.math.tan(fov_y * 0.5);
var m = Mat(4, Vec4f).zero;
m.c0.x = 1.0 / (aspect * t);
m.c1.y = 1.0 / t;
m.c2.z = -1.0 * (z_far * z_near) / (z_far - z_near);
m.c3.z = -2.0 * (z_far * z_near) / (z_far - z_near);
return m;
}
pub inline fn vkPerspective(fov_y: f32, aspect: f32, z_near: f32, z_far: f32) Self {
var m = glPerspective(fov_y, aspect, z_near, z_far);
m.c1.y *= -1.0;
return m;
}
} else struct {};
};
}

View file

@ -13,18 +13,65 @@ pub const Vec2u = Vec(2, u32);
pub const Vec3u = Vec(3, u32);
pub const Vec4u = Vec(4, u32);
pub fn Vec(comptime nelem: comptime_int, comptime T: anytype) type {
pub const Vec2b = BoolVec(2);
pub const Vec3b = BoolVec(3);
pub const Vec4b = BoolVec(4);
pub fn BoolVec(comptime nelem: comptime_int) type {
assert(nelem > 1);
assert(nelem <= 4);
return packed struct {
const Self = @This();
x: bool = false,
y: bool = false,
z: if (nelem > 2) bool else void = if (nelem > 2) false else {},
w: if (nelem > 3) bool else void = if (nelem > 3) false else {},
pub inline fn all2(self: Self) bool {
return self.x and self.y;
}
// 3-element operations
pub usingnamespace if (nelem >= 3) struct {
pub inline fn all3(self: Self) bool {
return self.all2() and if (nelem > 2) self.z else true;
}
} else struct {};
pub usingnamespace if (nelem >= 4) struct {
pub inline fn all4(self: Self) bool {
return self.all3() and if (nelem > 3) self.w else true;
}
} else struct {};
};
}
pub fn Vec(comptime nelem: comptime_int, comptime T: type) type {
assert(nelem > 1);
assert(nelem <= 4);
return packed struct {
const Self = @This();
pub const nelem = nelem;
pub const T = T;
const Vecnb = BoolVec(Self.nelem);
x: T = 0,
y: T = 0,
z: if (nelem > 2) T else void = if (nelem > 2) 0 else {},
w: if (nelem > 3) T else void = if (nelem > 3) 0 else {},
pub inline fn s(a: T) Self {
return Self{
.x = a,
.y = a,
.z = if (nelem > 2) a else {},
.w = if (nelem > 3) a else {},
};
}
pub inline fn add(lhs: Self, rhs: Self) Self {
return Self{
.x = lhs.x + rhs.x,
@ -65,6 +112,15 @@ pub fn Vec(comptime nelem: comptime_int, comptime T: anytype) type {
return lhs.mulvs(1 / rhs);
}
pub inline fn min(a: Self, b: Self) Self {
return Self{
.x = @minimum(a.x, b.x),
.y = @minimum(a.y, b.y),
.z = if (nelem > 2) @minimum(a.z, b.z) else {},
.w = if (nelem > 3) @minimum(a.w, b.w) else {},
};
}
pub inline fn max(a: Self, b: Self) Self {
return Self{
.x = @maximum(a.x, b.x),
@ -90,6 +146,24 @@ pub fn Vec(comptime nelem: comptime_int, comptime T: anytype) type {
return self.divvs(self.length());
}
pub inline fn lteq(lhs: Self, rhs: Self) Vecnb {
return Vecnb{
.x = lhs.x <= rhs.x,
.y = lhs.y <= rhs.y,
.z = if (nelem > 2) lhs.z <= rhs.z else {},
.w = if (nelem > 3) lhs.w <= rhs.w else {},
};
}
pub inline fn gteq(lhs: Self, rhs: Self) Vecnb {
return Vecnb{
.x = lhs.x >= rhs.x,
.y = lhs.y >= rhs.y,
.z = if (nelem > 2) lhs.z >= rhs.z else {},
.w = if (nelem > 3) lhs.w >= rhs.w else {},
};
}
// 3-element operations
pub usingnamespace if (nelem >= 3) struct {
pub inline fn sum3(self: Self) T {
@ -158,71 +232,3 @@ test "vec" {
try std.testing.expectEqual(@as(i32, 6), boop.sum());
}
// TODO: comptime magic?
pub const Float4x4 = packed struct {
const Self = @This();
pub const zero = Self{};
pub const id = Self{
.c0 = .{ .x = 1, .y = 0, .z = 0, .w = 0 },
.c1 = .{ .x = 0, .y = 1, .z = 0, .w = 0 },
.c2 = .{ .x = 0, .y = 0, .z = 1, .w = 0 },
.c3 = .{ .x = 0, .y = 0, .z = 0, .w = 1 },
};
c0: Vec4f = .{},
c1: Vec4f = .{},
c2: Vec4f = .{},
c3: Vec4f = .{},
pub inline fn mulCol(self: Self, col: Vec4f) Vec4f {
const a = self.c0.mulvs(col.x);
const b = self.c1.mulvs(col.y);
const c = self.c2.mulvs(col.z);
const d = self.c0.mulvs(col.w);
return a.add(b).add(c.add(d));
}
pub inline fn mul(a: Self, b: Self) Self {
return Self{
.c0 = a.mulCol(b.c0),
.c1 = a.mulCol(b.c1),
.c2 = a.mulCol(b.c2),
.c3 = a.mulCol(b.c3),
};
}
pub inline fn lookAt(eye: Vec4f, at: Vec4f, up: Vec4f) Float4x4 {
// right-handed (ew)
const f = at.sub(eye).normalize3();
const s = f.cross3(up).normalize3();
const u = s.cross3(f).normalize3();
const tx = -s.dot3(eye);
const ty = -u.dot3(eye);
const tz = f.dot3(eye);
return Float4x4{
.c0 = .{ .x = s.x, .y = u.x, .z = -f.x, .w = 0 },
.c1 = .{ .x = s.y, .y = u.y, .z = -f.y, .w = 0 },
.c2 = .{ .x = s.z, .y = u.z, .z = -f.z, .w = 0 },
.c3 = .{ .x = tx, .y = ty, .z = tz, .w = 1 },
};
}
pub inline fn glPerspective(fov_y: f32, aspect: f32, z_near: f32, z_far: f32) Self {
const t = std.math.tan(fov_y * 0.5);
var m = zero;
m.c0.x = 1.0 / (aspect * t);
m.c1.y = 1.0 / t;
m.c2.z = -1.0 * (z_far * z_near) / (z_far - z_near);
m.c3.z = -2.0 * (z_far * z_near) / (z_far - z_near);
return m;
}
pub inline fn vkPerspective(fov_y: f32, aspect: f32, z_near: f32, z_far: f32) Self {
var m = glPerspective(fov_y, aspect, z_near, z_far);
m.c1.y *= -1.0;
return m;
}
};

View file

@ -1,6 +1,6 @@
const std = @import("std");
const Vec4f = @import("../math/vec.zig").Vec4f;
const Float4x4 = @import("../math/vec.zig").Float4x4;
const Mat4f = @import("../math/mat.zig").Mat4f;
const Quat = @import("../math/quat.zig").Quat;
const Self = @This();
@ -17,16 +17,16 @@ pub fn get() Self {
return s_camera;
}
pub inline fn getProj(self: *const Self, aspect: f32) Float4x4 {
return Float4x4.vkPerspective(std.math.degreesToRadians(f32, self.fov_y), aspect, self.z_near, self.z_far);
pub inline fn getProj(self: *const Self, aspect: f32) Mat4f {
return Mat4f.vkPerspective(std.math.degreesToRadians(f32, self.fov_y), aspect, self.z_near, self.z_far);
}
pub inline fn getView(self: *const Self) Float4x4 {
pub inline fn getView(self: *const Self) Mat4f {
const pos = self.position;
const rot = self.rotation;
return Float4x4.lookAt(pos, pos.add(rot.fwd()), rot.up());
return Mat4f.lookAt(pos, pos.add(rot.fwd()), rot.up());
}
pub inline fn getWorldToClip(self: *const Self, aspect: f32) Float4x4 {
pub inline fn getWorldToClip(self: *const Self, aspect: f32) Mat4f {
return self.getProj(aspect).mul(self.getView());
}

132
src/rendering/Entities.zig Normal file
View file

@ -0,0 +1,132 @@
const std = @import("std");
const assert = std.debug.assert;
const UUID = @import("../common/uuid.zig").UUID;
const mat = @import("../math/mat.zig");
const Mat4f = mat.Mat4f;
const Mat3f = mat.Mat3f;
const vec = @import("../math/vec.zig");
const Vec4f = vec.Vec4f;
const Quat = @import("../math/quat.zig").Quat;
const Box3d = @import("../math/box.zig").Box3d;
const Mesh = @import("meshes.zig").Mesh;
const Material = @import("materials.zig").Material;
// TODO memory
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
const Entities = @This();
var s_entities: Entities = undefined;
names: std.ArrayList(UUID),
meshes: std.ArrayList(Mesh.Id),
bounds: std.ArrayList(Box3d),
materials: std.ArrayList(Material),
matrices: std.ArrayList(Mat4f),
inv_matrices: std.ArrayList(Mat3f),
translations: std.ArrayList(Vec4f),
rotations: std.ArrayList(Quat),
scales: std.ArrayList(Vec4f),
mod_time: std.time.Instant,
pub fn init() void {
s_entities.names = std.ArrayList(UUID).init(allocator);
s_entities.meshes = std.ArrayList(Mesh.Id).init(allocator);
s_entities.bounds = std.ArrayList(Box3d).init(allocator);
s_entities.materials = std.ArrayList(Material).init(allocator);
s_entities.matrices = std.ArrayList(Mat4f).init(allocator);
s_entities.inv_matrices = std.ArrayList(Mat3f).init(allocator);
s_entities.translations = std.ArrayList(Vec4f).init(allocator);
s_entities.rotations = std.ArrayList(Quat).init(allocator);
s_entities.scales = std.ArrayList(Vec4f).init(allocator);
s_entities.mod_time = std.time.Instant.now();
}
pub fn update() void {}
pub fn get() *const Entities {
return &s_entities;
}
pub fn deinit() void {
delete(get());
s_entities.names.deinit();
s_entities.meshes.deinit();
s_entities.bounds.deinit();
s_entities.materials.deinit();
s_entities.matrices.deinit();
s_entities.inv_matrices.deinit();
s_entities.translations.deinit();
s_entities.rotations.deinit();
s_entities.scales.deinit();
}
pub fn add(self: *const Entities, name: UUID) !i32 {
self.names.append(name);
self.translations.append(Vec4f.zero);
self.scales.append(Vec4f.one);
self.rotations.append(Quat.id);
self.matrices.append(Mat4f.id);
self.inv_matrices.append(Mat3f.id);
self.mod_time = std.time.Instant.now();
}
pub fn destroyAtIndex(ents: *const Entities, i: i32) void {
assert(i >= 0);
assert(i < ents.names.len);
ents.meshes[i].release();
ents.materials[i].albedo.release();
ents.materials[i].rome.release();
ents.materials[i].normal.release();
}
pub fn removeAtIndex(ents: *const Entities, i: i32) void {
ents.destroyAtIndex(i);
ents.names.swapRemove(i);
ents.meshes.swapRemove(i);
ents.bounds.swapRemove(i);
ents.materials.swapRemove(i);
ents.matrices.swapRemove(i);
ents.inv_matrices.swapRemove(i);
ents.translations.swapRemove(i);
ents.rotations.swapRemove(i);
ents.scales.swapRemove(i);
}
pub fn rm(ents: *const Entities, name: UUID) bool {
if (ents.find(name)) |i| {
ents.removeAtIndex(i);
ents.mod_time = std.time.Instant.now();
return true;
}
return false;
}
pub fn find(ents: *const Entities, name: UUID) ?i32 {
return ents.names.find(name);
}
pub fn clear(ents: *const Entities) void {
for (ents.names) |_, i| {
ents.destroyAtIndex(i);
}
ents.mod_time = std.time.Instant.now();
}
pub fn delete(ents: *const Entities) void {
ents.clear();
ents.names.clearAndFree();
ents.meshes.clearAndFree();
ents.bounds.clearAndFree();
ents.materials.clearAndFree();
ents.matrices.clearAndFree();
ents.inv_matrices.clearAndFree();
ents.translations.clearAndFree();
ents.rotations.clearAndFree();
ents.scales.clearAndFree();
}

View file

@ -0,0 +1,35 @@
const vec = @import("../math/vec.zig");
const Vec4f = vec.Vec4f;
const Texture = @import("textures.zig").Texture;
const DiskTexture = @import("textures.zig").DiskTexture;
pub const MatFlags = packed struct {
emissive: bool = false, // enable emissive term
sky: bool = false, // lookup emission from sky cubemap
water: bool = false,
slime: bool = false,
lava: bool = false,
refractive: bool = false, // enable refraction term
warped: bool = false, // uv animated
animated: bool = false, // keyframe animated
underwater: bool = false, // SURF_UNDERWATER
};
pub const Material = packed struct {
albedo: Texture.Id, // rgba8 srgb (albedo, alpha)
rome: Texture.Id, // rgba8 srgb (roughness, occlusion, metallic, emission)
normal: Texture.Id, // rg16 (tangent space xy)
flags: MatFlags,
mean_free_path: Vec4f, // .w = scatter dir 'g'
ior: f32, // index of refraction
bumpiness: f32,
};
pub const DiskMaterial = packed struct {
albedo: DiskTexture.Id,
rome: DiskTexture.Id,
normal: DiskTexture.Id,
flags: MatFlags,
ior: f32,
};

31
src/rendering/meshes.zig Normal file
View file

@ -0,0 +1,31 @@
const UUID = @import("uuid").UUID;
const vec = @import("../math/vec.zig");
const Vec4f = vec.Vec4f;
const Vec4i = vec.Vec4i;
pub const Mesh = packed struct {
const version = 5;
positions: [*]Vec4f,
normals: [*]Vec4f,
uvs: [*]Vec4f,
tex_indices: [*]Vec4i,
length: i32,
id: Id,
pub const Id = packed struct {
version: u8,
index: u24,
};
};
pub const DiskMesh = packed struct {
version: i32,
length: i32,
name: [64]u8,
pub const Id = packed struct {
id: UUID,
};
};

View file

@ -0,0 +1,67 @@
const vk = @import("vulkan");
const UUID = @import("../common/uuid.zig").UUID;
const Table = @import("../containers/table.zig").Table;
const Vec2i = @import("../math/vec.zig").Vec2i;
const TTable = Table(UUID, Texture);
var s_table: TTable = undefined;
pub fn init() void {
s_table = TTable.init();
}
pub fn update() void {}
pub fn deinit() void {}
pub fn getTable() *const TTable {
return &s_table;
}
pub const Texture = packed struct {
const Self = @This();
pub const Id = TTable.Id;
const PalRow = enum {
white,
brown,
light_blue,
green,
red,
orange,
gold,
peach,
purple,
magenta,
tan,
light_green,
yellow,
blue,
fire,
brights,
pub const count = @enumToInt(PalRow.brights);
};
size: Vec2i,
texels: [*]u8,
format: vk.Format,
slot: Id,
pub fn get(id: Id) ?*const Self {
return s_table.get(id);
}
pub fn isCurrent(id: Id) bool {
return s_table.exists(id);
}
};
pub const DiskTexture = packed struct {
version: i32,
format: vk.Format,
name: []u8,
size: Vec2i,
};

View file

@ -17,14 +17,17 @@ const framebuffer = @import("framebuffer.zig");
const Targets = @import("Targets.zig");
const RenderPass = @import("RenderPass.zig");
const Bindings = @import("Bindings.zig");
const Entities = @import("../Entities.zig");
const Texture = @import("../textures.zig").Texture;
const vec = @import("../../math/vec.zig");
const mat = @import("../../math/mat.zig");
const Vec2f = vec.Vec2f;
const Vec4f = vec.Vec4f;
const Vec4i = vec.Vec4i;
const Vec2u = vec.Vec2u;
const Vec4u = vec.Vec4u;
const Float4x4 = vec.Float4x4;
const Mat4f = mat.Mat4f;
pub fn init(device: *const Device, swapchain: *Swapchain) !void {
errdefer {
@ -73,7 +76,7 @@ pub fn execute(device: *const Device, swapchain: *Swapchain) !void {
const DepthPass = struct {
const PushConstants = packed struct {
local_to_clip: Float4x4,
local_to_clip: Mat4f,
};
var s_render_pass: vk.RenderPass = undefined;
@ -91,12 +94,6 @@ const DepthPass = struct {
.dst_access_mask = .{ .color_attachment_write_bit = true },
};
//var info = RenderPass.Description{
// .src_stage_mask = .{ .early_fragment_tests_bit = true },
// .src_access_mask = .{ .depth_stencil_attachment_read_bit = true },
// .dst_stage_mask = .{ .late_fragment_tests_bit = true },
// .dst_access_mask = .{ .depth_stencil_attachment_write_bit = true },
//};
info.attachments[0] = .{
.format = depth_buffer.format,
.layout = .depth_stencil_attachment_optimal,
@ -166,7 +163,6 @@ const DepthPass = struct {
defer pm_depth_execute.end();
const camera = Camera.get();
const world_to_clip = camera.getWorldToClip(swapchain.getAspect());
const attachments = &[_]*Image{Targets.getDepthBuffer(swapchain)};
@ -202,7 +198,7 @@ const DepthPass = struct {
const OpaquePass = struct {
const Globals = packed struct {
g_WorldToClip: Float4x4,
g_WorldToClip: Mat4f,
g_Eye: Vec4f,
g_HdrEnabled: f32,
@ -215,8 +211,8 @@ const OpaquePass = struct {
};
const PushConstants = packed struct {
kLocalToWorld: Float4x4,
kIMc0: Vec4f,
kLocalToWorld: Mat4f,
kIMc0: Vec4f, // components of inverse matrix
kIMc1: Vec4f,
kIMc2: Vec4f,
kTexInds: Vec4u,
@ -239,13 +235,6 @@ const OpaquePass = struct {
.dst_access_mask = .{ .color_attachment_write_bit = true },
};
//var info = RenderPass.Description{
// .src_stage_mask = .{ .late_fragment_tests_bit = true },
// .dst_stage_mask = .{ .early_fragment_tests_bit = true, .color_attachment_output_bit = true },
// .src_access_mask = .{ .depth_stencil_attachment_write_bit = true },
// .dst_access_mask = .{ .depth_stencil_attachment_read_bit = true, .color_attachment_write_bit = true },
//};
info.attachments[0] = .{
.format = depth_buffer.format,
.layout = .depth_stencil_attachment_optimal,
@ -436,10 +425,31 @@ const OpaquePass = struct {
cmdbuf.beginRenderPass(device, s_render_pass, fbuf, rect, &clear_values);
defer cmdbuf.endRenderPass(device);
// ITERATE THROUGH ENTITIES AND DRAW THEM FINALLY OMG
const ents = Entities.get();
for (ents.meshes.items) |mesh, i| {
const mAlbedo = Texture.get(ents.materials.items[i].albedo);
const mRome = Texture.get(ents.materials.items[i].rome);
const mNormal = Texture.get(ents.materials.items[i].normal);
const pc = PushConstants{
.kLocalToWorld = ents.matrices.items[i],
.kIMc0 = ents.inv_matrices.items[i].c0,
.kIMc1 = ents.inv_matrices.items[i].c1,
.kIMc2 = ents.inv_matrices.items[i].c2,
.kTexInds = .{
.x = if (mAlbedo) |albedo| albedo.slot.index else 0,
.y = if (mRome) |rome| rome.slot.index else 0,
.z = if (mNormal) |normal| normal.slot.index else 0,
.w = 0,
},
};
cmdbuf.pushConstants(device, &s_pass, &pc);
cmdbuf.drawMesh(mesh.id);
}
const pc = PushConstants{
.kLocalToWorld = Float4x4.id,
.kLocalToWorld = Mat4f.id,
.kIMc0 = Vec4f{ .x = 1, .y = 0, .z = 0, .w = 0 },
.kIMc1 = Vec4f{ .x = 0, .y = 1, .z = 0, .w = 0 },
.kIMc2 = Vec4f{ .x = 0, .y = 0, .z = 1, .w = 0 },