Refactor rendering logic ahead of new asset pipeline
This commit is contained in:
		
							parent
							
								
									96e10f0668
								
							
						
					
					
						commit
						04e8d69c37
					
				| @ -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,8 +2,6 @@ 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 hashes = @import("./hashes.zig"); | pub const hashes = @import("./hashes.zig"); | ||||||
| 
 | 
 | ||||||
| pub const heap = @import("./heap.zig"); | pub const heap = @import("./heap.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; |  | ||||||
| } |  | ||||||
| @ -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; | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										35
									
								
								src/main.zig
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								src/main.zig
									
									
									
									
									
								
							| @ -21,45 +21,28 @@ 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"); | ||||||
| 
 | 
 | ||||||
| 	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), assets: coral.Read(ona.gfx.Assets)) !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 = assets.res.primitives.quad_mesh, | ||||||
| 				.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}, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 		}); | 		}); | ||||||
|  | |||||||
							
								
								
									
										254
									
								
								src/ona/gfx.zig
									
									
									
									
									
								
							
							
						
						
									
										254
									
								
								src/ona/gfx.zig
									
									
									
									
									
								
							| @ -1,10 +1,10 @@ | |||||||
| 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 formats = @import("./gfx/formats.zig"); | ||||||
|  | 
 | ||||||
|  | const device = @import("./gfx/device.zig"); | ||||||
| 
 | 
 | ||||||
| const ext = @import("./ext.zig"); | const ext = @import("./ext.zig"); | ||||||
| 
 | 
 | ||||||
| @ -12,26 +12,47 @@ const msg = @import("./msg.zig"); | |||||||
| 
 | 
 | ||||||
| const std = @import("std"); | const std = @import("std"); | ||||||
| 
 | 
 | ||||||
|  | pub const Assets = struct { | ||||||
|  | 	context: device.Context, | ||||||
|  | 	primitives: Primitives, | ||||||
|  | 	formats: coral.stack.Sequential(Format), | ||||||
|  | 
 | ||||||
|  | 	pub const Format = struct { | ||||||
|  | 		extension: []const u8, | ||||||
|  | 		open: *const fn ([]const u8) std.mem.Allocator.Error!Handle, | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	const Primitives = struct { | ||||||
|  | 		quad_mesh: Handle, | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	pub fn close(self: *Assets, handle: Handle) void { | ||||||
|  | 		return self.context.close(handle); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pub fn open_file(self: *Assets, path: []const u8) std.mem.Allocator.Error!Handle { | ||||||
|  | 		for (self.formats.values) |format| { | ||||||
|  | 			if (!std.mem.endsWith(u8, path, format.extension)) { | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			return format.open(path); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return .none; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pub fn open_mesh_2d(self: *Assets, mesh_2d: Mesh2D) std.mem.Allocator.Error!Handle { | ||||||
|  | 		return self.context.open(.{.mesh_2d = mesh_2d}); | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| pub const Color = @Vector(4, f32); | pub const Color = @Vector(4, f32); | ||||||
| 
 | 
 | ||||||
| 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 +85,86 @@ pub const Input = union (enum) { | |||||||
| 
 | 
 | ||||||
| pub const Point2D = @Vector(2, f32); | pub const Point2D = @Vector(2, f32); | ||||||
| 
 | 
 | ||||||
|  | pub const Mesh2D = struct { | ||||||
|  | 	vertices: []const Vertex, | ||||||
|  | 	indices: []const u16, | ||||||
|  | 
 | ||||||
|  | 	pub const Vertex = struct { | ||||||
|  | 		xy: Point2D, | ||||||
|  | 		uv: Point2D, | ||||||
|  | 	}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | 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. | ||||||
|  | 		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()); | ||||||
|  | 		std.mem.swap(device.RenderChain, &renders.values[state.command_index], renders.get_ptr().?); | ||||||
|  | 		std.debug.assert(renders.pop()); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var renders = coral.stack.Sequential(device.RenderChain){.allocator = coral.heap.allocator}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | 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 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", | ||||||
| 
 | 		.open = formats.open_bmp, | ||||||
| 	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 +194,59 @@ 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{ | 	const half_extent = 0.5; | ||||||
| 		.device = device, | 
 | ||||||
| 		.sdl_window = sdl_window, | 	try world.set_resource(.none, Assets{ | ||||||
|  | 		.primitives = .{ | ||||||
|  | 			.quad_mesh = try context.open(.{ | ||||||
|  | 				.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}}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}), | ||||||
|  | 		}, | ||||||
|  | 
 | ||||||
|  | 		.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.context.deinit(); | ||||||
| 	ext.SDL_DestroyWindow(display.res.sdl_window); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 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}, | ||||||
|  | 			}; | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
							
								
								
									
										680
									
								
								src/ona/gfx/device.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										680
									
								
								src/ona/gfx/device.zig
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,680 @@ | |||||||
|  | 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 Asset = union (enum) { | ||||||
|  | 	texture: gfx.Texture, | ||||||
|  | 	mesh_2d: gfx.Mesh2D, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | 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(); | ||||||
|  | 		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, asset: Asset) 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, | ||||||
|  | 			.payload = asset, | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		try self.loop.closes.pending().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, | ||||||
|  | 		payload: Asset, | ||||||
|  | 
 | ||||||
|  | 		fn clone(command: OpenCommand, arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!OpenCommand { | ||||||
|  | 			const allocator = arena.allocator(); | ||||||
|  | 
 | ||||||
|  | 			return .{ | ||||||
|  | 				.payload = switch (command.payload) { | ||||||
|  | 					.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.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 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.payload) { | ||||||
|  | 					.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 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); | ||||||
							
								
								
									
										9
									
								
								src/ona/gfx/formats.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/ona/gfx/formats.zig
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | |||||||
|  | const gfx = @import("../gfx.zig"); | ||||||
|  | 
 | ||||||
|  | const std = @import("std"); | ||||||
|  | 
 | ||||||
|  | pub fn open_bmp(path: []const u8) std.mem.Allocator.Error!gfx.Handle { | ||||||
|  | 	// TODO: Implement. | ||||||
|  | 	_ = path; | ||||||
|  | 	unreachable; | ||||||
|  | } | ||||||
| @ -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