Compare commits
	
		
			No commits in common. "9cfabae9315643ff2f697eec2dbf8a414e7ffc68" and "96e10f0668964a930e1b9e18ac7abca44b0e9333" have entirely different histories.
		
	
	
		
			9cfabae931
			...
			96e10f0668
		
	
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -1,4 +1,4 @@
 | 
			
		||||
# Generated assets
 | 
			
		||||
/.zig-cache
 | 
			
		||||
/zig-cache
 | 
			
		||||
/zig-out
 | 
			
		||||
*.glsl.zig
 | 
			
		||||
 | 
			
		||||
@ -10,8 +10,8 @@
 | 
			
		||||
    },
 | 
			
		||||
    .dependencies = .{
 | 
			
		||||
        .sokol = .{
 | 
			
		||||
            .url = "git+https://github.com/floooh/sokol-zig.git#7c25767e51aa06dd5fb0684e4a8f2200d182ef27",
 | 
			
		||||
			.hash = "1220fa7f47fbaf2f1ed8c17fab2d23b6a85bcbbc4aa0b3802c90a3e8bf6fca1f8569",
 | 
			
		||||
            .url = "git+https://github.com/floooh/sokol-zig.git#796a3d3d54c22d20da9e91a9a9120d5423d1e700",
 | 
			
		||||
            .hash = "12209a187e071d76af00c02865677d170f844376866d062b1b5f82e4ecbd750c4e18",
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,7 @@ pub fn create_event(self: *Self, label: []const u8) std.mem.Allocator.Error!Even
 | 
			
		||||
 | 
			
		||||
	const index = self.event_systems.len();
 | 
			
		||||
 | 
			
		||||
	try self.event_systems.push_grow(systems);
 | 
			
		||||
	try self.event_systems.push(systems);
 | 
			
		||||
 | 
			
		||||
	return @enumFromInt(index);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@ pub const ascii = @import("./ascii.zig");
 | 
			
		||||
 | 
			
		||||
pub const dag = @import("./dag.zig");
 | 
			
		||||
 | 
			
		||||
pub const files = @import("./files.zig");
 | 
			
		||||
pub const debug = @import("./debug.zig");
 | 
			
		||||
 | 
			
		||||
pub const hashes = @import("./hashes.zig");
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,7 @@ pub fn Graph(comptime Payload: type) type {
 | 
			
		||||
		pub fn append(self: *Self, payload: Payload) std.mem.Allocator.Error!Node {
 | 
			
		||||
			const node = @as(Node, @enumFromInt(self.table.len()));
 | 
			
		||||
 | 
			
		||||
			try self.table.push_grow(.{
 | 
			
		||||
			try self.table.push(.{
 | 
			
		||||
				.payload = payload,
 | 
			
		||||
				.edges = .{.allocator = self.table.allocator},
 | 
			
		||||
			});
 | 
			
		||||
@ -83,7 +83,7 @@ pub fn Graph(comptime Payload: type) type {
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			if (slices.index_of(edges.values, 0, edge_node) == null) {
 | 
			
		||||
				try edges.push_grow(edge_node);
 | 
			
		||||
				try edges.push(edge_node);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return true;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										5
									
								
								src/coral/debug.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/coral/debug.zig
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
 | 
			
		||||
pub fn assert_ok(error_union: anytype) @typeInfo(@TypeOf(error_union)).ErrorUnion.payload {
 | 
			
		||||
	return error_union catch unreachable;
 | 
			
		||||
}
 | 
			
		||||
@ -1,124 +0,0 @@
 | 
			
		||||
const builtin = @import("builtin");
 | 
			
		||||
 | 
			
		||||
const io = @import("./io.zig");
 | 
			
		||||
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
 | 
			
		||||
pub const Error = error {
 | 
			
		||||
	FileNotFound,
 | 
			
		||||
	FileInaccessible,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const Stat = struct {
 | 
			
		||||
	size: u64,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const Storage = struct {
 | 
			
		||||
	userdata: *anyopaque,
 | 
			
		||||
	vtable: *const VTable,
 | 
			
		||||
 | 
			
		||||
	pub const VTable = struct {
 | 
			
		||||
		stat: *const fn (*anyopaque, []const u8) Error!Stat,
 | 
			
		||||
		read: *const fn (*anyopaque, []const u8, usize, []io.Byte) Error!usize,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub fn read_bytes(self: Storage, path: []const u8, offset: usize, output: []io.Byte) Error!usize {
 | 
			
		||||
		return self.vtable.read(self.userdata, path, offset, output);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn read_foreign(self: Storage, path: []const u8, offset: usize, comptime Type: type) Error!?Type {
 | 
			
		||||
		const decoded = (try self.read_native(path, offset, Type)) orelse {
 | 
			
		||||
			return null;
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		return switch (@typeInfo(Type)) {
 | 
			
		||||
			.Struct => std.mem.byteSwapAllFields(Type, &decoded),
 | 
			
		||||
			else => @byteSwap(decoded),
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn read_native(self: Storage, path: []const u8, offset: usize, comptime Type: type) Error!?Type {
 | 
			
		||||
		var buffer = @as([@sizeOf(Type)]io.Byte, undefined);
 | 
			
		||||
 | 
			
		||||
		if (try self.vtable.read(self.userdata, path, offset, &buffer) != buffer.len) {
 | 
			
		||||
			return null;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return @as(*align(1) const Type, @ptrCast(&buffer)).*;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub const read_little = switch (native_endian) {
 | 
			
		||||
		.little => read_native,
 | 
			
		||||
		.big => read_foreign,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub const read_big = switch (native_endian) {
 | 
			
		||||
		.little => read_foreign,
 | 
			
		||||
		.big => read_native,
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const bundle = init: {
 | 
			
		||||
	const Bundle = struct {
 | 
			
		||||
		fn full_path(path: []const u8) Error![4095:0]u8 {
 | 
			
		||||
			var buffer = [_:0]u8{0} ** 4095;
 | 
			
		||||
 | 
			
		||||
			_ = std.fs.cwd().realpath(path, &buffer) catch {
 | 
			
		||||
				return error.FileInaccessible;
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			return buffer;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fn read(_: *anyopaque, path: []const u8, offset: usize, output: []io.Byte) Error!usize {
 | 
			
		||||
			var file = std.fs.openFileAbsoluteZ(&(try full_path(path)), .{.mode = .read_only}) catch |open_error| {
 | 
			
		||||
				return switch (open_error) {
 | 
			
		||||
					error.FileNotFound => error.FileNotFound,
 | 
			
		||||
					else => error.FileInaccessible,
 | 
			
		||||
				};
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			defer file.close();
 | 
			
		||||
 | 
			
		||||
			if (offset != 0) {
 | 
			
		||||
				file.seekTo(offset) catch {
 | 
			
		||||
					return error.FileInaccessible;
 | 
			
		||||
				};
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return file.read(output) catch error.FileInaccessible;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fn stat(_: *anyopaque, path: []const u8) Error!Stat {
 | 
			
		||||
			const file_stat = get: {
 | 
			
		||||
				var file = std.fs.openFileAbsoluteZ(&(try full_path(path)), .{.mode = .read_only}) catch |open_error| {
 | 
			
		||||
					return switch (open_error) {
 | 
			
		||||
						error.FileNotFound => error.FileNotFound,
 | 
			
		||||
						else => error.FileInaccessible,
 | 
			
		||||
					};
 | 
			
		||||
				};
 | 
			
		||||
 | 
			
		||||
				defer file.close();
 | 
			
		||||
 | 
			
		||||
				break: get file.stat() catch {
 | 
			
		||||
					return error.FileInaccessible;
 | 
			
		||||
				};
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			return .{
 | 
			
		||||
				.size = file_stat.size,
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	break: init Storage{
 | 
			
		||||
		.userdata = undefined,
 | 
			
		||||
 | 
			
		||||
		.vtable = &.{
 | 
			
		||||
			.stat = Bundle.stat,
 | 
			
		||||
			.read = Bundle.read,
 | 
			
		||||
		},
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const native_endian = builtin.cpu.arch.endian();
 | 
			
		||||
@ -6,30 +6,23 @@ const slices = @import("./slices.zig");
 | 
			
		||||
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
 | 
			
		||||
pub const Writable = struct {
 | 
			
		||||
	data: []Byte,
 | 
			
		||||
 | 
			
		||||
	pub fn writer(self: *Writable) Writer {
 | 
			
		||||
		return Writer.bind(Writable, self, write);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn write(self: *Writable, buffer: []const u8) !usize {
 | 
			
		||||
		const range = @min(buffer.len, self.data.len);
 | 
			
		||||
 | 
			
		||||
		@memcpy(self.data[0 .. range], buffer[0 .. range]);
 | 
			
		||||
 | 
			
		||||
		self.data = self.data[range ..];
 | 
			
		||||
 | 
			
		||||
		return buffer.len;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const Byte = u8;
 | 
			
		||||
 | 
			
		||||
pub const Decoder = coral.io.Functor(coral.io.Error!void, &.{[]coral.Byte});
 | 
			
		||||
 | 
			
		||||
pub const Error = error {
 | 
			
		||||
	UnavailableResource,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub fn FixedBuffer(comptime len: usize, comptime default_value: anytype) type {
 | 
			
		||||
	const Value = @TypeOf(default_value);
 | 
			
		||||
 | 
			
		||||
	return struct {
 | 
			
		||||
		filled: usize = 0,
 | 
			
		||||
		values: [len]Value = [_]Value{default_value} ** len,
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn Functor(comptime Output: type, comptime input_types: []const type) type {
 | 
			
		||||
	const InputTuple = std.meta.Tuple(input_types);
 | 
			
		||||
 | 
			
		||||
@ -126,6 +119,20 @@ pub fn Generator(comptime Output: type, comptime input_types: []const type) type
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub const NullWritable = struct {
 | 
			
		||||
	written: usize = 0,
 | 
			
		||||
 | 
			
		||||
	pub fn writer(self: *NullWritable) Writer {
 | 
			
		||||
		return Writer.bind(NullWritable, self, write);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn write(self: *NullWritable, buffer: []const u8) !usize {
 | 
			
		||||
		self.written += buffer.len;
 | 
			
		||||
 | 
			
		||||
		return buffer.len;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const PrintError = Error || error {
 | 
			
		||||
	IncompleteWrite,
 | 
			
		||||
};
 | 
			
		||||
@ -134,6 +141,8 @@ pub const Reader = Generator(Error!usize, &.{[]coral.Byte});
 | 
			
		||||
 | 
			
		||||
pub const Writer = Generator(Error!usize, &.{[]const coral.Byte});
 | 
			
		||||
 | 
			
		||||
const native_endian = builtin.cpu.arch.endian();
 | 
			
		||||
 | 
			
		||||
pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral.Byte {
 | 
			
		||||
	const buffer = coral.Stack(coral.Byte){.allocator = allocator};
 | 
			
		||||
 | 
			
		||||
@ -144,6 +153,20 @@ pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral.
 | 
			
		||||
	return buffer.to_allocation(streamed);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn are_equal(a: []const Byte, b: []const Byte) bool {
 | 
			
		||||
	if (a.len != b.len) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (0 .. a.len) |i| {
 | 
			
		||||
		if (a[i] != b[i]) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub const bits_per_byte = 8;
 | 
			
		||||
 | 
			
		||||
pub fn bytes_of(value: anytype) []const Byte {
 | 
			
		||||
@ -156,6 +179,14 @@ pub fn bytes_of(value: anytype) []const Byte {
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn ends_with(haystack: []const Byte, needle: []const Byte) bool {
 | 
			
		||||
	if (needle.len > haystack.len) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return are_equal(haystack[haystack.len - needle.len ..], needle);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn print(writer: Writer, utf8: []const u8) PrintError!void {
 | 
			
		||||
	if (try writer.yield(.{utf8}) != utf8.len) {
 | 
			
		||||
		return error.IncompleteWrite;
 | 
			
		||||
@ -177,6 +208,35 @@ pub fn skip_n(input: Reader, distance: u64) Error!void {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn read_foreign(input: Reader, comptime Type: type) Error!Type {
 | 
			
		||||
	const decoded = try read_native(input, Type);
 | 
			
		||||
 | 
			
		||||
	return switch (@typeInfo(input)) {
 | 
			
		||||
		.Struct => std.mem.byteSwapAllFields(Type, &decoded),
 | 
			
		||||
		else => @byteSwap(decoded),
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn read_native(input: Reader, comptime Type: type) Error!Type {
 | 
			
		||||
	var buffer = @as([@sizeOf(Type)]coral.Byte, undefined);
 | 
			
		||||
 | 
			
		||||
	if (try input.yield(.{&buffer}) != buffer.len) {
 | 
			
		||||
		return error.UnavailableResource;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return @as(*align(1) const Type, @ptrCast(&buffer)).*;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub const read_little = switch (native_endian) {
 | 
			
		||||
	.little => read_native,
 | 
			
		||||
	.big => read_foreign,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const read_big = switch (native_endian) {
 | 
			
		||||
	.little => read_foreign,
 | 
			
		||||
	.big => read_native,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub fn slice_sentineled(comptime sen: anytype, ptr: [*:sen]const @TypeOf(sen)) [:sen]const @TypeOf(sen) {
 | 
			
		||||
	var len = @as(usize, 0);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -53,7 +53,7 @@ pub const Table = struct {
 | 
			
		||||
			errdefer resource_allocator.destroy(allocated_resource);
 | 
			
		||||
 | 
			
		||||
			std.debug.assert(try self.table.emplace(value_id, .{
 | 
			
		||||
				.ptr = @ptrCast(allocated_resource),
 | 
			
		||||
				.ptr = allocated_resource,
 | 
			
		||||
			}));
 | 
			
		||||
 | 
			
		||||
			allocated_resource.* = value;
 | 
			
		||||
 | 
			
		||||
@ -29,10 +29,6 @@ pub fn Sequential(comptime Value: type) type {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pub fn grow(self: *Self, additional: usize) std.mem.Allocator.Error!void {
 | 
			
		||||
			if (additional == 0) {
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			const grown_capacity = self.cap + additional;
 | 
			
		||||
			const buffer = try self.allocator.alloc(Value, grown_capacity);
 | 
			
		||||
 | 
			
		||||
@ -91,39 +87,7 @@ pub fn Sequential(comptime Value: type) type {
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pub fn push(self: *Self, value: Value) bool {
 | 
			
		||||
			if (self.values.len == self.cap) {
 | 
			
		||||
				return false;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			const offset_index = self.values.len;
 | 
			
		||||
 | 
			
		||||
			self.values = self.values.ptr[0 .. self.values.len + 1];
 | 
			
		||||
 | 
			
		||||
			self.values[offset_index] = value;
 | 
			
		||||
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pub fn push_all(self: *Self, values: []const Value) bool {
 | 
			
		||||
			const new_length = self.values.len + values.len;
 | 
			
		||||
 | 
			
		||||
			if (new_length > self.cap) {
 | 
			
		||||
				return false;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			const offset_index = self.values.len;
 | 
			
		||||
 | 
			
		||||
			self.values = self.values.ptr[0 .. new_length];
 | 
			
		||||
 | 
			
		||||
			for (0 .. values.len) |index| {
 | 
			
		||||
				self.values[offset_index + index] = values[index];
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pub fn push_grow(self: *Self, value: Value) std.mem.Allocator.Error!void {
 | 
			
		||||
		pub fn push(self: *Self, value: Value) std.mem.Allocator.Error!void {
 | 
			
		||||
			if (self.values.len == self.cap) {
 | 
			
		||||
				try self.grow(@max(1, self.cap));
 | 
			
		||||
			}
 | 
			
		||||
@ -135,11 +99,27 @@ pub fn Sequential(comptime Value: type) type {
 | 
			
		||||
			self.values[offset_index] = value;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pub fn push_all(self: *Self, values: []const Value) std.mem.Allocator.Error!void {
 | 
			
		||||
			const new_length = self.values.len + values.len;
 | 
			
		||||
 | 
			
		||||
			if (new_length > self.cap) {
 | 
			
		||||
				try self.grow(new_length);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			const offset_index = self.values.len;
 | 
			
		||||
 | 
			
		||||
			self.values = self.values.ptr[0 .. new_length];
 | 
			
		||||
 | 
			
		||||
			for (0 .. values.len) |index| {
 | 
			
		||||
				self.values[offset_index + index] = values[index];
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pub fn push_many(self: *Self, n: usize, value: Value) std.mem.Allocator.Error!void {
 | 
			
		||||
			const new_length = self.values.len + n;
 | 
			
		||||
 | 
			
		||||
			if (new_length > self.cap) {
 | 
			
		||||
				return false;
 | 
			
		||||
				try self.grow(new_length);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			const offset_index = self.values.len;
 | 
			
		||||
@ -149,8 +129,6 @@ pub fn Sequential(comptime Value: type) type {
 | 
			
		||||
			for (0 .. n) |index| {
 | 
			
		||||
				self.values[offset_index + index] = value;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pub fn resize(self: *Self, size: usize, default_value: Value) std.mem.Allocator.Error!void {
 | 
			
		||||
@ -259,7 +237,7 @@ pub fn Parallel(comptime Value: type) type {
 | 
			
		||||
			return self.values.len;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pub fn push_grow(self: *Self, value: Value) std.mem.Allocator.Error!void {
 | 
			
		||||
		pub fn push(self: *Self, value: Value) std.mem.Allocator.Error!void {
 | 
			
		||||
			if (self.len() == self.cap) {
 | 
			
		||||
				try self.grow(@max(1, self.cap));
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
@ -51,7 +51,7 @@ pub const BindContext = struct {
 | 
			
		||||
		const id = resource.type_id(Resource);
 | 
			
		||||
 | 
			
		||||
		if (!self.accesses_resource(.read_write, id)) {
 | 
			
		||||
			try self.systems.graph.get_ptr(self.node).?.resource_accesses.push_grow(.{.read_write = id});
 | 
			
		||||
			try self.systems.graph.get_ptr(self.node).?.resource_accesses.push(.{.read_write = id});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const read_write_resource_nodes = lazily_create: {
 | 
			
		||||
@ -65,7 +65,7 @@ pub const BindContext = struct {
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		if (slices.index_of(read_write_resource_nodes.values, 0, self.node) == null) {
 | 
			
		||||
			try read_write_resource_nodes.push_grow(self.node);
 | 
			
		||||
			try read_write_resource_nodes.push(self.node);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return value;
 | 
			
		||||
@ -79,7 +79,7 @@ pub const BindContext = struct {
 | 
			
		||||
		const id = resource.type_id(Resource);
 | 
			
		||||
 | 
			
		||||
		if (!self.accesses_resource(.read_only, id)) {
 | 
			
		||||
			try self.systems.graph.get_ptr(self.node).?.resource_accesses.push_grow(.{.read_only = id});
 | 
			
		||||
			try self.systems.graph.get_ptr(self.node).?.resource_accesses.push(.{.read_only = id});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const read_only_resource_nodes = lazily_create: {
 | 
			
		||||
@ -93,7 +93,7 @@ pub const BindContext = struct {
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		if (slices.index_of(read_only_resource_nodes.values, 0, self.node) == null) {
 | 
			
		||||
			try read_only_resource_nodes.push_grow(self.node);
 | 
			
		||||
			try read_only_resource_nodes.push(self.node);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return value;
 | 
			
		||||
@ -291,7 +291,7 @@ pub const Schedule = struct {
 | 
			
		||||
						try populate_bundle(bundle, graph, edge);
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					try bundle.push_grow(node);
 | 
			
		||||
					try bundle.push(node);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				fn sort(schedule: *Schedule) !void {
 | 
			
		||||
@ -304,7 +304,7 @@ pub const Schedule = struct {
 | 
			
		||||
							continue;
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						try schedule.parallel_work_bundles.push_grow(.{.allocator = heap.allocator});
 | 
			
		||||
						try schedule.parallel_work_bundles.push(.{.allocator = heap.allocator});
 | 
			
		||||
 | 
			
		||||
						const bundle = schedule.parallel_work_bundles.get_ptr().?;
 | 
			
		||||
 | 
			
		||||
@ -328,9 +328,8 @@ pub const Schedule = struct {
 | 
			
		||||
								.main => {
 | 
			
		||||
									const extracted_work = work.values[index ..];
 | 
			
		||||
 | 
			
		||||
									try schedule.blocking_work.grow(extracted_work.len);
 | 
			
		||||
									try schedule.blocking_work.push_all(extracted_work);
 | 
			
		||||
 | 
			
		||||
									std.debug.assert(schedule.blocking_work.push_all(extracted_work));
 | 
			
		||||
									std.debug.assert(work.pop_many(extracted_work.len));
 | 
			
		||||
								},
 | 
			
		||||
							}
 | 
			
		||||
@ -489,7 +488,7 @@ pub const Schedule = struct {
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		try nodes.push_grow(node);
 | 
			
		||||
		try nodes.push(node);
 | 
			
		||||
 | 
			
		||||
		self.invalidate_work();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,8 @@ const ascii = @import("./ascii.zig");
 | 
			
		||||
 | 
			
		||||
const coral = @import("./coral.zig");
 | 
			
		||||
 | 
			
		||||
const debug = @import("./debug.zig");
 | 
			
		||||
 | 
			
		||||
const io = @import("./io.zig");
 | 
			
		||||
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
@ -14,7 +16,7 @@ pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8
 | 
			
		||||
 | 
			
		||||
	errdefer buffer.deinit();
 | 
			
		||||
 | 
			
		||||
	print_formatted(buffer.writer(), format, args) catch unreachable;
 | 
			
		||||
	debug.assert_try(print_formatted(buffer.writer(), format, args));
 | 
			
		||||
 | 
			
		||||
	return buffer.to_allocation(formatted_len, 0);
 | 
			
		||||
}
 | 
			
		||||
@ -22,7 +24,7 @@ pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8
 | 
			
		||||
fn count_formatted(comptime format: []const u8, args: anytype) usize {
 | 
			
		||||
	var count = io.defaultWritable{};
 | 
			
		||||
 | 
			
		||||
	print_formatted(count.writer(), format, args) catch unreachable;
 | 
			
		||||
	debug.assert_try(print_formatted(count.writer(), format, args));
 | 
			
		||||
 | 
			
		||||
	return count.written;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										37
									
								
								src/main.zig
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								src/main.zig
									
									
									
									
									
								
							@ -6,7 +6,6 @@ const ona = @import("ona");
 | 
			
		||||
 | 
			
		||||
const Actors = struct {
 | 
			
		||||
	instances: coral.stack.Sequential(ona.gfx.Point2D) = .{.allocator = coral.heap.allocator},
 | 
			
		||||
	quad_mesh_2d: ona.gfx.Handle = .none,
 | 
			
		||||
	body_texture: ona.gfx.Handle = .none,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -22,29 +21,45 @@ pub fn main() !void {
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn load(display: coral.Write(ona.gfx.Display), actors: coral.Write(Actors), assets: coral.Write(ona.gfx.Assets)) !void {
 | 
			
		||||
	display.res.width, display.res.height = .{1280, 720};
 | 
			
		||||
	actors.res.body_texture = try assets.res.open_file("actor.bmp");
 | 
			
		||||
	actors.res.quad_mesh_2d = try assets.res.open_quad_mesh_2d(@splat(1));
 | 
			
		||||
fn load(display: coral.ReadBlocking(ona.gfx.Display), actors: coral.Write(Actors), gfx: ona.gfx.Work) !void {
 | 
			
		||||
	display.res.resize(1280, 720);
 | 
			
		||||
 | 
			
		||||
	try actors.res.instances.push_grow(.{0, 0});
 | 
			
		||||
	const crap = [_]u32{
 | 
			
		||||
		0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF, 0xFF000000,
 | 
			
		||||
		0xFF000000, 0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF,
 | 
			
		||||
		0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF, 0xFF000000,
 | 
			
		||||
		0xFF000000, 0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	actors.res.body_texture = try gfx.queue.open(.{
 | 
			
		||||
		.resource = .{
 | 
			
		||||
			.texture = .{
 | 
			
		||||
				.data = coral.io.bytes_of(&crap),
 | 
			
		||||
				.width = 4,
 | 
			
		||||
				.access = .static,
 | 
			
		||||
				.format = .bgra8888,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	try actors.res.instances.push(.{0, 0});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn exit(actors: coral.Write(Actors)) void {
 | 
			
		||||
	actors.res.instances.deinit();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn render(queue: ona.gfx.Queue, actors: coral.Write(Actors)) !void {
 | 
			
		||||
fn render(gfx: ona.gfx.Work, actors: coral.Write(Actors)) !void {
 | 
			
		||||
	for (actors.res.instances.values) |instance| {
 | 
			
		||||
		try queue.commands.append(.{
 | 
			
		||||
		try gfx.queue.draw(.{
 | 
			
		||||
			.instance_2d = .{
 | 
			
		||||
				.mesh_2d = actors.res.quad_mesh_2d,
 | 
			
		||||
				.mesh_2d = gfx.primitives.quad_mesh,
 | 
			
		||||
				.texture = actors.res.body_texture,
 | 
			
		||||
 | 
			
		||||
				.transform = .{
 | 
			
		||||
					.origin = instance,
 | 
			
		||||
					.xbasis = .{64, 0},
 | 
			
		||||
					.ybasis = .{0, 64},
 | 
			
		||||
					.xbasis = .{100, 0},
 | 
			
		||||
					.ybasis = .{0, 100},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										270
									
								
								src/ona/gfx.zig
									
									
									
									
									
								
							
							
						
						
									
										270
									
								
								src/ona/gfx.zig
									
									
									
									
									
								
							@ -1,112 +1,37 @@
 | 
			
		||||
const App = @import("./App.zig");
 | 
			
		||||
 | 
			
		||||
pub const Queue = @import("./gfx/Queue.zig");
 | 
			
		||||
 | 
			
		||||
const coral = @import("coral");
 | 
			
		||||
 | 
			
		||||
const device = @import("./gfx/device.zig");
 | 
			
		||||
const Device = @import("./gfx/Device.zig");
 | 
			
		||||
 | 
			
		||||
const ext = @import("./ext.zig");
 | 
			
		||||
 | 
			
		||||
const formats = @import("./gfx/formats.zig");
 | 
			
		||||
 | 
			
		||||
const msg = @import("./msg.zig");
 | 
			
		||||
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
 | 
			
		||||
pub const Assets = struct {
 | 
			
		||||
	context: device.Context,
 | 
			
		||||
	formats: coral.stack.Sequential(Format),
 | 
			
		||||
	staging_arena: std.heap.ArenaAllocator,
 | 
			
		||||
 | 
			
		||||
	pub const Format = struct {
 | 
			
		||||
		extension: []const u8,
 | 
			
		||||
		file_desc: *const fn (*std.heap.ArenaAllocator, []const u8) Error!Desc,
 | 
			
		||||
 | 
			
		||||
		pub const Error = std.mem.Allocator.Error || coral.files.Error || error {
 | 
			
		||||
			FormatUnsupported,
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub fn open_file(self: *Assets, path: []const u8) (std.mem.Allocator.Error || Format.Error)!Handle {
 | 
			
		||||
		defer {
 | 
			
		||||
			const max_cache_size = 536870912;
 | 
			
		||||
 | 
			
		||||
			if (!self.staging_arena.reset(.{.retain_with_limit = max_cache_size})) {
 | 
			
		||||
				std.log.warn("failed to retain staging arena size of {} bytes", .{max_cache_size});
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for (self.formats.values) |format| {
 | 
			
		||||
			if (!std.mem.endsWith(u8, path, format.extension)) {
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return self.context.open(try format.file_desc(&self.staging_arena, path));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return .none;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn open_quad_mesh_2d(self: *Assets, extents: Point2D) std.mem.Allocator.Error!Handle {
 | 
			
		||||
		const width, const height = extents / @as(Point2D, @splat(2));
 | 
			
		||||
 | 
			
		||||
		return self.context.open(.{
 | 
			
		||||
			.mesh_2d = .{
 | 
			
		||||
				.indices = &.{0, 1, 2, 0, 2, 3},
 | 
			
		||||
 | 
			
		||||
				.vertices = &.{
 | 
			
		||||
					.{.xy = .{-width, height}, .uv = .{0, 1}},
 | 
			
		||||
					.{.xy = .{width, height}, .uv = .{1, 1}},
 | 
			
		||||
					.{.xy = .{width, -height}, .uv = .{1, 0}},
 | 
			
		||||
					.{.xy = .{-width, -height},  .uv = .{0, 0}},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const Color = @Vector(4, f32);
 | 
			
		||||
 | 
			
		||||
pub const Desc = union (enum) {
 | 
			
		||||
	texture: Texture,
 | 
			
		||||
	mesh_2d: Mesh2D,
 | 
			
		||||
 | 
			
		||||
	pub const Mesh2D = struct {
 | 
			
		||||
		vertices: []const Vertex,
 | 
			
		||||
		indices: []const u16,
 | 
			
		||||
 | 
			
		||||
		pub const Vertex = struct {
 | 
			
		||||
			xy: Point2D,
 | 
			
		||||
			uv: Point2D,
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub const Texture = struct {
 | 
			
		||||
		data: []const coral.io.Byte,
 | 
			
		||||
		width: u16,
 | 
			
		||||
		format: Format,
 | 
			
		||||
		access: Access,
 | 
			
		||||
 | 
			
		||||
		pub const Access = enum {
 | 
			
		||||
			static,
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		pub const Format = enum {
 | 
			
		||||
			rgba8,
 | 
			
		||||
			bgra8,
 | 
			
		||||
 | 
			
		||||
			pub fn byte_size(self: Format) usize {
 | 
			
		||||
				return switch (self) {
 | 
			
		||||
					.rgba8, .bgra8 => 4,
 | 
			
		||||
				};
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const Display = struct {
 | 
			
		||||
	width: u16 = 1280,
 | 
			
		||||
	height: u16 = 720,
 | 
			
		||||
	clear_color: Color = colors.black,
 | 
			
		||||
	sdl_window: *ext.SDL_Window,
 | 
			
		||||
	device: Device,
 | 
			
		||||
 | 
			
		||||
	pub fn resize(self: Display, width: u16, height: u16) void {
 | 
			
		||||
		ext.SDL_SetWindowSize(self.sdl_window, width, height);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn retitle(self: Display, title: []const u8) void {
 | 
			
		||||
		var sentineled_title = [_:0]u8{0} ** 255;
 | 
			
		||||
 | 
			
		||||
		@memcpy(sentineled_title[0 .. @min(title.len, sentineled_title.len)], title);
 | 
			
		||||
		ext.SDL_SetWindowTitle(self.sdl_window, &sentineled_title);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn set_resizable(self: Display, resizable: bool) void {
 | 
			
		||||
		ext.SDL_SetWindowResizable(self.sdl_window, @intFromBool(resizable));
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const Handle = enum (usize) {
 | 
			
		||||
@ -139,62 +64,69 @@ pub const Input = union (enum) {
 | 
			
		||||
 | 
			
		||||
pub const Point2D = @Vector(2, f32);
 | 
			
		||||
 | 
			
		||||
pub const Queue = struct {
 | 
			
		||||
	commands: *device.RenderList,
 | 
			
		||||
 | 
			
		||||
	pub const State = struct {
 | 
			
		||||
		command_index: usize,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub fn bind(_: coral.system.BindContext) std.mem.Allocator.Error!State {
 | 
			
		||||
		// TODO: Review how good of an idea this global state is, even if bind is guaranteed to always be ran on main.
 | 
			
		||||
		if (renders.is_empty()) {
 | 
			
		||||
			renders = .{.allocator = coral.heap.allocator};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const command_index = renders.len();
 | 
			
		||||
 | 
			
		||||
		try renders.push_grow(device.RenderChain.init(coral.heap.allocator));
 | 
			
		||||
 | 
			
		||||
		return .{
 | 
			
		||||
			.command_index = command_index,
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn init(state: *State) Queue {
 | 
			
		||||
		return .{
 | 
			
		||||
			.commands = renders.values[state.command_index].pending(),
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn unbind(state: *State) void {
 | 
			
		||||
		std.debug.assert(!renders.is_empty());
 | 
			
		||||
 | 
			
		||||
		const render = &renders.values[state.command_index];
 | 
			
		||||
 | 
			
		||||
		render.deinit();
 | 
			
		||||
		std.mem.swap(device.RenderChain, render, renders.get_ptr().?);
 | 
			
		||||
		std.debug.assert(renders.pop());
 | 
			
		||||
 | 
			
		||||
		if (renders.is_empty()) {
 | 
			
		||||
			Queue.renders.deinit();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var renders = coral.stack.Sequential(device.RenderChain){.allocator = coral.heap.allocator};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const Transform2D = extern struct {
 | 
			
		||||
	xbasis: Point2D = .{1, 0},
 | 
			
		||||
	ybasis: Point2D = .{0, 1},
 | 
			
		||||
	origin: Point2D = @splat(0),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const builtin_formats = [_]Assets.Format{
 | 
			
		||||
	.{
 | 
			
		||||
		.extension = "bmp",
 | 
			
		||||
		.file_desc = formats.bmp_file_desc,
 | 
			
		||||
	},
 | 
			
		||||
pub const Work = struct {
 | 
			
		||||
	queue: *Queue.Buffer,
 | 
			
		||||
	primitives: *const Primitives,
 | 
			
		||||
 | 
			
		||||
	const Primitives = struct {
 | 
			
		||||
		quad_mesh: Handle,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub const State = struct {
 | 
			
		||||
		queue: *Queue,
 | 
			
		||||
		primitives: *const Primitives,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub fn bind(context: coral.system.BindContext) std.mem.Allocator.Error!State {
 | 
			
		||||
		const queue = try Queue.create();
 | 
			
		||||
 | 
			
		||||
		return .{
 | 
			
		||||
			.primitives = (try context.register_read_only_resource_access(.none, Primitives)) orelse create: {
 | 
			
		||||
				const buffer = queue.pending();
 | 
			
		||||
				const half_extent = 0.5;
 | 
			
		||||
 | 
			
		||||
				try context.world.set_resource(.none, Primitives{
 | 
			
		||||
					.quad_mesh = try buffer.open(.{
 | 
			
		||||
						.label = "quad mesh primitive",
 | 
			
		||||
 | 
			
		||||
						.resource = .{
 | 
			
		||||
							.mesh_2d = .{
 | 
			
		||||
								.indices = &.{0, 1, 2, 0, 2, 3},
 | 
			
		||||
 | 
			
		||||
								.vertices = &.{
 | 
			
		||||
									.{.xy = .{-half_extent, half_extent}, .uv = .{0, 1}},
 | 
			
		||||
									.{.xy = .{half_extent, half_extent}, .uv = .{1, 1}},
 | 
			
		||||
									.{.xy = .{half_extent, -half_extent}, .uv = .{1, 0}},
 | 
			
		||||
									.{.xy = .{-half_extent, -half_extent},  .uv = .{0, 0}},
 | 
			
		||||
								},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					}),
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
				break: create (try context.register_read_only_resource_access(.none, Primitives)).?;
 | 
			
		||||
			},
 | 
			
		||||
 | 
			
		||||
			.queue = queue,
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn init(state: *State) Work {
 | 
			
		||||
		return .{
 | 
			
		||||
			.queue = state.queue.pending(),
 | 
			
		||||
			.primitives = state.primitives,
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn unbind(state: *State) void {
 | 
			
		||||
		state.queue.release();
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const colors = struct {
 | 
			
		||||
@ -224,45 +156,43 @@ pub fn poll(app: coral.Write(App), inputs: msg.Send(Input)) !void {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn setup(world: *coral.World, events: App.Events) (error {Unsupported} || std.Thread.SpawnError || std.mem.Allocator.Error)!void {
 | 
			
		||||
pub fn setup(world: *coral.World, events: App.Events) (error {SDLError} || std.Thread.SpawnError || std.mem.Allocator.Error)!void {
 | 
			
		||||
	if (ext.SDL_Init(ext.SDL_INIT_VIDEO) != 0) {
 | 
			
		||||
		return error.Unsupported;
 | 
			
		||||
		return error.SDLError;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var context = try device.Context.init();
 | 
			
		||||
	const sdl_window = create: {
 | 
			
		||||
		const position = ext.SDL_WINDOWPOS_CENTERED;
 | 
			
		||||
		const flags = ext.SDL_WINDOW_OPENGL;
 | 
			
		||||
		const width = 640;
 | 
			
		||||
		const height = 480;
 | 
			
		||||
 | 
			
		||||
	errdefer context.deinit();
 | 
			
		||||
		break: create ext.SDL_CreateWindow("Ona", position, position, width, height, flags) orelse {
 | 
			
		||||
			return error.SDLError;
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	var registered_formats = coral.stack.Sequential(Assets.Format){.allocator = coral.heap.allocator};
 | 
			
		||||
	errdefer ext.SDL_DestroyWindow(sdl_window);
 | 
			
		||||
 | 
			
		||||
	errdefer registered_formats.deinit();
 | 
			
		||||
	var device = try Device.init(sdl_window);
 | 
			
		||||
 | 
			
		||||
	try registered_formats.grow(builtin_formats.len);
 | 
			
		||||
	std.debug.assert(registered_formats.push_all(&builtin_formats));
 | 
			
		||||
	errdefer device.deinit();
 | 
			
		||||
 | 
			
		||||
	try world.set_resource(.none, Assets{
 | 
			
		||||
		.staging_arena = std.heap.ArenaAllocator.init(coral.heap.allocator),
 | 
			
		||||
		.formats = registered_formats,
 | 
			
		||||
		.context = context,
 | 
			
		||||
	try world.set_resource(.main, Display{
 | 
			
		||||
		.device = device,
 | 
			
		||||
		.sdl_window = sdl_window,
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	try world.set_resource(.none, Display{});
 | 
			
		||||
	try world.on_event(events.pre_update, coral.system_fn(poll), .{.label = "poll gfx"});
 | 
			
		||||
	try world.on_event(events.exit, coral.system_fn(stop), .{.label = "stop gfx"});
 | 
			
		||||
	try world.on_event(events.finish, coral.system_fn(synchronize), .{.label = "synchronize gfx"});
 | 
			
		||||
	try world.on_event(events.finish, coral.system_fn(submit), .{.label = "submit gfx"});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn stop(assets: coral.Write(Assets)) void {
 | 
			
		||||
	assets.res.staging_arena.deinit();
 | 
			
		||||
	assets.res.formats.deinit();
 | 
			
		||||
	assets.res.context.deinit();
 | 
			
		||||
pub fn stop(display: coral.WriteBlocking(Display)) void {
 | 
			
		||||
	display.res.device.deinit();
 | 
			
		||||
	ext.SDL_DestroyWindow(display.res.sdl_window);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn synchronize(assets: coral.Write(Assets), display: coral.Read(Display)) !void {
 | 
			
		||||
	assets.res.context.submit(.{
 | 
			
		||||
		.width = display.res.width,
 | 
			
		||||
		.height = display.res.height,
 | 
			
		||||
		.clear_color = display.res.clear_color,
 | 
			
		||||
		.renders = Queue.renders.values,
 | 
			
		||||
	});
 | 
			
		||||
pub fn submit(display: coral.WriteBlocking(Display)) void {
 | 
			
		||||
	display.res.device.submit();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										534
									
								
								src/ona/gfx/Device.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										534
									
								
								src/ona/gfx/Device.zig
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,534 @@
 | 
			
		||||
const Queue = @import("./Queue.zig");
 | 
			
		||||
 | 
			
		||||
const coral = @import("coral");
 | 
			
		||||
 | 
			
		||||
const ext = @import("../ext.zig");
 | 
			
		||||
 | 
			
		||||
const gfx = @import("../gfx.zig");
 | 
			
		||||
 | 
			
		||||
const sokol = @import("sokol");
 | 
			
		||||
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
 | 
			
		||||
thread: std.Thread,
 | 
			
		||||
clear_color: gfx.Color = gfx.colors.black,
 | 
			
		||||
state: *State,
 | 
			
		||||
 | 
			
		||||
const AtomicBool = std.atomic.Value(bool);
 | 
			
		||||
 | 
			
		||||
const Frame = struct {
 | 
			
		||||
	width: u16,
 | 
			
		||||
	height: u16,
 | 
			
		||||
	flushed_instance_2d_count: usize = 0,
 | 
			
		||||
	pushed_instance_2d_count: usize = 0,
 | 
			
		||||
	mesh_2d: gfx.Handle = .none,
 | 
			
		||||
	texture: gfx.Handle = .none,
 | 
			
		||||
 | 
			
		||||
	fn unflushed_instance_2d_count(self: Frame) usize {
 | 
			
		||||
		return self.pushed_instance_2d_count - self.flushed_instance_2d_count;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const Render = struct {
 | 
			
		||||
	resources: coral.stack.Sequential(Resource),
 | 
			
		||||
	instance_2d_pipeline: sokol.gfx.Pipeline,
 | 
			
		||||
	instance_2d_buffers: coral.stack.Sequential(sokol.gfx.Buffer),
 | 
			
		||||
 | 
			
		||||
	const Instance2D = extern struct {
 | 
			
		||||
		transform: gfx.Transform2D,
 | 
			
		||||
		tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)),
 | 
			
		||||
		depth: f32 = 0,
 | 
			
		||||
		texture_offset: gfx.Point2D = @splat(0),
 | 
			
		||||
		texture_size: gfx.Point2D = @splat(1),
 | 
			
		||||
 | 
			
		||||
		const buffer_indices = .{
 | 
			
		||||
			.mesh = 0,
 | 
			
		||||
			.instance = 1,
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		const instances_per_buffer = 512;
 | 
			
		||||
 | 
			
		||||
		const shader = @import("./shaders/instance_2d.glsl.zig");
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const Resource = union (enum) {
 | 
			
		||||
		empty,
 | 
			
		||||
		mesh_2d: Mesh2D,
 | 
			
		||||
		texture: Texture,
 | 
			
		||||
 | 
			
		||||
		const Mesh2D = struct {
 | 
			
		||||
			index_count: u32,
 | 
			
		||||
			vertex_buffer: sokol.gfx.Buffer,
 | 
			
		||||
			index_buffer: sokol.gfx.Buffer,
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		const Texture = struct {
 | 
			
		||||
			image: sokol.gfx.Image,
 | 
			
		||||
			sampler: sokol.gfx.Sampler,
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	fn deinit(self: *Render) void {
 | 
			
		||||
		for (self.instance_2d_buffers.values) |buffer| {
 | 
			
		||||
			sokol.gfx.destroyBuffer(buffer);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		self.instance_2d_buffers.deinit();
 | 
			
		||||
		sokol.gfx.destroyPipeline(self.instance_2d_pipeline);
 | 
			
		||||
		self.resources.deinit();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn init() Render {
 | 
			
		||||
		sokol.gfx.setup(.{
 | 
			
		||||
			.environment = .{
 | 
			
		||||
				.defaults = .{
 | 
			
		||||
					.color_format = .RGBA8,
 | 
			
		||||
					.depth_format = .DEPTH_STENCIL,
 | 
			
		||||
					.sample_count = 1,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
 | 
			
		||||
			.logger = .{
 | 
			
		||||
				.func = sokol.log.func,
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		return .{
 | 
			
		||||
			.instance_2d_pipeline = sokol.gfx.makePipeline(.{
 | 
			
		||||
				.label = "2D drawing pipeline",
 | 
			
		||||
 | 
			
		||||
				.layout = .{
 | 
			
		||||
					.attrs = get: {
 | 
			
		||||
						var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16;
 | 
			
		||||
 | 
			
		||||
						attrs[Instance2D.shader.ATTR_vs_mesh_xy] = .{
 | 
			
		||||
							.format = .FLOAT2,
 | 
			
		||||
							.buffer_index = Instance2D.buffer_indices.mesh,
 | 
			
		||||
						};
 | 
			
		||||
 | 
			
		||||
						attrs[Instance2D.shader.ATTR_vs_mesh_uv] = .{
 | 
			
		||||
							.format = .FLOAT2,
 | 
			
		||||
							.buffer_index = Instance2D.buffer_indices.mesh,
 | 
			
		||||
						};
 | 
			
		||||
 | 
			
		||||
						attrs[Instance2D.shader.ATTR_vs_instance_xbasis] = .{
 | 
			
		||||
							.format = .FLOAT2,
 | 
			
		||||
							.buffer_index = Instance2D.buffer_indices.instance,
 | 
			
		||||
						};
 | 
			
		||||
 | 
			
		||||
						attrs[Instance2D.shader.ATTR_vs_instance_ybasis] = .{
 | 
			
		||||
							.format = .FLOAT2,
 | 
			
		||||
							.buffer_index = Instance2D.buffer_indices.instance,
 | 
			
		||||
						};
 | 
			
		||||
 | 
			
		||||
						attrs[Instance2D.shader.ATTR_vs_instance_origin] = .{
 | 
			
		||||
							.format = .FLOAT2,
 | 
			
		||||
							.buffer_index = Instance2D.buffer_indices.instance,
 | 
			
		||||
						};
 | 
			
		||||
 | 
			
		||||
						attrs[Instance2D.shader.ATTR_vs_instance_tint] = .{
 | 
			
		||||
							.format = .UBYTE4N,
 | 
			
		||||
							.buffer_index = Instance2D.buffer_indices.instance,
 | 
			
		||||
						};
 | 
			
		||||
 | 
			
		||||
						attrs[Instance2D.shader.ATTR_vs_instance_depth] = .{
 | 
			
		||||
							.format = .FLOAT,
 | 
			
		||||
							.buffer_index = Instance2D.buffer_indices.instance,
 | 
			
		||||
						};
 | 
			
		||||
 | 
			
		||||
						attrs[Instance2D.shader.ATTR_vs_instance_rect] = .{
 | 
			
		||||
							.format = .FLOAT4,
 | 
			
		||||
							.buffer_index = Instance2D.buffer_indices.instance,
 | 
			
		||||
						};
 | 
			
		||||
 | 
			
		||||
						break: get attrs;
 | 
			
		||||
					},
 | 
			
		||||
 | 
			
		||||
					.buffers = get: {
 | 
			
		||||
						var buffers = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8;
 | 
			
		||||
 | 
			
		||||
						buffers[Instance2D.buffer_indices.instance].step_func = .PER_INSTANCE;
 | 
			
		||||
 | 
			
		||||
						break: get buffers;
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
 | 
			
		||||
				.shader = sokol.gfx.makeShader(Instance2D.shader.draw2dShaderDesc(sokol.gfx.queryBackend())),
 | 
			
		||||
				.index_type = .UINT16,
 | 
			
		||||
			}),
 | 
			
		||||
 | 
			
		||||
			.instance_2d_buffers = .{.allocator = coral.heap.allocator},
 | 
			
		||||
			.resources = .{.allocator = coral.heap.allocator},
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn insert_resource(self: *Render, handle: gfx.Handle, resource: Resource) !void {
 | 
			
		||||
		const handle_index = handle.index() orelse {
 | 
			
		||||
			return error.InvalidHandle;
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		const resource_count = self.resources.len();
 | 
			
		||||
 | 
			
		||||
		if (handle_index < resource_count) {
 | 
			
		||||
			const empty_resource = &self.resources.values[handle_index];
 | 
			
		||||
 | 
			
		||||
			if (empty_resource.* != .empty) {
 | 
			
		||||
				return error.InvalidHandle;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			empty_resource.* = resource;
 | 
			
		||||
		} else {
 | 
			
		||||
			if (handle_index != resource_count) {
 | 
			
		||||
				return error.InvalidIndex;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			try self.resources.push(resource);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn flush_instance_2ds(self: *Render, frame: *Frame) void {
 | 
			
		||||
		const unflushed_count = frame.unflushed_instance_2d_count();
 | 
			
		||||
 | 
			
		||||
		if (unflushed_count == 0) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		sokol.gfx.applyPipeline(self.instance_2d_pipeline);
 | 
			
		||||
 | 
			
		||||
		sokol.gfx.applyUniforms(.VS, Instance2D.shader.SLOT_Screen, sokol.gfx.asRange(&Instance2D.shader.Screen{
 | 
			
		||||
			.screen_size = .{@floatFromInt(frame.width), @floatFromInt(frame.height)},
 | 
			
		||||
		}));
 | 
			
		||||
 | 
			
		||||
		const mesh_2d = self.resources.values[frame.mesh_2d.index().?].mesh_2d;
 | 
			
		||||
		const texture = self.resources.values[frame.texture.index().?].texture;
 | 
			
		||||
 | 
			
		||||
		var bindings = sokol.gfx.Bindings{
 | 
			
		||||
			.vertex_buffers = get: {
 | 
			
		||||
				var buffers = [_]sokol.gfx.Buffer{.{}} ** 8;
 | 
			
		||||
 | 
			
		||||
				buffers[Instance2D.buffer_indices.mesh] = mesh_2d.vertex_buffer;
 | 
			
		||||
 | 
			
		||||
				break: get buffers;
 | 
			
		||||
			},
 | 
			
		||||
 | 
			
		||||
			.index_buffer = mesh_2d.index_buffer,
 | 
			
		||||
 | 
			
		||||
			.fs = .{
 | 
			
		||||
				.images = get: {
 | 
			
		||||
					var images = [_]sokol.gfx.Image{.{}} ** 12;
 | 
			
		||||
 | 
			
		||||
					images[0] = texture.image;
 | 
			
		||||
 | 
			
		||||
					break: get images;
 | 
			
		||||
				},
 | 
			
		||||
 | 
			
		||||
				.samplers = get: {
 | 
			
		||||
					var samplers = [_]sokol.gfx.Sampler{.{}} ** 8;
 | 
			
		||||
 | 
			
		||||
					samplers[0] = texture.sampler;
 | 
			
		||||
 | 
			
		||||
					break: get samplers;
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		while (frame.flushed_instance_2d_count < frame.pushed_instance_2d_count) {
 | 
			
		||||
			const buffer_index = frame.flushed_instance_2d_count / Instance2D.instances_per_buffer;
 | 
			
		||||
			const buffer_offset = frame.flushed_instance_2d_count % Instance2D.instances_per_buffer;
 | 
			
		||||
			const instances_to_flush = @min(Instance2D.instances_per_buffer - buffer_offset, unflushed_count);
 | 
			
		||||
 | 
			
		||||
			bindings.vertex_buffers[Instance2D.buffer_indices.instance] = self.instance_2d_buffers.values[buffer_index];
 | 
			
		||||
			bindings.vertex_buffer_offsets[Instance2D.buffer_indices.instance] = @intCast(buffer_offset);
 | 
			
		||||
 | 
			
		||||
			sokol.gfx.applyBindings(bindings);
 | 
			
		||||
			sokol.gfx.draw(0, mesh_2d.index_count, @intCast(instances_to_flush));
 | 
			
		||||
 | 
			
		||||
			frame.flushed_instance_2d_count += instances_to_flush;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn push_instance_2d(self: *Render, frame: *Frame, command: Queue.DrawCommand.Instance) std.mem.Allocator.Error!void {
 | 
			
		||||
		if (command.mesh_2d != frame.mesh_2d or command.texture != frame.texture) {
 | 
			
		||||
			self.flush_instance_2ds(frame);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		frame.mesh_2d = command.mesh_2d;
 | 
			
		||||
		frame.texture = command.texture;
 | 
			
		||||
 | 
			
		||||
		const has_filled_buffer = (frame.pushed_instance_2d_count % Instance2D.instances_per_buffer) == 0;
 | 
			
		||||
		const pushed_buffer_count = frame.pushed_instance_2d_count / Instance2D.instances_per_buffer;
 | 
			
		||||
 | 
			
		||||
		if (has_filled_buffer and pushed_buffer_count == self.instance_2d_buffers.len()) {
 | 
			
		||||
			const instance_buffer = sokol.gfx.makeBuffer(.{
 | 
			
		||||
				.size = @sizeOf(Instance2D) * Instance2D.instances_per_buffer,
 | 
			
		||||
				.usage = .STREAM,
 | 
			
		||||
				.label = "2D drawing instance buffer",
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			errdefer sokol.gfx.destroyBuffer(instance_buffer);
 | 
			
		||||
 | 
			
		||||
			try self.instance_2d_buffers.push(instance_buffer);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		_ = sokol.gfx.appendBuffer(self.instance_2d_buffers.get().?, sokol.gfx.asRange(&Instance2D{
 | 
			
		||||
			.transform = command.transform,
 | 
			
		||||
		}));
 | 
			
		||||
 | 
			
		||||
		frame.pushed_instance_2d_count += 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn remove_resource(self: *Render, handle: gfx.Handle) ?Resource {
 | 
			
		||||
		if (handle.index()) |handle_index| {
 | 
			
		||||
			const resource = self.resources.values[handle_index];
 | 
			
		||||
 | 
			
		||||
			if (resource != .empty) {
 | 
			
		||||
				self.resources.values[handle_index] = .empty;
 | 
			
		||||
 | 
			
		||||
				return resource;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return null;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const Self = @This();
 | 
			
		||||
 | 
			
		||||
const State = struct {
 | 
			
		||||
	finished: std.Thread.Semaphore = .{},
 | 
			
		||||
	is_running: AtomicBool = AtomicBool.init(true),
 | 
			
		||||
	ready: std.Thread.Semaphore = .{},
 | 
			
		||||
	clear_color: gfx.Color = gfx.colors.black,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub fn deinit(self: *Self) void {
 | 
			
		||||
	self.state.is_running.store(false, .monotonic);
 | 
			
		||||
	self.state.ready.post();
 | 
			
		||||
	self.thread.join();
 | 
			
		||||
	coral.heap.allocator.destroy(self.state);
 | 
			
		||||
 | 
			
		||||
	self.* = undefined;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn init(window: *ext.SDL_Window) (std.mem.Allocator.Error || std.Thread.SpawnError)!Self {
 | 
			
		||||
	const state = try coral.heap.allocator.create(State);
 | 
			
		||||
 | 
			
		||||
	errdefer coral.heap.allocator.destroy(state);
 | 
			
		||||
 | 
			
		||||
	state.* = .{};
 | 
			
		||||
 | 
			
		||||
	const thread = try std.Thread.spawn(.{}, run, .{window, state});
 | 
			
		||||
 | 
			
		||||
	thread.setName("Ona Graphics") catch {
 | 
			
		||||
		std.log.warn("failed to name the graphics thread", .{});
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	return .{
 | 
			
		||||
		.thread = thread,
 | 
			
		||||
		.state = state,
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn process_close_command(command: Queue.CloseCommand, rendering: *Render) !void {
 | 
			
		||||
	const resource = &rendering.resources.values[command.handle.index().?];
 | 
			
		||||
 | 
			
		||||
	switch (resource.*) {
 | 
			
		||||
		.empty => {}, // TODO: Handle this.
 | 
			
		||||
 | 
			
		||||
		.mesh_2d => |mesh_2d| {
 | 
			
		||||
			sokol.gfx.destroyBuffer(mesh_2d.vertex_buffer);
 | 
			
		||||
			sokol.gfx.destroyBuffer(mesh_2d.index_buffer);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		.texture => |texture| {
 | 
			
		||||
			sokol.gfx.destroyImage(texture.image);
 | 
			
		||||
			sokol.gfx.destroySampler(texture.sampler);
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resource.* = .empty;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn process_draw_command(command: Queue.DrawCommand, render: *Render, frame: *Frame) !void {
 | 
			
		||||
	switch (command) {
 | 
			
		||||
		.instance_2d => |instance_2d| {
 | 
			
		||||
			try render.push_instance_2d(frame, instance_2d);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		.post_process => |post_process| {
 | 
			
		||||
			render.flush_instance_2ds(frame);
 | 
			
		||||
			// sokol.gfx.applyPipeline(self.post_process_pipeline);
 | 
			
		||||
 | 
			
		||||
			_ = post_process;
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	render.flush_instance_2ds(frame);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn process_open_command(command: Queue.OpenCommand, render: *Render) !void {
 | 
			
		||||
	switch (command.resource) {
 | 
			
		||||
		.texture => |texture| {
 | 
			
		||||
			const stride = texture.width * texture.format.byte_size();
 | 
			
		||||
 | 
			
		||||
			const image = sokol.gfx.makeImage(.{
 | 
			
		||||
				.width = texture.width,
 | 
			
		||||
				.height = @intCast(texture.data.len / stride),
 | 
			
		||||
 | 
			
		||||
				.data = .{
 | 
			
		||||
					.subimage = get: {
 | 
			
		||||
						var subimage = [_][16]sokol.gfx.Range{.{.{}} ** 16} ** 6;
 | 
			
		||||
 | 
			
		||||
						subimage[0][0] = sokol.gfx.asRange(texture.data);
 | 
			
		||||
 | 
			
		||||
						break: get subimage;
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			errdefer sokol.gfx.destroyImage(image);
 | 
			
		||||
 | 
			
		||||
			const sampler = sokol.gfx.makeSampler(.{});
 | 
			
		||||
 | 
			
		||||
			errdefer sokol.gfx.destroySampler(sampler);
 | 
			
		||||
 | 
			
		||||
			try render.insert_resource(command.handle, .{
 | 
			
		||||
				.texture = .{
 | 
			
		||||
					.sampler = sampler,
 | 
			
		||||
					.image = image,
 | 
			
		||||
				},
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		.mesh_2d => |mesh_2d| {
 | 
			
		||||
			const index_buffer = sokol.gfx.makeBuffer(.{
 | 
			
		||||
				.data = sokol.gfx.asRange(mesh_2d.indices),
 | 
			
		||||
				.type = .INDEXBUFFER,
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			const vertex_buffer = sokol.gfx.makeBuffer(.{
 | 
			
		||||
				.data = sokol.gfx.asRange(mesh_2d.vertices),
 | 
			
		||||
				.type = .VERTEXBUFFER,
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			errdefer {
 | 
			
		||||
				sokol.gfx.destroyBuffer(index_buffer);
 | 
			
		||||
				sokol.gfx.destroyBuffer(vertex_buffer);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (mesh_2d.indices.len > std.math.maxInt(u32)) {
 | 
			
		||||
				return error.OutOfMemory;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			try render.insert_resource(command.handle, .{
 | 
			
		||||
				.mesh_2d = .{
 | 
			
		||||
					.index_buffer = index_buffer,
 | 
			
		||||
					.vertex_buffer = vertex_buffer,
 | 
			
		||||
					.index_count = @intCast(mesh_2d.indices.len),
 | 
			
		||||
				},
 | 
			
		||||
			});
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn run(window: *ext.SDL_Window, state: *State) !void {
 | 
			
		||||
	const context = configure_and_create: {
 | 
			
		||||
		var result = @as(c_int, 0);
 | 
			
		||||
 | 
			
		||||
		result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_FLAGS, ext.SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
 | 
			
		||||
		result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_PROFILE_MASK, ext.SDL_GL_CONTEXT_PROFILE_CORE);
 | 
			
		||||
		result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MAJOR_VERSION, 3);
 | 
			
		||||
		result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MINOR_VERSION, 3);
 | 
			
		||||
		result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_DOUBLEBUFFER, 1);
 | 
			
		||||
 | 
			
		||||
		if (result != 0) {
 | 
			
		||||
			return error.Unsupported;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		break: configure_and_create ext.SDL_GL_CreateContext(window);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	sokol.gfx.setup(.{
 | 
			
		||||
		.environment = .{
 | 
			
		||||
			.defaults = .{
 | 
			
		||||
				.color_format = .RGBA8,
 | 
			
		||||
				.depth_format = .DEPTH_STENCIL,
 | 
			
		||||
				.sample_count = 1,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		.logger = .{
 | 
			
		||||
			.func = sokol.log.func,
 | 
			
		||||
		},
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	defer {
 | 
			
		||||
		sokol.gfx.shutdown();
 | 
			
		||||
		ext.SDL_GL_DeleteContext(context);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var render = Render.init();
 | 
			
		||||
 | 
			
		||||
	defer render.deinit();
 | 
			
		||||
 | 
			
		||||
	state.finished.post();
 | 
			
		||||
 | 
			
		||||
	while (state.is_running.load(.monotonic)) {
 | 
			
		||||
		state.ready.wait();
 | 
			
		||||
 | 
			
		||||
		defer state.finished.post();
 | 
			
		||||
 | 
			
		||||
		var frame = init_frame: {
 | 
			
		||||
			var width, var height = [_]c_int{0, 0};
 | 
			
		||||
 | 
			
		||||
			ext.SDL_GL_GetDrawableSize(window, &width, &height);
 | 
			
		||||
			std.debug.assert(width > 0 and height > 0);
 | 
			
		||||
 | 
			
		||||
			break: init_frame Frame{
 | 
			
		||||
				.width = @intCast(width),
 | 
			
		||||
				.height = @intCast(height),
 | 
			
		||||
			};
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		sokol.gfx.beginPass(.{
 | 
			
		||||
			.swapchain = .{
 | 
			
		||||
				.width = frame.width,
 | 
			
		||||
				.height = frame.height,
 | 
			
		||||
				.sample_count = 1,
 | 
			
		||||
				.color_format = .RGBA8,
 | 
			
		||||
				.depth_format = .DEPTH_STENCIL,
 | 
			
		||||
				.gl = .{.framebuffer = 0},
 | 
			
		||||
			},
 | 
			
		||||
 | 
			
		||||
			.action = .{
 | 
			
		||||
				.colors = get: {
 | 
			
		||||
					var actions = [_]sokol.gfx.ColorAttachmentAction{.{}} ** 4;
 | 
			
		||||
 | 
			
		||||
					actions[0] = .{
 | 
			
		||||
						.load_action = .CLEAR,
 | 
			
		||||
						.clear_value = @as(sokol.gfx.Color, @bitCast(state.clear_color)),
 | 
			
		||||
					};
 | 
			
		||||
 | 
			
		||||
					break: get actions;
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		try Queue.visit_open_commands(process_open_command, .{&render});
 | 
			
		||||
		try Queue.visit_draw_commands(process_draw_command, .{&render, &frame});
 | 
			
		||||
		try Queue.visit_close_commands(process_close_command, .{&render});
 | 
			
		||||
 | 
			
		||||
		sokol.gfx.endPass();
 | 
			
		||||
		sokol.gfx.commit();
 | 
			
		||||
		ext.SDL_GL_SwapWindow(window);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn submit(self: *Self) void {
 | 
			
		||||
	self.state.finished.wait();
 | 
			
		||||
 | 
			
		||||
	self.state.clear_color = self.clear_color;
 | 
			
		||||
 | 
			
		||||
	Queue.swap();
 | 
			
		||||
	self.state.ready.post();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										309
									
								
								src/ona/gfx/Queue.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										309
									
								
								src/ona/gfx/Queue.zig
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,309 @@
 | 
			
		||||
const coral = @import("coral");
 | 
			
		||||
 | 
			
		||||
const gfx = @import("../gfx.zig");
 | 
			
		||||
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
 | 
			
		||||
buffers: [2]Buffer,
 | 
			
		||||
is_swapped: bool = false,
 | 
			
		||||
ref_count: AtomicCount = AtomicCount.init(1),
 | 
			
		||||
has_next: ?*Self = null,
 | 
			
		||||
has_prev: ?*Self = null,
 | 
			
		||||
 | 
			
		||||
const AtomicCount = std.atomic.Value(usize);
 | 
			
		||||
 | 
			
		||||
pub const Buffer = struct {
 | 
			
		||||
	arena: std.heap.ArenaAllocator,
 | 
			
		||||
	closed_handles: coral.stack.Sequential(usize),
 | 
			
		||||
	open_commands: coral.stack.Sequential(OpenCommand),
 | 
			
		||||
	draw_commands: coral.stack.Sequential(DrawCommand),
 | 
			
		||||
	close_commands: coral.stack.Sequential(CloseCommand),
 | 
			
		||||
 | 
			
		||||
	pub fn clear(self: *Buffer) void {
 | 
			
		||||
		self.close_commands.clear();
 | 
			
		||||
		self.draw_commands.clear();
 | 
			
		||||
		self.open_commands.clear();
 | 
			
		||||
 | 
			
		||||
		if (!self.arena.reset(.retain_capacity)) {
 | 
			
		||||
			std.log.warn("failed to reset the buffer of a gfx queue with retained capacity", .{});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn deinit(self: *Buffer) void {
 | 
			
		||||
		self.arena.deinit();
 | 
			
		||||
		self.closed_handles.deinit();
 | 
			
		||||
		self.open_commands.deinit();
 | 
			
		||||
		self.draw_commands.deinit();
 | 
			
		||||
		self.close_commands.deinit();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn init(allocator: std.mem.Allocator) Buffer {
 | 
			
		||||
		return .{
 | 
			
		||||
			.arena = std.heap.ArenaAllocator.init(allocator),
 | 
			
		||||
			.closed_handles = .{.allocator = allocator},
 | 
			
		||||
			.open_commands = .{.allocator = allocator},
 | 
			
		||||
			.draw_commands = .{.allocator = allocator},
 | 
			
		||||
			.close_commands = .{.allocator = allocator},
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn draw(self: *Buffer, command: DrawCommand) std.mem.Allocator.Error!void {
 | 
			
		||||
		try self.draw_commands.push(switch (command) {
 | 
			
		||||
			.instance_2d => |instance_2d| .{.instance_2d = instance_2d},
 | 
			
		||||
			.post_process => |post_process| .{.post_process = post_process},
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn open(self: *Buffer, command: OpenCommand) std.mem.Allocator.Error!gfx.Handle {
 | 
			
		||||
		const reserved_handle = @as(gfx.Handle, switch (command.handle) {
 | 
			
		||||
			.none => @enumFromInt(reserve_handle: {
 | 
			
		||||
				if (self.closed_handles.get()) |handle| {
 | 
			
		||||
					std.debug.assert(self.closed_handles.pop());
 | 
			
		||||
 | 
			
		||||
					break: reserve_handle handle;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				break: reserve_handle next_handle.fetchAdd(1, .monotonic);
 | 
			
		||||
			}),
 | 
			
		||||
 | 
			
		||||
			_ => |handle| handle,
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		std.debug.assert(reserved_handle != .none);
 | 
			
		||||
 | 
			
		||||
		const arena_allocator = self.arena.allocator();
 | 
			
		||||
 | 
			
		||||
		try self.open_commands.push(.{
 | 
			
		||||
			.resource = switch (command.resource) {
 | 
			
		||||
				.texture => |texture| .{
 | 
			
		||||
					.texture = .{
 | 
			
		||||
						.data = try arena_allocator.dupe(coral.io.Byte, texture.data),
 | 
			
		||||
						.width = texture.width,
 | 
			
		||||
						.format = texture.format,
 | 
			
		||||
						.access = texture.access,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
 | 
			
		||||
				.mesh_2d => |mesh_2d| .{
 | 
			
		||||
					.mesh_2d = .{
 | 
			
		||||
						.indices = try arena_allocator.dupe(u16, mesh_2d.indices),
 | 
			
		||||
						.vertices = try arena_allocator.dupe(OpenCommand.Mesh2D.Vertex, mesh_2d.vertices),
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
 | 
			
		||||
			.label = if (command.label) |label| try arena_allocator.dupe(coral.io.Byte, label) else null,
 | 
			
		||||
			.handle = reserved_handle,
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		return reserved_handle;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const CloseCommand = struct {
 | 
			
		||||
	handle: gfx.Handle,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const DrawCommand = union (enum) {
 | 
			
		||||
	instance_2d: Instance,
 | 
			
		||||
	post_process: PostProcess,
 | 
			
		||||
 | 
			
		||||
	pub const Instance = struct {
 | 
			
		||||
		texture: gfx.Handle,
 | 
			
		||||
		mesh_2d: gfx.Handle,
 | 
			
		||||
		transform: gfx.Transform2D,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub const PostProcess = struct {
 | 
			
		||||
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const OpenCommand = struct {
 | 
			
		||||
	handle: gfx.Handle = .none,
 | 
			
		||||
	label: ?[]const u8 = null,
 | 
			
		||||
 | 
			
		||||
	resource: union (enum) {
 | 
			
		||||
		texture: Texture,
 | 
			
		||||
		mesh_2d: Mesh2D,
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	pub const Mesh2D = struct {
 | 
			
		||||
		vertices: []const Vertex,
 | 
			
		||||
		indices: []const u16,
 | 
			
		||||
 | 
			
		||||
		pub const Vertex = struct {
 | 
			
		||||
			xy: gfx.Point2D,
 | 
			
		||||
			uv: gfx.Point2D,
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub const Texture = struct {
 | 
			
		||||
		data: []const coral.io.Byte,
 | 
			
		||||
		width: u16,
 | 
			
		||||
		format: Format,
 | 
			
		||||
		access: Access,
 | 
			
		||||
 | 
			
		||||
		pub const Access = enum {
 | 
			
		||||
			static,
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		pub const Format = enum {
 | 
			
		||||
			rgba8888,
 | 
			
		||||
			bgra8888,
 | 
			
		||||
			argb8888,
 | 
			
		||||
			rgb888,
 | 
			
		||||
			bgr888,
 | 
			
		||||
 | 
			
		||||
			pub fn byte_size(self: Format) usize {
 | 
			
		||||
				return switch (self) {
 | 
			
		||||
					.rgba8888, .bgra8888, .argb8888 => 4,
 | 
			
		||||
					.rgb888, .bgr888 => 3,
 | 
			
		||||
				};
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub fn acquire(self: *Self) void {
 | 
			
		||||
	self.ref_count.fetchAdd(1, .monotonic);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn create() std.mem.Allocator.Error!*Self {
 | 
			
		||||
	const queue = try coral.heap.allocator.create(Self);
 | 
			
		||||
 | 
			
		||||
	errdefer coral.heap.allocator.destroy(queue);
 | 
			
		||||
 | 
			
		||||
	queue.* = .{
 | 
			
		||||
		.buffers = .{Buffer.init(coral.heap.allocator), Buffer.init(coral.heap.allocator)},
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	mutex.lock();
 | 
			
		||||
 | 
			
		||||
	defer mutex.unlock();
 | 
			
		||||
 | 
			
		||||
	if (has_tail) |tail| {
 | 
			
		||||
		tail.has_next = queue;
 | 
			
		||||
		queue.has_prev = tail;
 | 
			
		||||
	} else {
 | 
			
		||||
		std.debug.assert(has_head == null);
 | 
			
		||||
 | 
			
		||||
		has_head = queue;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	has_tail = queue;
 | 
			
		||||
 | 
			
		||||
	return queue;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn pending(self: *Self) *Buffer {
 | 
			
		||||
	return &self.buffers[@intFromBool(self.is_swapped)];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn release(self: *Self) void {
 | 
			
		||||
	if (self.ref_count.fetchSub(1, .monotonic) == 1) {
 | 
			
		||||
		mutex.lock();
 | 
			
		||||
 | 
			
		||||
		defer mutex.unlock();
 | 
			
		||||
 | 
			
		||||
		if (self.has_prev) |prev| {
 | 
			
		||||
			prev.has_next = self.has_next;
 | 
			
		||||
		} else {
 | 
			
		||||
			has_head = self.has_next;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (self.has_next) |next| {
 | 
			
		||||
			next.has_prev = self.has_prev;
 | 
			
		||||
		} else {
 | 
			
		||||
			has_tail = self.has_prev;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for (&self.buffers) |*buffer| {
 | 
			
		||||
			buffer.deinit();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		coral.heap.allocator.destroy(self);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn submitted(self: *Self) *Buffer {
 | 
			
		||||
	return &self.buffers[@intFromBool(!self.is_swapped)];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const Self = @This();
 | 
			
		||||
 | 
			
		||||
var has_head = @as(?*Self, null);
 | 
			
		||||
 | 
			
		||||
var has_tail = @as(?*Self, null);
 | 
			
		||||
 | 
			
		||||
var mutex = std.Thread.Mutex{};
 | 
			
		||||
 | 
			
		||||
var next_handle = AtomicCount.init(1);
 | 
			
		||||
 | 
			
		||||
pub fn swap() void {
 | 
			
		||||
	mutex.lock();
 | 
			
		||||
 | 
			
		||||
	defer mutex.unlock();
 | 
			
		||||
 | 
			
		||||
	var has_node = has_head;
 | 
			
		||||
 | 
			
		||||
	while (has_node) |node| : (has_node = node.has_next) {
 | 
			
		||||
		node.is_swapped = !node.is_swapped;
 | 
			
		||||
 | 
			
		||||
		node.pending().clear();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn visit_close_commands(visit: anytype, args: anytype) !void {
 | 
			
		||||
	mutex.lock();
 | 
			
		||||
 | 
			
		||||
	defer mutex.unlock();
 | 
			
		||||
 | 
			
		||||
	var has_node = has_head;
 | 
			
		||||
	var iterations = @as(usize, 0);
 | 
			
		||||
 | 
			
		||||
	while (has_node) |node| : ({
 | 
			
		||||
		has_node = node.has_next;
 | 
			
		||||
		iterations += 1;
 | 
			
		||||
	}) {
 | 
			
		||||
		for (node.submitted().close_commands.values) |command| {
 | 
			
		||||
			try @call(.auto, visit, .{command} ++ args);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn visit_draw_commands(visit: anytype, args: anytype) !void {
 | 
			
		||||
	mutex.lock();
 | 
			
		||||
 | 
			
		||||
	defer mutex.unlock();
 | 
			
		||||
 | 
			
		||||
	var has_node = has_head;
 | 
			
		||||
	var iterations = @as(usize, 0);
 | 
			
		||||
 | 
			
		||||
	while (has_node) |node| : ({
 | 
			
		||||
		has_node = node.has_next;
 | 
			
		||||
		iterations += 1;
 | 
			
		||||
	}) {
 | 
			
		||||
		for (node.submitted().draw_commands.values) |command| {
 | 
			
		||||
			try @call(.auto, visit, .{command} ++ args);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn visit_open_commands(visit: anytype, args: anytype) !void {
 | 
			
		||||
	mutex.lock();
 | 
			
		||||
 | 
			
		||||
	defer mutex.unlock();
 | 
			
		||||
 | 
			
		||||
	var has_node = has_head;
 | 
			
		||||
	var iterations = @as(usize, 0);
 | 
			
		||||
 | 
			
		||||
	while (has_node) |node| : ({
 | 
			
		||||
		has_node = node.has_next;
 | 
			
		||||
		iterations += 1;
 | 
			
		||||
	}) {
 | 
			
		||||
		for (node.submitted().open_commands.values) |command| {
 | 
			
		||||
			try @call(.auto, visit, .{command} ++ args);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -1,80 +0,0 @@
 | 
			
		||||
const coral = @import("coral");
 | 
			
		||||
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
 | 
			
		||||
pub fn Chain(comptime Command: type, comptime clone_command: ?Clone(Command)) type {
 | 
			
		||||
	return struct {
 | 
			
		||||
		swap_lists: [2]CommandList,
 | 
			
		||||
		swap_state: u1 = 0,
 | 
			
		||||
 | 
			
		||||
		const CommandList = List(Command, clone_command);
 | 
			
		||||
 | 
			
		||||
		const Self = @This();
 | 
			
		||||
 | 
			
		||||
		pub fn deinit(self: *Self) void {
 | 
			
		||||
			for (&self.swap_lists) |*list| {
 | 
			
		||||
				list.deinit();
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			self.* = undefined;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pub fn init(allocator: std.mem.Allocator) Self {
 | 
			
		||||
			return .{
 | 
			
		||||
				.swap_lists = .{CommandList.init(allocator), CommandList.init(allocator)},
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pub fn pending(self: *Self) *CommandList {
 | 
			
		||||
			return &self.swap_lists[self.swap_state];
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pub fn submitted(self: *Self) *CommandList {
 | 
			
		||||
			return &self.swap_lists[self.swap_state ^ 1];
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pub fn swap(self: *Self) void {
 | 
			
		||||
			self.swap_state ^= 1;
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn Clone(comptime Command: type) type {
 | 
			
		||||
	return fn (Command, *std.heap.ArenaAllocator) std.mem.Allocator.Error!Command;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn List(comptime Command: type, comptime clone_command: ?Clone(Command)) type {
 | 
			
		||||
	return struct {
 | 
			
		||||
		arena: std.heap.ArenaAllocator,
 | 
			
		||||
		stack: coral.stack.Sequential(Command),
 | 
			
		||||
 | 
			
		||||
		const Self = @This();
 | 
			
		||||
 | 
			
		||||
		pub fn append(self: *Self, command: Command) std.mem.Allocator.Error!void {
 | 
			
		||||
			return self.stack.push_grow(if (clone_command) |clone| try clone(command, &self.arena) else command);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pub fn clear(self: *Self) void {
 | 
			
		||||
			self.stack.clear();
 | 
			
		||||
 | 
			
		||||
			if (!self.arena.reset(.retain_capacity)) {
 | 
			
		||||
				std.log.warn("failed to reset the buffer of a gfx queue with retained capacity", .{});
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pub fn deinit(self: *Self) void {
 | 
			
		||||
			self.arena.deinit();
 | 
			
		||||
			self.stack.deinit();
 | 
			
		||||
 | 
			
		||||
			self.* = undefined;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pub fn init(allocator: std.mem.Allocator) Self {
 | 
			
		||||
			return .{
 | 
			
		||||
				.arena = std.heap.ArenaAllocator.init(allocator),
 | 
			
		||||
				.stack = .{.allocator = allocator},
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,691 +0,0 @@
 | 
			
		||||
const commands = @import("./commands.zig");
 | 
			
		||||
 | 
			
		||||
const coral = @import("coral");
 | 
			
		||||
 | 
			
		||||
const ext = @import("../ext.zig");
 | 
			
		||||
 | 
			
		||||
const gfx = @import("../gfx.zig");
 | 
			
		||||
 | 
			
		||||
const sokol = @import("sokol");
 | 
			
		||||
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
 | 
			
		||||
pub const Context = struct {
 | 
			
		||||
	window: *ext.SDL_Window,
 | 
			
		||||
	thread: std.Thread,
 | 
			
		||||
	loop: *Loop,
 | 
			
		||||
 | 
			
		||||
	pub const Submission = struct {
 | 
			
		||||
		width: u16,
 | 
			
		||||
		height: u16,
 | 
			
		||||
		clear_color: gfx.Color = gfx.colors.black,
 | 
			
		||||
		renders: []RenderChain,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub fn close(self: *Context, handle: gfx.Handle) void {
 | 
			
		||||
		const handle_index = handle.index() orelse {
 | 
			
		||||
			return;
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		const close_commands = self.loop.closes.pending();
 | 
			
		||||
 | 
			
		||||
		std.debug.assert(close_commands.stack.cap > close_commands.stack.len());
 | 
			
		||||
		close_commands.append(.{.index = handle_index}) catch unreachable;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn deinit(self: *Context) void {
 | 
			
		||||
		self.loop.is_running.store(false, .monotonic);
 | 
			
		||||
		self.loop.ready.post();
 | 
			
		||||
		self.thread.join();
 | 
			
		||||
		self.loop.deinit();
 | 
			
		||||
		coral.heap.allocator.destroy(self.loop);
 | 
			
		||||
		ext.SDL_DestroyWindow(self.window);
 | 
			
		||||
 | 
			
		||||
		self.* = undefined;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn init() !Context {
 | 
			
		||||
		const window = create: {
 | 
			
		||||
			const position = ext.SDL_WINDOWPOS_CENTERED;
 | 
			
		||||
			const flags = ext.SDL_WINDOW_OPENGL;
 | 
			
		||||
			const width = 640;
 | 
			
		||||
			const height = 480;
 | 
			
		||||
 | 
			
		||||
			break: create ext.SDL_CreateWindow("Ona", position, position, width, height, flags) orelse {
 | 
			
		||||
				return error.Unsupported;
 | 
			
		||||
			};
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		errdefer ext.SDL_DestroyWindow(window);
 | 
			
		||||
 | 
			
		||||
		const loop = try coral.heap.allocator.create(Loop);
 | 
			
		||||
 | 
			
		||||
		errdefer coral.heap.allocator.destroy(loop);
 | 
			
		||||
 | 
			
		||||
		loop.* = .{};
 | 
			
		||||
 | 
			
		||||
		return .{
 | 
			
		||||
			.loop = loop,
 | 
			
		||||
			.window = window,
 | 
			
		||||
 | 
			
		||||
			.thread = spawn: {
 | 
			
		||||
				const thread = try std.Thread.spawn(.{}, Loop.run, .{loop, window});
 | 
			
		||||
 | 
			
		||||
				thread.setName("Ona Graphics") catch {
 | 
			
		||||
					std.log.warn("failed to name the graphics thread", .{});
 | 
			
		||||
				};
 | 
			
		||||
 | 
			
		||||
				break: spawn thread;
 | 
			
		||||
			},
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn open(self: *Context, desc: gfx.Desc) std.mem.Allocator.Error!gfx.Handle {
 | 
			
		||||
		const open_commands = self.loop.opens.pending();
 | 
			
		||||
		const index = self.loop.closed_indices.get() orelse open_commands.stack.len();
 | 
			
		||||
 | 
			
		||||
		try open_commands.append(.{
 | 
			
		||||
			.index = index,
 | 
			
		||||
			.desc = desc,
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		const pending_closes = self.loop.closes.pending();
 | 
			
		||||
 | 
			
		||||
		if (pending_closes.stack.len() == pending_closes.stack.cap) {
 | 
			
		||||
			try pending_closes.stack.grow(1);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		_ = self.loop.closed_indices.pop();
 | 
			
		||||
 | 
			
		||||
		return @enumFromInt(index + 1);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn submit(self: *Context, submission: Submission) void {
 | 
			
		||||
		self.loop.finished.wait();
 | 
			
		||||
 | 
			
		||||
		defer self.loop.ready.post();
 | 
			
		||||
 | 
			
		||||
		for (submission.renders) |*render| {
 | 
			
		||||
			render.swap();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		self.loop.opens.swap();
 | 
			
		||||
		self.loop.closes.swap();
 | 
			
		||||
 | 
			
		||||
		var last_width, var last_height = [_]c_int{0, 0};
 | 
			
		||||
 | 
			
		||||
		ext.SDL_GetWindowSize(self.window, &last_width, &last_height);
 | 
			
		||||
 | 
			
		||||
		if (submission.width != last_width or submission.height != last_height) {
 | 
			
		||||
			ext.SDL_SetWindowSize(self.window, submission.width, submission.height);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		self.loop.clear_color = submission.clear_color;
 | 
			
		||||
		self.loop.renders = submission.renders;
 | 
			
		||||
 | 
			
		||||
		self.loop.ready.post();
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const Loop = struct {
 | 
			
		||||
	ready: std.Thread.Semaphore = .{},
 | 
			
		||||
	finished: std.Thread.Semaphore = .{},
 | 
			
		||||
	clear_color: gfx.Color = gfx.colors.black,
 | 
			
		||||
	is_running: AtomicBool = AtomicBool.init(true),
 | 
			
		||||
	renders: []RenderChain = &.{},
 | 
			
		||||
	closes: CloseChain = CloseChain.init(coral.heap.allocator),
 | 
			
		||||
	opens: OpenChain = OpenChain.init(coral.heap.allocator),
 | 
			
		||||
	closed_indices: coral.stack.Sequential(usize) = .{.allocator = coral.heap.allocator},
 | 
			
		||||
 | 
			
		||||
	const AtomicBool = std.atomic.Value(bool);
 | 
			
		||||
 | 
			
		||||
	const CloseCommand = struct {
 | 
			
		||||
		index: usize,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const OpenCommand = struct {
 | 
			
		||||
		index: usize,
 | 
			
		||||
		desc: gfx.Desc,
 | 
			
		||||
 | 
			
		||||
		fn clone(command: OpenCommand, arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!OpenCommand {
 | 
			
		||||
			const allocator = arena.allocator();
 | 
			
		||||
 | 
			
		||||
			return .{
 | 
			
		||||
				.desc = switch (command.desc) {
 | 
			
		||||
					.texture => |texture| .{
 | 
			
		||||
						.texture = .{
 | 
			
		||||
							.data = try allocator.dupe(coral.io.Byte, texture.data),
 | 
			
		||||
							.width = texture.width,
 | 
			
		||||
							.format = texture.format,
 | 
			
		||||
							.access = texture.access,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
 | 
			
		||||
					.mesh_2d => |mesh_2d| .{
 | 
			
		||||
						.mesh_2d = .{
 | 
			
		||||
							.vertices = try allocator.dupe(gfx.Desc.Mesh2D.Vertex, mesh_2d.vertices),
 | 
			
		||||
							.indices = try allocator.dupe(u16, mesh_2d.indices),
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
 | 
			
		||||
				.index = command.index,
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const CloseChain = commands.Chain(CloseCommand, null);
 | 
			
		||||
 | 
			
		||||
	const OpenChain = commands.Chain(OpenCommand, OpenCommand.clone);
 | 
			
		||||
 | 
			
		||||
	fn deinit(self: *Loop) void {
 | 
			
		||||
		self.closes.deinit();
 | 
			
		||||
		self.opens.deinit();
 | 
			
		||||
		self.closed_indices.deinit();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn run(self: *Loop, window: *ext.SDL_Window) !void {
 | 
			
		||||
		const context = configure_and_create: {
 | 
			
		||||
			var result = @as(c_int, 0);
 | 
			
		||||
 | 
			
		||||
			result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_FLAGS, ext.SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
 | 
			
		||||
			result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_PROFILE_MASK, ext.SDL_GL_CONTEXT_PROFILE_CORE);
 | 
			
		||||
			result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MAJOR_VERSION, 3);
 | 
			
		||||
			result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MINOR_VERSION, 3);
 | 
			
		||||
			result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_DOUBLEBUFFER, 1);
 | 
			
		||||
 | 
			
		||||
			if (result != 0) {
 | 
			
		||||
				return error.Unsupported;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			break: configure_and_create ext.SDL_GL_CreateContext(window);
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		sokol.gfx.setup(.{
 | 
			
		||||
			.environment = .{
 | 
			
		||||
				.defaults = .{
 | 
			
		||||
					.color_format = .RGBA8,
 | 
			
		||||
					.depth_format = .DEPTH_STENCIL,
 | 
			
		||||
					.sample_count = 1,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
 | 
			
		||||
			.logger = .{
 | 
			
		||||
				.func = sokol.log.func,
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		defer {
 | 
			
		||||
			sokol.gfx.shutdown();
 | 
			
		||||
			ext.SDL_GL_DeleteContext(context);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var rendering = Rendering.init();
 | 
			
		||||
 | 
			
		||||
		defer rendering.deinit();
 | 
			
		||||
 | 
			
		||||
		self.finished.post();
 | 
			
		||||
 | 
			
		||||
		while (self.is_running.load(.monotonic)) {
 | 
			
		||||
			self.ready.wait();
 | 
			
		||||
 | 
			
		||||
			defer self.finished.post();
 | 
			
		||||
 | 
			
		||||
			const open_commands = self.opens.submitted();
 | 
			
		||||
 | 
			
		||||
			defer open_commands.clear();
 | 
			
		||||
 | 
			
		||||
			for (open_commands.stack.values) |command| {
 | 
			
		||||
				switch (command.desc) {
 | 
			
		||||
					.texture => |texture| {
 | 
			
		||||
						const stride = texture.width * texture.format.byte_size();
 | 
			
		||||
 | 
			
		||||
						const image = sokol.gfx.makeImage(.{
 | 
			
		||||
							.width = texture.width,
 | 
			
		||||
							.height = @intCast(texture.data.len / stride),
 | 
			
		||||
 | 
			
		||||
							.data = .{
 | 
			
		||||
								.subimage = get: {
 | 
			
		||||
									var subimage = [_][16]sokol.gfx.Range{.{.{}} ** 16} ** 6;
 | 
			
		||||
 | 
			
		||||
									subimage[0][0] = sokol.gfx.asRange(texture.data);
 | 
			
		||||
 | 
			
		||||
									break: get subimage;
 | 
			
		||||
								},
 | 
			
		||||
							},
 | 
			
		||||
 | 
			
		||||
							.pixel_format = switch (texture.format) {
 | 
			
		||||
								.rgba8 => .RGBA8,
 | 
			
		||||
								.bgra8 => .BGRA8,
 | 
			
		||||
							},
 | 
			
		||||
						});
 | 
			
		||||
 | 
			
		||||
						errdefer sokol.gfx.destroyImage(image);
 | 
			
		||||
 | 
			
		||||
						const sampler = sokol.gfx.makeSampler(.{});
 | 
			
		||||
 | 
			
		||||
						errdefer sokol.gfx.destroySampler(sampler);
 | 
			
		||||
 | 
			
		||||
						try rendering.insert_object(command.index, .{
 | 
			
		||||
							.texture = .{
 | 
			
		||||
								.sampler = sampler,
 | 
			
		||||
								.image = image,
 | 
			
		||||
							},
 | 
			
		||||
						});
 | 
			
		||||
					},
 | 
			
		||||
 | 
			
		||||
					.mesh_2d => |mesh_2d| {
 | 
			
		||||
						const index_buffer = sokol.gfx.makeBuffer(.{
 | 
			
		||||
							.data = sokol.gfx.asRange(mesh_2d.indices),
 | 
			
		||||
							.type = .INDEXBUFFER,
 | 
			
		||||
						});
 | 
			
		||||
 | 
			
		||||
						const vertex_buffer = sokol.gfx.makeBuffer(.{
 | 
			
		||||
							.data = sokol.gfx.asRange(mesh_2d.vertices),
 | 
			
		||||
							.type = .VERTEXBUFFER,
 | 
			
		||||
						});
 | 
			
		||||
 | 
			
		||||
						errdefer {
 | 
			
		||||
							sokol.gfx.destroyBuffer(index_buffer);
 | 
			
		||||
							sokol.gfx.destroyBuffer(vertex_buffer);
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						if (mesh_2d.indices.len > std.math.maxInt(u32)) {
 | 
			
		||||
							return error.OutOfMemory;
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						try rendering.insert_object(command.index, .{
 | 
			
		||||
							.mesh_2d = .{
 | 
			
		||||
								.index_buffer = index_buffer,
 | 
			
		||||
								.vertex_buffer = vertex_buffer,
 | 
			
		||||
								.index_count = @intCast(mesh_2d.indices.len),
 | 
			
		||||
							},
 | 
			
		||||
						});
 | 
			
		||||
					},
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var frame = init_frame: {
 | 
			
		||||
				var width, var height = [_]c_int{0, 0};
 | 
			
		||||
 | 
			
		||||
				ext.SDL_GL_GetDrawableSize(window, &width, &height);
 | 
			
		||||
				std.debug.assert(width > 0 and height > 0);
 | 
			
		||||
 | 
			
		||||
				break: init_frame Rendering.Frame{
 | 
			
		||||
					.width = @intCast(width),
 | 
			
		||||
					.height = @intCast(height),
 | 
			
		||||
				};
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			sokol.gfx.beginPass(.{
 | 
			
		||||
				.swapchain = .{
 | 
			
		||||
					.width = frame.width,
 | 
			
		||||
					.height = frame.height,
 | 
			
		||||
					.sample_count = 1,
 | 
			
		||||
					.color_format = .RGBA8,
 | 
			
		||||
					.depth_format = .DEPTH_STENCIL,
 | 
			
		||||
					.gl = .{.framebuffer = 0},
 | 
			
		||||
				},
 | 
			
		||||
 | 
			
		||||
				.action = .{
 | 
			
		||||
					.colors = get: {
 | 
			
		||||
						var actions = [_]sokol.gfx.ColorAttachmentAction{.{}} ** 4;
 | 
			
		||||
 | 
			
		||||
						actions[0] = .{
 | 
			
		||||
							.load_action = .CLEAR,
 | 
			
		||||
							.clear_value = @as(sokol.gfx.Color, @bitCast(self.clear_color)),
 | 
			
		||||
						};
 | 
			
		||||
 | 
			
		||||
						break: get actions;
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			for (self.renders) |*render| {
 | 
			
		||||
				const render_commands = render.submitted();
 | 
			
		||||
 | 
			
		||||
				defer render_commands.clear();
 | 
			
		||||
 | 
			
		||||
				for (render_commands.stack.values) |command| {
 | 
			
		||||
					switch (command) {
 | 
			
		||||
						.instance_2d => |instance_2d| {
 | 
			
		||||
							try rendering.push_instance_2d(&frame, instance_2d);
 | 
			
		||||
						},
 | 
			
		||||
 | 
			
		||||
						.post_process => |post_process| {
 | 
			
		||||
							rendering.flush_instance_2ds(&frame);
 | 
			
		||||
							// sokol.gfx.applyPipeline(self.post_process_pipeline);
 | 
			
		||||
 | 
			
		||||
							_ = post_process;
 | 
			
		||||
						},
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			rendering.flush_instance_2ds(&frame);
 | 
			
		||||
			sokol.gfx.endPass();
 | 
			
		||||
			sokol.gfx.commit();
 | 
			
		||||
			ext.SDL_GL_SwapWindow(window);
 | 
			
		||||
 | 
			
		||||
			const close_commands = self.closes.submitted();
 | 
			
		||||
 | 
			
		||||
			defer close_commands.clear();
 | 
			
		||||
 | 
			
		||||
			for (close_commands.stack.values) |command| {
 | 
			
		||||
				const object = &rendering.objects.values[command.index];
 | 
			
		||||
 | 
			
		||||
				switch (object.*) {
 | 
			
		||||
					.empty => {}, // TODO: Handle double-closes.
 | 
			
		||||
 | 
			
		||||
					.mesh_2d => |mesh_2d| {
 | 
			
		||||
						sokol.gfx.destroyBuffer(mesh_2d.vertex_buffer);
 | 
			
		||||
						sokol.gfx.destroyBuffer(mesh_2d.index_buffer);
 | 
			
		||||
					},
 | 
			
		||||
 | 
			
		||||
					.texture => |texture| {
 | 
			
		||||
						sokol.gfx.destroyImage(texture.image);
 | 
			
		||||
						sokol.gfx.destroySampler(texture.sampler);
 | 
			
		||||
					},
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				object.* = .empty;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const Rendering = struct {
 | 
			
		||||
	objects: coral.stack.Sequential(Object),
 | 
			
		||||
	instance_2d_pipeline: sokol.gfx.Pipeline,
 | 
			
		||||
	instance_2d_buffers: coral.stack.Sequential(sokol.gfx.Buffer),
 | 
			
		||||
 | 
			
		||||
	const Instance2D = extern struct {
 | 
			
		||||
		transform: gfx.Transform2D,
 | 
			
		||||
		tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)),
 | 
			
		||||
		depth: f32 = 0,
 | 
			
		||||
		texture_offset: gfx.Point2D = @splat(0),
 | 
			
		||||
		texture_size: gfx.Point2D = @splat(1),
 | 
			
		||||
 | 
			
		||||
		const buffer_indices = .{
 | 
			
		||||
			.mesh = 0,
 | 
			
		||||
			.instance = 1,
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		const instances_per_buffer = 512;
 | 
			
		||||
 | 
			
		||||
		const shader = @import("./shaders/instance_2d.glsl.zig");
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const Frame = struct {
 | 
			
		||||
		width: u16,
 | 
			
		||||
		height: u16,
 | 
			
		||||
		flushed_instance_2d_count: usize = 0,
 | 
			
		||||
		pushed_instance_2d_count: usize = 0,
 | 
			
		||||
		mesh_2d: gfx.Handle = .none,
 | 
			
		||||
		texture: gfx.Handle = .none,
 | 
			
		||||
 | 
			
		||||
		fn unflushed_instance_2d_count(self: Frame) usize {
 | 
			
		||||
			return self.pushed_instance_2d_count - self.flushed_instance_2d_count;
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const Object = union (enum) {
 | 
			
		||||
		empty,
 | 
			
		||||
 | 
			
		||||
		mesh_2d: struct {
 | 
			
		||||
			index_count: u32,
 | 
			
		||||
			vertex_buffer: sokol.gfx.Buffer,
 | 
			
		||||
			index_buffer: sokol.gfx.Buffer,
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		texture: struct {
 | 
			
		||||
			image: sokol.gfx.Image,
 | 
			
		||||
			sampler: sokol.gfx.Sampler,
 | 
			
		||||
		},
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	fn deinit(self: *Rendering) void {
 | 
			
		||||
		for (self.instance_2d_buffers.values) |buffer| {
 | 
			
		||||
			sokol.gfx.destroyBuffer(buffer);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		self.instance_2d_buffers.deinit();
 | 
			
		||||
		sokol.gfx.destroyPipeline(self.instance_2d_pipeline);
 | 
			
		||||
		self.objects.deinit();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn init() Rendering {
 | 
			
		||||
		sokol.gfx.setup(.{
 | 
			
		||||
			.environment = .{
 | 
			
		||||
				.defaults = .{
 | 
			
		||||
					.color_format = .RGBA8,
 | 
			
		||||
					.depth_format = .DEPTH_STENCIL,
 | 
			
		||||
					.sample_count = 1,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
 | 
			
		||||
			.logger = .{
 | 
			
		||||
				.func = sokol.log.func,
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		return .{
 | 
			
		||||
			.instance_2d_pipeline = sokol.gfx.makePipeline(.{
 | 
			
		||||
				.label = "2D drawing pipeline",
 | 
			
		||||
 | 
			
		||||
				.layout = .{
 | 
			
		||||
					.attrs = get: {
 | 
			
		||||
						var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16;
 | 
			
		||||
 | 
			
		||||
						attrs[Instance2D.shader.ATTR_vs_mesh_xy] = .{
 | 
			
		||||
							.format = .FLOAT2,
 | 
			
		||||
							.buffer_index = Instance2D.buffer_indices.mesh,
 | 
			
		||||
						};
 | 
			
		||||
 | 
			
		||||
						attrs[Instance2D.shader.ATTR_vs_mesh_uv] = .{
 | 
			
		||||
							.format = .FLOAT2,
 | 
			
		||||
							.buffer_index = Instance2D.buffer_indices.mesh,
 | 
			
		||||
						};
 | 
			
		||||
 | 
			
		||||
						attrs[Instance2D.shader.ATTR_vs_instance_xbasis] = .{
 | 
			
		||||
							.format = .FLOAT2,
 | 
			
		||||
							.buffer_index = Instance2D.buffer_indices.instance,
 | 
			
		||||
						};
 | 
			
		||||
 | 
			
		||||
						attrs[Instance2D.shader.ATTR_vs_instance_ybasis] = .{
 | 
			
		||||
							.format = .FLOAT2,
 | 
			
		||||
							.buffer_index = Instance2D.buffer_indices.instance,
 | 
			
		||||
						};
 | 
			
		||||
 | 
			
		||||
						attrs[Instance2D.shader.ATTR_vs_instance_origin] = .{
 | 
			
		||||
							.format = .FLOAT2,
 | 
			
		||||
							.buffer_index = Instance2D.buffer_indices.instance,
 | 
			
		||||
						};
 | 
			
		||||
 | 
			
		||||
						attrs[Instance2D.shader.ATTR_vs_instance_tint] = .{
 | 
			
		||||
							.format = .UBYTE4N,
 | 
			
		||||
							.buffer_index = Instance2D.buffer_indices.instance,
 | 
			
		||||
						};
 | 
			
		||||
 | 
			
		||||
						attrs[Instance2D.shader.ATTR_vs_instance_depth] = .{
 | 
			
		||||
							.format = .FLOAT,
 | 
			
		||||
							.buffer_index = Instance2D.buffer_indices.instance,
 | 
			
		||||
						};
 | 
			
		||||
 | 
			
		||||
						attrs[Instance2D.shader.ATTR_vs_instance_rect] = .{
 | 
			
		||||
							.format = .FLOAT4,
 | 
			
		||||
							.buffer_index = Instance2D.buffer_indices.instance,
 | 
			
		||||
						};
 | 
			
		||||
 | 
			
		||||
						break: get attrs;
 | 
			
		||||
					},
 | 
			
		||||
 | 
			
		||||
					.buffers = get: {
 | 
			
		||||
						var buffers = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8;
 | 
			
		||||
 | 
			
		||||
						buffers[Instance2D.buffer_indices.instance].step_func = .PER_INSTANCE;
 | 
			
		||||
 | 
			
		||||
						break: get buffers;
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
 | 
			
		||||
				.shader = sokol.gfx.makeShader(Instance2D.shader.draw2dShaderDesc(sokol.gfx.queryBackend())),
 | 
			
		||||
				.index_type = .UINT16,
 | 
			
		||||
			}),
 | 
			
		||||
 | 
			
		||||
			.instance_2d_buffers = .{.allocator = coral.heap.allocator},
 | 
			
		||||
			.objects = .{.allocator = coral.heap.allocator},
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn flush_instance_2ds(self: *Rendering, frame: *Frame) void {
 | 
			
		||||
		const unflushed_count = frame.unflushed_instance_2d_count();
 | 
			
		||||
 | 
			
		||||
		if (unflushed_count == 0) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		sokol.gfx.applyPipeline(self.instance_2d_pipeline);
 | 
			
		||||
 | 
			
		||||
		sokol.gfx.applyUniforms(.VS, Instance2D.shader.SLOT_Screen, sokol.gfx.asRange(&Instance2D.shader.Screen{
 | 
			
		||||
			.screen_size = .{@floatFromInt(frame.width), @floatFromInt(frame.height)},
 | 
			
		||||
		}));
 | 
			
		||||
 | 
			
		||||
		const mesh_2d = self.objects.values[frame.mesh_2d.index().?].mesh_2d;
 | 
			
		||||
		const texture = self.objects.values[frame.texture.index().?].texture;
 | 
			
		||||
 | 
			
		||||
		var bindings = sokol.gfx.Bindings{
 | 
			
		||||
			.vertex_buffers = get: {
 | 
			
		||||
				var buffers = [_]sokol.gfx.Buffer{.{}} ** 8;
 | 
			
		||||
 | 
			
		||||
				buffers[Instance2D.buffer_indices.mesh] = mesh_2d.vertex_buffer;
 | 
			
		||||
 | 
			
		||||
				break: get buffers;
 | 
			
		||||
			},
 | 
			
		||||
 | 
			
		||||
			.index_buffer = mesh_2d.index_buffer,
 | 
			
		||||
 | 
			
		||||
			.fs = .{
 | 
			
		||||
				.images = get: {
 | 
			
		||||
					var images = [_]sokol.gfx.Image{.{}} ** 12;
 | 
			
		||||
 | 
			
		||||
					images[0] = texture.image;
 | 
			
		||||
 | 
			
		||||
					break: get images;
 | 
			
		||||
				},
 | 
			
		||||
 | 
			
		||||
				.samplers = get: {
 | 
			
		||||
					var samplers = [_]sokol.gfx.Sampler{.{}} ** 8;
 | 
			
		||||
 | 
			
		||||
					samplers[0] = texture.sampler;
 | 
			
		||||
 | 
			
		||||
					break: get samplers;
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		while (frame.flushed_instance_2d_count < frame.pushed_instance_2d_count) {
 | 
			
		||||
			const buffer_index = frame.flushed_instance_2d_count / Instance2D.instances_per_buffer;
 | 
			
		||||
			const buffer_offset = frame.flushed_instance_2d_count % Instance2D.instances_per_buffer;
 | 
			
		||||
			const instances_to_flush = @min(Instance2D.instances_per_buffer - buffer_offset, unflushed_count);
 | 
			
		||||
 | 
			
		||||
			bindings.vertex_buffers[Instance2D.buffer_indices.instance] = self.instance_2d_buffers.values[buffer_index];
 | 
			
		||||
			bindings.vertex_buffer_offsets[Instance2D.buffer_indices.instance] = @intCast(buffer_offset);
 | 
			
		||||
 | 
			
		||||
			sokol.gfx.applyBindings(bindings);
 | 
			
		||||
			sokol.gfx.draw(0, mesh_2d.index_count, @intCast(instances_to_flush));
 | 
			
		||||
 | 
			
		||||
			frame.flushed_instance_2d_count += instances_to_flush;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn insert_object(self: *Rendering, index: usize, object: Object) !void {
 | 
			
		||||
		const resource_count = self.objects.len();
 | 
			
		||||
 | 
			
		||||
		if (index < resource_count) {
 | 
			
		||||
			const empty_object = &self.objects.values[index];
 | 
			
		||||
 | 
			
		||||
			if (empty_object.* != .empty) {
 | 
			
		||||
				return error.InvalidHandle;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			empty_object.* = object;
 | 
			
		||||
		} else {
 | 
			
		||||
			if (index != resource_count) {
 | 
			
		||||
				return error.InvalidIndex;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			try self.objects.push_grow(object);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn push_instance_2d(self: *Rendering, frame: *Frame, command: RenderCommand.Instance) std.mem.Allocator.Error!void {
 | 
			
		||||
		if (command.mesh_2d != frame.mesh_2d or command.texture != frame.texture) {
 | 
			
		||||
			self.flush_instance_2ds(frame);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		frame.mesh_2d = command.mesh_2d;
 | 
			
		||||
		frame.texture = command.texture;
 | 
			
		||||
 | 
			
		||||
		const has_filled_buffer = (frame.pushed_instance_2d_count % Instance2D.instances_per_buffer) == 0;
 | 
			
		||||
		const pushed_buffer_count = frame.pushed_instance_2d_count / Instance2D.instances_per_buffer;
 | 
			
		||||
 | 
			
		||||
		if (has_filled_buffer and pushed_buffer_count == self.instance_2d_buffers.len()) {
 | 
			
		||||
			const instance_buffer = sokol.gfx.makeBuffer(.{
 | 
			
		||||
				.size = @sizeOf(Instance2D) * Instance2D.instances_per_buffer,
 | 
			
		||||
				.usage = .STREAM,
 | 
			
		||||
				.label = "2D drawing instance buffer",
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			errdefer sokol.gfx.destroyBuffer(instance_buffer);
 | 
			
		||||
 | 
			
		||||
			try self.instance_2d_buffers.push_grow(instance_buffer);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		_ = sokol.gfx.appendBuffer(self.instance_2d_buffers.get().?, sokol.gfx.asRange(&Instance2D{
 | 
			
		||||
			.transform = command.transform,
 | 
			
		||||
		}));
 | 
			
		||||
 | 
			
		||||
		frame.pushed_instance_2d_count += 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn remove_object(self: *Rendering, index: usize) ?Object {
 | 
			
		||||
		const object = self.objects.values[index];
 | 
			
		||||
 | 
			
		||||
		if (object != .empty) {
 | 
			
		||||
			self.objects.values[index] = .empty;
 | 
			
		||||
 | 
			
		||||
			return object;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return null;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const RenderCommand = union (enum) {
 | 
			
		||||
	instance_2d: Instance,
 | 
			
		||||
	post_process: PostProcess,
 | 
			
		||||
 | 
			
		||||
	pub const Instance = struct {
 | 
			
		||||
		texture: gfx.Handle,
 | 
			
		||||
		mesh_2d: gfx.Handle,
 | 
			
		||||
		transform: gfx.Transform2D,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub const PostProcess = struct {
 | 
			
		||||
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	fn clone(self: RenderCommand, arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!RenderCommand {
 | 
			
		||||
		_ = arena;
 | 
			
		||||
 | 
			
		||||
		return switch (self) {
 | 
			
		||||
			.instance_2d => |instance_2d| .{.instance_2d = instance_2d},
 | 
			
		||||
			.post_process => |post_process| .{.post_process = post_process},
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const RenderChain = commands.Chain(RenderCommand, RenderCommand.clone);
 | 
			
		||||
 | 
			
		||||
pub const RenderList = commands.List(RenderCommand, RenderCommand.clone);
 | 
			
		||||
@ -1,77 +0,0 @@
 | 
			
		||||
const coral = @import("coral");
 | 
			
		||||
 | 
			
		||||
const gfx = @import("../gfx.zig");
 | 
			
		||||
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
 | 
			
		||||
pub fn bmp_file_desc(arena: *std.heap.ArenaAllocator, path: []const u8) gfx.Assets.Format.Error!gfx.Desc {
 | 
			
		||||
	const header = try coral.files.bundle.read_little(path, 0, extern struct {
 | 
			
		||||
		type: [2]u8 align (1),
 | 
			
		||||
		file_size: u32 align (1),
 | 
			
		||||
		reserved: [2]u16 align (1),
 | 
			
		||||
		image_offset: u32 align (1),
 | 
			
		||||
		header_size: u32 align (1),
 | 
			
		||||
		pixel_width: i32 align (1),
 | 
			
		||||
		pixel_height: i32 align (1),
 | 
			
		||||
		color_planes: u16 align (1),
 | 
			
		||||
		bits_per_pixel: u16 align (1),
 | 
			
		||||
		compression_method: u32 align (1),
 | 
			
		||||
		image_size: u32 align(1),
 | 
			
		||||
		pixels_per_meter_x: i32 align (1),
 | 
			
		||||
		pixels_per_meter_y: i32 align (1),
 | 
			
		||||
		palette_colors_used: u32 align (1),
 | 
			
		||||
		important_colors_used: u32 align (1),
 | 
			
		||||
	}) orelse {
 | 
			
		||||
		return error.FormatUnsupported;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	if (!std.mem.eql(u8, &header.type, "BM")) {
 | 
			
		||||
		return error.FormatUnsupported;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const pixel_width = std.math.cast(u16, header.pixel_width) orelse {
 | 
			
		||||
		return error.FormatUnsupported;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const pixels = try arena.allocator().alloc(coral.io.Byte, header.image_size);
 | 
			
		||||
	const bytes_per_pixel = header.bits_per_pixel / coral.io.bits_per_byte;
 | 
			
		||||
	const alignment = 4;
 | 
			
		||||
	const byte_stride = pixel_width * bytes_per_pixel;
 | 
			
		||||
	const padded_byte_stride = alignment * @divTrunc((byte_stride + alignment - 1), alignment);
 | 
			
		||||
	const byte_padding = coral.scalars.sub(padded_byte_stride, byte_stride) orelse 0;
 | 
			
		||||
	var buffer_offset: usize = 0;
 | 
			
		||||
	var file_offset = @as(usize, header.image_offset);
 | 
			
		||||
 | 
			
		||||
	switch (header.bits_per_pixel) {
 | 
			
		||||
		32 => {
 | 
			
		||||
			while (buffer_offset < pixels.len) {
 | 
			
		||||
				const line = pixels[buffer_offset .. buffer_offset + byte_stride];
 | 
			
		||||
 | 
			
		||||
				if (try coral.files.bundle.read_bytes(path, file_offset, line) != byte_stride) {
 | 
			
		||||
					return error.FormatUnsupported;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				for (0 .. pixel_width) |i| {
 | 
			
		||||
					const line_offset = i * 4;
 | 
			
		||||
					const pixel = line[line_offset .. line_offset + 4];
 | 
			
		||||
 | 
			
		||||
					std.mem.swap(u8, &pixel[0], &pixel[2]);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				file_offset += line.len + byte_padding;
 | 
			
		||||
				buffer_offset += padded_byte_stride;
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		else => return error.FormatUnsupported,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return .{
 | 
			
		||||
		.texture = .{
 | 
			
		||||
			.width = pixel_width,
 | 
			
		||||
			.data = pixels,
 | 
			
		||||
			.format = .rgba8,
 | 
			
		||||
			.access = .static,
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
@ -51,10 +51,6 @@ out vec4 texel;
 | 
			
		||||
 | 
			
		||||
void main() {
 | 
			
		||||
	texel = texture(sampler2D(tex, smp), uv) * color;
 | 
			
		||||
 | 
			
		||||
	if (texel.a == 0) {
 | 
			
		||||
		discard;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,7 @@ const std = @import("std");
 | 
			
		||||
fn Channel(comptime Message: type) type {
 | 
			
		||||
	return struct {
 | 
			
		||||
		buffers: [2]coral.stack.Sequential(Message),
 | 
			
		||||
		swap_index: u1 = 0,
 | 
			
		||||
		swapped: bool = false,
 | 
			
		||||
		ticks: u1 = 0,
 | 
			
		||||
 | 
			
		||||
		const Self = @This();
 | 
			
		||||
@ -28,9 +28,9 @@ fn Channel(comptime Message: type) type {
 | 
			
		||||
			channel.res.ticks = coral.scalars.add(channel.res.ticks, 1) orelse 0;
 | 
			
		||||
 | 
			
		||||
			if (channel.res.ticks == 0) {
 | 
			
		||||
				channel.res.swap_index ^= 1;
 | 
			
		||||
				channel.res.swapped = !channel.res.swapped;
 | 
			
		||||
 | 
			
		||||
				channel.res.buffers[channel.res.swap_index].clear();
 | 
			
		||||
				channel.res.buffers[@intFromBool(channel.res.swapped)].clear();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -44,11 +44,11 @@ fn Channel(comptime Message: type) type {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fn messages(self: Self) []const Message {
 | 
			
		||||
			return self.buffers[self.swap_index ^ 1].values;
 | 
			
		||||
			return self.buffers[@intFromBool(!self.swapped)].values;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fn push(self: *Self, message: Message) std.mem.Allocator.Error!void {
 | 
			
		||||
			try self.buffers[self.swap_index].push_grow(message);
 | 
			
		||||
			try self.buffers[@intFromBool(self.swapped)].push(message);
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user