Compare commits
	
		
			4 Commits
		
	
	
		
			96e10f0668
			...
			9cfabae931
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 9cfabae931 | |||
| 4125aa0ddb | |||
| ee88f58e53 | |||
| 04e8d69c37 | 
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -1,4 +1,4 @@
 | 
				
			|||||||
# Generated assets
 | 
					# Generated assets
 | 
				
			||||||
/zig-cache
 | 
					/.zig-cache
 | 
				
			||||||
/zig-out
 | 
					/zig-out
 | 
				
			||||||
*.glsl.zig
 | 
					*.glsl.zig
 | 
				
			||||||
 | 
				
			|||||||
@ -10,8 +10,8 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    .dependencies = .{
 | 
					    .dependencies = .{
 | 
				
			||||||
        .sokol = .{
 | 
					        .sokol = .{
 | 
				
			||||||
            .url = "git+https://github.com/floooh/sokol-zig.git#796a3d3d54c22d20da9e91a9a9120d5423d1e700",
 | 
					            .url = "git+https://github.com/floooh/sokol-zig.git#7c25767e51aa06dd5fb0684e4a8f2200d182ef27",
 | 
				
			||||||
            .hash = "12209a187e071d76af00c02865677d170f844376866d062b1b5f82e4ecbd750c4e18",
 | 
								.hash = "1220fa7f47fbaf2f1ed8c17fab2d23b6a85bcbbc4aa0b3802c90a3e8bf6fca1f8569",
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -32,7 +32,7 @@ pub fn create_event(self: *Self, label: []const u8) std.mem.Allocator.Error!Even
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	const index = self.event_systems.len();
 | 
						const index = self.event_systems.len();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	try self.event_systems.push(systems);
 | 
						try self.event_systems.push_grow(systems);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return @enumFromInt(index);
 | 
						return @enumFromInt(index);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@ pub const ascii = @import("./ascii.zig");
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
pub const dag = @import("./dag.zig");
 | 
					pub const dag = @import("./dag.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const debug = @import("./debug.zig");
 | 
					pub const files = @import("./files.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const hashes = @import("./hashes.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 {
 | 
							pub fn append(self: *Self, payload: Payload) std.mem.Allocator.Error!Node {
 | 
				
			||||||
			const node = @as(Node, @enumFromInt(self.table.len()));
 | 
								const node = @as(Node, @enumFromInt(self.table.len()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			try self.table.push(.{
 | 
								try self.table.push_grow(.{
 | 
				
			||||||
				.payload = payload,
 | 
									.payload = payload,
 | 
				
			||||||
				.edges = .{.allocator = self.table.allocator},
 | 
									.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) {
 | 
								if (slices.index_of(edges.values, 0, edge_node) == null) {
 | 
				
			||||||
				try edges.push(edge_node);
 | 
									try edges.push_grow(edge_node);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			return true;
 | 
								return true;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +0,0 @@
 | 
				
			|||||||
const std = @import("std");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn assert_ok(error_union: anytype) @typeInfo(@TypeOf(error_union)).ErrorUnion.payload {
 | 
					 | 
				
			||||||
	return error_union catch unreachable;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										124
									
								
								src/coral/files.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								src/coral/files.zig
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,124 @@
 | 
				
			|||||||
 | 
					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,23 +6,30 @@ const slices = @import("./slices.zig");
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const std = @import("std");
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const Byte = u8;
 | 
					pub const Writable = struct {
 | 
				
			||||||
 | 
						data: []Byte,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const Decoder = coral.io.Functor(coral.io.Error!void, &.{[]coral.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 Error = error {
 | 
					pub const Error = error {
 | 
				
			||||||
	UnavailableResource,
 | 
						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 {
 | 
					pub fn Functor(comptime Output: type, comptime input_types: []const type) type {
 | 
				
			||||||
	const InputTuple = std.meta.Tuple(input_types);
 | 
						const InputTuple = std.meta.Tuple(input_types);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -119,20 +126,6 @@ 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 {
 | 
					pub const PrintError = Error || error {
 | 
				
			||||||
	IncompleteWrite,
 | 
						IncompleteWrite,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@ -141,8 +134,6 @@ pub const Reader = Generator(Error!usize, &.{[]coral.Byte});
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
pub const Writer = Generator(Error!usize, &.{[]const 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 {
 | 
					pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral.Byte {
 | 
				
			||||||
	const buffer = coral.Stack(coral.Byte){.allocator = allocator};
 | 
						const buffer = coral.Stack(coral.Byte){.allocator = allocator};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -153,20 +144,6 @@ pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral.
 | 
				
			|||||||
	return buffer.to_allocation(streamed);
 | 
						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 const bits_per_byte = 8;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn bytes_of(value: anytype) []const Byte {
 | 
					pub fn bytes_of(value: anytype) []const Byte {
 | 
				
			||||||
@ -179,14 +156,6 @@ 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 {
 | 
					pub fn print(writer: Writer, utf8: []const u8) PrintError!void {
 | 
				
			||||||
	if (try writer.yield(.{utf8}) != utf8.len) {
 | 
						if (try writer.yield(.{utf8}) != utf8.len) {
 | 
				
			||||||
		return error.IncompleteWrite;
 | 
							return error.IncompleteWrite;
 | 
				
			||||||
@ -208,35 +177,6 @@ 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) {
 | 
					pub fn slice_sentineled(comptime sen: anytype, ptr: [*:sen]const @TypeOf(sen)) [:sen]const @TypeOf(sen) {
 | 
				
			||||||
	var len = @as(usize, 0);
 | 
						var len = @as(usize, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -53,7 +53,7 @@ pub const Table = struct {
 | 
				
			|||||||
			errdefer resource_allocator.destroy(allocated_resource);
 | 
								errdefer resource_allocator.destroy(allocated_resource);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			std.debug.assert(try self.table.emplace(value_id, .{
 | 
								std.debug.assert(try self.table.emplace(value_id, .{
 | 
				
			||||||
				.ptr = allocated_resource,
 | 
									.ptr = @ptrCast(allocated_resource),
 | 
				
			||||||
			}));
 | 
								}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			allocated_resource.* = value;
 | 
								allocated_resource.* = value;
 | 
				
			||||||
 | 
				
			|||||||
@ -29,6 +29,10 @@ pub fn Sequential(comptime Value: type) type {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pub fn grow(self: *Self, additional: usize) std.mem.Allocator.Error!void {
 | 
							pub fn grow(self: *Self, additional: usize) std.mem.Allocator.Error!void {
 | 
				
			||||||
 | 
								if (additional == 0) {
 | 
				
			||||||
 | 
									return;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const grown_capacity = self.cap + additional;
 | 
								const grown_capacity = self.cap + additional;
 | 
				
			||||||
			const buffer = try self.allocator.alloc(Value, grown_capacity);
 | 
								const buffer = try self.allocator.alloc(Value, grown_capacity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -87,7 +91,39 @@ pub fn Sequential(comptime Value: type) type {
 | 
				
			|||||||
			return true;
 | 
								return true;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pub fn push(self: *Self, value: Value) std.mem.Allocator.Error!void {
 | 
							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 {
 | 
				
			||||||
			if (self.values.len == self.cap) {
 | 
								if (self.values.len == self.cap) {
 | 
				
			||||||
				try self.grow(@max(1, self.cap));
 | 
									try self.grow(@max(1, self.cap));
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@ -99,27 +135,11 @@ pub fn Sequential(comptime Value: type) type {
 | 
				
			|||||||
			self.values[offset_index] = value;
 | 
								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 {
 | 
							pub fn push_many(self: *Self, n: usize, value: Value) std.mem.Allocator.Error!void {
 | 
				
			||||||
			const new_length = self.values.len + n;
 | 
								const new_length = self.values.len + n;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (new_length > self.cap) {
 | 
								if (new_length > self.cap) {
 | 
				
			||||||
				try self.grow(new_length);
 | 
									return false;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const offset_index = self.values.len;
 | 
								const offset_index = self.values.len;
 | 
				
			||||||
@ -129,6 +149,8 @@ pub fn Sequential(comptime Value: type) type {
 | 
				
			|||||||
			for (0 .. n) |index| {
 | 
								for (0 .. n) |index| {
 | 
				
			||||||
				self.values[offset_index + index] = value;
 | 
									self.values[offset_index + index] = value;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pub fn resize(self: *Self, size: usize, default_value: Value) std.mem.Allocator.Error!void {
 | 
							pub fn resize(self: *Self, size: usize, default_value: Value) std.mem.Allocator.Error!void {
 | 
				
			||||||
@ -237,7 +259,7 @@ pub fn Parallel(comptime Value: type) type {
 | 
				
			|||||||
			return self.values.len;
 | 
								return self.values.len;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pub fn push(self: *Self, value: Value) std.mem.Allocator.Error!void {
 | 
							pub fn push_grow(self: *Self, value: Value) std.mem.Allocator.Error!void {
 | 
				
			||||||
			if (self.len() == self.cap) {
 | 
								if (self.len() == self.cap) {
 | 
				
			||||||
				try self.grow(@max(1, self.cap));
 | 
									try self.grow(@max(1, self.cap));
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
				
			|||||||
@ -51,7 +51,7 @@ pub const BindContext = struct {
 | 
				
			|||||||
		const id = resource.type_id(Resource);
 | 
							const id = resource.type_id(Resource);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (!self.accesses_resource(.read_write, id)) {
 | 
							if (!self.accesses_resource(.read_write, id)) {
 | 
				
			||||||
			try self.systems.graph.get_ptr(self.node).?.resource_accesses.push(.{.read_write = id});
 | 
								try self.systems.graph.get_ptr(self.node).?.resource_accesses.push_grow(.{.read_write = id});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const read_write_resource_nodes = lazily_create: {
 | 
							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) {
 | 
							if (slices.index_of(read_write_resource_nodes.values, 0, self.node) == null) {
 | 
				
			||||||
			try read_write_resource_nodes.push(self.node);
 | 
								try read_write_resource_nodes.push_grow(self.node);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return value;
 | 
							return value;
 | 
				
			||||||
@ -79,7 +79,7 @@ pub const BindContext = struct {
 | 
				
			|||||||
		const id = resource.type_id(Resource);
 | 
							const id = resource.type_id(Resource);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (!self.accesses_resource(.read_only, id)) {
 | 
							if (!self.accesses_resource(.read_only, id)) {
 | 
				
			||||||
			try self.systems.graph.get_ptr(self.node).?.resource_accesses.push(.{.read_only = id});
 | 
								try self.systems.graph.get_ptr(self.node).?.resource_accesses.push_grow(.{.read_only = id});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const read_only_resource_nodes = lazily_create: {
 | 
							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) {
 | 
							if (slices.index_of(read_only_resource_nodes.values, 0, self.node) == null) {
 | 
				
			||||||
			try read_only_resource_nodes.push(self.node);
 | 
								try read_only_resource_nodes.push_grow(self.node);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return value;
 | 
							return value;
 | 
				
			||||||
@ -291,7 +291,7 @@ pub const Schedule = struct {
 | 
				
			|||||||
						try populate_bundle(bundle, graph, edge);
 | 
											try populate_bundle(bundle, graph, edge);
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					try bundle.push(node);
 | 
										try bundle.push_grow(node);
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				fn sort(schedule: *Schedule) !void {
 | 
									fn sort(schedule: *Schedule) !void {
 | 
				
			||||||
@ -304,7 +304,7 @@ pub const Schedule = struct {
 | 
				
			|||||||
							continue;
 | 
												continue;
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						try schedule.parallel_work_bundles.push(.{.allocator = heap.allocator});
 | 
											try schedule.parallel_work_bundles.push_grow(.{.allocator = heap.allocator});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						const bundle = schedule.parallel_work_bundles.get_ptr().?;
 | 
											const bundle = schedule.parallel_work_bundles.get_ptr().?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -328,8 +328,9 @@ pub const Schedule = struct {
 | 
				
			|||||||
								.main => {
 | 
													.main => {
 | 
				
			||||||
									const extracted_work = work.values[index ..];
 | 
														const extracted_work = work.values[index ..];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
									try schedule.blocking_work.push_all(extracted_work);
 | 
														try schedule.blocking_work.grow(extracted_work.len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
														std.debug.assert(schedule.blocking_work.push_all(extracted_work));
 | 
				
			||||||
									std.debug.assert(work.pop_many(extracted_work.len));
 | 
														std.debug.assert(work.pop_many(extracted_work.len));
 | 
				
			||||||
								},
 | 
													},
 | 
				
			||||||
							}
 | 
												}
 | 
				
			||||||
@ -488,7 +489,7 @@ pub const Schedule = struct {
 | 
				
			|||||||
			});
 | 
								});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		try nodes.push(node);
 | 
							try nodes.push_grow(node);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		self.invalidate_work();
 | 
							self.invalidate_work();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
@ -2,8 +2,6 @@ const ascii = @import("./ascii.zig");
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const coral = @import("./coral.zig");
 | 
					const coral = @import("./coral.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const debug = @import("./debug.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const io = @import("./io.zig");
 | 
					const io = @import("./io.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const std = @import("std");
 | 
					const std = @import("std");
 | 
				
			||||||
@ -16,7 +14,7 @@ pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	errdefer buffer.deinit();
 | 
						errdefer buffer.deinit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	debug.assert_try(print_formatted(buffer.writer(), format, args));
 | 
						print_formatted(buffer.writer(), format, args) catch unreachable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return buffer.to_allocation(formatted_len, 0);
 | 
						return buffer.to_allocation(formatted_len, 0);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -24,7 +22,7 @@ pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8
 | 
				
			|||||||
fn count_formatted(comptime format: []const u8, args: anytype) usize {
 | 
					fn count_formatted(comptime format: []const u8, args: anytype) usize {
 | 
				
			||||||
	var count = io.defaultWritable{};
 | 
						var count = io.defaultWritable{};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	debug.assert_try(print_formatted(count.writer(), format, args));
 | 
						print_formatted(count.writer(), format, args) catch unreachable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return count.written;
 | 
						return count.written;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										37
									
								
								src/main.zig
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								src/main.zig
									
									
									
									
									
								
							@ -6,6 +6,7 @@ const ona = @import("ona");
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const Actors = struct {
 | 
					const Actors = struct {
 | 
				
			||||||
	instances: coral.stack.Sequential(ona.gfx.Point2D) = .{.allocator = coral.heap.allocator},
 | 
						instances: coral.stack.Sequential(ona.gfx.Point2D) = .{.allocator = coral.heap.allocator},
 | 
				
			||||||
 | 
						quad_mesh_2d: ona.gfx.Handle = .none,
 | 
				
			||||||
	body_texture: ona.gfx.Handle = .none,
 | 
						body_texture: ona.gfx.Handle = .none,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -21,45 +22,29 @@ pub fn main() !void {
 | 
				
			|||||||
	});
 | 
						});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn load(display: coral.ReadBlocking(ona.gfx.Display), actors: coral.Write(Actors), gfx: ona.gfx.Work) !void {
 | 
					fn load(display: coral.Write(ona.gfx.Display), actors: coral.Write(Actors), assets: coral.Write(ona.gfx.Assets)) !void {
 | 
				
			||||||
	display.res.resize(1280, 720);
 | 
						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));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const crap = [_]u32{
 | 
						try actors.res.instances.push_grow(.{0, 0});
 | 
				
			||||||
		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 {
 | 
					fn exit(actors: coral.Write(Actors)) void {
 | 
				
			||||||
	actors.res.instances.deinit();
 | 
						actors.res.instances.deinit();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn render(gfx: ona.gfx.Work, actors: coral.Write(Actors)) !void {
 | 
					fn render(queue: ona.gfx.Queue, actors: coral.Write(Actors)) !void {
 | 
				
			||||||
	for (actors.res.instances.values) |instance| {
 | 
						for (actors.res.instances.values) |instance| {
 | 
				
			||||||
		try gfx.queue.draw(.{
 | 
							try queue.commands.append(.{
 | 
				
			||||||
			.instance_2d = .{
 | 
								.instance_2d = .{
 | 
				
			||||||
				.mesh_2d = gfx.primitives.quad_mesh,
 | 
									.mesh_2d = actors.res.quad_mesh_2d,
 | 
				
			||||||
				.texture = actors.res.body_texture,
 | 
									.texture = actors.res.body_texture,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				.transform = .{
 | 
									.transform = .{
 | 
				
			||||||
					.origin = instance,
 | 
										.origin = instance,
 | 
				
			||||||
					.xbasis = .{100, 0},
 | 
										.xbasis = .{64, 0},
 | 
				
			||||||
					.ybasis = .{0, 100},
 | 
										.ybasis = .{0, 64},
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										270
									
								
								src/ona/gfx.zig
									
									
									
									
									
								
							
							
						
						
									
										270
									
								
								src/ona/gfx.zig
									
									
									
									
									
								
							@ -1,37 +1,112 @@
 | 
				
			|||||||
const App = @import("./App.zig");
 | 
					const App = @import("./App.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const Queue = @import("./gfx/Queue.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const coral = @import("coral");
 | 
					const coral = @import("coral");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Device = @import("./gfx/Device.zig");
 | 
					const device = @import("./gfx/device.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ext = @import("./ext.zig");
 | 
					const ext = @import("./ext.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const formats = @import("./gfx/formats.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const msg = @import("./msg.zig");
 | 
					const msg = @import("./msg.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const std = @import("std");
 | 
					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 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 {
 | 
					pub const Display = struct {
 | 
				
			||||||
	sdl_window: *ext.SDL_Window,
 | 
						width: u16 = 1280,
 | 
				
			||||||
	device: Device,
 | 
						height: u16 = 720,
 | 
				
			||||||
 | 
						clear_color: Color = colors.black,
 | 
				
			||||||
	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) {
 | 
					pub const Handle = enum (usize) {
 | 
				
			||||||
@ -64,69 +139,62 @@ pub const Input = union (enum) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
pub const Point2D = @Vector(2, f32);
 | 
					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 {
 | 
					pub const Transform2D = extern struct {
 | 
				
			||||||
	xbasis: Point2D = .{1, 0},
 | 
						xbasis: Point2D = .{1, 0},
 | 
				
			||||||
	ybasis: Point2D = .{0, 1},
 | 
						ybasis: Point2D = .{0, 1},
 | 
				
			||||||
	origin: Point2D = @splat(0),
 | 
						origin: Point2D = @splat(0),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const Work = struct {
 | 
					const builtin_formats = [_]Assets.Format{
 | 
				
			||||||
	queue: *Queue.Buffer,
 | 
						.{
 | 
				
			||||||
	primitives: *const Primitives,
 | 
							.extension = "bmp",
 | 
				
			||||||
 | 
							.file_desc = formats.bmp_file_desc,
 | 
				
			||||||
	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 {
 | 
					pub const colors = struct {
 | 
				
			||||||
@ -156,43 +224,45 @@ pub fn poll(app: coral.Write(App), inputs: msg.Send(Input)) !void {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn setup(world: *coral.World, events: App.Events) (error {SDLError} || std.Thread.SpawnError || std.mem.Allocator.Error)!void {
 | 
					pub fn setup(world: *coral.World, events: App.Events) (error {Unsupported} || std.Thread.SpawnError || std.mem.Allocator.Error)!void {
 | 
				
			||||||
	if (ext.SDL_Init(ext.SDL_INIT_VIDEO) != 0) {
 | 
						if (ext.SDL_Init(ext.SDL_INIT_VIDEO) != 0) {
 | 
				
			||||||
		return error.SDLError;
 | 
							return error.Unsupported;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const sdl_window = create: {
 | 
						var context = try device.Context.init();
 | 
				
			||||||
		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 {
 | 
						errdefer context.deinit();
 | 
				
			||||||
			return error.SDLError;
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	errdefer ext.SDL_DestroyWindow(sdl_window);
 | 
						var registered_formats = coral.stack.Sequential(Assets.Format){.allocator = coral.heap.allocator};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var device = try Device.init(sdl_window);
 | 
						errdefer registered_formats.deinit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	errdefer device.deinit();
 | 
						try registered_formats.grow(builtin_formats.len);
 | 
				
			||||||
 | 
						std.debug.assert(registered_formats.push_all(&builtin_formats));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	try world.set_resource(.main, Display{
 | 
						try world.set_resource(.none, Assets{
 | 
				
			||||||
		.device = device,
 | 
							.staging_arena = std.heap.ArenaAllocator.init(coral.heap.allocator),
 | 
				
			||||||
		.sdl_window = sdl_window,
 | 
							.formats = registered_formats,
 | 
				
			||||||
 | 
							.context = context,
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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.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.exit, coral.system_fn(stop), .{.label = "stop gfx"});
 | 
				
			||||||
	try world.on_event(events.finish, coral.system_fn(submit), .{.label = "submit gfx"});
 | 
						try world.on_event(events.finish, coral.system_fn(synchronize), .{.label = "synchronize gfx"});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn stop(display: coral.WriteBlocking(Display)) void {
 | 
					pub fn stop(assets: coral.Write(Assets)) void {
 | 
				
			||||||
	display.res.device.deinit();
 | 
						assets.res.staging_arena.deinit();
 | 
				
			||||||
	ext.SDL_DestroyWindow(display.res.sdl_window);
 | 
						assets.res.formats.deinit();
 | 
				
			||||||
 | 
						assets.res.context.deinit();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn submit(display: coral.WriteBlocking(Display)) void {
 | 
					pub fn synchronize(assets: coral.Write(Assets), display: coral.Read(Display)) !void {
 | 
				
			||||||
	display.res.device.submit();
 | 
						assets.res.context.submit(.{
 | 
				
			||||||
 | 
							.width = display.res.width,
 | 
				
			||||||
 | 
							.height = display.res.height,
 | 
				
			||||||
 | 
							.clear_color = display.res.clear_color,
 | 
				
			||||||
 | 
							.renders = Queue.renders.values,
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,534 +0,0 @@
 | 
				
			|||||||
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();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,309 +0,0 @@
 | 
				
			|||||||
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);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										80
									
								
								src/ona/gfx/commands.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/ona/gfx/commands.zig
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,80 @@
 | 
				
			|||||||
 | 
					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},
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										691
									
								
								src/ona/gfx/device.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										691
									
								
								src/ona/gfx/device.zig
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,691 @@
 | 
				
			|||||||
 | 
					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);
 | 
				
			||||||
							
								
								
									
										77
									
								
								src/ona/gfx/formats.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/ona/gfx/formats.zig
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,77 @@
 | 
				
			|||||||
 | 
					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,6 +51,10 @@ out vec4 texel;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void main() {
 | 
					void main() {
 | 
				
			||||||
	texel = texture(sampler2D(tex, smp), uv) * color;
 | 
						texel = texture(sampler2D(tex, smp), uv) * color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (texel.a == 0) {
 | 
				
			||||||
 | 
							discard;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@end
 | 
					@end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -7,7 +7,7 @@ const std = @import("std");
 | 
				
			|||||||
fn Channel(comptime Message: type) type {
 | 
					fn Channel(comptime Message: type) type {
 | 
				
			||||||
	return struct {
 | 
						return struct {
 | 
				
			||||||
		buffers: [2]coral.stack.Sequential(Message),
 | 
							buffers: [2]coral.stack.Sequential(Message),
 | 
				
			||||||
		swapped: bool = false,
 | 
							swap_index: u1 = 0,
 | 
				
			||||||
		ticks: u1 = 0,
 | 
							ticks: u1 = 0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const Self = @This();
 | 
							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;
 | 
								channel.res.ticks = coral.scalars.add(channel.res.ticks, 1) orelse 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (channel.res.ticks == 0) {
 | 
								if (channel.res.ticks == 0) {
 | 
				
			||||||
				channel.res.swapped = !channel.res.swapped;
 | 
									channel.res.swap_index ^= 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				channel.res.buffers[@intFromBool(channel.res.swapped)].clear();
 | 
									channel.res.buffers[channel.res.swap_index].clear();
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -44,11 +44,11 @@ fn Channel(comptime Message: type) type {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		fn messages(self: Self) []const Message {
 | 
							fn messages(self: Self) []const Message {
 | 
				
			||||||
			return self.buffers[@intFromBool(!self.swapped)].values;
 | 
								return self.buffers[self.swap_index ^ 1].values;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		fn push(self: *Self, message: Message) std.mem.Allocator.Error!void {
 | 
							fn push(self: *Self, message: Message) std.mem.Allocator.Error!void {
 | 
				
			||||||
			try self.buffers[@intFromBool(self.swapped)].push(message);
 | 
								try self.buffers[self.swap_index].push_grow(message);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user