renderer-mvp/refactor-tech #50
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1,2 +1,4 @@ | |||||||
|  | # Generated assets | ||||||
| /zig-cache | /zig-cache | ||||||
| /zig-out | /zig-out | ||||||
|  | *.glsl.zig | ||||||
|  | |||||||
							
								
								
									
										39
									
								
								src/main.zig
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								src/main.zig
									
									
									
									
									
								
							| @ -5,8 +5,8 @@ const std = @import("std"); | |||||||
| const ona = @import("ona"); | const ona = @import("ona"); | ||||||
| 
 | 
 | ||||||
| const Actors = struct { | const Actors = struct { | ||||||
| 	instances: coral.stack.Sequential(ona.gfx.Queue.Instance2D) = .{.allocator = coral.heap.allocator}, | 	instances: coral.stack.Sequential(ona.gfx.Point2D) = .{.allocator = coral.heap.allocator}, | ||||||
| 	body_texture: ona.gfx.Queue.Handle = .none, | 	body_texture: ona.gfx.Handle = .none, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const Player = struct { | const Player = struct { | ||||||
| @ -21,15 +21,9 @@ pub fn main() !void { | |||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn load(display: coral.ReadBlocking(ona.gfx.Display), actors: coral.Write(Actors), gfx: ona.gfx.Queue) !void { | fn load(display: coral.ReadBlocking(ona.gfx.Display), actors: coral.Write(Actors), gfx: ona.gfx.Work) !void { | ||||||
| 	display.res.resize(1280, 720); | 	display.res.resize(1280, 720); | ||||||
| 
 | 
 | ||||||
| 	try actors.res.instances.push_many(800, .{ |  | ||||||
| 		.origin = .{75, 75}, |  | ||||||
| 		.xbasis = .{100, 0}, |  | ||||||
| 		.ybasis = .{0, 100}, |  | ||||||
| 	}); |  | ||||||
| 
 |  | ||||||
| 	const crap = [_]u32{ | 	const crap = [_]u32{ | ||||||
| 		0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF, 0xFF000000, | 		0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF, 0xFF000000, | ||||||
| 		0xFF000000, 0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF, | 		0xFF000000, 0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF, | ||||||
| @ -37,7 +31,7 @@ fn load(display: coral.ReadBlocking(ona.gfx.Display), actors: coral.Write(Actors | |||||||
| 		0xFF000000, 0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF, | 		0xFF000000, 0xFFFFFFFF, 0xFF000000, 0xFFFFFFFF, | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	actors.res.body_texture = try gfx.buffer.open(.{ | 	actors.res.body_texture = try gfx.queue.open(.{ | ||||||
| 		.resource = .{ | 		.resource = .{ | ||||||
| 			.texture = .{ | 			.texture = .{ | ||||||
| 				.data = coral.io.bytes_of(&crap), | 				.data = coral.io.bytes_of(&crap), | ||||||
| @ -47,22 +41,33 @@ fn load(display: coral.ReadBlocking(ona.gfx.Display), actors: coral.Write(Actors | |||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 	}); | 	}); | ||||||
|  | 
 | ||||||
|  | 	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.Queue, actors: coral.Write(Actors)) !void { | fn render(gfx: ona.gfx.Work, actors: coral.Write(Actors)) !void { | ||||||
| 	try gfx.buffer.draw_2d(.{ | 	for (actors.res.instances.values) |instance| { | ||||||
| 		.mesh_2d = gfx.primitives.quad_mesh, | 		try gfx.queue.draw(.{ | ||||||
| 		.instances = actors.res.instances.values, | 			.instance_2d = .{ | ||||||
| 		.texture = actors.res.body_texture, | 				.mesh_2d = gfx.primitives.quad_mesh, | ||||||
| 	}); | 				.texture = actors.res.body_texture, | ||||||
|  | 
 | ||||||
|  | 				.transform = .{ | ||||||
|  | 					.origin = instance, | ||||||
|  | 					.xbasis = .{100, 0}, | ||||||
|  | 					.ybasis = .{0, 100}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn update(player: coral.Read(Player), actors: coral.Write(Actors), mapping: coral.Read(ona.act.Mapping)) !void { | fn update(player: coral.Read(Player), actors: coral.Write(Actors), mapping: coral.Read(ona.act.Mapping)) !void { | ||||||
| 	actors.res.instances.values[0].origin += .{ | 	actors.res.instances.values[0] += .{ | ||||||
| 		mapping.res.axis_strength(player.res.move_x), | 		mapping.res.axis_strength(player.res.move_x), | ||||||
| 		mapping.res.axis_strength(player.res.move_y), | 		mapping.res.axis_strength(player.res.move_y), | ||||||
| 	}; | 	}; | ||||||
|  | |||||||
							
								
								
									
										108
									
								
								src/ona/gfx.zig
									
									
									
									
									
								
							
							
						
						
									
										108
									
								
								src/ona/gfx.zig
									
									
									
									
									
								
							| @ -1,22 +1,21 @@ | |||||||
| const App = @import("./App.zig"); | const App = @import("./App.zig"); | ||||||
| 
 | 
 | ||||||
| const Device = @import("./gfx/Device.zig"); |  | ||||||
| 
 |  | ||||||
| pub const Queue = @import("./gfx/Queue.zig"); | pub const Queue = @import("./gfx/Queue.zig"); | ||||||
| 
 | 
 | ||||||
| pub const color = @import("./gfx/color.zig"); |  | ||||||
| 
 |  | ||||||
| const coral = @import("coral"); | const coral = @import("coral"); | ||||||
| 
 | 
 | ||||||
|  | const Device = @import("./gfx/Device.zig"); | ||||||
|  | 
 | ||||||
| const ext = @import("./ext.zig"); | const ext = @import("./ext.zig"); | ||||||
| 
 | 
 | ||||||
| const msg = @import("./msg.zig"); | const msg = @import("./msg.zig"); | ||||||
| 
 | 
 | ||||||
| const std = @import("std"); | const std = @import("std"); | ||||||
| 
 | 
 | ||||||
|  | pub const Color = @Vector(4, f32); | ||||||
|  | 
 | ||||||
| pub const Display = struct { | pub const Display = struct { | ||||||
| 	sdl_window: *ext.SDL_Window, | 	sdl_window: *ext.SDL_Window, | ||||||
| 	clear_color: color.Value = color.black, |  | ||||||
| 	device: Device, | 	device: Device, | ||||||
| 
 | 
 | ||||||
| 	pub fn resize(self: Display, width: u16, height: u16) void { | 	pub fn resize(self: Display, width: u16, height: u16) void { | ||||||
| @ -35,11 +34,17 @@ pub const Display = struct { | |||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| pub const Error = error { | pub const Handle = enum (usize) { | ||||||
| 	SDLError, | 	none, | ||||||
| }; | 	_, | ||||||
| 
 | 
 | ||||||
| pub const Handle = Queue.Handle; | 	pub fn index(self: Handle) ?usize { | ||||||
|  | 		return switch (self) { | ||||||
|  | 			.none => null, | ||||||
|  | 			_ => @intFromEnum(self) - 1, | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| pub const Input = union (enum) { | pub const Input = union (enum) { | ||||||
| 	key_up: Key, | 	key_up: Key, | ||||||
| @ -57,8 +62,85 @@ pub const Input = union (enum) { | |||||||
| 	}; | 	}; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| pub const MeshPrimitives = struct { | pub const Point2D = @Vector(2, f32); | ||||||
| 	quad_mesh: Handle, | 
 | ||||||
|  | pub const Transform2D = extern struct { | ||||||
|  | 	xbasis: Point2D = .{1, 0}, | ||||||
|  | 	ybasis: Point2D = .{0, 1}, | ||||||
|  | 	origin: Point2D = @splat(0), | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const Work = struct { | ||||||
|  | 	queue: *Queue.Buffer, | ||||||
|  | 	primitives: *const Primitives, | ||||||
|  | 
 | ||||||
|  | 	const Primitives = struct { | ||||||
|  | 		quad_mesh: Handle, | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	pub const State = struct { | ||||||
|  | 		queue: *Queue, | ||||||
|  | 		primitives: *const Primitives, | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	pub fn bind(context: coral.system.BindContext) std.mem.Allocator.Error!State { | ||||||
|  | 		const queue = try Queue.create(); | ||||||
|  | 
 | ||||||
|  | 		return .{ | ||||||
|  | 			.primitives = (try context.register_read_only_resource_access(.none, Primitives)) orelse create: { | ||||||
|  | 				const buffer = queue.pending(); | ||||||
|  | 				const half_extent = 0.5; | ||||||
|  | 
 | ||||||
|  | 				try context.world.set_resource(.none, Primitives{ | ||||||
|  | 					.quad_mesh = try buffer.open(.{ | ||||||
|  | 						.label = "quad mesh primitive", | ||||||
|  | 
 | ||||||
|  | 						.resource = .{ | ||||||
|  | 							.mesh_2d = .{ | ||||||
|  | 								.indices = &.{0, 1, 2, 0, 2, 3}, | ||||||
|  | 
 | ||||||
|  | 								.vertices = &.{ | ||||||
|  | 									.{.xy = .{-half_extent, half_extent}, .uv = .{0, 1}}, | ||||||
|  | 									.{.xy = .{half_extent, half_extent}, .uv = .{1, 1}}, | ||||||
|  | 									.{.xy = .{half_extent, -half_extent}, .uv = .{1, 0}}, | ||||||
|  | 									.{.xy = .{-half_extent, -half_extent},  .uv = .{0, 0}}, | ||||||
|  | 								}, | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}), | ||||||
|  | 				}); | ||||||
|  | 
 | ||||||
|  | 				break: create (try context.register_read_only_resource_access(.none, Primitives)).?; | ||||||
|  | 			}, | ||||||
|  | 
 | ||||||
|  | 			.queue = queue, | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pub fn init(state: *State) Work { | ||||||
|  | 		return .{ | ||||||
|  | 			.queue = state.queue.pending(), | ||||||
|  | 			.primitives = state.primitives, | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pub fn unbind(state: *State) void { | ||||||
|  | 		state.queue.release(); | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const colors = struct { | ||||||
|  | 	pub const black = greyscale(0); | ||||||
|  | 
 | ||||||
|  | 	pub const white = greyscale(1); | ||||||
|  | 
 | ||||||
|  | 	pub fn greyscale(v: f32) Color { | ||||||
|  | 		return .{v, v, v, 1}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pub fn rgb(r: f32, g: f32, b: f32) Color { | ||||||
|  | 		return .{r, g, b, 1}; | ||||||
|  | 	} | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| pub fn poll(app: coral.Write(App), inputs: msg.Send(Input)) !void { | pub fn poll(app: coral.Write(App), inputs: msg.Send(Input)) !void { | ||||||
| @ -74,7 +156,7 @@ pub fn poll(app: coral.Write(App), inputs: msg.Send(Input)) !void { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn setup(world: *coral.World, events: App.Events) (Error || std.Thread.SpawnError || std.mem.Allocator.Error)!void { | pub fn setup(world: *coral.World, events: App.Events) (error {SDLError} || std.Thread.SpawnError || std.mem.Allocator.Error)!void { | ||||||
| 	if (ext.SDL_Init(ext.SDL_INIT_VIDEO) != 0) { | 	if (ext.SDL_Init(ext.SDL_INIT_VIDEO) != 0) { | ||||||
| 		return error.SDLError; | 		return error.SDLError; | ||||||
| 	} | 	} | ||||||
| @ -112,5 +194,5 @@ pub fn stop(display: coral.WriteBlocking(Display)) void { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn submit(display: coral.WriteBlocking(Display)) void { | pub fn submit(display: coral.WriteBlocking(Display)) void { | ||||||
| 	display.res.device.submit(display.res.sdl_window, display.res.clear_color); | 	display.res.device.submit(); | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,119 +1,144 @@ | |||||||
| const Queue = @import("./Queue.zig"); | const Queue = @import("./Queue.zig"); | ||||||
| 
 | 
 | ||||||
| const color = @import("./color.zig"); |  | ||||||
| 
 |  | ||||||
| const coral = @import("coral"); | const coral = @import("coral"); | ||||||
| 
 | 
 | ||||||
| const draw_2d = @import("./shaders/draw_2d.glsl.zig"); |  | ||||||
| 
 |  | ||||||
| const ext = @import("../ext.zig"); | const ext = @import("../ext.zig"); | ||||||
| 
 | 
 | ||||||
|  | const gfx = @import("../gfx.zig"); | ||||||
|  | 
 | ||||||
| const sokol = @import("sokol"); | const sokol = @import("sokol"); | ||||||
| 
 | 
 | ||||||
| const std = @import("std"); | const std = @import("std"); | ||||||
| 
 | 
 | ||||||
| thread: std.Thread, | thread: std.Thread, | ||||||
| render_state: *RenderState, | clear_color: gfx.Color = gfx.colors.black, | ||||||
|  | state: *State, | ||||||
| 
 | 
 | ||||||
| const AtomicBool = std.atomic.Value(bool); | const AtomicBool = std.atomic.Value(bool); | ||||||
| 
 | 
 | ||||||
| const RenderWork = struct { | const Frame = struct { | ||||||
| 	pipeline_2d: sokol.gfx.Pipeline, | 	width: u16, | ||||||
| 	instance_2d_sampler: sokol.gfx.Sampler, | 	height: u16, | ||||||
| 	instance_2d_buffers: coral.stack.Sequential(sokol.gfx.Buffer), | 	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), | 	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) { | 	const Resource = union (enum) { | ||||||
| 		mesh_2d: struct { | 		empty, | ||||||
|  | 		mesh_2d: Mesh2D, | ||||||
|  | 		texture: Texture, | ||||||
|  | 
 | ||||||
|  | 		const Mesh2D = struct { | ||||||
| 			index_count: u32, | 			index_count: u32, | ||||||
| 			vertex_buffer: sokol.gfx.Buffer, | 			vertex_buffer: sokol.gfx.Buffer, | ||||||
| 			index_buffer: sokol.gfx.Buffer, | 			index_buffer: sokol.gfx.Buffer, | ||||||
| 		}, | 		}; | ||||||
| 
 | 
 | ||||||
| 		texture: struct { | 		const Texture = struct { | ||||||
| 			image: sokol.gfx.Image, | 			image: sokol.gfx.Image, | ||||||
| 		}, | 			sampler: sokol.gfx.Sampler, | ||||||
|  | 		}; | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	const buffer_indices = .{ | 	fn deinit(self: *Render) void { | ||||||
| 		.mesh = 0, |  | ||||||
| 		.instance = 1, |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	fn deinit(self: *RenderWork) void { |  | ||||||
| 		sokol.gfx.destroyPipeline(self.pipeline_2d); |  | ||||||
| 
 |  | ||||||
| 		for (self.instance_2d_buffers.values) |buffer| { | 		for (self.instance_2d_buffers.values) |buffer| { | ||||||
| 			sokol.gfx.destroyBuffer(buffer); | 			sokol.gfx.destroyBuffer(buffer); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		self.instance_2d_buffers.deinit(); | 		self.instance_2d_buffers.deinit(); | ||||||
| 
 | 		sokol.gfx.destroyPipeline(self.instance_2d_pipeline); | ||||||
| 		for (self.resources.values) |resource| { |  | ||||||
| 			switch (resource) { |  | ||||||
| 				.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); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		self.resources.deinit(); | 		self.resources.deinit(); | ||||||
| 
 |  | ||||||
| 		self.* = undefined; |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	fn init(allocator: std.mem.Allocator) RenderWork { | 	fn init() Render { | ||||||
|  | 		sokol.gfx.setup(.{ | ||||||
|  | 			.environment = .{ | ||||||
|  | 				.defaults = .{ | ||||||
|  | 					.color_format = .RGBA8, | ||||||
|  | 					.depth_format = .DEPTH_STENCIL, | ||||||
|  | 					.sample_count = 1, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 
 | ||||||
|  | 			.logger = .{ | ||||||
|  | 				.func = sokol.log.func, | ||||||
|  | 			}, | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
| 		return .{ | 		return .{ | ||||||
| 			.pipeline_2d = sokol.gfx.makePipeline(.{ | 			.instance_2d_pipeline = sokol.gfx.makePipeline(.{ | ||||||
| 				.label = "2D drawing pipeline", | 				.label = "2D drawing pipeline", | ||||||
| 
 | 
 | ||||||
| 				.layout = .{ | 				.layout = .{ | ||||||
| 					.attrs = get: { | 					.attrs = get: { | ||||||
| 						var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16; | 						var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16; | ||||||
| 
 | 
 | ||||||
| 						attrs[draw_2d.ATTR_vs_mesh_xy] = .{ | 						attrs[Instance2D.shader.ATTR_vs_mesh_xy] = .{ | ||||||
| 							.format = .FLOAT2, | 							.format = .FLOAT2, | ||||||
| 							.buffer_index = buffer_indices.mesh, | 							.buffer_index = Instance2D.buffer_indices.mesh, | ||||||
| 						}; | 						}; | ||||||
| 
 | 
 | ||||||
| 						attrs[draw_2d.ATTR_vs_mesh_uv] = .{ | 						attrs[Instance2D.shader.ATTR_vs_mesh_uv] = .{ | ||||||
| 							.format = .FLOAT2, | 							.format = .FLOAT2, | ||||||
| 							.buffer_index = buffer_indices.mesh, | 							.buffer_index = Instance2D.buffer_indices.mesh, | ||||||
| 						}; | 						}; | ||||||
| 
 | 
 | ||||||
| 						attrs[draw_2d.ATTR_vs_instance_xbasis] = .{ | 						attrs[Instance2D.shader.ATTR_vs_instance_xbasis] = .{ | ||||||
| 							.format = .FLOAT2, | 							.format = .FLOAT2, | ||||||
| 							.buffer_index = buffer_indices.instance, | 							.buffer_index = Instance2D.buffer_indices.instance, | ||||||
| 						}; | 						}; | ||||||
| 
 | 
 | ||||||
| 						attrs[draw_2d.ATTR_vs_instance_ybasis] = .{ | 						attrs[Instance2D.shader.ATTR_vs_instance_ybasis] = .{ | ||||||
| 							.format = .FLOAT2, | 							.format = .FLOAT2, | ||||||
| 							.buffer_index = buffer_indices.instance, | 							.buffer_index = Instance2D.buffer_indices.instance, | ||||||
| 						}; | 						}; | ||||||
| 
 | 
 | ||||||
| 						attrs[draw_2d.ATTR_vs_instance_origin] = .{ | 						attrs[Instance2D.shader.ATTR_vs_instance_origin] = .{ | ||||||
| 							.format = .FLOAT2, | 							.format = .FLOAT2, | ||||||
| 							.buffer_index = buffer_indices.instance, | 							.buffer_index = Instance2D.buffer_indices.instance, | ||||||
| 						}; | 						}; | ||||||
| 
 | 
 | ||||||
| 						attrs[draw_2d.ATTR_vs_instance_color] = .{ | 						attrs[Instance2D.shader.ATTR_vs_instance_tint] = .{ | ||||||
| 							.format = .UBYTE4N, | 							.format = .UBYTE4N, | ||||||
| 							.buffer_index = buffer_indices.instance, | 							.buffer_index = Instance2D.buffer_indices.instance, | ||||||
| 						}; | 						}; | ||||||
| 
 | 
 | ||||||
| 						attrs[draw_2d.ATTR_vs_instance_depth] = .{ | 						attrs[Instance2D.shader.ATTR_vs_instance_depth] = .{ | ||||||
| 							.format = .FLOAT, | 							.format = .FLOAT, | ||||||
| 							.buffer_index = buffer_indices.instance, | 							.buffer_index = Instance2D.buffer_indices.instance, | ||||||
| 						}; | 						}; | ||||||
| 
 | 
 | ||||||
| 						attrs[draw_2d.ATTR_vs_instance_rect] = .{ | 						attrs[Instance2D.shader.ATTR_vs_instance_rect] = .{ | ||||||
| 							.format = .FLOAT4, | 							.format = .FLOAT4, | ||||||
| 							.buffer_index = buffer_indices.instance, | 							.buffer_index = Instance2D.buffer_indices.instance, | ||||||
| 						}; | 						}; | ||||||
| 
 | 
 | ||||||
| 						break: get attrs; | 						break: get attrs; | ||||||
| @ -122,283 +147,306 @@ const RenderWork = struct { | |||||||
| 					.buffers = get: { | 					.buffers = get: { | ||||||
| 						var buffers = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8; | 						var buffers = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8; | ||||||
| 
 | 
 | ||||||
| 						buffers[buffer_indices.instance].step_func = .PER_INSTANCE; | 						buffers[Instance2D.buffer_indices.instance].step_func = .PER_INSTANCE; | ||||||
| 
 | 
 | ||||||
| 						break: get buffers; | 						break: get buffers; | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
| 
 | 
 | ||||||
| 				.shader = sokol.gfx.makeShader(draw_2d.draw2dShaderDesc(sokol.gfx.queryBackend())), | 				.shader = sokol.gfx.makeShader(Instance2D.shader.draw2dShaderDesc(sokol.gfx.queryBackend())), | ||||||
| 				.index_type = .UINT16, | 				.index_type = .UINT16, | ||||||
| 			}), | 			}), | ||||||
| 
 | 
 | ||||||
| 			.instance_2d_sampler = sokol.gfx.makeSampler(.{ |  | ||||||
| 				.label = "instance 2D sampler", |  | ||||||
| 			}), |  | ||||||
| 
 |  | ||||||
| 			.instance_2d_buffers = .{.allocator = coral.heap.allocator}, | 			.instance_2d_buffers = .{.allocator = coral.heap.allocator}, | ||||||
| 			.resources = .{.allocator = allocator}, | 			.resources = .{.allocator = coral.heap.allocator}, | ||||||
| 		}; | 		}; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	fn process_draw_2d_commands(self: *RenderWork, commands: []const Queue.Buffer.Draw2DCommand, target: Queue.Target) std.mem.Allocator.Error!void { | 	fn insert_resource(self: *Render, handle: gfx.Handle, resource: Resource) !void { | ||||||
| 		const max_instances = 512; | 		const handle_index = handle.index() orelse { | ||||||
| 		var instance_2d_buffers_used = @as(usize, 0); | 			return error.InvalidHandle; | ||||||
|  | 		}; | ||||||
| 
 | 
 | ||||||
| 		sokol.gfx.applyPipeline(self.pipeline_2d); | 		const resource_count = self.resources.len(); | ||||||
| 
 | 
 | ||||||
| 		sokol.gfx.applyUniforms(.VS, draw_2d.SLOT_Screen, sokol.gfx.asRange(&draw_2d.Screen{ | 		if (handle_index < resource_count) { | ||||||
| 			.screen_size = .{target.width, target.height}, | 			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)}, | ||||||
| 		})); | 		})); | ||||||
| 
 | 
 | ||||||
| 		for (commands) |command| { | 		const mesh_2d = self.resources.values[frame.mesh_2d.index().?].mesh_2d; | ||||||
| 			const mesh_2d = &self.resources.values[command.mesh_2d.index().?].mesh_2d; | 		const texture = self.resources.values[frame.texture.index().?].texture; | ||||||
| 			const texture = &self.resources.values[command.texture.index().?].texture; |  | ||||||
| 			const instance_size = @sizeOf(Queue.Instance2D); |  | ||||||
| 			const full_instance_buffer_count = command.instances.len / max_instances; |  | ||||||
| 
 | 
 | ||||||
| 			for (0 .. full_instance_buffer_count) |i| { | 		var bindings = sokol.gfx.Bindings{ | ||||||
| 				defer instance_2d_buffers_used += 1; | 			.vertex_buffers = get: { | ||||||
|  | 				var buffers = [_]sokol.gfx.Buffer{.{}} ** 8; | ||||||
| 
 | 
 | ||||||
| 				if (instance_2d_buffers_used == self.instance_2d_buffers.len()) { | 				buffers[Instance2D.buffer_indices.mesh] = mesh_2d.vertex_buffer; | ||||||
| 					const instance_2d_buffer = sokol.gfx.makeBuffer(.{ |  | ||||||
| 						.size = @sizeOf(Queue.Instance2D) * max_instances, |  | ||||||
| 						.usage = .STREAM, |  | ||||||
| 						.label = "2D drawing instance buffer", |  | ||||||
| 					}); |  | ||||||
| 
 | 
 | ||||||
| 					errdefer sokol.gfx.destroyBuffer(instance_2d_buffer); | 				break: get buffers; | ||||||
|  | 			}, | ||||||
| 
 | 
 | ||||||
| 					try self.instance_2d_buffers.push(instance_2d_buffer); | 			.index_buffer = mesh_2d.index_buffer, | ||||||
| 				} |  | ||||||
| 
 | 
 | ||||||
| 				sokol.gfx.applyBindings(.{ | 			.fs = .{ | ||||||
| 					.vertex_buffers = get_buffers: { | 				.images = get: { | ||||||
| 						var buffers = [_]sokol.gfx.Buffer{.{}} ** 8; | 					var images = [_]sokol.gfx.Image{.{}} ** 12; | ||||||
| 
 | 
 | ||||||
| 						buffers[buffer_indices.instance] = self.instance_2d_buffers.values[instance_2d_buffers_used]; | 					images[0] = texture.image; | ||||||
| 						buffers[buffer_indices.mesh] = mesh_2d.vertex_buffer; |  | ||||||
| 
 | 
 | ||||||
| 						break: get_buffers buffers; | 					break: get images; | ||||||
| 					}, |  | ||||||
| 
 |  | ||||||
| 					.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] = self.instance_2d_sampler; |  | ||||||
| 
 |  | ||||||
| 							break: get samplers; |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}); |  | ||||||
| 
 |  | ||||||
| 				sokol.gfx.updateBuffer(self.instance_2d_buffers.values[instance_2d_buffers_used], .{ |  | ||||||
| 					.ptr = command.instances.ptr + (max_instances * i), |  | ||||||
| 					.size = instance_size * max_instances, |  | ||||||
| 				}); |  | ||||||
| 
 |  | ||||||
| 				sokol.gfx.draw(0, mesh_2d.index_count, max_instances); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			defer instance_2d_buffers_used += 1; |  | ||||||
| 
 |  | ||||||
| 			if (instance_2d_buffers_used == self.instance_2d_buffers.len()) { |  | ||||||
| 				const instance_2d_buffer = sokol.gfx.makeBuffer(.{ |  | ||||||
| 					.size = @sizeOf(Queue.Instance2D) * max_instances, |  | ||||||
| 					.usage = .STREAM, |  | ||||||
| 					.label = "2D drawing instance buffer", |  | ||||||
| 				}); |  | ||||||
| 
 |  | ||||||
| 				errdefer sokol.gfx.destroyBuffer(instance_2d_buffer); |  | ||||||
| 
 |  | ||||||
| 				try self.instance_2d_buffers.push(instance_2d_buffer); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			sokol.gfx.applyBindings(.{ |  | ||||||
| 				.vertex_buffers = get_buffers: { |  | ||||||
| 					var buffers = [_]sokol.gfx.Buffer{.{}} ** 8; |  | ||||||
| 
 |  | ||||||
| 					buffers[buffer_indices.instance] = self.instance_2d_buffers.values[instance_2d_buffers_used]; |  | ||||||
| 					buffers[buffer_indices.mesh] = mesh_2d.vertex_buffer; |  | ||||||
| 
 |  | ||||||
| 					break: get_buffers buffers; |  | ||||||
| 				}, | 				}, | ||||||
| 
 | 
 | ||||||
| 				.fs = .{ | 				.samplers = get: { | ||||||
| 					.images = get: { | 					var samplers = [_]sokol.gfx.Sampler{.{}} ** 8; | ||||||
| 						var images = [_]sokol.gfx.Image{.{}} ** 12; |  | ||||||
| 
 | 
 | ||||||
| 						images[0] = texture.image; | 					samplers[0] = texture.sampler; | ||||||
| 
 | 
 | ||||||
| 						break: get images; | 					break: get samplers; | ||||||
| 					}, |  | ||||||
| 
 |  | ||||||
| 					.samplers = get: { |  | ||||||
| 						var samplers = [_]sokol.gfx.Sampler{.{}} ** 8; |  | ||||||
| 
 |  | ||||||
| 						samplers[0] = self.instance_2d_sampler; |  | ||||||
| 
 |  | ||||||
| 						break: get samplers; |  | ||||||
| 					}, |  | ||||||
| 				}, | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}; | ||||||
| 
 | 
 | ||||||
| 				.index_buffer = mesh_2d.index_buffer, | 		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); | ||||||
| 
 | 
 | ||||||
| 			const remaining_instances = command.instances.len % max_instances; | 			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.updateBuffer(self.instance_2d_buffers.values[instance_2d_buffers_used], .{ | 			sokol.gfx.applyBindings(bindings); | ||||||
| 				.ptr = command.instances.ptr + full_instance_buffer_count, | 			sokol.gfx.draw(0, mesh_2d.index_count, @intCast(instances_to_flush)); | ||||||
| 				.size = instance_size * remaining_instances, |  | ||||||
| 			}); |  | ||||||
| 
 | 
 | ||||||
| 			sokol.gfx.draw(0, mesh_2d.index_count, @intCast(remaining_instances)); | 			frame.flushed_instance_2d_count += instances_to_flush; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	fn process_open_commands(self: *RenderWork, commands: []const Queue.Buffer.OpenCommand) std.mem.Allocator.Error!void { | 	fn push_instance_2d(self: *Render, frame: *Frame, command: Queue.DrawCommand.Instance) std.mem.Allocator.Error!void { | ||||||
| 		for (commands) |command| { | 		if (command.mesh_2d != frame.mesh_2d or command.texture != frame.texture) { | ||||||
| 			switch (command.resource) { | 			self.flush_instance_2ds(frame); | ||||||
| 				.texture => |texture| { | 		} | ||||||
| 					const stride = texture.width * texture.format.byte_size(); |  | ||||||
| 
 | 
 | ||||||
| 					const image = sokol.gfx.makeImage(.{ | 		frame.mesh_2d = command.mesh_2d; | ||||||
| 						.width = texture.width, | 		frame.texture = command.texture; | ||||||
| 						.height = @intCast(texture.data.len / stride), |  | ||||||
| 
 | 
 | ||||||
| 						.data = .{ | 		const has_filled_buffer = (frame.pushed_instance_2d_count % Instance2D.instances_per_buffer) == 0; | ||||||
| 							.subimage = get: { | 		const pushed_buffer_count = frame.pushed_instance_2d_count / Instance2D.instances_per_buffer; | ||||||
| 								var subimage = [_][16]sokol.gfx.Range{.{.{}} ** 16} ** 6; |  | ||||||
| 
 | 
 | ||||||
| 								subimage[0][0] = sokol.gfx.asRange(texture.data); | 		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", | ||||||
|  | 			}); | ||||||
| 
 | 
 | ||||||
| 								break: get subimage; | 			errdefer sokol.gfx.destroyBuffer(instance_buffer); | ||||||
| 							}, |  | ||||||
| 						}, |  | ||||||
| 					}); |  | ||||||
| 
 | 
 | ||||||
| 					errdefer sokol.gfx.destroyImage(image); | 			try self.instance_2d_buffers.push(instance_buffer); | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 					try self.resources.push(.{ | 		_ = sokol.gfx.appendBuffer(self.instance_2d_buffers.get().?, sokol.gfx.asRange(&Instance2D{ | ||||||
| 						.texture = .{ | 			.transform = command.transform, | ||||||
| 							.image = image, | 		})); | ||||||
| 						}, |  | ||||||
| 					}); |  | ||||||
| 				}, |  | ||||||
| 
 | 
 | ||||||
| 				.mesh_2d => |mesh_2d| { | 		frame.pushed_instance_2d_count += 1; | ||||||
| 					const index_buffer = sokol.gfx.makeBuffer(.{ | 	} | ||||||
| 						.data = sokol.gfx.asRange(mesh_2d.indices), |  | ||||||
| 						.type = .INDEXBUFFER, |  | ||||||
| 					}); |  | ||||||
| 
 | 
 | ||||||
| 					const vertex_buffer = sokol.gfx.makeBuffer(.{ | 	fn remove_resource(self: *Render, handle: gfx.Handle) ?Resource { | ||||||
| 						.data = sokol.gfx.asRange(mesh_2d.vertices), | 		if (handle.index()) |handle_index| { | ||||||
| 						.type = .VERTEXBUFFER, | 			const resource = self.resources.values[handle_index]; | ||||||
| 					}); |  | ||||||
| 
 | 
 | ||||||
| 					errdefer { | 			if (resource != .empty) { | ||||||
| 						sokol.gfx.destroyBuffer(index_buffer); | 				self.resources.values[handle_index] = .empty; | ||||||
| 						sokol.gfx.destroyBuffer(vertex_buffer); |  | ||||||
| 					} |  | ||||||
| 
 | 
 | ||||||
| 					if (mesh_2d.indices.len > std.math.maxInt(u32)) { | 				return resource; | ||||||
| 						return error.OutOfMemory; |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					try self.resources.push(.{ |  | ||||||
| 						.mesh_2d = .{ |  | ||||||
| 							.index_buffer = index_buffer, |  | ||||||
| 							.vertex_buffer = vertex_buffer, |  | ||||||
| 							.index_count = @intCast(mesh_2d.indices.len), |  | ||||||
| 						}, |  | ||||||
| 					}); |  | ||||||
| 				}, |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	fn process_queue(self: *RenderWork, buffer: *const Queue.Buffer, target: Queue.Target) std.mem.Allocator.Error!void { | 		return null; | ||||||
| 		try self.process_open_commands(buffer.open_commands.values); |  | ||||||
| 		try self.process_draw_2d_commands(buffer.draw_2d_commands.values, target); |  | ||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const RenderState = struct { |  | ||||||
| 	finished: std.Thread.Semaphore = .{}, |  | ||||||
| 	is_running: AtomicBool = AtomicBool.init(true), |  | ||||||
| 	ready: std.Thread.Semaphore = .{}, |  | ||||||
| 	clear_color: color.Value = color.compress(color.black), |  | ||||||
| 	pixel_width: c_int = 0, |  | ||||||
| 	pixel_height: c_int = 0, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const Self = @This(); | 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 { | pub fn deinit(self: *Self) void { | ||||||
| 	self.render_state.is_running.store(false, .monotonic); | 	self.state.is_running.store(false, .monotonic); | ||||||
| 	self.render_state.ready.post(); | 	self.state.ready.post(); | ||||||
| 	self.thread.join(); | 	self.thread.join(); | ||||||
| 	coral.heap.allocator.destroy(self.render_state); | 	coral.heap.allocator.destroy(self.state); | ||||||
| 
 | 
 | ||||||
| 	self.* = undefined; | 	self.* = undefined; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn init(sdl_window: *ext.SDL_Window) (std.mem.Allocator.Error || std.Thread.SpawnError)!Self { | pub fn init(window: *ext.SDL_Window) (std.mem.Allocator.Error || std.Thread.SpawnError)!Self { | ||||||
| 	const render_state = try coral.heap.allocator.create(RenderState); | 	const state = try coral.heap.allocator.create(State); | ||||||
| 
 | 
 | ||||||
| 	errdefer coral.heap.allocator.destroy(render_state); | 	errdefer coral.heap.allocator.destroy(state); | ||||||
| 
 | 
 | ||||||
| 	render_state.* = .{}; | 	state.* = .{}; | ||||||
| 
 | 
 | ||||||
| 	const self = Self{ | 	const thread = try std.Thread.spawn(.{}, run, .{window, state}); | ||||||
| 		.thread = spawn_thread: { |  | ||||||
| 			const thread = try std.Thread.spawn(.{}, run, .{sdl_window, render_state}); |  | ||||||
| 
 | 
 | ||||||
| 			thread.setName("Ona Graphics") catch { | 	thread.setName("Ona Graphics") catch { | ||||||
| 				std.log.warn("failed to name the graphics thread", .{}); | 		std.log.warn("failed to name the graphics thread", .{}); | ||||||
| 			}; |  | ||||||
| 
 |  | ||||||
| 			break: spawn_thread thread; |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		.render_state = render_state, |  | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	self.submit(sdl_window, .{0, 0, 0, 1}); | 	return .{ | ||||||
| 
 | 		.thread = thread, | ||||||
| 	return self; | 		.state = state, | ||||||
|  | 	}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn run(sdl_window: *ext.SDL_Window, render_state: *RenderState) !void { | fn process_close_command(command: Queue.CloseCommand, rendering: *Render) !void { | ||||||
| 	var result = @as(c_int, 0); | 	const resource = &rendering.resources.values[command.handle.index().?]; | ||||||
| 
 | 
 | ||||||
| 	result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_FLAGS, ext.SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); | 	switch (resource.*) { | ||||||
| 	result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_PROFILE_MASK, ext.SDL_GL_CONTEXT_PROFILE_CORE); | 		.empty => {}, // TODO: Handle this. | ||||||
| 	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) { | 		.mesh_2d => |mesh_2d| { | ||||||
| 		std.log.err("failed to set necessary OpenGL flags in graphics", .{}); | 			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); | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	const context = ext.SDL_GL_CreateContext(sdl_window); | 	resource.* = .empty; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| 	defer ext.SDL_GL_DeleteContext(context); | 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); | ||||||
|  | 		}, | ||||||
| 
 | 
 | ||||||
| 	render_state.finished.post(); | 		.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(.{ | 	sokol.gfx.setup(.{ | ||||||
| 		.environment = .{ | 		.environment = .{ | ||||||
| @ -414,21 +462,38 @@ fn run(sdl_window: *ext.SDL_Window, render_state: *RenderState) !void { | |||||||
| 		}, | 		}, | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| 	defer sokol.gfx.shutdown(); | 	defer { | ||||||
|  | 		sokol.gfx.shutdown(); | ||||||
|  | 		ext.SDL_GL_DeleteContext(context); | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	var render_work = RenderWork.init(coral.heap.allocator); | 	var render = Render.init(); | ||||||
| 
 | 
 | ||||||
| 	defer render_work.deinit(); | 	defer render.deinit(); | ||||||
| 
 | 
 | ||||||
| 	while (render_state.is_running.load(.monotonic)) { | 	state.finished.post(); | ||||||
| 		render_state.ready.wait(); |  | ||||||
| 
 | 
 | ||||||
| 		defer render_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(.{ | 		sokol.gfx.beginPass(.{ | ||||||
| 			.swapchain = .{ | 			.swapchain = .{ | ||||||
| 				.width = render_state.pixel_width, | 				.width = frame.width, | ||||||
| 				.height = render_state.pixel_height, | 				.height = frame.height, | ||||||
| 				.sample_count = 1, | 				.sample_count = 1, | ||||||
| 				.color_format = .RGBA8, | 				.color_format = .RGBA8, | ||||||
| 				.depth_format = .DEPTH_STENCIL, | 				.depth_format = .DEPTH_STENCIL, | ||||||
| @ -441,32 +506,29 @@ fn run(sdl_window: *ext.SDL_Window, render_state: *RenderState) !void { | |||||||
| 
 | 
 | ||||||
| 					actions[0] = .{ | 					actions[0] = .{ | ||||||
| 						.load_action = .CLEAR, | 						.load_action = .CLEAR, | ||||||
| 						.clear_value = @as(sokol.gfx.Color, @bitCast(render_state.clear_color)), | 						.clear_value = @as(sokol.gfx.Color, @bitCast(state.clear_color)), | ||||||
| 					}; | 					}; | ||||||
| 
 | 
 | ||||||
| 					break: get actions; | 					break: get actions; | ||||||
| 				}, | 				}, | ||||||
| 			} | 			}, | ||||||
| 		}); | 		}); | ||||||
| 
 | 
 | ||||||
| 		try Queue.consume_submitted(Queue.Consumer.bind(RenderWork, &render_work, RenderWork.process_queue), .{ | 		try Queue.visit_open_commands(process_open_command, .{&render}); | ||||||
| 			.width = @floatFromInt(render_state.pixel_width), | 		try Queue.visit_draw_commands(process_draw_command, .{&render, &frame}); | ||||||
| 			.height = @floatFromInt(render_state.pixel_height), | 		try Queue.visit_close_commands(process_close_command, .{&render}); | ||||||
| 		}); |  | ||||||
| 
 | 
 | ||||||
| 		sokol.gfx.endPass(); | 		sokol.gfx.endPass(); | ||||||
| 		sokol.gfx.commit(); | 		sokol.gfx.commit(); | ||||||
| 		ext.SDL_GL_SwapWindow(sdl_window); | 		ext.SDL_GL_SwapWindow(window); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn submit(self: Self, sdl_window: *ext.SDL_Window, clear_color: color.Value) void { | pub fn submit(self: *Self) void { | ||||||
| 	self.render_state.finished.wait(); | 	self.state.finished.wait(); | ||||||
| 	ext.SDL_GL_GetDrawableSize(sdl_window, &self.render_state.pixel_width, &self.render_state.pixel_height); |  | ||||||
| 	std.debug.assert(self.render_state.pixel_width > 0 and self.render_state.pixel_height > 0); |  | ||||||
| 
 | 
 | ||||||
| 	self.render_state.clear_color = clear_color; | 	self.state.clear_color = self.clear_color; | ||||||
| 
 | 
 | ||||||
| 	Queue.swap(); | 	Queue.swap(); | ||||||
| 	self.render_state.ready.post(); | 	self.state.ready.post(); | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,11 +1,14 @@ | |||||||
| const color = @import("./color.zig"); |  | ||||||
| 
 |  | ||||||
| const coral = @import("coral"); | const coral = @import("coral"); | ||||||
| 
 | 
 | ||||||
|  | const gfx = @import("../gfx.zig"); | ||||||
|  | 
 | ||||||
| const std = @import("std"); | const std = @import("std"); | ||||||
| 
 | 
 | ||||||
| buffer: *Buffer, | buffers: [2]Buffer, | ||||||
| primitives: *const Primitives, | is_swapped: bool = false, | ||||||
|  | ref_count: AtomicCount = AtomicCount.init(1), | ||||||
|  | has_next: ?*Self = null, | ||||||
|  | has_prev: ?*Self = null, | ||||||
| 
 | 
 | ||||||
| const AtomicCount = std.atomic.Value(usize); | const AtomicCount = std.atomic.Value(usize); | ||||||
| 
 | 
 | ||||||
| @ -13,65 +16,24 @@ pub const Buffer = struct { | |||||||
| 	arena: std.heap.ArenaAllocator, | 	arena: std.heap.ArenaAllocator, | ||||||
| 	closed_handles: coral.stack.Sequential(usize), | 	closed_handles: coral.stack.Sequential(usize), | ||||||
| 	open_commands: coral.stack.Sequential(OpenCommand), | 	open_commands: coral.stack.Sequential(OpenCommand), | ||||||
| 	draw_2d_commands: coral.stack.Sequential(Draw2DCommand), | 	draw_commands: coral.stack.Sequential(DrawCommand), | ||||||
| 	close_commands: coral.stack.Sequential(CloseCommand), | 	close_commands: coral.stack.Sequential(CloseCommand), | ||||||
| 
 | 
 | ||||||
| 	pub const CloseCommand = struct { | 	pub fn clear(self: *Buffer) void { | ||||||
| 		handle: Handle, | 		self.close_commands.clear(); | ||||||
| 	}; | 		self.draw_commands.clear(); | ||||||
|  | 		self.open_commands.clear(); | ||||||
| 
 | 
 | ||||||
| 	pub const Draw2DCommand = struct { | 		if (!self.arena.reset(.retain_capacity)) { | ||||||
| 		instances: []const Instance2D, | 			std.log.warn("failed to reset the buffer of a gfx queue with retained capacity", .{}); | ||||||
| 		texture: Handle, | 		} | ||||||
| 		mesh_2d: Handle, | 	} | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	pub const OpenCommand = struct { |  | ||||||
| 		handle: Handle = .none, |  | ||||||
| 		label: ?[]const u8 = null, |  | ||||||
| 
 |  | ||||||
| 		resource: union (enum) { |  | ||||||
| 			texture: Texture, |  | ||||||
| 			mesh_2d: Mesh2D, |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		pub const Mesh2D = struct { |  | ||||||
| 			vertices: []const Vertex2D, |  | ||||||
| 			indices: []const u16, |  | ||||||
| 		}; |  | ||||||
| 
 |  | ||||||
| 		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, |  | ||||||
| 					}; |  | ||||||
| 				} |  | ||||||
| 			}; |  | ||||||
| 		}; |  | ||||||
| 	}; |  | ||||||
| 
 | 
 | ||||||
| 	fn deinit(self: *Buffer) void { | 	fn deinit(self: *Buffer) void { | ||||||
| 		self.arena.deinit(); | 		self.arena.deinit(); | ||||||
| 		self.closed_handles.deinit(); | 		self.closed_handles.deinit(); | ||||||
| 		self.open_commands.deinit(); | 		self.open_commands.deinit(); | ||||||
| 		self.draw_2d_commands.deinit(); | 		self.draw_commands.deinit(); | ||||||
| 		self.close_commands.deinit(); | 		self.close_commands.deinit(); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @ -80,31 +42,20 @@ pub const Buffer = struct { | |||||||
| 			.arena = std.heap.ArenaAllocator.init(allocator), | 			.arena = std.heap.ArenaAllocator.init(allocator), | ||||||
| 			.closed_handles = .{.allocator = allocator}, | 			.closed_handles = .{.allocator = allocator}, | ||||||
| 			.open_commands = .{.allocator = allocator}, | 			.open_commands = .{.allocator = allocator}, | ||||||
| 			.draw_2d_commands = .{.allocator = allocator}, | 			.draw_commands = .{.allocator = allocator}, | ||||||
| 			.close_commands = .{.allocator = allocator}, | 			.close_commands = .{.allocator = allocator}, | ||||||
| 		}; | 		}; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	pub fn clear(self: *Buffer) void { | 	pub fn draw(self: *Buffer, command: DrawCommand) std.mem.Allocator.Error!void { | ||||||
| 		self.close_commands.clear(); | 		try self.draw_commands.push(switch (command) { | ||||||
| 		self.draw_2d_commands.clear(); | 			.instance_2d => |instance_2d| .{.instance_2d = instance_2d}, | ||||||
| 		self.open_commands.clear(); | 			.post_process => |post_process| .{.post_process = post_process}, | ||||||
| 
 |  | ||||||
| 		if (!self.arena.reset(.retain_capacity)) { |  | ||||||
| 			std.log.warn("failed to reset the buffer of a gfx queue with retained capacity", .{}); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	pub fn draw_2d(self: *Buffer, command: Draw2DCommand) std.mem.Allocator.Error!void { |  | ||||||
| 		try self.draw_2d_commands.push(.{ |  | ||||||
| 			.instances = try self.arena.allocator().dupe(Instance2D, command.instances), |  | ||||||
| 			.texture = command.texture, |  | ||||||
| 			.mesh_2d = command.mesh_2d, |  | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	pub fn open(self: *Buffer, command: OpenCommand) std.mem.Allocator.Error!Handle { | 	pub fn open(self: *Buffer, command: OpenCommand) std.mem.Allocator.Error!gfx.Handle { | ||||||
| 		const reserved_handle = @as(Handle, switch (command.handle) { | 		const reserved_handle = @as(gfx.Handle, switch (command.handle) { | ||||||
| 			.none => @enumFromInt(reserve_handle: { | 			.none => @enumFromInt(reserve_handle: { | ||||||
| 				if (self.closed_handles.get()) |handle| { | 				if (self.closed_handles.get()) |handle| { | ||||||
| 					std.debug.assert(self.closed_handles.pop()); | 					std.debug.assert(self.closed_handles.pop()); | ||||||
| @ -136,7 +87,7 @@ pub const Buffer = struct { | |||||||
| 				.mesh_2d => |mesh_2d| .{ | 				.mesh_2d => |mesh_2d| .{ | ||||||
| 					.mesh_2d = .{ | 					.mesh_2d = .{ | ||||||
| 						.indices = try arena_allocator.dupe(u16, mesh_2d.indices), | 						.indices = try arena_allocator.dupe(u16, mesh_2d.indices), | ||||||
| 						.vertices = try arena_allocator.dupe(Vertex2D, mesh_2d.vertices), | 						.vertices = try arena_allocator.dupe(OpenCommand.Mesh2D.Vertex, mesh_2d.vertices), | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| @ -149,101 +100,77 @@ pub const Buffer = struct { | |||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| pub const Consumer = coral.io.Generator(std.mem.Allocator.Error!void, &.{*const Buffer, Target}); | pub const CloseCommand = struct { | ||||||
|  | 	handle: gfx.Handle, | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| pub const Handle = enum (usize) { | pub const DrawCommand = union (enum) { | ||||||
| 	none, | 	instance_2d: Instance, | ||||||
| 	_, | 	post_process: PostProcess, | ||||||
| 
 | 
 | ||||||
| 	pub fn index(self: Handle) ?usize { | 	pub const Instance = struct { | ||||||
| 		return switch (self) { | 		texture: gfx.Handle, | ||||||
| 			.none => null, | 		mesh_2d: gfx.Handle, | ||||||
| 			_ => @intFromEnum(self) - 1, | 		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 Instance2D = extern struct { | 	pub const Texture = struct { | ||||||
| 	xbasis: Point2D = .{1, 0}, | 		data: []const coral.io.Byte, | ||||||
| 	ybasis: Point2D = .{0, 1}, | 		width: u16, | ||||||
| 	origin: Point2D = @splat(0), | 		format: Format, | ||||||
| 	color: color.Compressed = color.compress(color.white), | 		access: Access, | ||||||
| 	depth: f32 = 0, |  | ||||||
| 	texture_offset: Point2D = @splat(0), |  | ||||||
| 	texture_size: Point2D = @splat(1), |  | ||||||
| }; |  | ||||||
| 
 | 
 | ||||||
| const Node = struct { | 		pub const Access = enum { | ||||||
| 	buffers: [2]Buffer, | 			static, | ||||||
| 	is_swapped: bool = false, | 		}; | ||||||
| 	ref_count: AtomicCount = AtomicCount.init(1), |  | ||||||
| 	has_next: ?*Node = null, |  | ||||||
| 	has_prev: ?*Node = null, |  | ||||||
| 
 | 
 | ||||||
| 	fn acquire(self: *Node) void { | 		pub const Format = enum { | ||||||
| 		self.ref_count.fetchAdd(1, .monotonic); | 			rgba8888, | ||||||
| 	} | 			bgra8888, | ||||||
|  | 			argb8888, | ||||||
|  | 			rgb888, | ||||||
|  | 			bgr888, | ||||||
| 
 | 
 | ||||||
| 	fn pending(self: *Node) *Buffer { | 			pub fn byte_size(self: Format) usize { | ||||||
| 		return &self.buffers[@intFromBool(self.is_swapped)]; | 				return switch (self) { | ||||||
| 	} | 					.rgba8888, .bgra8888, .argb8888 => 4, | ||||||
| 
 | 					.rgb888, .bgr888 => 3, | ||||||
| 	fn release(self: *Node) 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); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	fn submitted(self: *Node) *Buffer { |  | ||||||
| 		return &self.buffers[@intFromBool(!self.is_swapped)]; |  | ||||||
| 	} |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| pub const Point2D = @Vector(2, f32); | pub fn acquire(self: *Self) void { | ||||||
|  | 	self.ref_count.fetchAdd(1, .monotonic); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| pub const Primitives = struct { | pub fn create() std.mem.Allocator.Error!*Self { | ||||||
| 	quad_mesh: Handle, | 	const queue = try coral.heap.allocator.create(Self); | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const Self = @This(); |  | ||||||
| 
 |  | ||||||
| pub const State = struct { |  | ||||||
| 	node: *Node, |  | ||||||
| 	primitives: *const Primitives, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| pub const Target = struct { |  | ||||||
| 	width: f32, |  | ||||||
| 	height: f32, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| pub const Vertex2D = struct { |  | ||||||
| 	xy: Point2D, |  | ||||||
| 	uv: Point2D, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| pub fn bind(context: coral.system.BindContext) std.mem.Allocator.Error!State { |  | ||||||
| 	const queue = try coral.heap.allocator.create(Node); |  | ||||||
| 
 | 
 | ||||||
| 	errdefer coral.heap.allocator.destroy(queue); | 	errdefer coral.heap.allocator.destroy(queue); | ||||||
| 
 | 
 | ||||||
| @ -266,68 +193,49 @@ pub fn bind(context: coral.system.BindContext) std.mem.Allocator.Error!State { | |||||||
| 
 | 
 | ||||||
| 	has_tail = queue; | 	has_tail = queue; | ||||||
| 
 | 
 | ||||||
| 	return .{ | 	return queue; | ||||||
| 		.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)).?; |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		.node = queue, |  | ||||||
| 	}; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn consume_submitted(consumer: Consumer, target: Target) std.mem.Allocator.Error!void { | pub fn pending(self: *Self) *Buffer { | ||||||
| 	mutex.lock(); | 	return &self.buffers[@intFromBool(self.is_swapped)]; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| 	defer mutex.unlock(); | pub fn release(self: *Self) void { | ||||||
|  | 	if (self.ref_count.fetchSub(1, .monotonic) == 1) { | ||||||
|  | 		mutex.lock(); | ||||||
| 
 | 
 | ||||||
| 	var has_node = has_head; | 		defer mutex.unlock(); | ||||||
| 	var iterations = @as(usize, 0); |  | ||||||
| 
 | 
 | ||||||
| 	while (has_node) |node| : ({ | 		if (self.has_prev) |prev| { | ||||||
| 		has_node = node.has_next; | 			prev.has_next = self.has_next; | ||||||
| 		iterations += 1; | 		} else { | ||||||
| 	}) { | 			has_head = self.has_next; | ||||||
| 		const buffer = &node.buffers[@intFromBool(!node.is_swapped)]; | 		} | ||||||
| 
 | 
 | ||||||
| 		try consumer.yield(.{buffer, target}); | 		if (self.has_next) |next| { | ||||||
|  | 			next.has_prev = self.has_prev; | ||||||
|  | 		} else { | ||||||
|  | 			has_tail = self.has_prev; | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
| 		buffer.clear(); | 		for (&self.buffers) |*buffer| { | ||||||
|  | 			buffer.deinit(); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		coral.heap.allocator.destroy(self); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var has_head = @as(?*Node, null); | pub fn submitted(self: *Self) *Buffer { | ||||||
| 
 | 	return &self.buffers[@intFromBool(!self.is_swapped)]; | ||||||
| var has_tail = @as(?*Node, null); |  | ||||||
| 
 |  | ||||||
| pub fn init(state: *State) Self { |  | ||||||
| 	return .{ |  | ||||||
| 		.buffer = state.node.pending(), |  | ||||||
| 		.primitives = state.primitives, |  | ||||||
| 	}; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | const Self = @This(); | ||||||
|  | 
 | ||||||
|  | var has_head = @as(?*Self, null); | ||||||
|  | 
 | ||||||
|  | var has_tail = @as(?*Self, null); | ||||||
|  | 
 | ||||||
| var mutex = std.Thread.Mutex{}; | var mutex = std.Thread.Mutex{}; | ||||||
| 
 | 
 | ||||||
| var next_handle = AtomicCount.init(1); | var next_handle = AtomicCount.init(1); | ||||||
| @ -341,9 +249,61 @@ pub fn swap() void { | |||||||
| 
 | 
 | ||||||
| 	while (has_node) |node| : (has_node = node.has_next) { | 	while (has_node) |node| : (has_node = node.has_next) { | ||||||
| 		node.is_swapped = !node.is_swapped; | 		node.is_swapped = !node.is_swapped; | ||||||
|  | 
 | ||||||
|  | 		node.pending().clear(); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn unbind(state: *State) void { | pub fn visit_close_commands(visit: anytype, args: anytype) !void { | ||||||
| 	state.node.release(); | 	mutex.lock(); | ||||||
|  | 
 | ||||||
|  | 	defer mutex.unlock(); | ||||||
|  | 
 | ||||||
|  | 	var has_node = has_head; | ||||||
|  | 	var iterations = @as(usize, 0); | ||||||
|  | 
 | ||||||
|  | 	while (has_node) |node| : ({ | ||||||
|  | 		has_node = node.has_next; | ||||||
|  | 		iterations += 1; | ||||||
|  | 	}) { | ||||||
|  | 		for (node.submitted().close_commands.values) |command| { | ||||||
|  | 			try @call(.auto, visit, .{command} ++ args); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub fn visit_draw_commands(visit: anytype, args: anytype) !void { | ||||||
|  | 	mutex.lock(); | ||||||
|  | 
 | ||||||
|  | 	defer mutex.unlock(); | ||||||
|  | 
 | ||||||
|  | 	var has_node = has_head; | ||||||
|  | 	var iterations = @as(usize, 0); | ||||||
|  | 
 | ||||||
|  | 	while (has_node) |node| : ({ | ||||||
|  | 		has_node = node.has_next; | ||||||
|  | 		iterations += 1; | ||||||
|  | 	}) { | ||||||
|  | 		for (node.submitted().draw_commands.values) |command| { | ||||||
|  | 			try @call(.auto, visit, .{command} ++ args); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub fn visit_open_commands(visit: anytype, args: anytype) !void { | ||||||
|  | 	mutex.lock(); | ||||||
|  | 
 | ||||||
|  | 	defer mutex.unlock(); | ||||||
|  | 
 | ||||||
|  | 	var has_node = has_head; | ||||||
|  | 	var iterations = @as(usize, 0); | ||||||
|  | 
 | ||||||
|  | 	while (has_node) |node| : ({ | ||||||
|  | 		has_node = node.has_next; | ||||||
|  | 		iterations += 1; | ||||||
|  | 	}) { | ||||||
|  | 		for (node.submitted().open_commands.values) |command| { | ||||||
|  | 			try @call(.auto, visit, .{command} ++ args); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,15 +0,0 @@ | |||||||
| pub const Compressed = @Vector(4, u8); |  | ||||||
| 
 |  | ||||||
| pub const Value = @Vector(4, f32); |  | ||||||
| 
 |  | ||||||
| pub const black = Value{0, 0, 0, 1}; |  | ||||||
| 
 |  | ||||||
| pub fn compress(color: Value) Compressed { |  | ||||||
| 	return @intFromFloat(color * @as(Value, @splat(255))); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn rgb(r: f32, g: f32, b: f32) Value { |  | ||||||
| 	return .{r, g, b, 1}; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub const white = Value{1, 1, 1, 1}; |  | ||||||
							
								
								
									
										1
									
								
								src/ona/gfx/shaders/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								src/ona/gfx/shaders/.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1 +0,0 @@ | |||||||
| *.glsl.zig |  | ||||||
| @ -8,7 +8,7 @@ in vec2 mesh_uv; | |||||||
| in vec2 instance_xbasis; | in vec2 instance_xbasis; | ||||||
| in vec2 instance_ybasis; | in vec2 instance_ybasis; | ||||||
| in vec2 instance_origin; | in vec2 instance_origin; | ||||||
| in vec4 instance_color; | in vec4 instance_tint; | ||||||
| in float instance_depth; | in float instance_depth; | ||||||
| in vec4 instance_rect; | in vec4 instance_rect; | ||||||
| 
 | 
 | ||||||
| @ -29,7 +29,7 @@ void main() { | |||||||
| 
 | 
 | ||||||
| 	// Set the position of the vertex in clip space | 	// Set the position of the vertex in clip space | ||||||
| 	gl_Position = vec4(ndc_position, instance_depth, 1.0); | 	gl_Position = vec4(ndc_position, instance_depth, 1.0); | ||||||
| 	color = instance_color; | 	color = instance_tint; | ||||||
| 
 | 
 | ||||||
| 	// Calculate the width and height from left, top, right, bottom configuration | 	// Calculate the width and height from left, top, right, bottom configuration | ||||||
| 	const vec2 rect_pos = instance_rect.xy; // left, top | 	const vec2 rect_pos = instance_rect.xy; // left, top | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user