Compare commits
6 commits
74e0a48ea5
...
73a91195b6
Author | SHA1 | Date | |
---|---|---|---|
73a91195b6 | |||
742f966e14 | |||
0aa55ae1a9 | |||
0930ffc47f | |||
5887223090 | |||
78293db515 |
16 changed files with 1169 additions and 103 deletions
|
@ -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, .{});
|
||||
|
|
|
@ -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
|
||||
|
|
5
gyro.zzz
5
gyro.zzz
|
@ -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:
|
||||
|
|
29
notes.txt
29
notes.txt
|
@ -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
20
src/common/uuid.zig
Normal 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
134
src/containers/queue.zig
Normal 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
423
src/containers/table.zig
Normal 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
104
src/math/box.zig
Normal 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
81
src/math/mat.zig
Normal 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 {};
|
||||
};
|
||||
}
|
144
src/math/vec.zig
144
src/math/vec.zig
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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
132
src/rendering/Entities.zig
Normal 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();
|
||||
}
|
35
src/rendering/materials.zig
Normal file
35
src/rendering/materials.zig
Normal 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
31
src/rendering/meshes.zig
Normal 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,
|
||||
};
|
||||
};
|
67
src/rendering/textures.zig
Normal file
67
src/rendering/textures.zig
Normal 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,
|
||||
};
|
|
@ -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 },
|
||||
|
|
Loading…
Reference in a new issue