From d5a61d3642a649caca8012d4b3c030ea91284f21 Mon Sep 17 00:00:00 2001 From: kayomn Date: Wed, 11 Jun 2025 09:07:14 +0100 Subject: [PATCH] Add initial SPIR-V shader backend --- src/coral/bytes.zig | 180 +++++++---- src/coral/coral.zig | 25 +- src/coral/hashes.zig | 11 + src/coral/list.zig | 125 ++++++++ src/coral/map.zig | 347 ++++++++++---------- src/coral/shaders.zig | 114 ++++--- src/coral/shaders/Root.zig | 207 +++++++----- src/coral/shaders/Scope.zig | 247 +++++++++------ src/coral/shaders/Type.zig | 112 +++++-- src/coral/shaders/spirv.zig | 609 +++++++++++++++++++++++++++++++++--- src/coral/tree.zig | 337 ++++++++++++++++++++ src/coral/utf8.zig | 61 ++-- src/ona/App.zig | 44 +-- src/ona/App/SystemGraph.zig | 30 +- src/ona/gfx.zig | 16 +- 15 files changed, 1867 insertions(+), 598 deletions(-) create mode 100644 src/coral/list.zig create mode 100644 src/coral/tree.zig diff --git a/src/coral/bytes.zig b/src/coral/bytes.zig index 9c22b9e..187dad8 100644 --- a/src/coral/bytes.zig +++ b/src/coral/bytes.zig @@ -38,6 +38,17 @@ pub const ReadWriteSpan = struct { unreachable; } + pub fn put(self: *ReadWriteSpan, byte: u8) bool { + if (self.write_cursor >= self.bytes.len) { + return false; + } + + self.bytes[self.write_cursor] = byte; + self.write_cursor += 1; + + return true; + } + pub fn write(self: *ReadWriteSpan, buffer: []const u8) usize { const written = @min(buffer.len, self.bytes.len - self.write_cursor); @@ -60,6 +71,20 @@ pub fn Span(comptime Ptr: type) type { }; } +pub const WriteCount = struct { + written: usize = 0, + + fn write(self: *WriteCount, input: []const u8) usize { + self.written += input.len; + + return input.len; + } + + fn writable(self: *WriteCount) Writable { + return .initRef(self, WriteCount.write); + } +}; + pub const Writable = coral.Callable(usize, &.{[]const u8}); pub const null_writer = Writable.initFn(writeNull); @@ -77,21 +102,9 @@ pub fn allocFormatted(allocator: std.mem.Allocator, comptime format: []const u8, } pub fn countFormatted(comptime format: []const u8, args: anytype) usize { - const Counter = struct { - written: usize, + var count = WriteCount{}; - const Self = @This(); - - fn write(self: *Self, input: []const u8) usize { - self.written += input.len; - - return input.len; - } - }; - - var count = Counter{ .written = 0 }; - - writeFormatted(.initRef(&count, Counter.write), format, args) catch unreachable; + writeFormatted(count.writable(), format, args) catch unreachable; return count.written; } @@ -192,6 +205,53 @@ pub fn writeN(output: Writable, data: []const u8, count: usize) ReadWriteError!v } } +pub fn writeFormatable(output: Writable, formatable: anytype) ReadWriteError!void { + const Formatable = @TypeOf(formatable); + + switch (@typeInfo(Formatable)) { + .pointer => |pointer| { + const error_message = std.fmt.comptimePrint("{s} is not a string-like type", .{@typeName(Formatable)}); + + switch (pointer.size) { + .one => switch (@typeInfo(pointer.child)) { + .array => |array| switch (array.child == u8) { + true => try coral.bytes.writeAll(output, formatable.*[0..]), + false => @compileError(error_message), + }, + + .@"struct", .@"union", .@"enum" => { + try formatable.writeFormat(output); + }, + + else => @compileError(error_message), + }, + + .slice => switch (pointer.child == u8) { + true => try coral.bytes.writeAll(output, formatable), + false => @compileError(error_message), + }, + + .many => switch ((pointer.sentinel() != null) and (pointer.child == u8)) { + true => try coral.bytes.writeAll(output, std.mem.span(formatable)), + false => @compileError(error_message), + }, + + else => false, + } + }, + + .@"struct", .@"union", .@"enum" => { + try formatable.writeFormat(output); + }, + + else => { + @compileError(std.fmt.comptimePrint("`{s}` is not a valid placeholder type", .{ + @typeName(Formatable), + })); + }, + } +} + pub fn writeFormatted(output: Writable, comptime format: []const u8, args: anytype) ReadWriteError!void { comptime { if (!std.unicode.utf8ValidateSlice(format)) { @@ -199,6 +259,13 @@ pub fn writeFormatted(output: Writable, comptime format: []const u8, args: anyty } } + const Args = @TypeOf(args); + + const args_struct = switch (@typeInfo(Args)) { + .@"struct" => |@"struct"| @"struct", + else => @compileError(std.fmt.comptimePrint("`args` must be a struct type, not {s}", .{@typeName(Args)})), + }; + comptime var tokens = formatting.TokenStream.init(format); inline while (comptime tokens.next()) |token| { @@ -216,58 +283,33 @@ pub fn writeFormatted(output: Writable, comptime format: []const u8, args: anyty }, .placeholder => |placeholder| { - const Args = @TypeOf(args); + if (args_struct.is_tuple) { + const index = comptime coral.utf8.DecFormat.c.parse(usize, placeholder) orelse { + @compileError(std.fmt.comptimePrint("{s} in format string `{s} is not a valid tuple index", .{ + placeholder, + format, + })); + }; - if (!@hasField(Args, placeholder)) { - @compileError(std.fmt.comptimePrint("format string `{s}` uses field `{s}` not present in {s}", .{ - format, - placeholder, - @typeName(Args), - })); - } + if (index >= args.len) { + @compileError(std.fmt.comptimePrint("format string `{s}` uses index `{s}` not present in {s}", .{ + format, + placeholder, + @typeName(Args), + })); + } - const field = @field(args, placeholder); - const Field = @TypeOf(field); + try writeFormatable(output, args[index]); + } else { + if (!@hasField(Args, placeholder)) { + @compileError(std.fmt.comptimePrint("format string `{s}` uses field `{s}` not present in {s}", .{ + format, + placeholder, + @typeName(Args), + })); + } - switch (@typeInfo(Field)) { - .pointer => |pointer| { - const error_message = std.fmt.comptimePrint("{s} is not a string-like type", .{@typeName(Field)}); - - switch (pointer.size) { - .one => switch (@typeInfo(pointer.child)) { - .array => |array| switch (array.child == u8) { - true => try coral.bytes.writeAll(output, field.*[0..]), - false => @compileError(error_message), - }, - - .@"struct", .@"union", .@"enum" => { - try field.writeFormat(output); - }, - - else => @compileError(error_message), - }, - - .slice => switch (pointer.child == u8) { - true => try coral.bytes.writeAll(output, field), - false => @compileError(error_message), - }, - - .many => switch ((pointer.sentinel() != null) and (pointer.child == u8)) { - true => try coral.bytes.writeAll(output, std.mem.span(field)), - false => @compileError(error_message), - }, - - else => false, - } - }, - - .@"struct", .@"union", .@"enum" => { - try field.writeFormat(output); - }, - - else => { - @compileError("Unsupported placeholder type"); - }, + try writeFormatable(output, @field(args, placeholder)); } }, } @@ -289,11 +331,15 @@ pub fn writeLittle(output: Writable, value: anytype) ReadWriteError!void { std.mem.byteSwapAllFields(Value, ©); - try writeAll(output, std.mem.asBytes(&value)); + try writeAll(output, std.mem.asBytes(©)); }, .int, .float, .bool => { - try writeAll(@byteSwap(value)); + try writeAll(output, std.mem.asBytes(&@byteSwap(value))); + }, + + .@"enum" => { + try writeLittle(output, @intFromEnum(value)); }, else => { @@ -308,7 +354,7 @@ fn writeNull(buffer: []const u8) usize { return buffer.len; } -pub fn writeSentineled(output: Writable, data: []const u8, sentinel: u8) void { +pub fn writeSentineled(output: Writable, data: []const u8, sentinel: u8) coral.bytes.ReadWriteError!void { try writeAll(output, data); - try writeAll((&sentinel)[0..1]); + try writeAll(output, (&sentinel)[0..1]); } diff --git a/src/coral/coral.zig b/src/coral/coral.zig index e180d93..c754758 100644 --- a/src/coral/coral.zig +++ b/src/coral/coral.zig @@ -10,6 +10,8 @@ pub const hashes = @import("./hashes.zig"); pub const heap = @import("./heap.zig"); +pub const list = @import("./list.zig"); + pub const map = @import("./map.zig"); pub const scalars = @import("./scalars.zig"); @@ -18,6 +20,8 @@ pub const shaders = @import("./shaders.zig"); const std = @import("std"); +pub const tree = @import("./tree.zig"); + pub const utf8 = @import("./utf8.zig"); pub fn Callable(comptime Output: type, comptime input_types: []const type) type { @@ -98,6 +102,19 @@ pub fn Callable(comptime Output: type, comptime input_types: []const type) type }; } +pub fn KeyValuePair(comptime Key: type, comptime Value: type) type { + return struct { + key: Key, + value: Value, + + const Self = @This(); + + pub fn tuple(self: Self) struct { Key, Value } { + return .{ self.key, self.value }; + } + }; +} + pub const ShortString = extern struct { buffer: [max]u8, remaining: u8, @@ -242,15 +259,11 @@ pub fn Stack(comptime Value: type) type { } pub fn get(self: Self) ?*Value { - if (self.values.len == 0) { - return null; - } - - return &self.values[self.values.len - 1]; + return if (self.isEmpty()) null else &self.values[self.values.len - 1]; } pub fn pop(self: *Self) ?Value { - if (self.values.len == 0) { + if (self.isEmpty()) { return null; } diff --git a/src/coral/hashes.zig b/src/coral/hashes.zig index 54dddec..a293bda 100644 --- a/src/coral/hashes.zig +++ b/src/coral/hashes.zig @@ -1,5 +1,10 @@ const std = @import("std"); +/// +/// Non-cryptographic, hashing function suitable for scenarios where a quick and lightweight hash is needed. +/// +/// Unlike Jenkins hashing, djb2 is not optimized for a specific sequence size. +/// pub fn djb2(comptime Int: type, bytes: []const u8) Int { var hash = @as(Int, @intCast(5381)); @@ -10,6 +15,12 @@ pub fn djb2(comptime Int: type, bytes: []const u8) Int { return hash; } +/// +/// Non-cryptographic hashing function that is designed to be fast over secure. +/// +/// It is particularly effective for small to medium-sized keys, providing low collision rates and provides good +/// distribution properties. +/// pub fn jenkins(comptime Int: type, bytes: []const u8) Int { var hash = @as(Int, 0); diff --git a/src/coral/list.zig b/src/coral/list.zig new file mode 100644 index 0000000..a7ed735 --- /dev/null +++ b/src/coral/list.zig @@ -0,0 +1,125 @@ +const std = @import("std"); + +pub fn Linked(comptime Value: type, comptime block_size: usize) type { + return struct { + has_head_block: ?*Block, + has_tail_block: ?*Block, + + const Block = struct { + values: std.BoundedArray(Value, block_size) = .{}, + has_next: ?*Block = null, + }; + + const Self = @This(); + + pub const Values = struct { + has_block: ?*Block, + block_index: std.math.IntFittingRange(0, block_size), + + pub fn next(self: *Values) ?*Value { + var block = self.has_block orelse { + return null; + }; + + if (self.block_index >= block.values.len) { + self.has_block = block.has_next; + self.block_index = 0; + + block = self.has_block orelse { + return null; + }; + } + + defer { + self.block_index += 1; + } + + return &block.values.slice()[self.block_index]; + } + }; + + pub fn append(self: *Self, allocator: std.mem.Allocator, value: Value) std.mem.Allocator.Error!*Value { + const tail_block = self.has_tail_block orelse create: { + const block = try allocator.create(Block); + + block.* = .{}; + self.has_head_block = block; + self.has_tail_block = block; + + break :create block; + }; + + tail_block.values.append(value) catch { + const block = try allocator.create(Block); + + block.* = .{}; + tail_block.has_next = block; + self.has_tail_block = block; + + block.values.append(value) catch { + unreachable; + }; + }; + + return &tail_block.values.slice()[tail_block.values.len - 1]; + } + + pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { + var blocks = self.has_head_block; + + while (blocks) |block| { + const has_next = block.has_next; + + allocator.destroy(block); + + blocks = has_next; + } + + self.has_head_block = undefined; + self.has_tail_block = undefined; + } + + pub const empty = Self{ + .has_head_block = null, + .has_tail_block = null, + }; + + pub fn get(self: Self, index: usize) ?*Value { + if (self.has_tail_block) |tail_block| { + if (tail_block.values.len == 0) { + std.debug.assert(self.has_head_block == self.has_tail_block); + + return if (tail_block.values.len == 0) null else tail_block.values.slice()[index]; + } + } + + return null; + } + + pub fn isEmpty(self: Self) bool { + return self.has_head_block == null; + } + + pub fn len(self: Self) usize { + if (self.has_tail_block) |tail_block| { + var accounted = tail_block.values.len; + var blocks = self.has_head_block; + + while (blocks != self.has_tail_block) : (blocks = blocks.?.has_next) { + accounted += block_size; + } + + return accounted; + } + + return 0; + } + + pub fn values(self: *const Self) Values { + return Values{ + .has_block = self.has_head_block, + .block_index = 0, + }; + } + }; +} diff --git a/src/coral/map.zig b/src/coral/map.zig index 5f02523..429ca4d 100644 --- a/src/coral/map.zig +++ b/src/coral/map.zig @@ -10,7 +10,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits( entry_map: []?Entry, len: usize, - pub const Entry = struct { + const Entry = struct { key: Key, value: Value, @@ -33,7 +33,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits( return &table.entry_map[hashed_key].?; }); - if (traits.are_equal(table_entry.key, self.key)) { + if (traits.areEqual(table_entry.key, self.key)) { return null; } @@ -42,16 +42,18 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits( } }; - pub const Entries = struct { - table: *const Self, + pub const Keys = struct { + context: *const Self, iterations: usize, - pub fn next(self: *Entries) ?*Entry { - while (self.iterations < self.table.entry_map.len) { - defer self.iterations += 1; + pub fn next(self: *Keys) ?Key { + while (self.iterations < self.context.entry_map.len) { + defer { + self.iterations += 1; + } - if (self.table.entry_map[self.iterations]) |*entry| { - return entry; + if (self.context.entry_map[self.iterations]) |entry| { + return entry.key; } } @@ -61,77 +63,24 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits( const Self = @This(); - pub const empty = Self{ - .entry_map = &.{}, - .len = 0, - }; + pub const Values = struct { + context: *const Self, + iterations: usize, - pub fn entries(self: *const Self) Entries { - return .{ - .table = self, - .iterations = 0, - }; - } - - pub fn isEmpty(self: Self) bool { - return self.len == 0; - } - - pub fn remove(self: *Self, key: Key) ?Entry { - const hash_max = @min(max_int, self.entry_map.len); - var hashed_key = key.hash() % hash_max; - - while (true) { - const entry = &(self.entry_map[hashed_key] orelse continue); - - if (self.keys_equal(entry.key, key)) { - const original_entry = entry.*; - - self.entry_map[hashed_key] = null; - - return original_entry; - } - - hashed_key = (hashed_key +% 1) % hash_max; - } - } - - pub fn replace(self: *Self, key: Key, value: Value) std.mem.Allocator.Error!?Entry { - try self.rehash(load_max); - - std.debug.assert(self.entry_map.len > self.len); - - { - const hash_max = @min(max_int, self.entry_map.len); - var hashed_key = traits.hash(key) % hash_max; - - while (true) { - const entry = &(self.entry_map[hashed_key] orelse { - self.entry_map[hashed_key] = .{ - .key = key, - .value = value, - }; - - self.len += 1; - - return null; - }); - - if (traits.are_equal(key, entry.key)) { - const original_entry = entry.*; - - entry.* = .{ - .key = key, - .value = value, - }; - - return original_entry; + pub fn next(self: *Values) ?*Value { + while (self.iterations < self.context.entry_map.len) { + defer { + self.iterations += 1; } - hashed_key = (hashed_key +% 1) % hash_max; + if (self.context.entry_map[self.iterations]) |*entry| { + return &entry.value; + } } + + return null; } - } + }; pub fn clear(self: *Self) void { for (self.entry_map) |*entry| { @@ -142,41 +91,19 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits( } pub fn deinit(self: *Self) void { - if (self.entry_map.len == 0) { - return; + if (self.entry_map.len != 0) { + coral.heap.allocator.free(self.entry_map); } - coral.heap.allocator.free(self.entry_map); - self.* = undefined; } - pub fn get(self: Self, key: Key) ?*Value { - if (self.len == 0) { - return null; - } + pub const empty = Self{ + .entry_map = &.{}, + .len = 0, + }; - const hash_max = @min(max_int, self.entry_map.len); - var hashed_key = traits.hash(key) % hash_max; - var iterations = @as(usize, 0); - - while (iterations < self.len) : (iterations += 1) { - const entry = &(self.entry_map[hashed_key] orelse return null); - - if (traits.are_equal(entry.key, key)) { - return &entry.value; - } - - hashed_key = (hashed_key +% 1) % hash_max; - } - - return null; - } - - pub fn emplace(self: *Self, key: Key, value: Value) std.mem.Allocator.Error!switch (Value) { - void => bool, - else => ?*Value, - } { + pub fn insert(self: *Self, key: Key, value: Value) std.mem.Allocator.Error!bool { try self.rehash(load_max); std.debug.assert(self.entry_map.len > self.len); @@ -186,16 +113,39 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits( .value = value, }; - if (entry.writeInto(self)) |written_entry| { - return switch (Value) { - void => true, - else => &written_entry.value, - }; + return entry.writeInto(self) != null; + } + + pub fn get(self: Self, key: Key) ?*Value { + if (!self.isEmpty()) { + const hash_max = @min(max_int, self.entry_map.len); + var hashed_key = traits.hash(key) % hash_max; + var iterations = @as(usize, 0); + + while (iterations < self.len) : (iterations += 1) { + const entry = &(self.entry_map[hashed_key] orelse { + return null; + }); + + if (traits.areEqual(entry.key, key)) { + return &entry.value; + } + + hashed_key = (hashed_key +% 1) % hash_max; + } } - return switch (Value) { - void => false, - else => null, + return null; + } + + pub fn isEmpty(self: Self) bool { + return self.len == 0; + } + + pub fn keys(self: *const Self) Keys { + return .{ + .context = self, + .iterations = 0, }; } @@ -210,7 +160,9 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits( var table = empty; - errdefer table.deinit(); + errdefer { + table.deinit(); + } table.entry_map = allocate: { const min_len = @max(1, self.len); @@ -238,59 +190,139 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits( self.* = table; } + + pub fn remove(self: *Self, key: Key) ?coral.KeyValuePair(Key, Value) { + const hash_max = @min(max_int, self.entry_map.len); + var hashed_key = traits.hash(key) % hash_max; + + while (true) { + const entry = &(self.entry_map[hashed_key] orelse continue); + + if (traits.areEqual(entry.key, key)) { + const original_entry = entry.*; + + self.entry_map[hashed_key] = null; + + return .{ + .key = original_entry.key, + .value = original_entry.value, + }; + } + + hashed_key = (hashed_key +% 1) % hash_max; + } + } + + pub fn replace(self: *Self, key: Key, value: Value) std.mem.Allocator.Error!?coral.KeyValuePair(Key, Value) { + try self.rehash(load_max); + + std.debug.assert(self.entry_map.len > self.len); + + { + const hash_max = @min(max_int, self.entry_map.len); + var hashed_key = traits.hash(key) % hash_max; + + while (true) { + const entry = &(self.entry_map[hashed_key] orelse { + self.entry_map[hashed_key] = .{ + .key = key, + .value = value, + }; + + self.len += 1; + + return null; + }); + + if (traits.areEqual(key, entry.key)) { + const original_entry = entry.*; + + entry.* = .{ + .key = key, + .value = value, + }; + + return .{ + .key = original_entry.key, + .value = original_entry.value, + }; + } + + hashed_key = (hashed_key +% 1) % hash_max; + } + } + } + + pub fn values(self: *const Self) Values { + return .{ + .context = self, + .iterations = 0, + }; + } }; } pub fn Traits(comptime Key: type) type { return struct { - are_equal: fn (Key, Key) bool, + areEqual: fn (Key, Key) bool, hash: fn (Key) usize, }; } -pub fn enumTraits(comptime Enum: type) Traits(Enum) { - const enums = struct { - fn are_equal(a: Enum, b: Enum) bool { - return a == b; - } +pub fn scalarTraits(comptime Scalar: type) Traits(Scalar) { + const traits = switch (@typeInfo(Scalar)) { + .@"enum" => |@"enum"| struct { + fn areEqual(a: Scalar, b: Scalar) bool { + return a == b; + } - fn hash(value: Enum) usize { - return @intFromEnum(value) % std.math.maxInt(usize); - } + fn hash(value: Scalar) usize { + return switch (@sizeOf(@"enum".tag_type) > @sizeOf(usize)) { + true => @intFromEnum(value) % std.math.maxInt(usize), + false => @intFromEnum(value), + }; + } + }, + + .pointer => struct { + fn areEqual(a: Scalar, b: Scalar) bool { + return a == b; + } + + fn hash(value: Scalar) usize { + return @intFromPtr(value); + } + }, + + .int => |int| struct { + fn areEqual(a: Scalar, b: Scalar) bool { + return a == b; + } + + fn hash(value: Scalar) Scalar { + return switch (int.bits > @bitSizeOf(usize)) { + true => value % std.math.maxInt(usize), + false => value, + }; + } + }, + + else => { + @compileError(std.fmt.comptimePrint("parameter `Scalar` must be a scalar type, not {s}", .{ + @typeName(Scalar), + })); + }, }; return .{ - .are_equal = enums.are_equal, - .hash = enums.hash, - }; -} - -pub fn ptrTraits(comptime Ptr: type) Traits(Ptr) { - const pointers = struct { - fn are_equal(a: Ptr, b: Ptr) bool { - return a == b; - } - - fn hash(value: Ptr) usize { - return @intFromPtr(value); - } - }; - - return switch (@typeInfo(Ptr)) { - .pointer => .{ - .are_equal = pointers.are_equal, - .hash = pointers.hash, - }, - - else => @compileError(std.fmt.comptimePrint("parameter `Ptr` must be a pointer type, not {s}", .{ - @typeName(Ptr), - })), + .areEqual = traits.areEqual, + .hash = traits.hash, }; } pub const string_traits = init: { const strings = struct { - fn are_equal(a: []const u8, b: []const u8) bool { + fn areEqual(a: []const u8, b: []const u8) bool { return std.mem.eql(u8, a, b); } @@ -300,24 +332,7 @@ pub const string_traits = init: { }; break :init Traits([]const u8){ - .are_equal = strings.are_equal, + .areEqual = strings.areEqual, .hash = strings.hash, }; }; - -pub const usize_traits = init: { - const usizes = struct { - fn are_equal(a: usize, b: usize) bool { - return a == b; - } - - fn hash(value: usize) usize { - return value; - } - }; - - break :init Traits(usize){ - .are_equal = usizes.are_equal, - .hash = usizes.hash, - }; -}; diff --git a/src/coral/shaders.zig b/src/coral/shaders.zig index 77c5d80..9294ae5 100644 --- a/src/coral/shaders.zig +++ b/src/coral/shaders.zig @@ -8,8 +8,6 @@ pub const Type = @import("./shaders/Type.zig"); const coral = @import("./coral.zig"); -const glsl = @import("./shaders/glsl.zig"); - const spirv = @import("./shaders/spirv.zig"); const std = @import("std"); @@ -24,7 +22,33 @@ pub const Argument = struct { pub const Block = struct { scope: *const Scope, depth: usize, - has_statement: ?*const Statement = null, + statements: ?*const Statement = null, + + pub fn hasLastStatement(self: Block) ?*const Statement { + var statement = self.statements orelse { + return null; + }; + + while (true) { + statement = switch (statement.*) { + .declare_local => |local_declaration| local_declaration.has_next orelse { + return statement; + }, + + .mutate_local => |local_mutation| local_mutation.has_next orelse { + return statement; + }, + + .mutate_output => |output_mutation| output_mutation.has_next orelse { + return statement; + }, + + .return_expression => { + return statement; + }, + }; + } + } }; pub const DefinitionError = std.mem.Allocator.Error || error{ @@ -33,8 +57,7 @@ pub const DefinitionError = std.mem.Allocator.Error || error{ }; pub const Expression = union(enum) { - float: Float, - int: Int, + constant: [:0]const u8, group_expression: *const Expression, add: BinaryOperation, subtract: BinaryOperation, @@ -65,17 +88,7 @@ pub const Expression = union(enum) { pub const BinaryOperation = struct { rhs_expression: *const Expression, lhs_expression: *const Expression, - - pub fn inferType(self: BinaryOperation) TypeError!*const Type { - const lhs_type = try self.lhs_expression.inferType(); - const rhs_type = try self.rhs_expression.inferType(); - - if (lhs_type != rhs_type) { - return error.IncompatibleTypes; - } - - return lhs_type; - } + type: *const Type, }; pub const Builtin = struct { @@ -118,17 +131,14 @@ pub const Expression = union(enum) { parameter_types: []const *const Type, }; - pub const Float = struct { - whole: [:0]const u8, - decimal: [:0]const u8, - }; - pub const Intrinsic = struct { allowed_parameter_types: []const *const Type, expected_parameter_count: usize, first_argument: *const Argument, pub fn inferType(self: Intrinsic) TypeError!*const Type { + std.debug.assert(self.expected_parameter_count != 0); + const return_type = try self.first_argument.expression.inferType(); if (std.mem.indexOfScalar(*const Type, self.allowed_parameter_types, return_type) == null) { @@ -136,11 +146,11 @@ pub const Expression = union(enum) { } var has_next_argument = self.first_argument.has_next; - var arguments_remaining = self.expected_parameter_count; + var arguments_remaining = self.expected_parameter_count - 1; while (has_next_argument) |next_argument| : ({ has_next_argument = next_argument.has_next; - arguments_remaining += 1; + arguments_remaining -= 1; }) { if (arguments_remaining == 0) { return error.IncompatibleArguments; @@ -171,20 +181,21 @@ pub const Expression = union(enum) { pub const Invocation = struct { function: *const Function, - has_argument: ?*const Argument = null, + arguments: ?*const Argument = null, + argument_count: usize = 0, pub fn inferType(self: Invocation) TypeError!*const Type { - var has_parameter = self.function.has_parameter; - var has_argument = self.has_argument; + var parameters = self.function.parameters; + var arguments = self.arguments; - while (has_parameter) |parameter| { - const argument = has_argument orelse { + while (parameters) |parameter| { + const argument = arguments orelse { return error.IncompatibleArguments; }; defer { - has_parameter = parameter.has_next; - has_argument = argument.has_next; + parameters = parameter.has_next; + arguments = argument.has_next; } if (parameter.type != try argument.expression.inferType()) { @@ -192,7 +203,7 @@ pub const Expression = union(enum) { } } - if (has_argument != null) { + if (arguments != null) { return error.IncompatibleArguments; } @@ -200,10 +211,6 @@ pub const Expression = union(enum) { } }; - pub const Int = struct { - literal: [:0]const u8, - }; - pub const OutputMutation = struct { output: *const Output, expression: *const Expression, @@ -216,8 +223,11 @@ pub const Expression = union(enum) { pub fn inferType(self: Expression) TypeError!*const Type { return switch (self) { - .float => .float, - .int => .int, + .constant => |constant| switch (std.mem.indexOfScalar(u8, constant, '.') == null) { + true => .int, + false => .float, + }, + .negate_expression, .group_expression => |expression| expression.inferType(), .abs, .pow, .sin => |generic| generic.inferType(), .convert => |convert| convert.target_type, @@ -245,28 +255,30 @@ pub const Expression = union(enum) { .greater_equal, .lesser_than, .lesser_equal, - => |binary_op| binary_op.inferType(), + => |binary_op| binary_op.type, }; } }; pub const Function = struct { identifier: [:0]const u8, + signature: [:0]const u8, has_return_type: ?*const Type = null, - has_parameter: ?*const Parameter = null, + parameters: ?*const Parameter = null, + parameter_count: usize = 0, block: *const Block, }; -pub const GenerationError = coral.bytes.ReadWriteError || error{ - UnsupportedFeature, -}; - pub const Input = struct { identifier: [:0]const u8, type: *const Type, location: u8, }; +pub const InternError = std.mem.Allocator.Error || error{ + TooManyConstants, +}; + pub const Output = struct { identifier: [:0]const u8, type: *const Type, @@ -279,9 +291,10 @@ pub const Parameter = struct { has_next: ?*const Parameter = null, }; -pub const ParsingError = std.mem.Allocator.Error || tokens.ExpectationError || DefinitionError || TypeError || error{ +pub const ParsingError = std.mem.Allocator.Error || tokens.ExpectationError || DefinitionError || TypeError || InternError || error{ ImmutableStorage, UndefinedIdentifier, + MissingReturn, }; pub const Texture = struct { @@ -298,7 +311,7 @@ pub const Statement = union(enum) { declare_local: LocalDeclaration, mutate_local: LocalMutation, mutate_output: OutputMutation, - return_expression: ?*const Expression, + return_expression: *const Expression, pub const LocalDeclaration = struct { local: *const Local, @@ -337,6 +350,13 @@ pub const Uniform = struct { has_field: ?*const Field = null, }; -pub const generateGlsl = glsl.generate; +pub fn compileFragmentSpirv(arena: *std.heap.ArenaAllocator, function: *const coral.shaders.Function) spirv.BuildError!spirv.Module { + var module = spirv.Module{ + .capabilities = &.{.shader}, + .memory_model = .{ .logical, .glsl450 }, + }; -pub const generateSpirv = spirv.generate; + _ = try module.shaderEntryPoint(arena, .fragment, function); + + return module; +} diff --git a/src/coral/shaders/Root.zig b/src/coral/shaders/Root.zig index fa31f20..0153ed6 100644 --- a/src/coral/shaders/Root.zig +++ b/src/coral/shaders/Root.zig @@ -9,7 +9,7 @@ inputs: std.BoundedArray(*const coral.shaders.Input, 15) = .{}, outputs: std.BoundedArray(*const coral.shaders.Output, 15) = .{}, uniforms: std.BoundedArray(*const coral.shaders.Uniform, 15) = .{}, textures: std.BoundedArray(*const coral.shaders.Texture, 15) = .{}, -functions: std.BoundedArray(*const coral.shaders.Function, 255) = .{}, +functions: std.BoundedArray(*const coral.shaders.Function, max_functions) = .{}, pub const ParseResult = union(enum) { ok, @@ -28,9 +28,7 @@ pub fn clear(self: *Self) void { } pub fn defineFunction(self: *Self, arena: *std.heap.ArenaAllocator, function: coral.shaders.Function) coral.shaders.DefinitionError!void { - const defined = try self.scope.define(arena, function); - - self.functions.append(defined) catch |append_error| { + self.functions.append(try self.scope.define(arena, function)) catch |append_error| { return switch (append_error) { error.Overflow => error.TooManySymbols, }; @@ -38,9 +36,7 @@ pub fn defineFunction(self: *Self, arena: *std.heap.ArenaAllocator, function: co } pub fn defineInput(self: *Self, arena: *std.heap.ArenaAllocator, input: coral.shaders.Input) coral.shaders.DefinitionError!void { - const defined = try self.scope.define(arena, input); - - self.inputs.append(defined) catch |append_error| { + self.inputs.append(try self.scope.define(arena, input)) catch |append_error| { return switch (append_error) { error.Overflow => error.TooManySymbols, }; @@ -48,35 +44,49 @@ pub fn defineInput(self: *Self, arena: *std.heap.ArenaAllocator, input: coral.sh } pub fn defineOutput(self: *Self, arena: *std.heap.ArenaAllocator, output: coral.shaders.Output) coral.shaders.DefinitionError!void { - const defined = try self.scope.define(arena, output); - - self.outputs.append(defined) catch |append_error| { + self.outputs.append(try self.scope.define(arena, output)) catch |append_error| { return switch (append_error) { error.Overflow => error.TooManySymbols, }; }; } -pub fn defineTexture(self: *Self, arena: *std.heap.ArenaAllocator, input: coral.shaders.Texture) coral.shaders.DefinitionError!void { - const defined = try self.scope.define(arena, input); - - self.textures.append(defined) catch |append_error| { +pub fn defineTexture(self: *Self, arena: *std.heap.ArenaAllocator, texture: coral.shaders.Texture) coral.shaders.DefinitionError!void { + self.textures.append(try self.scope.define(arena, texture)) catch |append_error| { return switch (append_error) { error.Overflow => error.TooManySymbols, }; }; } -pub fn defineUniform(self: *Self, arena: *std.heap.ArenaAllocator, input: coral.shaders.Uniform) coral.shaders.DefinitionError!void { - const defined = try self.scope.define(arena, input); - - self.uniforms.append(defined) catch |append_error| { +pub fn defineUniform(self: *Self, arena: *std.heap.ArenaAllocator, uniform: coral.shaders.Uniform) coral.shaders.DefinitionError!void { + self.uniforms.append(try self.scope.define(arena, uniform)) catch |append_error| { return switch (append_error) { error.Overflow => error.TooManySymbols, }; }; } +pub fn hasFunction(self: Self, identifier: []const u8) ?*const coral.shaders.Function { + return self.scope.hasLocal(coral.shaders.Function, identifier); +} + +pub fn hasInput(self: Self, identifier: []const u8) ?*const coral.shaders.Input { + return self.scope.hasLocal(coral.shaders.Input, identifier); +} + +pub fn hasOutput(self: Self, identifier: []const u8) ?*const coral.shaders.Output { + return self.scope.hasLocal(coral.shaders.Output, identifier); +} + +pub fn hasTexture(self: Self, identifier: []const u8) ?*const coral.shaders.Texture { + return self.scope.hasLocal(coral.shaders.Texture, identifier); +} + +pub fn hasUniform(self: Self, identifier: []const u8) ?*const coral.shaders.Uniform { + return self.scope.hasLocal(coral.shaders.Uniform, identifier); +} + pub fn init(arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!Self { const scope = try arena.allocator().create(coral.shaders.Scope); @@ -85,80 +95,26 @@ pub fn init(arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!Self { return .{ .scope = scope }; } -pub fn parse(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!void { - errdefer { - self.clear(); - } +const max_functions = 255; - while (source.skip(.newline) != .end) { - const symbol = try self.scope.parse(arena, source); - - if (symbol.has(coral.shaders.Input)) |input| { - self.inputs.append(input) catch |append_error| { - return switch (append_error) { - error.Overflow => error.TooManySymbols, - }; - }; - - continue; - } - - if (symbol.has(coral.shaders.Output)) |output| { - self.outputs.append(output) catch |append_error| { - return switch (append_error) { - error.Overflow => error.TooManySymbols, - }; - }; - - continue; - } - - if (symbol.has(coral.shaders.Uniform)) |uniform| { - self.uniforms.append(uniform) catch |append_error| { - return switch (append_error) { - error.Overflow => error.TooManySymbols, - }; - }; - - continue; - } - - if (symbol.has(coral.shaders.Texture)) |texture| { - self.textures.append(texture) catch |append_error| { - return switch (append_error) { - error.Overflow => error.TooManySymbols, - }; - }; - - continue; - } - - if (symbol.has(coral.shaders.Function)) |function| { - self.functions.append(function) catch |append_error| { - return switch (append_error) { - error.Overflow => error.TooManySymbols, - }; - }; - - continue; - } - } -} - -pub fn parseText(self: *Self, arena: *std.heap.ArenaAllocator, source_text: []const u8) std.mem.Allocator.Error!ParseResult { +pub fn parse(self: *Self, arena: *std.heap.ArenaAllocator, source_text: []const u8) std.mem.Allocator.Error!ParseResult { errdefer { self.clear(); } var source = tokens.Stream.init(source_text); - self.parse(arena, &source) catch |parse_error| { + self.parseDocument(arena, &source) catch |parse_error| { const arena_allocator = arena.allocator(); return .{ .failure = try switch (parse_error) { error.OutOfMemory => error.OutOfMemory, + error.TooManyConstants => coral.bytes.allocFormatted(arena_allocator, "{location}: number of literals in the current scope exceeded", .{ + .location = source.location, + }), + error.ImmutableStorage => coral.bytes.allocFormatted(arena_allocator, "{location}: attempt to modify an immutable value", .{ .location = source.location, }), @@ -190,9 +146,102 @@ pub fn parseText(self: *Self, arena: *std.heap.ArenaAllocator, source_text: []co .location = source.location, .token = source.current, }), + + error.MissingReturn => coral.bytes.allocFormatted(arena_allocator, "{location}: value-returning function does not return at end", .{ + .location = source.location, + }), }, }; }; return .ok; } + +fn parseDocument(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!void { + while (source.skip(.newline) != .end) { + try switch (try source.current.expectAny(&.{.keyword_function})) { + .keyword_function => self.parseFunction(arena, source), + }; + } +} + +fn parseFunction(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!void { + const identifier = try arena.allocator().dupeZ(u8, try source.skip(.newline).expectIdentifier()); + + try source.skip(.newline).expect(.symbol_paren_left); + + const inner_scope = try self.scope.createScope(arena); + + const parameters = switch (try source.skip(.newline).expectAny(&.{ .symbol_paren_right, .identifier })) { + .identifier => try inner_scope.parseParameter(arena, source), + .symbol_paren_right => null, + }; + + const parameter_count = inner_scope.defined; + + try source.skip(.newline).expect(.symbol_arrow); + + var peeking = source.*; + const has_return_type = if (peeking.skip(.newline) == .symbol_brace_left) null else try self.scope.parseType(source); + const block = try inner_scope.parseBlock(arena, source); + + if (has_return_type) |return_type| { + const last_statement = block.hasLastStatement() orelse { + return error.MissingReturn; + }; + + if (last_statement.* != .return_expression) { + return error.MissingReturn; + } + + if (try last_statement.return_expression.inferType() != return_type) { + return error.IncompatibleTypes; + } + } + + try self.defineFunction(arena, .{ + .has_return_type = has_return_type, + .parameters = parameters, + .parameter_count = parameter_count, + .identifier = identifier, + .block = block, + .signature = try self.scope.intern(arena, try signature(arena, parameters, has_return_type)), + }); +} + +fn signature(arena: *std.heap.ArenaAllocator, parameters: ?*const coral.shaders.Parameter, has_return_type: ?*const coral.shaders.Type) std.mem.Allocator.Error![:0]const u8 { + var buffer_size = 2 + (if (has_return_type) |return_type| return_type.identifier.len else 0); + + if (parameters) |first_parameter| { + buffer_size += first_parameter.type.identifier.len; + + var has_parameter = first_parameter.has_next; + + while (has_parameter) |parameter| : (has_parameter = parameter.has_next) { + buffer_size += 1 + parameter.type.identifier.len; + } + } + + var buffer = coral.bytes.span(try arena.allocator().allocSentinel(u8, buffer_size, 0)); + + std.debug.assert(buffer.put('(')); + + if (parameters) |first_parameter| { + std.debug.assert(buffer.write(first_parameter.type.identifier) == first_parameter.type.identifier.len); + + var has_parameter = first_parameter.has_next; + + while (has_parameter) |parameter| : (has_parameter = parameter.has_next) { + std.debug.assert(buffer.put(',')); + std.debug.assert(buffer.write(parameter.type.identifier) == parameter.type.identifier.len); + } + } + + std.debug.assert(buffer.put(')')); + + if (has_return_type) |return_type| { + std.debug.assert(buffer.write(return_type.identifier) == return_type.identifier.len); + } + + return @ptrCast(buffer.bytes); +} diff --git a/src/coral/shaders/Scope.zig b/src/coral/shaders/Scope.zig index 05b86d4..e7d7321 100644 --- a/src/coral/shaders/Scope.zig +++ b/src/coral/shaders/Scope.zig @@ -4,15 +4,20 @@ const std = @import("std"); const tokens = @import("./tokens.zig"); -identifiers: [max][:0]const u8 = undefined, -symbols: [max]coral.Box = undefined, -len: usize = 0, -has_enclosing: ?*const Self = null, +interned: coral.tree.Binary([:0]const u8, void, coral.tree.sliceTraits([:0]const u8)) = .empty, +identifiers: [max_definitions][:0]const u8 = undefined, +symbols: [max_definitions]coral.Box = undefined, +defined: usize = 0, +has_enclosing: ?*Self = null, const Self = @This(); pub fn clear(self: *Self) void { - self.len = 0; + self.defined = 0; + + if (self.has_enclosing) |enclosing| { + enclosing.clear(); + } } fn create(arena: *std.heap.ArenaAllocator, node: anytype) std.mem.Allocator.Error!*@TypeOf(node) { @@ -23,7 +28,7 @@ fn create(arena: *std.heap.ArenaAllocator, node: anytype) std.mem.Allocator.Erro return allocation; } -pub fn createScope(self: *const Self, arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!*Self { +pub fn createScope(self: *Self, arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!*Self { return create(arena, Self{ .has_enclosing = self, }); @@ -51,32 +56,32 @@ pub fn define(self: *Self, arena: *std.heap.ArenaAllocator, symbol: anytype) cor })), }; - if (self.exists(identifier)) { + if (self.definitionExists(identifier)) { return error.DuplicateIdentifier; } - if (self.len >= max) { + if (self.defined >= max_definitions) { return error.TooManySymbols; } - const stored_symbol = &self.symbols[self.len]; + const stored_symbol = &self.symbols[self.defined]; - self.identifiers[self.len] = identifier; + self.identifiers[self.defined] = identifier; stored_symbol.* = try .initWithAllocator(arena.allocator(), symbol); - self.len += 1; + self.defined += 1; return stored_symbol.has(Symbol).?; } -pub fn exists(self: Self, identifier: []const u8) bool { - for (self.identifiers) |existing_identifier| { +fn definitionExists(self: Self, identifier: []const u8) bool { + for (self.identifiers[0..self.defined]) |existing_identifier| { if (std.mem.eql(u8, existing_identifier, identifier)) { return true; } } if (self.has_enclosing) |enclosing| { - return enclosing.exists(identifier); + return enclosing.definitionExists(identifier); } return false; @@ -95,7 +100,7 @@ pub fn hasGlobal(self: *const Self, comptime Symbol: type, identifier: []const u } pub fn hasLocal(self: *const Self, comptime Symbol: type, identifier: []const u8) ?*const Symbol { - for (0..self.len) |i| { + for (0..self.defined) |i| { if (std.mem.eql(u8, self.identifiers[i], identifier)) { if (self.symbols[i].has(Symbol)) |symbol| { return symbol; @@ -106,48 +111,22 @@ pub fn hasLocal(self: *const Self, comptime Symbol: type, identifier: []const u8 return null; } -pub const max = 256; +pub fn intern(self: *Self, arena: *std.heap.ArenaAllocator, value: [:0]const u8) std.mem.Allocator.Error![:0]const u8 { + var scope: ?*Self = self; -pub fn parse(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!coral.Box { - const index = self.len; - - switch (try source.current.expectAny(&.{.keyword_function})) { - .keyword_function => { - const function = try self.define(arena, coral.shaders.Function{ - .identifier = try arena.allocator().dupeZ(u8, try source.skip(.newline).expectIdentifier()), - - .block = &.{ - .scope = &.{}, - .depth = 0, - }, - }); - - try source.skip(.newline).expect(.symbol_paren_left); - - const inner_scope = try self.createScope(arena); - - function.has_parameter = switch (try source.skip(.newline).expectAny(&.{ .symbol_paren_right, .identifier })) { - .identifier => try inner_scope.parseParameter(arena, source), - .symbol_paren_right => null, - }; - - try source.skip(.newline).expect(.symbol_arrow); - - var peeking = source.*; - - if (peeking.skip(.newline) != .symbol_brace_left) { - function.has_return_type = try self.parseType(source); - } - - function.block = try inner_scope.parseBlock(arena, source); - }, + while (scope) |current| : (scope = current.has_enclosing) { + if (current.interned.getKey(value)) |existing| { + return existing; + } } - std.debug.assert((index + 1) == self.len); + std.debug.assert(try self.interned.insert(arena.allocator(), value, {}) != null); - return self.symbols[index]; + return value; } +const max_definitions = 256; + fn parseArgument(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Argument { const expression = try self.parseExpression(arena, source); @@ -173,14 +152,14 @@ fn parseArgument(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.S }; } -fn parseBlock(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Block { +pub fn parseBlock(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Block { try source.skip(.newline).expect(.symbol_brace_left); return create(arena, coral.shaders.Block{ .scope = self, .depth = source.depth, - .has_statement = switch (source.skip(.newline)) { + .statements = switch (source.skip(.newline)) { .symbol_brace_right => null, else => try self.parseBlockStatement(arena, source), }, @@ -253,7 +232,7 @@ fn parseBlockStatement(self: *Self, arena: *std.heap.ArenaAllocator, source: *to const local = try self.define(arena, coral.shaders.Local{ .is_constant = source.current != .keyword_var, .identifier = try arena.allocator().dupeZ(u8, try source.next().expectIdentifier()), - .expression = &.{ .int = .{ .literal = "0" } }, + .expression = &.{ .constant = try self.intern(arena, "0") }, .type = .int, }); @@ -300,84 +279,140 @@ fn parseExpression(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens } fn parseAdditiveExpression(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Expression { - const expression = try self.parseEqualityExpression(arena, source); + const lhs_expression = try self.parseEqualityExpression(arena, source); if (source.current == .symbol_plus) { + const rhs_expression = try self.parseEqualityExpression(arena, source); + const lhs_type = try lhs_expression.inferType(); + + if (lhs_type != try rhs_expression.inferType()) { + return error.IncompatibleTypes; + } + return create(arena, coral.shaders.Expression{ .add = .{ - .rhs_expression = try self.parseEqualityExpression(arena, source), - .lhs_expression = expression, + .rhs_expression = rhs_expression, + .lhs_expression = lhs_expression, + .type = lhs_type, }, }); } if (source.current == .symbol_minus) { + const rhs_expression = try self.parseEqualityExpression(arena, source); + const lhs_type = try lhs_expression.inferType(); + + if (lhs_type != try rhs_expression.inferType()) { + return error.IncompatibleTypes; + } + return create(arena, coral.shaders.Expression{ .subtract = .{ - .rhs_expression = try self.parseEqualityExpression(arena, source), - .lhs_expression = expression, + .rhs_expression = rhs_expression, + .lhs_expression = lhs_expression, + .type = lhs_type, }, }); } - return expression; + return lhs_expression; } fn parseComparativeExpression(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Expression { - const expression = try self.parseTermExpression(arena, source); + const lhs_expression = try self.parseTermExpression(arena, source); if (source.current == .symbol_greater_than) { + const rhs_expression = try self.parseTermExpression(arena, source); + const lhs_type = try lhs_expression.inferType(); + + if (lhs_type != try rhs_expression.inferType()) { + return error.IncompatibleTypes; + } + return create(arena, coral.shaders.Expression{ .greater_than = .{ - .rhs_expression = try self.parseTermExpression(arena, source), - .lhs_expression = expression, + .rhs_expression = rhs_expression, + .lhs_expression = lhs_expression, + .type = lhs_type, }, }); } if (source.current == .symbol_greater_equals) { + const rhs_expression = try self.parseTermExpression(arena, source); + const lhs_type = try lhs_expression.inferType(); + + if (lhs_type != try rhs_expression.inferType()) { + return error.IncompatibleTypes; + } + return create(arena, coral.shaders.Expression{ .greater_equal = .{ - .rhs_expression = try self.parseTermExpression(arena, source), - .lhs_expression = expression, + .rhs_expression = rhs_expression, + .lhs_expression = lhs_expression, + .type = lhs_type, }, }); } if (source.current == .symbol_lesser_than) { + const rhs_expression = try self.parseTermExpression(arena, source); + const lhs_type = try lhs_expression.inferType(); + + if (lhs_type != try rhs_expression.inferType()) { + return error.IncompatibleTypes; + } + return create(arena, coral.shaders.Expression{ .divide = .{ - .rhs_expression = try self.parseTermExpression(arena, source), - .lhs_expression = expression, + .rhs_expression = rhs_expression, + .lhs_expression = lhs_expression, + .type = lhs_type, }, }); } if (source.current == .symbol_lesser_equals) { + const rhs_expression = try self.parseTermExpression(arena, source); + const lhs_type = try lhs_expression.inferType(); + + if (lhs_type != try rhs_expression.inferType()) { + return error.IncompatibleTypes; + } + return create(arena, coral.shaders.Expression{ .divide = .{ - .rhs_expression = try self.parseTermExpression(arena, source), - .lhs_expression = expression, + .rhs_expression = rhs_expression, + .lhs_expression = lhs_expression, + .type = lhs_type, }, }); } - return expression; + return lhs_expression; } fn parseEqualityExpression(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Expression { - const expression = try self.parseComparativeExpression(arena, source); + const lhs_expression = try self.parseComparativeExpression(arena, source); if (source.current == .symbol_double_equals) { + const rhs_expression = try self.parseComparativeExpression(arena, source); + const lhs_type = try lhs_expression.inferType(); + + if (lhs_type != try rhs_expression.inferType()) { + return error.IncompatibleTypes; + } + return create(arena, coral.shaders.Expression{ .equal = .{ - .rhs_expression = try self.parseComparativeExpression(arena, source), - .lhs_expression = expression, + .rhs_expression = rhs_expression, + .lhs_expression = lhs_expression, + .type = lhs_type, }, }); } - return expression; + return lhs_expression; } fn parseFactorExpression(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Expression { @@ -421,22 +456,8 @@ fn parseOperandExpression(self: *Self, arena: *std.heap.ArenaAllocator, source: }, .scalar => { - const arena_allocator = arena.allocator(); - const scalar = try source.current.expectScalar(); - - if (std.mem.indexOfScalar(u8, scalar, '.')) |index| { - return create(arena, coral.shaders.Expression{ - .float = .{ - .whole = try arena_allocator.dupeZ(u8, scalar[0..index]), - .decimal = try arena_allocator.dupeZ(u8, scalar[index + 1 ..]), - }, - }); - } - - return create(arena, coral.shaders.Expression{ - .int = .{ - .literal = try arena_allocator.dupeZ(u8, scalar), - }, + return try create(arena, coral.shaders.Expression{ + .constant = try arena.allocator().dupeZ(u8, try source.current.expectScalar()), }); }, @@ -458,10 +479,22 @@ fn parseOperandExpression(self: *Self, arena: *std.heap.ArenaAllocator, source: }, else => { + const arguments = try self.parseArgument(arena, source); + var argument_count: usize = 1; + + { + var subsequent_arguments = arguments.has_next; + + while (subsequent_arguments) |argument| : (subsequent_arguments = argument.has_next) { + argument_count += 1; + } + } + return try create(arena, coral.shaders.Expression{ .invoke = .{ .function = function, - .has_argument = try self.parseArgument(arena, source), + .argument_count = argument_count, + .arguments = arguments, }, }); }, @@ -643,30 +676,46 @@ fn parseOperandExpression(self: *Self, arena: *std.heap.ArenaAllocator, source: } fn parseTermExpression(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Expression { - const expression = try self.parseFactorExpression(arena, source); + const lhs_expression = try self.parseFactorExpression(arena, source); if (source.current == .symbol_asterisk) { + const rhs_expression = try self.parseFactorExpression(arena, source); + const lhs_type = try lhs_expression.inferType(); + + if (lhs_type != try rhs_expression.inferType()) { + return error.IncompatibleTypes; + } + return try create(arena, coral.shaders.Expression{ .multiply = .{ - .rhs_expression = try self.parseFactorExpression(arena, source), - .lhs_expression = expression, + .rhs_expression = rhs_expression, + .lhs_expression = lhs_expression, + .type = lhs_type, }, }); } if (source.current == .symbol_forward_slash) { + const rhs_expression = try self.parseFactorExpression(arena, source); + const lhs_type = try lhs_expression.inferType(); + + if (lhs_type != try rhs_expression.inferType()) { + return error.IncompatibleTypes; + } + return try create(arena, coral.shaders.Expression{ .divide = .{ - .rhs_expression = try self.parseFactorExpression(arena, source), - .lhs_expression = expression, + .rhs_expression = rhs_expression, + .lhs_expression = lhs_expression, + .type = lhs_type, }, }); } - return expression; + return lhs_expression; } -fn parseParameter(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!?*const coral.shaders.Parameter { +pub fn parseParameter(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!?*const coral.shaders.Parameter { const parameter = try self.define(arena, coral.shaders.Parameter{ .identifier = try arena.allocator().dupeZ(u8, try source.current.expectIdentifier()), .type = .int, @@ -684,7 +733,7 @@ fn parseParameter(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens. return parameter; } -fn parseType(self: *const Self, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Type { +pub fn parseType(self: *const Self, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Type { return switch (source.skip(.newline)) { .keyword_float => .float, .keyword_float4x4 => .float4x4, diff --git a/src/coral/shaders/Type.zig b/src/coral/shaders/Type.zig index e47ad8e..f48170c 100644 --- a/src/coral/shaders/Type.zig +++ b/src/coral/shaders/Type.zig @@ -1,65 +1,117 @@ const coral = @import("../coral.zig"); +const std = @import("std"); + identifier: [:0]const u8, layout: Layout, pub const Layout = union(enum) { - handle, - scalar, - vector: VectorWidth, - matrix: VectorWidth, - record_fields: ?*const coral.shaders.Field, + float: Scalar, + signed: Scalar, + unsigned: Scalar, + vector: Vector, + matrix: Matrix, + texture: Texture, + record: Record, }; const Self = @This(); -pub const VectorWidth = enum(u2) { - @"1", +pub const Dimensions = enum(u2) { @"2", @"3", @"4", + + pub fn count(self: Dimensions) std.math.IntFittingRange(2, 4) { + return switch (self) { + .@"2" => 2, + .@"3" => 3, + .@"4" => 4, + }; + } +}; + +pub const Matrix = struct { + element: *const Self, + dimensions: Dimensions, +}; + +pub const Record = struct { + field_count: usize = 0, + fields: ?*const coral.shaders.Field = null, +}; + +pub const Scalar = struct { + bits: u8, +}; + +pub const Texture = struct { + dimensions: Dimensions, + is_depth: bool, + is_arary: bool, + is_multi_sampled: bool, +}; + +pub const Vector = struct { + element: *const Self, + dimensions: Dimensions, }; pub const float = &Self{ .identifier = "float", - .layout = .scalar, + .layout = .{ .float = .{ .bits = 32 } }, }; pub const float2 = &Self{ .identifier = "float2", - .layout = .{ .vector = .@"2" }, + + .layout = .{ + .vector = .{ + .element = .float, + .dimensions = .@"2", + }, + }, }; pub const float3 = &Self{ .identifier = "float3", - .layout = .{ .vector = .@"3" }, + + .layout = .{ + .vector = .{ + .element = .float, + .dimensions = .@"3", + }, + }, }; pub const float4 = &Self{ .identifier = "float4", - .layout = .{ .vector = .@"4" }, + + .layout = .{ + .vector = .{ + .element = .float, + .dimensions = .@"4", + }, + }, }; pub const float4x4 = &Self{ .identifier = "float4x4", - .layout = .{ .matrix = .@"4" }, + + .layout = .{ + .matrix = .{ + .element = .float, + .dimensions = .@"2", + }, + }, }; pub fn hasField(self: Self, field_identifier: []const u8) ?*const coral.shaders.Field { return switch (self.layout) { - .scalar, .matrix, .handle => null, - .record_fields => |has_field| if (has_field) |field| field.has(field_identifier) else null, - - .vector => |vector_width| switch (vector_width) { - .@"1" => switch (field_identifier.len) { - 1 => switch (field_identifier[0]) { - 'x', 'r' => .vector_x, - else => null, - }, - - else => null, - }, + .float, .signed, .unsigned, .matrix, .texture => null, + .record => |record| if (record.fields) |field| field.has(field_identifier) else null, + .vector => |vector| switch (vector.dimensions) { .@"2" => switch (field_identifier.len) { 1 => switch (field_identifier[0]) { 'x', 'r' => .vector_x, @@ -915,10 +967,18 @@ pub fn hasField(self: Self, field_identifier: []const u8) ?*const coral.shaders. pub const int = &Self{ .identifier = "int", - .layout = .scalar, + .layout = .{ .signed = .{ .bits = 32 } }, }; pub const texture2 = &Self{ .identifier = "texture2", - .layout = .handle, + + .layout = .{ + .texture = .{ + .dimensions = .@"2", + .is_arary = false, + .is_depth = false, + .is_multi_sampled = false, + }, + }, }; diff --git a/src/coral/shaders/spirv.zig b/src/coral/shaders/spirv.zig index 537de38..cf35f3b 100644 --- a/src/coral/shaders/spirv.zig +++ b/src/coral/shaders/spirv.zig @@ -2,73 +2,523 @@ const coral = @import("../coral.zig"); const std = @import("std"); +pub const AccessQualifier = enum(u32) { + read_only = 0, + write_only = 1, + read_write = 2, +}; + pub const AddressingModel = enum(u32) { logical = 0, }; +pub const BuildError = std.mem.Allocator.Error || error{OutOfIds}; + pub const Capability = enum(u32) { matrix = 0, shader = 1, }; -pub const Document = struct { - ids_assigned: u32 = 0, - ops: coral.Stack(u32) = .empty, +pub const Dim = enum(u32) { + @"1d" = 0, + @"2d" = 1, + @"3d" = 2, + cube = 4, +}; - fn nextId(self: *Document) u32 { - self.ids_assigned += 1; +pub const ExecutionModel = enum(u32) { + fragment = 4, +}; + +pub const FloatType = struct { + id: u32, + bits: u32, +}; + +pub const FunctionControl = packed struct(u32) { + @"inline": bool = false, + dont_inline: bool = false, + pure: bool = false, + @"const": bool = false, + reserved: u28 = 0, +}; + +pub const FunctionType = struct { + id: u32, + parameter_types: []const *const Type, + return_type: *const Type, +}; + +pub const ImageFormat = enum(u32) { + unknown = 0, +}; + +pub const ImageType = struct { + id: u32, + sampled_type: *const Type, + dimensions: Dim, + depth: u32, + arrayed: u32, + multi_sampled: u32, + sampled: u32, + format: ImageFormat, + access: AccessQualifier, +}; + +pub const IntType = struct { + id: u32, + bits: u32, + is_signed: bool, +}; + +pub const MemoryModel = enum(u32) { + glsl450 = 1, +}; + +pub const Module = struct { + ids_assigned: u32 = 0, + capabilities: []const Capability, + memory_model: struct { AddressingModel, MemoryModel }, + entry_points: PtrTree(*const coral.shaders.Function, EntryPoint) = .empty, + types: PtrTree(*allowzero const anyopaque, Type) = .empty, + inputs: PtrTree(*const coral.shaders.Input, Value) = .empty, + functions: PtrTree(*const coral.shaders.Function, Function) = .empty, + constants: PtrTree([*:0]const u8, Constant) = .empty, + + pub const Block = struct { + id: u32, + variables: PtrTree(*const coral.shaders.Local, Value) = .empty, + instructions: coral.list.Linked(Instruction, 8) = .empty, + + pub fn shaderExpression(self: *Block, module: *Module, function: *Function, arena: *std.heap.ArenaAllocator, shader_expression: *const coral.shaders.Expression) BuildError!*const Value { + const arena_allocator = arena.allocator(); + + switch (shader_expression.*) { + .group_expression => |expression| { + return self.shaderExpression(module, function, arena, expression); + }, + + .constant => |constant| { + return module.shaderConstant(arena, constant); + }, + + .get_input => |input| { + return module.shaderInput(arena, input); + }, + + .get_parameter => |parameter| { + return function.parameters.get(parameter) orelse { + @panic("parameter does not exist in function scope"); + }; + }, + + .invoke => |invoke| { + const arguments = try arena.allocator().alloc(*const Value, invoke.argument_count); + + { + var shader_arguments = invoke.arguments; + + for (arguments) |*argument| { + argument.* = try self.shaderExpression(module, function, arena, shader_arguments.?.expression); + shader_arguments = shader_arguments.?.has_next; + } + } + + const instruction = try self.instructions.append(arena_allocator, .{ + .call_function = .{ + .arguments = arguments, + .function = try module.shaderFunction(arena, invoke.function), + + .result = .{ + .type = try module.shaderValueType(arena, invoke.function.has_return_type), + .id = try module.nextId(), + }, + }, + }); + + return &instruction.call_function.result; + }, + + .add => |binary_operation| { + const instruction = try self.instructions.append(arena_allocator, .{ + .add = .{ + .operands = .{ + try self.shaderExpression(module, function, arena, binary_operation.lhs_expression), + try self.shaderExpression(module, function, arena, binary_operation.rhs_expression), + }, + + .result = .{ + .type = try module.shaderValueType(arena, binary_operation.type), + .id = try module.nextId(), + }, + }, + }); + + return &instruction.add.result; + }, + + .subtract => |binary_operation| { + const instruction = try self.instructions.append(arena_allocator, .{ + .subtract = .{ + .operands = .{ + try self.shaderExpression(module, function, arena, binary_operation.lhs_expression), + try self.shaderExpression(module, function, arena, binary_operation.rhs_expression), + }, + + .result = .{ + .type = try module.shaderValueType(arena, binary_operation.type), + .id = try module.nextId(), + }, + }, + }); + + return &instruction.subtract.result; + }, + + .multiply => |binary_operation| { + const instruction = try self.instructions.append(arena_allocator, .{ + .add = .{ + .operands = .{ + try self.shaderExpression(module, function, arena, binary_operation.lhs_expression), + try self.shaderExpression(module, function, arena, binary_operation.rhs_expression), + }, + + .result = .{ + .type = try module.shaderValueType(arena, binary_operation.type), + .id = try module.nextId(), + }, + }, + }); + + return &instruction.multiply.result; + }, + + .divide => |binary_operation| { + const instruction = try self.instructions.append(arena_allocator, .{ + .add = .{ + .operands = .{ + try self.shaderExpression(module, function, arena, binary_operation.lhs_expression), + try self.shaderExpression(module, function, arena, binary_operation.rhs_expression), + }, + + .result = .{ + .type = try module.shaderValueType(arena, binary_operation.type), + .id = try module.nextId(), + }, + }, + }); + + return &instruction.divide.result; + }, + + else => unreachable, + } + } + + pub fn shaderVariable(self: *Block, module: *Module, function: *Function, arena: *std.heap.ArenaAllocator, local: *const coral.shaders.Local) BuildError!*const Value { + return self.variables.get(local) orelse { + const arena_allocator = arena.allocator(); + + const variable = (try self.variables.insert(arena_allocator, local, .{ + .type = try module.shaderValueType(arena, local.type), + .id = try module.nextId(), + })).?; + + _ = try self.instructions.append(arena_allocator, .{ + .store = .{ + .target = variable, + .source = try self.shaderExpression(module, function, arena, local.expression), + }, + }); + + return variable; + }; + } + }; + + pub const Constant = struct { + value: Value, + data: []const u32, + }; + + pub const EntryPoint = struct { + id: u32, + function: *const Function, + execution_model: ExecutionModel, + interfaces: PtrTree(*const Value, void) = .empty, + }; + + pub const Function = struct { + id: u32, + type: *const Type, + control: FunctionControl, + parameters: PtrTree(*const coral.shaders.Parameter, Value) = .empty, + block: Block, + }; + + pub const Instruction = union(enum) { + store: struct { target: *const Value, source: *const Value }, + call_function: FunctionCall, + add: BinaryOp, + subtract: BinaryOp, + multiply: BinaryOp, + divide: BinaryOp, + + pub const BinaryOp = struct { + result: Value, + operands: [2]*const Value, + }; + + pub const FunctionCall = struct { + result: Value, + function: *const Function, + arguments: []*const Value, + }; + }; + + fn PtrTree(comptime Ptr: type, comptime Node: type) type { + return coral.tree.Binary(Ptr, Node, coral.tree.scalarTraits(Ptr)); + } + + fn nextId(self: *Module) BuildError!u32 { + self.ids_assigned = coral.scalars.add(self.ids_assigned, 1) orelse { + return error.OutOfIds; + }; return self.ids_assigned; } - pub fn op(self: *Document, code: u32) std.mem.Allocator.Error!void { - try self.ops.pushGrow(std.mem.nativeToLittle(u32, code)); - } + pub fn shaderConstant(self: *Module, arena: *std.heap.ArenaAllocator, shader_constant: [:0]const u8) BuildError!*const Value { + if (self.constants.get(shader_constant)) |constant| { + return &constant.value; + } - pub fn opCapability(self: *Document, capability: Capability) std.mem.Allocator.Error!void { - try self.op(17); - try self.op(@intFromEnum(capability)); - } + const arena_allocator = arena.allocator(); + const data = try arena_allocator.alloc(u32, 1); - pub fn opEntryPoint(self: *Document, name: []const u8, interfaces: []const u32) std.mem.Allocator.Error!void { - try self.op(14); - try self.string(name); + if (std.mem.indexOfScalar(u8, shader_constant, '.') == null) { + data[0] = std.mem.nativeToLittle(u32, @bitCast(coral.utf8.DecFormat.c.parse(i32, shader_constant) orelse { + @panic("malformed constant"); + })); - for (interfaces) |interface| { - self.op(interface); + const constant = (try self.constants.insert(arena_allocator, shader_constant, .{ + .data = data, + + .value = .{ + .type = try self.shaderValueType(arena, .int), + .id = try self.nextId(), + }, + })).?; + + return &constant.value; + } else { + data[0] = std.mem.nativeToLittle(u32, @bitCast(coral.utf8.DecFormat.c.parse(f32, shader_constant) orelse { + @panic("malformed constant"); + })); + + const constant = (try self.constants.insert(arena_allocator, shader_constant, .{ + .data = data, + + .value = .{ + .type = try self.shaderValueType(arena, .float), + .id = try self.nextId(), + }, + })).?; + + return &constant.value; } } - pub fn opMemoryModel(self: *Document, addressing: AddressingModel, memory: MemoryModel) std.mem.Allocator.Error!void { - try self.op(14); - try self.op(@intFromEnum(addressing)); - try self.op(@intFromEnum(memory)); + pub fn shaderEntryPoint( + self: *Module, + arena: *std.heap.ArenaAllocator, + execution_model: ExecutionModel, + shader_function: *const coral.shaders.Function, + ) BuildError!*const EntryPoint { + return self.entry_points.get(shader_function) orelse { + const arena_allocator = arena.allocator(); + + const entry_point = (try self.entry_points.insert(arena_allocator, shader_function, .{ + .execution_model = execution_model, + .function = try self.shaderFunction(arena, shader_function), + .id = try self.nextId(), + })).?; + + // try entry_point.registerBlockInterfaces(shader_function.block); + + return entry_point; + }; } - pub fn opExtInstImport(self: *Document, extension: []const u8) std.mem.Allocator.Error!u32 { - const id = self.nextId(); + pub fn shaderFunction( + self: *Module, + arena: *std.heap.ArenaAllocator, + shader_function: *const coral.shaders.Function, + ) BuildError!*const Function { + return self.functions.get(shader_function) orelse { + const arena_allocator = arena.allocator(); - try self.op(11); - try self.op(id); - try self.string(extension); + const function = (try self.functions.insert(arena_allocator, shader_function, .{ + .control = .{}, + .type = try self.shaderFunctionType(arena, shader_function), + .id = try self.nextId(), + .block = .{ .id = try self.nextId() }, + })).?; - return id; + { + var parameters = shader_function.parameters; + + while (parameters) |parameter| : (parameters = parameter.has_next) { + _ = try function.parameters.insert(arena_allocator, parameter, .{ + .type = try self.shaderValueType(arena, parameter.type), + .id = try self.nextId(), + }); + } + } + + { + var statements = shader_function.block.statements; + + while (statements) |statement| { + switch (statement.*) { + .declare_local => |local_declaration| { + _ = try function.block.shaderVariable(self, function, arena, local_declaration.local); + statements = local_declaration.has_next; + }, + + .mutate_local => {}, + .mutate_output => {}, + + .return_expression => |_| {}, + } + } + } + + return function; + }; } - pub fn string(self: *Document, data: []const u8) std.mem.Allocator.Error!void { - const data_len_with_sentinel = data.len + 1; - const word_count = (data_len_with_sentinel + 4) / 4; + pub fn shaderFunctionType( + self: *Module, + arena: *std.heap.ArenaAllocator, + shader_function: *const coral.shaders.Function, + ) BuildError!*const Type { + const function_signature_address: *allowzero const anyopaque = @ptrCast(shader_function.signature); - try self.ops.grow(word_count); + return self.types.get(function_signature_address) orelse { + const arena_allocator = arena.allocator(); + const parameter_types = try arena_allocator.alloc(*const Type, shader_function.parameter_count); - std.debug.assert(self.ops.pushMany(word_count, 0)); + { + var parameters = shader_function.parameters; - const buffer = std.mem.sliceAsBytes(self.ops.values[(self.ops.values.len - word_count)..]); + for (parameter_types) |*parameter_type| { + parameter_type.* = try self.shaderValueType(arena, parameters.?.type); + parameters = parameters.?.has_next; + } + } - @memcpy(buffer[0..data.len], data); + return (try self.types.insert(arena_allocator, function_signature_address, .{ + .function = .{ + .parameter_types = parameter_types, + .return_type = try self.shaderValueType(arena, shader_function.has_return_type), + .id = try self.nextId(), + }, + })).?; + }; } - pub fn writeTo(self: Document, spirv: coral.bytes.Writable) coral.bytes.ReadWriteError!void { + pub fn shaderInput( + self: *Module, + arena: *std.heap.ArenaAllocator, + shader_input: *const coral.shaders.Input, + ) BuildError!*const Value { + return self.inputs.get(shader_input) orelse (try self.inputs.insert(arena.allocator(), shader_input, .{ + .type = try self.shaderValueType(arena, shader_input.type), + .id = try self.nextId(), + })).?; + } + + pub fn shaderValueType( + self: *Module, + arena: *std.heap.ArenaAllocator, + has_shader_type: ?*const coral.shaders.Type, + ) BuildError!*const Type { + const arena_allocator = arena.allocator(); + const symbol_address: *allowzero const anyopaque = @ptrCast(has_shader_type); + + return self.types.get(symbol_address) orelse { + const id = try self.nextId(); + + const symbol = has_shader_type orelse { + return (try self.types.insert(arena_allocator, symbol_address, .{ + .void = .{ + .id = id, + }, + })).?; + }; + + switch (symbol.layout) { + .signed, .unsigned => |scalar| { + return (try self.types.insert(arena_allocator, symbol_address, .{ + .int = .{ + .bits = scalar.bits, + .is_signed = (symbol.layout == .signed), + .id = id, + }, + })).?; + }, + + .float => |float| { + return (try self.types.insert(arena_allocator, symbol_address, .{ + .float = .{ + .bits = float.bits, + .id = id, + }, + })).?; + }, + + .vector => |vector| { + return (try self.types.insert(arena_allocator, symbol_address, .{ + .vector = .{ + .id = id, + .component_type = try self.shaderValueType(arena, vector.element), + .component_len = vector.dimensions.count(), + }, + })).?; + }, + + .matrix, .record => unreachable, + + .texture => { + const image_type = try arena_allocator.create(Type); + + image_type.* = .{ + .image = .{ + .depth = 0, + .arrayed = 0, + .multi_sampled = 0, + .sampled = 1, + .dimensions = .@"2d", + .format = .unknown, + .access = .read_only, + .sampled_type = try self.shaderValueType(arena, .float), + .id = try self.nextId(), + }, + }; + + return (try self.types.insert(arena_allocator, symbol_address, .{ + .sampled_image = .{ + .image_type = image_type, + .id = try self.nextId(), + }, + })).?; + }, + } + }; + } + + pub fn writeTo(self: Module, spirv: coral.bytes.Writable) coral.bytes.ReadWriteError!void { const Header = extern struct { magic_number: u32 = 0x07230203, // SPIR-V magic number in little-endian version: u32, // SPIR-V version (e.g., 0x00010000 for 1.0) @@ -77,6 +527,12 @@ pub const Document = struct { reserved: u32 = 0, // Reserved, always 0 }; + const OpInstruction = enum(u32) { + memory_model = 14, + entry_point = 15, + execution_mode = 16, + }; + try coral.bytes.writeLittle(spirv, Header{ .magic_number = 0x07230203, .version = 0x00010000, @@ -84,24 +540,81 @@ pub const Document = struct { .id_upper_bound = self.ids_assigned, }); - return coral.bytes.writeAll(spirv, std.mem.sliceAsBytes(self.ops.values)); + { + const addressing, const memory = self.memory_model; + + try coral.bytes.writeLittle(spirv, OpInstruction.memory_model); + try coral.bytes.writeLittle(spirv, addressing); + try coral.bytes.writeLittle(spirv, memory); + } + + { + var entry_points = self.entry_points.keyValues(); + + while (entry_points.next()) |entry_point| { + try coral.bytes.writeLittle(spirv, OpInstruction.entry_point); + try coral.bytes.writeLittle(spirv, entry_point.value.execution_model); + try coral.bytes.writeLittle(spirv, entry_point.value.function.id); + try writeString(spirv, entry_point.key.identifier); + + var interfaces = entry_point.value.interfaces.keyValues(); + + while (interfaces.nextKey()) |interface| { + try coral.bytes.writeLittle(spirv, interface.*.id); + } + + try coral.bytes.writeLittle(spirv, OpInstruction.execution_mode); + try coral.bytes.writeLittle(spirv, entry_point.value.function.id); + try coral.bytes.writeLittle(spirv, @as(u32, 7)); + } + } } }; -pub const MemoryModel = enum(u32) { - glsl450 = 1, +pub const Value = struct { + id: u32, + type: *const Type, }; -pub fn generate(spirv: coral.bytes.Writable, root: coral.shaders.Root) (coral.shaders.GenerationError || std.mem.Allocator.Error)!void { - // TODO: Finish and testing coverage. - var arena = std.heap.ArenaAllocator.init(coral.heap.allocator); +pub const SampledImageType = struct { + id: u32, + image_type: *const Type, +}; - var module = Module{ - .capabilities = &.{.shader}, - .memory_model = .{ .logical, .glsl450 }, - }; +pub const StructType = struct { + id: u32, + member_types: []const *const Type, +}; - const glsl_std_450 = module.importExtension(&arena, "GLSL.std.450"); - // TODO: specify frag entry point - const frag = module.entryPoint(.fragment, "main", .{}); +pub const Type = union(enum) { + void: VoidType, + float: FloatType, + vector: VectorType, + image: ImageType, + @"struct": StructType, + function: FunctionType, + int: IntType, + sampled_image: SampledImageType, +}; + +pub const VectorType = struct { + id: u32, + component_type: *const Type, + component_len: u32, +}; + +pub const VoidType = struct { + id: u32, +}; + +fn writeString(spirv: coral.bytes.Writable, data: []const u8) coral.bytes.ReadWriteError!void { + const data_len_with_sentinel = data.len + 1; + const word_size = @sizeOf(u32); + const word_count = (data_len_with_sentinel + word_size) / word_size; + + try coral.bytes.writeSentineled(spirv, data, 0); + + const padding = (word_count * word_size) - data_len_with_sentinel; + + try coral.bytes.writeN(spirv, "\x00", padding); } diff --git a/src/coral/tree.zig b/src/coral/tree.zig new file mode 100644 index 0000000..1188d3f --- /dev/null +++ b/src/coral/tree.zig @@ -0,0 +1,337 @@ +const coral = @import("./coral.zig"); + +const std = @import("std"); + +pub fn Binary(comptime Key: type, comptime Value: type, comptime traits: Traits(Key)) type { + return struct { + free_nodes: ?*Node = null, + active_nodes: ?*Node = null, + + const Node = struct { + key: Key, + value: Value, + has_left: ?*Node = null, + has_right: ?*Node = null, + has_parent: ?*Node = null, + + fn deinit(self: *Node, allocator: std.mem.Allocator) void { + self.has_parent = undefined; + + if (self.has_left) |left| { + left.deinit(allocator); + allocator.destroy(left); + } + + self.has_left = undefined; + + if (self.has_right) |right| { + right.deinit(allocator); + allocator.destroy(right); + } + + self.has_right = undefined; + } + }; + + pub const KeyValues = struct { + nodes: ?*Node, + + pub fn next(self: *KeyValues) ?coral.KeyValuePair(Key, *Value) { + var nodes = self.nodes; + + while (nodes) |node| { + const left = node.has_left orelse { + self.nodes = node.has_right; + + return .{ + .key = node.key, + .value = &node.value, + }; + }; + + // Find the rightmost node in left subtree or link back to current + var pred = left; + while (pred.has_right != null and pred.has_right != node) { + pred = pred.has_right.?; + } + + if (pred.has_right != null) { + pred.has_right = null; + self.nodes = node.has_right; + + return .{ + .key = node.key, + .value = &node.value, + }; + } + + pred.has_right = node; + self.nodes = node.has_left; + nodes = self.nodes; + } + + return null; + } + + pub fn nextKey(self: *KeyValues) ?Key { + return if (self.next()) |key_value| key_value.key else null; + } + + pub fn nextValue(self: *KeyValues) ?*Value { + return if (self.next()) |key_value| key_value.value else null; + } + }; + + const Self = @This(); + + pub fn clear(self: *Self) void { + var free_nodes: ?*Node = null; + + if (self.active_nodes) |root| { + // Push root onto stack + root.has_parent = free_nodes; + free_nodes = root; + self.active_nodes = null; + } + + while (free_nodes) |node| { + // Pop node from stack + free_nodes = node.has_parent; + + // Push children onto stack + if (node.has_left) |left| { + left.has_parent = free_nodes; + free_nodes = left; + } + + if (node.has_right) |right| { + right.has_parent = free_nodes; + free_nodes = right; + } + + // Add node to free list + node.has_left = null; + node.has_right = null; + node.has_parent = null; + node.key = undefined; + node.value = undefined; + node.has_right = self.free_nodes; + self.free_nodes = node; + } + } + + pub fn createNode(self: *Self, allocator: std.mem.Allocator, node: Node) std.mem.Allocator.Error!*Node { + if (self.free_nodes) |free_node| { + self.free_nodes = free_node.has_parent; + + free_node.* = node; + + return free_node; + } + + const created_node = try allocator.create(Node); + + created_node.* = node; + + return created_node; + } + + pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { + if (self.active_nodes) |node| { + node.deinit(allocator); + allocator.destroy(node); + } + + self.active_nodes = undefined; + } + + pub fn insert(self: *Self, allocator: std.mem.Allocator, key: Key, value: Value) std.mem.Allocator.Error!?*Value { + var node = self.active_nodes orelse { + self.active_nodes = try self.createNode(allocator, .{ + .key = key, + .value = value, + }); + + return &self.active_nodes.?.value; + }; + + while (true) { + switch (traits.compare(key, node.key)) { + .equal => { + return null; + }, + + .lesser => { + node = node.has_left orelse { + node.has_left = try self.createNode(allocator, .{ + .key = key, + .value = value, + .has_parent = node, + }); + + return &node.has_left.?.value; + }; + }, + + .greater => { + node = node.has_right orelse { + node.has_right = try self.createNode(allocator, .{ + .key = key, + .value = value, + .has_parent = node, + }); + + return &node.has_right.?.value; + }; + }, + } + } + } + + pub const empty = Self{ + .active_nodes = null, + }; + + pub fn get(self: Self, key: Key) ?*Value { + var nodes = self.active_nodes; + + while (nodes) |node| { + nodes = switch (traits.compare(key, node.key)) { + .lesser => node.has_left, + .greater => node.has_right, + + .equal => { + return &node.value; + }, + }; + } + + return null; + } + + pub fn getKey(self: Self, key: Key) ?Key { + var nodes = self.active_nodes; + + while (nodes) |node| { + nodes = switch (traits.compare(key, node.key)) { + .lesser => node.has_left, + .greater => node.has_right, + + .equal => { + return node.key; + }, + }; + } + + return null; + } + + pub fn isEmpty(self: *Self) bool { + return self.active_nodes == null; + } + + pub fn keyValues(self: *const Self) KeyValues { + return .{ .nodes = self.active_nodes }; + } + }; +} + +pub const Comparison = enum(i2) { + lesser = -1, + equal = 0, + greater = 1, +}; + +pub fn Traits(comptime Key: type) type { + return struct { + compare: fn (Key, Key) Comparison, + }; +} + +pub fn scalarTraits(comptime Scalar: type) Traits(Scalar) { + const traits = switch (@typeInfo(Scalar)) { + .@"enum" => struct { + fn compare(a: Scalar, b: Scalar) Comparison { + const a_int = @intFromEnum(a); + const b_int = @intFromEnum(b); + + if (a_int < b_int) { + return .lesser; + } + + if (a_int > b_int) { + return .greater; + } + + return .equal; + } + }, + + .pointer => struct { + fn compare(a: Scalar, b: Scalar) Comparison { + const a_int = @intFromPtr(a); + const b_int = @intFromPtr(b); + + if (a_int < b_int) { + return .lesser; + } + + if (a_int > b_int) { + return .greater; + } + + return .equal; + } + }, + + .int => struct { + fn compare(a: Scalar, b: Scalar) Comparison { + if (a < b) { + return .lesser; + } + + if (a > b) { + return .greater; + } + + return .equal; + } + }, + + else => { + @compileError(std.fmt.comptimePrint("parameter `Scalar` must be a scalar type, not {s}", .{ + @typeName(Scalar), + })); + }, + }; + + return .{ + .compare = traits.compare, + }; +} + +pub fn sliceTraits(comptime Slice: type) Traits(Slice) { + const slice_pointer = switch (@typeInfo(Slice)) { + .pointer => |pointer| pointer, + + else => { + @compileError(std.fmt.comptimePrint("parameter `Slice` must be a slice type, not {s}", .{ + @typeName(Slice), + })); + }, + }; + + const traits = struct { + fn compare(a: Slice, b: Slice) Comparison { + return switch (std.mem.order(slice_pointer.child, a, b)) { + .lt => .lesser, + .gt => .greater, + .eq => .equal, + }; + } + }; + + return .{ + .compare = traits.compare, + }; +} diff --git a/src/coral/utf8.zig b/src/coral/utf8.zig index 61428ac..45561cf 100644 --- a/src/coral/utf8.zig +++ b/src/coral/utf8.zig @@ -44,16 +44,16 @@ pub fn Dec(comptime Value: type) type { } pub fn writeFormat(self: Self, writer: coral.bytes.Writable) coral.bytes.ReadWriteError!void { - if (self.value == 0) { - return coral.bytes.writeAll(writer, switch (self.format.positive_prefix) { - .none => "0", - .plus => "+0", - .space => " 0", - }); - } - switch (@typeInfo(Value)) { .int => |int| { + if (self.value == 0) { + return coral.bytes.writeAll(writer, switch (self.format.positive_prefix) { + .none => "0", + .plus => "+0", + .space => " 0", + }); + } + const radix = 10; var buffer = [_]u8{0} ** (1 + @max(int.bits, 1)); var buffer_start = buffer.len - 1; @@ -81,6 +81,14 @@ pub fn Dec(comptime Value: type) type { }, .float => |float| { + if (self.value == 0) { + return coral.bytes.writeAll(writer, switch (self.format.positive_prefix) { + .none => "0", + .plus => "+0", + .space => " 0", + }); + } + if (self.value < 0) { try coral.bytes.writeAll(writer, "-"); } @@ -102,7 +110,18 @@ pub fn Dec(comptime Value: type) type { try repacked.writeFormat(writer); }, - else => @compileError("`" ++ @typeName(Value) ++ "` cannot be formatted to a decimal string"), + .@"enum" => |@"enum"| { + const repacked = Dec(@"enum".tag_type){ + .value = @intFromEnum(self.value), + .format = self.format, + }; + + try repacked.writeFormat(writer); + }, + + else => { + @compileError("`" ++ @typeName(Value) ++ "` is not a valid decimal value"); + }, } } }; @@ -112,13 +131,18 @@ pub const DecFormat = struct { delimiter: []const u8, positive_prefix: enum { none, plus, space }, - pub fn parse(self: DecFormat, utf8: []const u8, comptime Value: type) ?Value { + pub const c = &DecFormat{ + .delimiter = "", + .positive_prefix = .none, + }; + + pub fn parse(self: DecFormat, comptime Value: type, utf8: []const u8) ?Value { if (utf8.len == 0) { return null; } switch (@typeInfo(Value)) { - .Int => |int| { + .int => |int| { const has_sign = switch (utf8[0]) { '-', '+', ' ' => true, else => false, @@ -168,7 +192,7 @@ pub const DecFormat = struct { } }, - .Float => { + .float => { const has_sign = switch (utf8[0]) { '-', '+', ' ' => true, else => false, @@ -221,21 +245,12 @@ pub const DecFormat = struct { }; test "decimal parsing" { - const format = DecFormat{ - .delimiter = "", - .positive_prefix = .none, - }; - - try std.testing.expectEqual(format.parse("69", i64), 69); + try std.testing.expectEqual(DecFormat.c.parse("69", i64), 69); } pub fn cDec(value: anytype) Dec(@TypeOf(value)) { return .{ .value = value, - - .format = &.{ - .delimiter = "", - .positive_prefix = .none, - }, + .format = .c, }; } diff --git a/src/ona/App.zig b/src/ona/App.zig index 8b758c9..c0ac708 100644 --- a/src/ona/App.zig +++ b/src/ona/App.zig @@ -10,8 +10,8 @@ const ona = @import("./ona.zig"); const std = @import("std"); -initialized_states: coral.map.Hashed(*const coral.TypeId, coral.Box, coral.map.ptrTraits(*const coral.TypeId)), -named_systems: coral.map.Hashed([]const u8, SystemGraph, coral.map.string_traits), +initialized_states: coral.map.Hashed(*const coral.TypeId, coral.Box, coral.map.scalarTraits(*const coral.TypeId)), +named_systems: coral.tree.Binary([]const u8, SystemGraph, coral.tree.sliceTraits([]const u8)), is_running: bool, pub const RunError = std.mem.Allocator.Error || error{ @@ -26,26 +26,33 @@ pub const Time = struct { pub fn deinit(self: *Self) void { { - var entries = self.named_systems.entries(); + var key_values = self.named_systems.keyValues(); - while (entries.next()) |entry| { - entry.value.deinit(); + while (key_values.nextValue()) |system| { + system.deinit(); } - self.named_systems.deinit(); + self.named_systems.deinit(coral.heap.allocator); } { - var entries = self.initialized_states.entries(); + var states = self.initialized_states.values(); - while (entries.next()) |entry| { - entry.value.deinit(); + while (states.next()) |state| { + state.deinit(); } self.initialized_states.deinit(); } } +fn scheduleName(comptime schedule: anytype) [:0]const u8 { + return switch (@typeInfo(@TypeOf(schedule))) { + .enum_literal => @tagName(schedule), + else => @compileError("paramater `schedule` must be an enum literal type"), + }; +} + pub fn hasState(self: *Self, comptime State: type) ?*State { if (self.initialized_states.get(.of(State))) |boxed_state| { return boxed_state.has(State).?; @@ -69,7 +76,10 @@ pub fn init() std.mem.Allocator.Error!Self { } pub fn on(self: *Self, comptime schedule: anytype, behavior: *const Behavior) std.mem.Allocator.Error!void { - try (try self.systemNamed(schedule)).insert(self, behavior); + const schedule_name = scheduleName(schedule); + const systems = self.named_systems.get(schedule_name) orelse (try self.named_systems.insert(coral.heap.allocator, schedule_name, .empty)).?; + + try systems.insert(self, behavior); } pub fn putState(self: *Self, state: anytype) std.mem.Allocator.Error!*@TypeOf(state) { @@ -79,7 +89,10 @@ pub fn putState(self: *Self, state: anytype) std.mem.Allocator.Error!*@TypeOf(st } pub fn run(self: *Self, tasks: *coral.asio.TaskQueue, comptime schedule: anytype) RunError!void { - try (try self.systemNamed(schedule)).run(self, tasks); + const schedule_name = scheduleName(schedule); + const systems = self.named_systems.get(schedule_name) orelse (try self.named_systems.insert(coral.heap.allocator, schedule_name, .empty)).?; + + try systems.run(self, tasks); } pub fn setState(self: *Self, state: anytype) std.mem.Allocator.Error!void { @@ -95,12 +108,3 @@ pub fn setState(self: *Self, state: anytype) std.mem.Allocator.Error!void { entry.value.deinit(); } } - -fn systemNamed(self: *Self, comptime schedule: anytype) std.mem.Allocator.Error!*SystemGraph { - const schedule_name = switch (@typeInfo(@TypeOf(schedule))) { - .enum_literal => @tagName(schedule), - else => @compileError("paramater `schedule` must be an enum literal type"), - }; - - return self.named_systems.get(schedule_name) orelse (try self.named_systems.emplace(schedule_name, .empty)).?; -} diff --git a/src/ona/App/SystemGraph.zig b/src/ona/App/SystemGraph.zig index f881afe..e41a877 100644 --- a/src/ona/App/SystemGraph.zig +++ b/src/ona/App/SystemGraph.zig @@ -12,12 +12,12 @@ blocking_work: BehaviorSet, parallel_work: BehaviorSet, parallel_work_ranges: coral.Stack(usize), -const AccessMap = coral.map.Hashed(*const coral.TypeId, BehaviorSet, coral.map.ptrTraits(*const coral.TypeId)); +const AccessMap = coral.tree.Binary(*const coral.TypeId, BehaviorSet, coral.tree.scalarTraits(*const coral.TypeId)); const BehaviorSet = coral.Stack(*const ona.App.Behavior); fn Map(comptime Payload: type) type { - return coral.map.Hashed(*const ona.App.Behavior, Payload, coral.map.ptrTraits(*const ona.App.Behavior)); + return coral.tree.Binary(*const ona.App.Behavior, Payload, coral.tree.scalarTraits(*const ona.App.Behavior)); } const Self = @This(); @@ -32,26 +32,26 @@ pub const TypeDependency = struct { }; pub fn deinit(self: *Self) void { - self.processed.deinit(); + self.processed.deinit(coral.heap.allocator); self.parallel_work.deinit(); self.parallel_work_ranges.deinit(); self.blocking_work.deinit(); inline for (.{ &self.dependants_edges, &self.state_readers, &self.state_writers }) |map| { - var entries = map.entries(); + var key_values = map.keyValues(); - while (entries.next()) |entry| { - entry.value.deinit(); + while (key_values.nextValue()) |value| { + value.deinit(); } - map.deinit(); + map.deinit(coral.heap.allocator); } } pub fn dependOnBehavior(self: *Self, app: *ona.App, dependant: *const ona.App.Behavior, dependency: *const ona.App.Behavior) std.mem.Allocator.Error!void { try self.insert(app, dependant); - const edges = self.dependants_edges.get(dependant).?; + const edges = self.dependants_edges.get(dependant) orelse (try self.dependants_edges.insert(coral.heap.allocator, dependant, .empty)).?; if (std.mem.indexOfScalar(*const ona.App.Behavior, edges.values, dependency) == null) { try edges.pushGrow(dependency); @@ -59,7 +59,7 @@ pub fn dependOnBehavior(self: *Self, app: *ona.App, dependant: *const ona.App.Be } pub fn dependOnType(self: *Self, app: *ona.App, dependant: *const ona.App.Behavior, dependency: TypeDependency) std.mem.Allocator.Error!void { - var readers = self.state_readers.get(dependency.id) orelse (try self.state_readers.emplace(dependency.id, .empty)).?; + const readers = self.state_readers.get(dependency.id) orelse (try self.state_readers.insert(coral.heap.allocator, dependency.id, .empty)).?; if (std.mem.indexOfScalar(*const ona.App.Behavior, readers.values, dependant)) |index| { for (readers.values[0..index]) |reader| { @@ -78,7 +78,7 @@ pub fn dependOnType(self: *Self, app: *ona.App, dependant: *const ona.App.Behavi } if (!dependency.is_read_only) { - const writers = self.state_writers.get(dependency.id) orelse (try self.state_writers.emplace(dependency.id, .empty)).?; + const writers = self.state_writers.get(dependency.id) orelse (try self.state_writers.insert(coral.heap.allocator, dependency.id, .empty)).?; if (std.mem.indexOfScalar(*const ona.App.Behavior, writers.values, dependant)) |index| { for (writers.values[0..index]) |reader| { @@ -111,7 +111,7 @@ pub const empty = Self{ pub fn insert(self: *Self, app: *ona.App, behavior: *const ona.App.Behavior) std.mem.Allocator.Error!void { self.processed.clear(); - if ((try self.dependants_edges.emplace(behavior, .empty)) != null) { + if (try self.dependants_edges.insert(coral.heap.allocator, behavior, .empty) != null) { try behavior.on_insertion(behavior, app, self); } } @@ -133,7 +133,7 @@ fn process(self: *Self, behavior: *const ona.App.Behavior, dependencies: Behavio inherited_traits = inherited_traits.derived(traits); } - if (try self.processed.emplace(behavior, {})) { + if (try self.processed.insert(coral.heap.allocator, behavior, {}) != null) { try switch (inherited_traits.is_thread_unsafe) { true => self.blocking_work.pushGrow(behavior), false => self.parallel_work.pushGrow(behavior), @@ -153,11 +153,11 @@ pub fn run(self: *Self, app: *ona.App, tasks: *coral.asio.TaskQueue) (std.mem.Al self.parallel_work.clear(); self.parallel_work_ranges.clear(); - var dependents_edges = self.dependants_edges.entries(); + var dependants_edges = self.dependants_edges.keyValues(); - while (dependents_edges.next()) |entry| { + while (dependants_edges.next()) |dependants_edge| { const parallel_work_offset = self.parallel_work.values.len; - const flags = try self.process(entry.key, entry.value); + const flags = try self.process(dependants_edge.key, dependants_edge.value.*); if (flags.is_thread_unsafe) { std.debug.assert(parallel_work_offset == self.parallel_work.values.len); diff --git a/src/ona/gfx.zig b/src/ona/gfx.zig index 584c98e..640c0bd 100644 --- a/src/ona/gfx.zig +++ b/src/ona/gfx.zig @@ -70,11 +70,21 @@ fn compile_shaders(_: ona.Write(Context), assets: ona.Assets) !void { .location = 0, }); - switch (try root.parseText(&arena, try assets.load(shader_path, arena.allocator()))) { + switch (try root.parse(&arena, try assets.load(shader_path, arena.allocator()))) { .ok => { + const spirv_module = try coral.shaders.compileFragmentSpirv(&arena, root.hasFunction("frag") orelse { + std.log.err("effect shader {s} requires an entry-point named `frag`", .{shader_path}); + + return error.ShaderParseFailure; + }); + var codes = coral.Stack(u8).empty; - try coral.shaders.generateSpirv(coral.bytes.stackWriter(&codes), root); + defer { + codes.deinit(); + } + + try spirv_module.writeTo(coral.bytes.stackWriter(&codes)); std.log.info("{s}", .{codes.values}); }, @@ -86,6 +96,8 @@ fn compile_shaders(_: ona.Write(Context), assets: ona.Assets) !void { }, } + @breakpoint(); + // const basic_shader = ext.SDL_CreateGPUShader(gpu_device, &.{ // .code = spirv_code, // .code_size = spirv_code.len,