renderer-mvp/asset-pipeline #53
| @ -2,6 +2,8 @@ pub const ascii = @import("./ascii.zig"); | ||||
| 
 | ||||
| pub const dag = @import("./dag.zig"); | ||||
| 
 | ||||
| pub const files = @import("./files.zig"); | ||||
| 
 | ||||
| pub const hashes = @import("./hashes.zig"); | ||||
| 
 | ||||
| pub const heap = @import("./heap.zig"); | ||||
|  | ||||
							
								
								
									
										124
									
								
								src/coral/files.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								src/coral/files.zig
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,124 @@ | ||||
| const builtin = @import("builtin"); | ||||
| 
 | ||||
| const io = @import("./io.zig"); | ||||
| 
 | ||||
| const std = @import("std"); | ||||
| 
 | ||||
| pub const Error = error { | ||||
| 	FileNotFound, | ||||
| 	FileInaccessible, | ||||
| }; | ||||
| 
 | ||||
| pub const Stat = struct { | ||||
| 	size: u64, | ||||
| }; | ||||
| 
 | ||||
| pub const Storage = struct { | ||||
| 	userdata: *anyopaque, | ||||
| 	vtable: *const VTable, | ||||
| 
 | ||||
| 	pub const VTable = struct { | ||||
| 		stat: *const fn (*anyopaque, []const u8) Error!Stat, | ||||
| 		read: *const fn (*anyopaque, []const u8, usize, []io.Byte) Error!usize, | ||||
| 	}; | ||||
| 
 | ||||
| 	pub fn read_bytes(self: Storage, path: []const u8, offset: usize, output: []io.Byte) Error!usize { | ||||
| 		return self.vtable.read(self.userdata, path, offset, output); | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn read_foreign(self: Storage, path: []const u8, offset: usize, comptime Type: type) Error!?Type { | ||||
| 		const decoded = (try self.read_native(path, offset, Type)) orelse { | ||||
| 			return null; | ||||
| 		}; | ||||
| 
 | ||||
| 		return switch (@typeInfo(Type)) { | ||||
| 			.Struct => std.mem.byteSwapAllFields(Type, &decoded), | ||||
| 			else => @byteSwap(decoded), | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn read_native(self: Storage, path: []const u8, offset: usize, comptime Type: type) Error!?Type { | ||||
| 		var buffer = @as([@sizeOf(Type)]io.Byte, undefined); | ||||
| 
 | ||||
| 		if (try self.vtable.read(self.userdata, path, offset, &buffer) != buffer.len) { | ||||
| 			return null; | ||||
| 		} | ||||
| 
 | ||||
| 		return @as(*align(1) const Type, @ptrCast(&buffer)).*; | ||||
| 	} | ||||
| 
 | ||||
| 	pub const read_little = switch (native_endian) { | ||||
| 		.little => read_native, | ||||
| 		.big => read_foreign, | ||||
| 	}; | ||||
| 
 | ||||
| 	pub const read_big = switch (native_endian) { | ||||
| 		.little => read_foreign, | ||||
| 		.big => read_native, | ||||
| 	}; | ||||
| }; | ||||
| 
 | ||||
| pub const bundle = init: { | ||||
| 	const Bundle = struct { | ||||
| 		fn full_path(path: []const u8) Error![4095:0]u8 { | ||||
| 			var buffer = [_:0]u8{0} ** 4095; | ||||
| 
 | ||||
| 			_ = std.fs.cwd().realpath(path, &buffer) catch { | ||||
| 				return error.FileInaccessible; | ||||
| 			}; | ||||
| 
 | ||||
| 			return buffer; | ||||
| 		} | ||||
| 
 | ||||
| 		fn read(_: *anyopaque, path: []const u8, offset: usize, output: []io.Byte) Error!usize { | ||||
| 			var file = std.fs.openFileAbsoluteZ(&(try full_path(path)), .{.mode = .read_only}) catch |open_error| { | ||||
| 				return switch (open_error) { | ||||
| 					error.FileNotFound => error.FileNotFound, | ||||
| 					else => error.FileInaccessible, | ||||
| 				}; | ||||
| 			}; | ||||
| 
 | ||||
| 			defer file.close(); | ||||
| 
 | ||||
| 			if (offset != 0) { | ||||
| 				file.seekTo(offset) catch { | ||||
| 					return error.FileInaccessible; | ||||
| 				}; | ||||
| 			} | ||||
| 
 | ||||
| 			return file.read(output) catch error.FileInaccessible; | ||||
| 		} | ||||
| 
 | ||||
| 		fn stat(_: *anyopaque, path: []const u8) Error!Stat { | ||||
| 			const file_stat = get: { | ||||
| 				var file = std.fs.openFileAbsoluteZ(&(try full_path(path)), .{.mode = .read_only}) catch |open_error| { | ||||
| 					return switch (open_error) { | ||||
| 						error.FileNotFound => error.FileNotFound, | ||||
| 						else => error.FileInaccessible, | ||||
| 					}; | ||||
| 				}; | ||||
| 
 | ||||
| 				defer file.close(); | ||||
| 
 | ||||
| 				break: get file.stat() catch { | ||||
| 					return error.FileInaccessible; | ||||
| 				}; | ||||
| 			}; | ||||
| 
 | ||||
| 			return .{ | ||||
| 				.size = file_stat.size, | ||||
| 			}; | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
| 	break: init Storage{ | ||||
| 		.userdata = undefined, | ||||
| 
 | ||||
| 		.vtable = &.{ | ||||
| 			.stat = Bundle.stat, | ||||
| 			.read = Bundle.read, | ||||
| 		}, | ||||
| 	}; | ||||
| }; | ||||
| 
 | ||||
| const native_endian = builtin.cpu.arch.endian(); | ||||
| @ -6,23 +6,30 @@ const slices = @import("./slices.zig"); | ||||
| 
 | ||||
| const std = @import("std"); | ||||
| 
 | ||||
| pub const Byte = u8; | ||||
| pub const Writable = struct { | ||||
| 
					
					kayomn marked this conversation as resolved
					
				 | ||||
| 	data: []Byte, | ||||
| 
 | ||||
| pub const Decoder = coral.io.Functor(coral.io.Error!void, &.{[]coral.Byte}); | ||||
| 	pub fn writer(self: *Writable) Writer { | ||||
| 		return Writer.bind(Writable, self, write); | ||||
| 	} | ||||
| 
 | ||||
| 	fn write(self: *Writable, buffer: []const u8) !usize { | ||||
| 		const range = @min(buffer.len, self.data.len); | ||||
| 
 | ||||
| 		@memcpy(self.data[0 .. range], buffer[0 .. range]); | ||||
| 
 | ||||
| 		self.data = self.data[range ..]; | ||||
| 
 | ||||
| 		return buffer.len; | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| pub const Byte = u8; | ||||
| 
 | ||||
| pub const Error = error { | ||||
| 	UnavailableResource, | ||||
| }; | ||||
| 
 | ||||
| pub fn FixedBuffer(comptime len: usize, comptime default_value: anytype) type { | ||||
| 	const Value = @TypeOf(default_value); | ||||
| 
 | ||||
| 	return struct { | ||||
| 		filled: usize = 0, | ||||
| 		values: [len]Value = [_]Value{default_value} ** len, | ||||
| 	}; | ||||
| } | ||||
| 
 | ||||
| pub fn Functor(comptime Output: type, comptime input_types: []const type) type { | ||||
| 	const InputTuple = std.meta.Tuple(input_types); | ||||
| 
 | ||||
| @ -119,20 +126,6 @@ pub fn Generator(comptime Output: type, comptime input_types: []const type) type | ||||
| 	}; | ||||
| } | ||||
| 
 | ||||
| pub const NullWritable = struct { | ||||
| 	written: usize = 0, | ||||
| 
 | ||||
| 	pub fn writer(self: *NullWritable) Writer { | ||||
| 		return Writer.bind(NullWritable, self, write); | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn write(self: *NullWritable, buffer: []const u8) !usize { | ||||
| 		self.written += buffer.len; | ||||
| 
 | ||||
| 		return buffer.len; | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| pub const PrintError = Error || error { | ||||
| 	IncompleteWrite, | ||||
| }; | ||||
| @ -141,8 +134,6 @@ pub const Reader = Generator(Error!usize, &.{[]coral.Byte}); | ||||
| 
 | ||||
| pub const Writer = Generator(Error!usize, &.{[]const coral.Byte}); | ||||
| 
 | ||||
| const native_endian = builtin.cpu.arch.endian(); | ||||
| 
 | ||||
| pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral.Byte { | ||||
| 	const buffer = coral.Stack(coral.Byte){.allocator = allocator}; | ||||
| 
 | ||||
| @ -153,20 +144,6 @@ pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral. | ||||
| 	return buffer.to_allocation(streamed); | ||||
| } | ||||
| 
 | ||||
| pub fn are_equal(a: []const Byte, b: []const Byte) bool { | ||||
| 	if (a.len != b.len) { | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	for (0 .. a.len) |i| { | ||||
| 		if (a[i] != b[i]) { | ||||
| 			return false; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| pub const bits_per_byte = 8; | ||||
| 
 | ||||
| pub fn bytes_of(value: anytype) []const Byte { | ||||
| @ -179,14 +156,6 @@ pub fn bytes_of(value: anytype) []const Byte { | ||||
| 	}; | ||||
| } | ||||
| 
 | ||||
| pub fn ends_with(haystack: []const Byte, needle: []const Byte) bool { | ||||
| 	if (needle.len > haystack.len) { | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	return are_equal(haystack[haystack.len - needle.len ..], needle); | ||||
| } | ||||
| 
 | ||||
| pub fn print(writer: Writer, utf8: []const u8) PrintError!void { | ||||
| 	if (try writer.yield(.{utf8}) != utf8.len) { | ||||
| 		return error.IncompleteWrite; | ||||
| @ -208,35 +177,6 @@ pub fn skip_n(input: Reader, distance: u64) Error!void { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| pub fn read_foreign(input: Reader, comptime Type: type) Error!Type { | ||||
| 	const decoded = try read_native(input, Type); | ||||
| 
 | ||||
| 	return switch (@typeInfo(input)) { | ||||
| 		.Struct => std.mem.byteSwapAllFields(Type, &decoded), | ||||
| 		else => @byteSwap(decoded), | ||||
| 	}; | ||||
| } | ||||
| 
 | ||||
| pub fn read_native(input: Reader, comptime Type: type) Error!Type { | ||||
| 	var buffer = @as([@sizeOf(Type)]coral.Byte, undefined); | ||||
| 
 | ||||
| 	if (try input.yield(.{&buffer}) != buffer.len) { | ||||
| 		return error.UnavailableResource; | ||||
| 	} | ||||
| 
 | ||||
| 	return @as(*align(1) const Type, @ptrCast(&buffer)).*; | ||||
| } | ||||
| 
 | ||||
| pub const read_little = switch (native_endian) { | ||||
| 	.little => read_native, | ||||
| 	.big => read_foreign, | ||||
| }; | ||||
| 
 | ||||
| pub const read_big = switch (native_endian) { | ||||
| 	.little => read_foreign, | ||||
| 	.big => read_native, | ||||
| }; | ||||
| 
 | ||||
| pub fn slice_sentineled(comptime sen: anytype, ptr: [*:sen]const @TypeOf(sen)) [:sen]const @TypeOf(sen) { | ||||
| 	var len = @as(usize, 0); | ||||
| 
 | ||||
|  | ||||
| @ -6,6 +6,7 @@ const ona = @import("ona"); | ||||
| 
 | ||||
| const Actors = struct { | ||||
| 	instances: coral.stack.Sequential(ona.gfx.Point2D) = .{.allocator = coral.heap.allocator}, | ||||
| 	quad_mesh_2d: ona.gfx.Handle = .none, | ||||
| 	body_texture: ona.gfx.Handle = .none, | ||||
| }; | ||||
| 
 | ||||
| @ -24,6 +25,7 @@ pub fn main() !void { | ||||
| fn load(display: coral.Write(ona.gfx.Display), actors: coral.Write(Actors), assets: coral.Write(ona.gfx.Assets)) !void { | ||||
| 	display.res.width, display.res.height = .{1280, 720}; | ||||
| 	actors.res.body_texture = try assets.res.open_file("actor.bmp"); | ||||
| 	actors.res.quad_mesh_2d = try assets.res.open_quad_mesh_2d(@splat(1)); | ||||
| 
 | ||||
| 	try actors.res.instances.push_grow(.{0, 0}); | ||||
| } | ||||
| @ -32,11 +34,11 @@ fn exit(actors: coral.Write(Actors)) void { | ||||
| 	actors.res.instances.deinit(); | ||||
| } | ||||
| 
 | ||||
| fn render(queue: ona.gfx.Queue, actors: coral.Write(Actors), assets: coral.Read(ona.gfx.Assets)) !void { | ||||
| fn render(queue: ona.gfx.Queue, actors: coral.Write(Actors)) !void { | ||||
| 	for (actors.res.instances.values) |instance| { | ||||
| 		try queue.commands.append(.{ | ||||
| 			.instance_2d = .{ | ||||
| 				.mesh_2d = assets.res.primitives.quad_mesh, | ||||
| 				.mesh_2d = actors.res.quad_mesh_2d, | ||||
| 				.texture = actors.res.body_texture, | ||||
| 
 | ||||
| 				.transform = .{ | ||||
|  | ||||
							
								
								
									
										154
									
								
								src/ona/gfx.zig
									
									
									
									
									
								
							
							
						
						
									
										154
									
								
								src/ona/gfx.zig
									
									
									
									
									
								
							| @ -14,41 +14,99 @@ const std = @import("std"); | ||||
| 
 | ||||
| pub const Assets = struct { | ||||
| 	context: device.Context, | ||||
| 	primitives: Primitives, | ||||
| 	formats: coral.stack.Sequential(Format), | ||||
| 	staging_arena: std.heap.ArenaAllocator, | ||||
| 
 | ||||
| 	pub const Format = struct { | ||||
| 		extension: []const u8, | ||||
| 		open: *const fn ([]const u8) std.mem.Allocator.Error!Handle, | ||||
| 		open_file: *const fn (*std.heap.ArenaAllocator, []const u8) Error!Desc, | ||||
| 
 | ||||
| 		pub const Error = std.mem.Allocator.Error || coral.files.Error || error { | ||||
| 			Unsupported, | ||||
| 		}; | ||||
| 	}; | ||||
| 
 | ||||
| 	const Primitives = struct { | ||||
| 		quad_mesh: Handle, | ||||
| 	}; | ||||
| 	pub fn open_file(self: *Assets, path: []const u8) (std.mem.Allocator.Error || Format.Error)!Handle { | ||||
| 
					
					kayomn marked this conversation as resolved
					
						
						
							Outdated
						
					
				 
				
					
						kayomn
						commented  Wants to accept the desired storage source as an argument for loading resources from non-bundled locations like user storage or caller-defined storage implementations. Wants to accept the desired storage source as an argument for loading resources from non-bundled locations like user storage or caller-defined storage implementations. | ||||
| 		defer { | ||||
| 			const max_cache_size = 536870912; | ||||
| 
 | ||||
| 	pub fn close(self: *Assets, handle: Handle) void { | ||||
| 		return self.context.close(handle); | ||||
| 	} | ||||
| 			if (!self.staging_arena.reset(.{.retain_with_limit = max_cache_size})) { | ||||
| 				std.log.warn("failed to retain staging arena size of {} bytes", .{max_cache_size}); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 	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 self.context.open(try format.open_file(&self.staging_arena, 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 fn open_quad_mesh_2d(self: *Assets, extents: Point2D) std.mem.Allocator.Error!Handle { | ||||
| 		const width, const height = extents / @as(Point2D, @splat(2)); | ||||
| 
 | ||||
| 		return self.context.open(.{ | ||||
| 			.mesh_2d = .{ | ||||
| 				.indices = &.{0, 1, 2, 0, 2, 3}, | ||||
| 
 | ||||
| 				.vertices = &.{ | ||||
| 					.{.xy = .{-width, height}, .uv = .{0, 1}}, | ||||
| 					.{.xy = .{width, height}, .uv = .{1, 1}}, | ||||
| 					.{.xy = .{width, -height}, .uv = .{1, 0}}, | ||||
| 					.{.xy = .{-width, -height},  .uv = .{0, 0}}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}); | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| pub const Color = @Vector(4, f32); | ||||
| 
 | ||||
| pub const Desc = union (enum) { | ||||
| 	texture: Texture, | ||||
| 	mesh_2d: Mesh2D, | ||||
| 
 | ||||
| 	pub const Mesh2D = struct { | ||||
| 		vertices: []const Vertex, | ||||
| 		indices: []const u16, | ||||
| 
 | ||||
| 		pub const Vertex = struct { | ||||
| 			xy: Point2D, | ||||
| 			uv: Point2D, | ||||
| 		}; | ||||
| 	}; | ||||
| 
 | ||||
| 	pub const Texture = struct { | ||||
| 		data: []const coral.io.Byte, | ||||
| 		width: u16, | ||||
| 		format: Format, | ||||
| 		access: Access, | ||||
| 
 | ||||
| 		pub const Access = enum { | ||||
| 			static, | ||||
| 		}; | ||||
| 
 | ||||
| 		pub const Format = enum { | ||||
| 			rgba8888, | ||||
| 			bgra8888, | ||||
| 			argb8888, | ||||
| 			rgb888, | ||||
| 			bgr888, | ||||
| 
 | ||||
| 			pub fn byte_size(self: Format) usize { | ||||
| 				return switch (self) { | ||||
| 					.rgba8888, .bgra8888, .argb8888 => 4, | ||||
| 					.rgb888, .bgr888 => 3, | ||||
| 				}; | ||||
| 			} | ||||
| 		}; | ||||
| 	}; | ||||
| }; | ||||
| 
 | ||||
| pub const Display = struct { | ||||
| 	width: u16 = 1280, | ||||
| 	height: u16 = 720, | ||||
| @ -85,16 +143,6 @@ pub const Input = union (enum) { | ||||
| 
 | ||||
| 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, | ||||
| 
 | ||||
| @ -104,6 +152,10 @@ pub const Queue = struct { | ||||
| 
 | ||||
| 	pub fn bind(_: coral.system.BindContext) std.mem.Allocator.Error!State { | ||||
| 		// TODO: Review how good of an idea this global state is, even if bind is guaranteed to always be ran on main. | ||||
| 		if (renders.is_empty()) { | ||||
| 			renders = .{.allocator = coral.heap.allocator}; | ||||
| 		} | ||||
| 
 | ||||
| 		const command_index = renders.len(); | ||||
| 
 | ||||
| 		try renders.push_grow(device.RenderChain.init(coral.heap.allocator)); | ||||
| @ -121,39 +173,21 @@ pub const Queue = struct { | ||||
| 
 | ||||
| 	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().?); | ||||
| 
 | ||||
| 		const render = &renders.values[state.command_index]; | ||||
| 
 | ||||
| 		render.deinit(); | ||||
| 		std.mem.swap(device.RenderChain, render, renders.get_ptr().?); | ||||
| 		std.debug.assert(renders.pop()); | ||||
| 
 | ||||
| 		if (renders.is_empty()) { | ||||
| 			Queue.renders.deinit(); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	var renders = coral.stack.Sequential(device.RenderChain){.allocator = coral.heap.allocator}; | ||||
| }; | ||||
| 
 | ||||
| pub const 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 { | ||||
| 	xbasis: Point2D = .{1, 0}, | ||||
| 	ybasis: Point2D = .{0, 1}, | ||||
| @ -163,7 +197,7 @@ pub const Transform2D = extern struct { | ||||
| const builtin_formats = [_]Assets.Format{ | ||||
| 	.{ | ||||
| 		.extension = "bmp", | ||||
| 		.open = formats.open_bmp, | ||||
| 		.open_file = formats.load_bmp, | ||||
| 	}, | ||||
| }; | ||||
| 
 | ||||
| @ -210,24 +244,8 @@ pub fn setup(world: *coral.World, events: App.Events) (error {Unsupported} || st | ||||
| 	try registered_formats.grow(builtin_formats.len); | ||||
| 	std.debug.assert(registered_formats.push_all(&builtin_formats)); | ||||
| 
 | ||||
| 	const half_extent = 0.5; | ||||
| 
 | ||||
| 	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}}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}), | ||||
| 		}, | ||||
| 
 | ||||
| 		.staging_arena = std.heap.ArenaAllocator.init(coral.heap.allocator), | ||||
| 		.formats = registered_formats, | ||||
| 		.context = context, | ||||
| 	}); | ||||
| @ -239,6 +257,8 @@ pub fn setup(world: *coral.World, events: App.Events) (error {Unsupported} || st | ||||
| } | ||||
| 
 | ||||
| pub fn stop(assets: coral.Write(Assets)) void { | ||||
| 	assets.res.staging_arena.deinit(); | ||||
| 	assets.res.formats.deinit(); | ||||
| 	assets.res.context.deinit(); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -10,11 +10,6 @@ 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, | ||||
| @ -42,6 +37,7 @@ pub const Context = struct { | ||||
| 		self.loop.is_running.store(false, .monotonic); | ||||
| 		self.loop.ready.post(); | ||||
| 		self.thread.join(); | ||||
| 		self.loop.deinit(); | ||||
| 		coral.heap.allocator.destroy(self.loop); | ||||
| 		ext.SDL_DestroyWindow(self.window); | ||||
| 
 | ||||
| @ -84,16 +80,20 @@ pub const Context = struct { | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn open(self: *Context, asset: Asset) std.mem.Allocator.Error!gfx.Handle { | ||||
| 	pub fn open(self: *Context, desc: gfx.Desc) std.mem.Allocator.Error!gfx.Handle { | ||||
| 		const open_commands = self.loop.opens.pending(); | ||||
| 		const index = self.loop.closed_indices.get() orelse open_commands.stack.len(); | ||||
| 
 | ||||
| 		try open_commands.append(.{ | ||||
| 			.index = index, | ||||
| 			.payload = asset, | ||||
| 			.desc = desc, | ||||
| 		}); | ||||
| 
 | ||||
| 		try self.loop.closes.pending().stack.grow(1); | ||||
| 		const pending_closes = self.loop.closes.pending(); | ||||
| 
 | ||||
| 		if (pending_closes.stack.len() == pending_closes.stack.cap) { | ||||
| 			try pending_closes.stack.grow(1); | ||||
| 		} | ||||
| 
 | ||||
| 		_ = self.loop.closed_indices.pop(); | ||||
| 
 | ||||
| @ -145,13 +145,13 @@ const Loop = struct { | ||||
| 
 | ||||
| 	const OpenCommand = struct { | ||||
| 		index: usize, | ||||
| 		payload: Asset, | ||||
| 		desc: gfx.Desc, | ||||
| 
 | ||||
| 		fn clone(command: OpenCommand, arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!OpenCommand { | ||||
| 			const allocator = arena.allocator(); | ||||
| 
 | ||||
| 			return .{ | ||||
| 				.payload = switch (command.payload) { | ||||
| 				.desc = switch (command.desc) { | ||||
| 					.texture => |texture| .{ | ||||
| 						.texture = .{ | ||||
| 							.data = try allocator.dupe(coral.io.Byte, texture.data), | ||||
| @ -163,7 +163,7 @@ const Loop = struct { | ||||
| 
 | ||||
| 					.mesh_2d => |mesh_2d| .{ | ||||
| 						.mesh_2d = .{ | ||||
| 							.vertices = try allocator.dupe(gfx.Mesh2D.Vertex, mesh_2d.vertices), | ||||
| 							.vertices = try allocator.dupe(gfx.Desc.Mesh2D.Vertex, mesh_2d.vertices), | ||||
| 							.indices = try allocator.dupe(u16, mesh_2d.indices), | ||||
| 						}, | ||||
| 					}, | ||||
| @ -178,6 +178,12 @@ const Loop = struct { | ||||
| 
 | ||||
| 	const OpenChain = commands.Chain(OpenCommand, OpenCommand.clone); | ||||
| 
 | ||||
| 	fn deinit(self: *Loop) void { | ||||
| 		self.closes.deinit(); | ||||
| 		self.opens.deinit(); | ||||
| 		self.closed_indices.deinit(); | ||||
| 	} | ||||
| 
 | ||||
| 	fn run(self: *Loop, window: *ext.SDL_Window) !void { | ||||
| 		const context = configure_and_create: { | ||||
| 			var result = @as(c_int, 0); | ||||
| @ -230,7 +236,7 @@ const Loop = struct { | ||||
| 			defer open_commands.clear(); | ||||
| 
 | ||||
| 			for (open_commands.stack.values) |command| { | ||||
| 				switch (command.payload) { | ||||
| 				switch (command.desc) { | ||||
| 					.texture => |texture| { | ||||
| 						const stride = texture.width * texture.format.byte_size(); | ||||
| 
 | ||||
|  | ||||
| @ -1,9 +1,69 @@ | ||||
| const coral = @import("coral"); | ||||
| 
 | ||||
| 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; | ||||
| pub fn load_bmp(arena: *std.heap.ArenaAllocator, path: []const u8) gfx.Assets.Format.Error!gfx.Desc { | ||||
| 	const header = try coral.files.bundle.read_little(path, 0, extern struct { | ||||
| 		type: [2]u8 align (1), | ||||
| 		file_size: u32 align (1), | ||||
| 		reserved: [2]u16 align (1), | ||||
| 		image_offset: u32 align (1), | ||||
| 		header_size: u32 align (1), | ||||
| 		pixel_width: i32 align (1), | ||||
| 		pixel_height: i32 align (1), | ||||
| 		color_planes: u16 align (1), | ||||
| 		bits_per_pixel: u16 align (1), | ||||
| 		compression_method: u32 align (1), | ||||
| 		image_size: u32 align(1), | ||||
| 		pixels_per_meter_x: i32 align (1), | ||||
| 		pixels_per_meter_y: i32 align (1), | ||||
| 		palette_colors_used: u32 align (1), | ||||
| 		important_colors_used: u32 align (1), | ||||
| 	}) orelse { | ||||
| 		return error.Unsupported; | ||||
| 	}; | ||||
| 
 | ||||
| 	if (!std.mem.eql(u8, &header.type, "BM")) { | ||||
| 		return error.Unsupported; | ||||
| 	} | ||||
| 
 | ||||
| 	const pixel_width = std.math.cast(u16, header.pixel_width) orelse { | ||||
| 		return error.Unsupported; | ||||
| 	}; | ||||
| 
 | ||||
| 	const pixels = try arena.allocator().alloc(coral.io.Byte, header.image_size); | ||||
| 	const bytes_per_pixel = header.bits_per_pixel / coral.io.bits_per_byte; | ||||
| 	const alignment = 4; | ||||
| 	const byte_stride = pixel_width * bytes_per_pixel; | ||||
| 	const padded_byte_stride = alignment * @divTrunc((byte_stride + alignment - 1), alignment); | ||||
| 	const byte_padding = coral.scalars.sub(padded_byte_stride, byte_stride) orelse 0; | ||||
| 	var buffer_offset: usize = 0; | ||||
| 	var file_offset = @as(usize, header.image_offset); | ||||
| 
 | ||||
| 	while (buffer_offset < pixels.len) { | ||||
| 		const line = pixels[buffer_offset .. buffer_offset + byte_stride]; | ||||
| 
 | ||||
| 		if (try coral.files.bundle.read_bytes(path, file_offset, line) != byte_stride) { | ||||
| 			return error.Unsupported; | ||||
| 		} | ||||
| 
 | ||||
| 		file_offset = line.len + byte_padding; | ||||
| 		buffer_offset += padded_byte_stride; | ||||
| 	} | ||||
| 
 | ||||
| 	return .{ | ||||
| 		.texture = .{ | ||||
| 			.format = switch (header.bits_per_pixel) { | ||||
| 				24 => .bgr888, | ||||
| 				32 => .bgra8888, | ||||
| 				else => return error.Unsupported, | ||||
| 			}, | ||||
| 
 | ||||
| 			.width = pixel_width, | ||||
| 			.data = pixels, | ||||
| 			.access = .static, | ||||
| 		} | ||||
| 	}; | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	
Unused addition.