renderer-mvp/post-processing #56
							
								
								
									
										14
									
								
								build.zig
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								build.zig
									
									
									
									
									
								
							| @ -194,17 +194,7 @@ pub fn build(b: *std.Build) !void { | ||||
| 			const vertex_binary_sub_path_utf8 = vertex_binary_sub_path.utf8(); | ||||
| 			const fragment_binary_sub_path_utf8 = fragment_binary_sub_path.utf8(); | ||||
| 
 | ||||
| 			const link_command = b.addSystemCommand(&.{ | ||||
| 				"spirv-link", | ||||
| 				vertex_binary_sub_path_utf8, | ||||
| 				fragment_binary_sub_path_utf8, | ||||
| 				"-o", | ||||
| 				pending_shader.binary_sub_path.utf8(), | ||||
| 			}); | ||||
| 
 | ||||
| 			exe.step.dependOn(&link_command.step); | ||||
| 
 | ||||
| 			link_command.step.dependOn(compile_vertex: { | ||||
| 			exe.step.dependOn(compile_vertex: { | ||||
| 				const compile_command = b.addSystemCommand(&.{ | ||||
| 					"glslangValidator", | ||||
| 					"-V", | ||||
| @ -216,7 +206,7 @@ pub fn build(b: *std.Build) !void { | ||||
| 				break: compile_vertex &compile_command.step; | ||||
| 			}); | ||||
| 
 | ||||
| 			link_command.step.dependOn(compile_fragment: { | ||||
| 			exe.step.dependOn(compile_fragment: { | ||||
| 				const compile_command = b.addSystemCommand(&.{ | ||||
| 					"glslangValidator", | ||||
| 					"-V", | ||||
|  | ||||
| @ -7,22 +7,19 @@ layout (location = 1) in vec2 uv; | ||||
| 
 | ||||
| layout (location = 0) out vec4 texel; | ||||
| 
 | ||||
| layout (binding = 0) readonly buffer Camera { | ||||
| 	mat4 projection_matrix; | ||||
| }; | ||||
| 
 | ||||
| layout (binding = 1) readonly buffer Material { | ||||
| layout (binding = 0) readonly buffer Effect { | ||||
| 	float effect_magnitude; | ||||
| }; | ||||
| 
 | ||||
| void main() { | ||||
| 	const vec2 red_channel_uv = uv + vec2(effect_magnitude, 0.0); | ||||
| 	const vec2 blue_channel_uv = uv + vec2(-effect_magnitude, 0.0); | ||||
| 	const vec4 original_texel = texture(sprite, uv); | ||||
| 	vec4 color1 = texture(sprite, uv) / 3.0; | ||||
|     vec4 color2 = texture(sprite, uv + 0.002 * effect_magnitude) / 3.0; | ||||
|     vec4 color3 = texture(sprite, uv - 0.002 * effect_magnitude) / 3.0; | ||||
| 
 | ||||
| 	texel = vec4(texture(sprite, red_channel_uv).r, original_texel.g, texture(sprite, blue_channel_uv).b, original_texel.a) * color; | ||||
|     color1 *= 2.0; | ||||
|     color2.g = 0.0; | ||||
|     color2.b = 0.0; | ||||
|     color3.r = 0.0; | ||||
| 
 | ||||
| 	if (texel.a == 0) { | ||||
| 		discard; | ||||
| 	} | ||||
|     texel = color * (color1 + color2 + color3); | ||||
| } | ||||
|  | ||||
							
								
								
									
										32
									
								
								src/main.zig
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								src/main.zig
									
									
									
									
									
								
							| @ -4,10 +4,15 @@ const std = @import("std"); | ||||
| 
 | ||||
| const ona = @import("ona"); | ||||
| 
 | ||||
| const ChromaticAberration = extern struct { | ||||
| 	magnitude: f32, | ||||
| }; | ||||
| 
 | ||||
| const Actors = struct { | ||||
| 	instances: coral.stack.Sequential(@Vector(2, f32)) = .{.allocator = coral.heap.allocator}, | ||||
| 	body_texture: ona.gfx.Texture = .default, | ||||
| 	render_texture: ona.gfx.Texture = .default, | ||||
| 	ca_effect: ona.gfx.Effect = .default, | ||||
| }; | ||||
| 
 | ||||
| const Player = struct { | ||||
| @ -37,6 +42,8 @@ fn load(config: ona.Write(ona.gfx.Config), actors: ona.Write(Actors), assets: on | ||||
| 		}, | ||||
| 	}); | ||||
| 
 | ||||
| 	actors.res.ca_effect = try assets.res.load_effect_file(coral.files.bundle, "./ca.frag.spv"); | ||||
| 
 | ||||
| 	try actors.res.instances.push_grow(.{0, 0}); | ||||
| } | ||||
| 
 | ||||
| @ -44,12 +51,9 @@ fn exit(actors: ona.Write(Actors)) void { | ||||
| 	actors.res.instances.deinit(); | ||||
| } | ||||
| 
 | ||||
| fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors), config: ona.Read(ona.gfx.Config)) !void { | ||||
| 	try commands.set_target(.{ | ||||
| 		.texture = actors.res.render_texture, | ||||
| 		.clear_color = .{0, 0, 0, 0}, | ||||
| 		.clear_depth = 0, | ||||
| 		.clear_stencil = 0, | ||||
| fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors)) !void { | ||||
| 	try commands.set_effect(actors.res.ca_effect, ChromaticAberration{ | ||||
| 		.magnitude = 15.0, | ||||
| 	}); | ||||
| 
 | ||||
| 	for (actors.res.instances.values) |instance| { | ||||
| @ -63,22 +67,6 @@ fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors), config: ona.Rea | ||||
| 			}, | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	try commands.set_target(.{ | ||||
| 		.clear_color = null, | ||||
| 		.clear_depth = null, | ||||
| 		.clear_stencil = null, | ||||
| 	}); | ||||
| 
 | ||||
| 	try commands.draw_texture(.{ | ||||
| 		.texture = actors.res.render_texture, | ||||
| 
 | ||||
| 		.transform = .{ | ||||
| 			.origin = .{@floatFromInt(config.res.width / 2), @floatFromInt(config.res.height / 2)}, | ||||
| 			.xbasis = .{@floatFromInt(config.res.width), 0}, | ||||
| 			.ybasis = .{0, @floatFromInt(config.res.height)}, | ||||
| 		}, | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| fn update(player: ona.Read(Player), actors: ona.Write(Actors), mapping: ona.Read(ona.act.Mapping)) !void { | ||||
|  | ||||
| @ -25,12 +25,11 @@ const std = @import("std"); | ||||
| pub const Assets = struct { | ||||
| 	window: *ext.SDL_Window, | ||||
| 	texture_formats: coral.stack.Sequential(TextureFormat), | ||||
| 	staging_arena: std.heap.ArenaAllocator, | ||||
| 	frame_rendered: std.Thread.ResetEvent = .{}, | ||||
| 
 | ||||
| 	pub const LoadError = std.mem.Allocator.Error; | ||||
| 
 | ||||
| 	pub const LoadFileError = LoadError || coral.files.AccessError || error { | ||||
| 	pub const LoadFileError = LoadError || coral.files.ReadAllError || error { | ||||
| 		FormatUnsupported, | ||||
| 	}; | ||||
| 
 | ||||
| @ -41,7 +40,6 @@ pub const Assets = struct { | ||||
| 
 | ||||
| 	fn deinit(self: *Assets) void { | ||||
| 		rendering.enqueue_work(.shutdown); | ||||
| 		self.staging_arena.deinit(); | ||||
| 		self.texture_formats.deinit(); | ||||
| 	} | ||||
| 
 | ||||
| @ -64,12 +62,40 @@ pub const Assets = struct { | ||||
| 		try rendering.startup(window); | ||||
| 
 | ||||
| 		return .{ | ||||
| 			.staging_arena = std.heap.ArenaAllocator.init(coral.heap.allocator), | ||||
| 			.texture_formats = .{.allocator = coral.heap.allocator}, | ||||
| 			.window = window, | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn load_effect_file(_: *Assets, storage: coral.files.Storage, path: []const u8) LoadFileError!Effect { | ||||
| 		if (!std.mem.endsWith(u8, path, ".spv")) { | ||||
| 			return error.FormatUnsupported; | ||||
| 		} | ||||
| 
 | ||||
| 		const fragment_file_stat = try storage.stat(path); | ||||
| 		const fragment_spirv = try coral.heap.allocator.alloc(u32, fragment_file_stat.size / @alignOf(u32)); | ||||
| 
 | ||||
| 		defer { | ||||
| 			coral.heap.allocator.free(fragment_spirv); | ||||
| 		} | ||||
| 
 | ||||
| 		_ = try storage.read_all(path, std.mem.sliceAsBytes(fragment_spirv), .{}); | ||||
| 
 | ||||
| 		var loaded = coral.asyncio.Future(std.mem.Allocator.Error!Effect){}; | ||||
| 
 | ||||
| 		rendering.enqueue_work(.{ | ||||
| 			.load_effect = .{ | ||||
| 				.desc = .{ | ||||
| 					.fragment_spirv = fragment_spirv, | ||||
| 				}, | ||||
| 
 | ||||
| 				.loaded = &loaded, | ||||
| 			}, | ||||
| 		}); | ||||
| 
 | ||||
| 		return loaded.get().*; | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn load_texture(_: *Assets, desc: Texture.Desc) std.mem.Allocator.Error!Texture { | ||||
| 		var loaded = coral.asyncio.Future(std.mem.Allocator.Error!Texture){}; | ||||
| 
 | ||||
| @ -84,12 +110,10 @@ pub const Assets = struct { | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn load_texture_file(self: *Assets, storage: coral.files.Storage, path: []const u8) LoadFileError!Texture { | ||||
| 		defer { | ||||
| 			const max_cache_size = 536870912; | ||||
| 		var arena = std.heap.ArenaAllocator.init(coral.heap.allocator); | ||||
| 
 | ||||
| 			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}); | ||||
| 			} | ||||
| 		defer { | ||||
| 			arena.deinit(); | ||||
| 		} | ||||
| 
 | ||||
| 		for (self.texture_formats.values) |format| { | ||||
| @ -97,12 +121,26 @@ pub const Assets = struct { | ||||
| 				continue; | ||||
| 			} | ||||
| 
 | ||||
| 			return self.load_texture(try format.load_file(&self.staging_arena, storage, path)); | ||||
| 			return self.load_texture(try format.load_file(&arena, storage, path)); | ||||
| 		} | ||||
| 
 | ||||
| 		return error.FormatUnsupported; | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn update_effect(_: *Assets, effect: Effect, properties: anytype) bool { | ||||
| 		var setted = coral.asyncio.Future(bool){}; | ||||
| 
 | ||||
| 		rendering.enqueue_work(.{ | ||||
| 			.update_effect = .{ | ||||
| 				.properties = std.mem.asBytes(properties), | ||||
| 				.effect = effect, | ||||
| 				.setted = &setted, | ||||
| 			}, | ||||
| 		}); | ||||
| 
 | ||||
| 		return setted.get().*; | ||||
| 	} | ||||
| 
 | ||||
| 	pub const thread_restriction = .main; | ||||
| }; | ||||
| 
 | ||||
| @ -137,6 +175,15 @@ pub const Commands = struct { | ||||
| 		try self.list.append(.{.draw_texture = command}); | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn set_effect(self: Commands, effect: handles.Effect, properties: anytype) std.mem.Allocator.Error!void { | ||||
| 		try self.list.append(.{ | ||||
| 			.set_effect = .{ | ||||
| 				.properties = std.mem.asBytes(&properties), | ||||
| 				.effect = effect, | ||||
| 			}, | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn set_target(self: Commands, command: rendering.Command.SetTarget) std.mem.Allocator.Error!void { | ||||
| 		try self.list.append(.{.set_target = command}); | ||||
| 	} | ||||
| @ -164,6 +211,8 @@ pub const Input = union (enum) { | ||||
| 	}; | ||||
| }; | ||||
| 
 | ||||
| pub const Effect = handles.Effect; | ||||
| 
 | ||||
| pub const Rect = struct { | ||||
| 	left: f32, | ||||
| 	top: f32, | ||||
|  | ||||
| @ -2,6 +2,10 @@ const coral = @import("coral"); | ||||
| 
 | ||||
| const std = @import("std"); | ||||
| 
 | ||||
| pub const Effect = Handle(struct { | ||||
| 	fragment_spirv: []const u32, | ||||
| }); | ||||
| 
 | ||||
| fn Handle(comptime HandleDesc: type) type { | ||||
| 	return enum (u32) { | ||||
| 		default, | ||||
|  | ||||
| @ -16,6 +16,7 @@ const std = @import("std"); | ||||
| 
 | ||||
| pub const Command = union (enum) { | ||||
| 	draw_texture: DrawTexture, | ||||
| 	set_effect: SetEffect, | ||||
| 	set_target: SetTarget, | ||||
| 
 | ||||
| 	pub const DrawTexture = struct { | ||||
| @ -23,6 +24,11 @@ pub const Command = union (enum) { | ||||
| 		transform: lina.Transform2D, | ||||
| 	}; | ||||
| 
 | ||||
| 	pub const SetEffect = struct { | ||||
| 		effect: handles.Effect, | ||||
| 		properties: []const coral.io.Byte, | ||||
| 	}; | ||||
| 
 | ||||
| 	pub const SetTarget = struct { | ||||
| 		texture: ?handles.Texture = null, | ||||
| 		clear_color: ?lina.Color, | ||||
| @ -31,10 +37,16 @@ pub const Command = union (enum) { | ||||
| 	}; | ||||
| 
 | ||||
| 	fn clone(self: Command, arena: *std.heap.ArenaAllocator) !Command { | ||||
| 		_ = arena; | ||||
| 
 | ||||
| 		return switch (self) { | ||||
| 			.draw_texture => |draw_texture| .{.draw_texture = draw_texture}, | ||||
| 
 | ||||
| 			.set_effect => |set_effect| .{ | ||||
| 				.set_effect = .{ | ||||
| 					.properties = try arena.allocator().dupe(coral.io.Byte, set_effect.properties), | ||||
| 					.effect = set_effect.effect, | ||||
| 				}, | ||||
| 			}, | ||||
| 
 | ||||
| 			.set_target => |set_target| .{.set_target = set_target}, | ||||
| 		}; | ||||
| 	} | ||||
| @ -42,6 +54,7 @@ pub const Command = union (enum) { | ||||
| 	fn process(self: Command, pools: *Pools, frame: *Frame) !void { | ||||
| 		try switch (self) { | ||||
| 			.draw_texture => |draw_texture| frame.draw_texture(pools, draw_texture), | ||||
| 			.set_effect => |set_effect| frame.set_effect(pools, set_effect), | ||||
| 			.set_target => |set_target| frame.set_target(pools, set_target), | ||||
| 		}; | ||||
| 	} | ||||
| @ -123,6 +136,7 @@ const Frame = struct { | ||||
| 	flushed_count: usize = 0, | ||||
| 	current_source_texture: handles.Texture = .default, | ||||
| 	current_target_texture: ?handles.Texture = null, | ||||
| 	current_effect: handles.Effect = .default, | ||||
| 
 | ||||
| 	const DrawTexture = extern struct { | ||||
| 		transform: lina.Transform2D, | ||||
| @ -175,29 +189,29 @@ const Frame = struct { | ||||
| 			.render => |render| { | ||||
| 				bindings.fs.images[0] = render.color_image; | ||||
| 				bindings.fs.samplers[0] = render.sampler; | ||||
| 				bindings.vs.images[0] = render.color_image; | ||||
| 				bindings.vs.samplers[0] = render.sampler; | ||||
| 			}, | ||||
| 
 | ||||
| 			.static => |static| { | ||||
| 				bindings.fs.images[0] = static.image; | ||||
| 				bindings.fs.samplers[0] = static.sampler; | ||||
| 				bindings.vs.images[0] = static.image; | ||||
| 				bindings.vs.samplers[0] = static.sampler; | ||||
| 			}, | ||||
| 		} | ||||
| 
 | ||||
| 		if (self.current_target_texture) |target_texture| { | ||||
| 			const target_view_buffer = pools.textures.get(@intFromEnum(target_texture)).?.render.view_buffer; | ||||
| 
 | ||||
| 			bindings.fs.storage_buffers[0] = target_view_buffer; | ||||
| 			bindings.vs.storage_buffers[0] = target_view_buffer; | ||||
| 		} else { | ||||
| 			bindings.fs.storage_buffers[0] = view_buffer; | ||||
| 			bindings.vs.storage_buffers[0] = view_buffer; | ||||
| 		} | ||||
| 
 | ||||
| 		sokol.gfx.applyPipeline(texture_batching_pipeline); | ||||
| 		const effect = pools.effects.get(@intFromEnum(self.current_effect)).?; | ||||
| 
 | ||||
| 		sokol.gfx.applyPipeline(effect.pipeline); | ||||
| 
 | ||||
| 		if (effect.has_properties_buffer) |properties_buffer| { | ||||
| 			bindings.fs.storage_buffers[0] = properties_buffer; | ||||
| 		} | ||||
| 
 | ||||
| 		while (true) { | ||||
| 			const buffer_index = self.flushed_count / batches_per_buffer; | ||||
| @ -217,11 +231,29 @@ const Frame = struct { | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn set_effect(self: *Frame, pools: *Pools, command: Command.SetEffect) void { | ||||
| 		if (command.effect != self.current_effect) { | ||||
| 			self.flush(pools); | ||||
| 		} | ||||
| 
 | ||||
| 		self.current_effect = command.effect; | ||||
| 
 | ||||
| 		if (command.properties.len != 0) { | ||||
| 			if (pools.effects.get(@intFromEnum(self.current_effect)).?.has_properties_buffer) |properties_buffer| { | ||||
| 				sokol.gfx.updateBuffer(properties_buffer, sokol.gfx.asRange(command.properties)); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn set_target(self: *Frame, pools: *Pools, command: Command.SetTarget) void { | ||||
| 		sokol.gfx.endPass(); | ||||
| 
 | ||||
| 		var pass = sokol.gfx.Pass{ | ||||
| 			.action = .{.stencil = .{.load_action = .CLEAR}}, | ||||
| 			.action = .{ | ||||
| 				.stencil = .{ | ||||
| 					.load_action = .CLEAR, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}; | ||||
| 
 | ||||
| 		if (command.clear_color) |color| { | ||||
| @ -265,15 +297,24 @@ const Frame = struct { | ||||
| 	var texture_batch_buffers = coral.stack.Sequential(sokol.gfx.Buffer){.allocator = coral.heap.allocator}; | ||||
| 
 | ||||
| 	const batches_per_buffer = 512; | ||||
| 
 | ||||
| 	var texture_batching_pipeline: sokol.gfx.Pipeline = undefined; | ||||
| }; | ||||
| 
 | ||||
| const Pools = struct { | ||||
| 	effects: EffectPool, | ||||
| 	textures: TexturePool, | ||||
| 
 | ||||
| 	const EffectPool = coral.Pool(resources.Effect); | ||||
| 
 | ||||
| 	const TexturePool = coral.Pool(resources.Texture); | ||||
| 
 | ||||
| 	fn create_effect(self: *Pools, desc: handles.Effect.Desc) !handles.Effect { | ||||
| 		var effect = try resources.Effect.init(desc); | ||||
| 
 | ||||
| 		errdefer effect.deinit(); | ||||
| 
 | ||||
| 		return @enumFromInt(try self.effects.insert(effect)); | ||||
| 	} | ||||
| 
 | ||||
| 	fn create_texture(self: *Pools, desc: handles.Texture.Desc) !handles.Texture { | ||||
| 		var texture = try resources.Texture.init(desc); | ||||
| 
 | ||||
| @ -292,12 +333,28 @@ const Pools = struct { | ||||
| 		self.textures.deinit(); | ||||
| 	} | ||||
| 
 | ||||
| 	fn destroy_texture(self: *Pools, texture_key: handles.Texture) bool { | ||||
| 		switch (texture_key) { | ||||
| 	fn destroy_effect(self: *Pools, handle: handles.Effect) bool { | ||||
| 		switch (handle) { | ||||
| 			.default => {}, | ||||
| 
 | ||||
| 			else => { | ||||
| 				var texture = self.textures.remove(@intFromEnum(texture_key)) orelse { | ||||
| 				var effect = self.effects.remove(@intFromEnum(handle)) orelse { | ||||
| 					return false; | ||||
| 				}; | ||||
| 
 | ||||
| 				effect.deinit(); | ||||
| 			}, | ||||
| 		} | ||||
| 
 | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	fn destroy_texture(self: *Pools, handle: handles.Texture) bool { | ||||
| 		switch (handle) { | ||||
| 			.default => {}, | ||||
| 
 | ||||
| 			else => { | ||||
| 				var texture = self.textures.remove(@intFromEnum(handle)) orelse { | ||||
| 					return false; | ||||
| 				}; | ||||
| 
 | ||||
| @ -310,10 +367,17 @@ const Pools = struct { | ||||
| 
 | ||||
| 	fn init(allocator: std.mem.Allocator) !Pools { | ||||
| 		var pools = Pools{ | ||||
| 			.effects = EffectPool.init(allocator), | ||||
| 			.textures = TexturePool.init(allocator), | ||||
| 		}; | ||||
| 
 | ||||
| 		errdefer pools.deinit(); | ||||
| 		errdefer { | ||||
| 			pools.deinit(); | ||||
| 		} | ||||
| 
 | ||||
| 		_ = try pools.create_effect(.{ | ||||
| 			.fragment_spirv = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")), | ||||
| 		}); | ||||
| 
 | ||||
| 		const default_texture_data = [_]u32{ | ||||
| 			0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, | ||||
| @ -343,16 +407,23 @@ const Pools = struct { | ||||
| 
 | ||||
| pub const Work = union (enum) { | ||||
| 	create_commands: CreateCommandsWork, | ||||
| 	load_effect: LoadEffectWork, | ||||
| 	load_texture: LoadTextureWork, | ||||
| 	render_frame: RenderFrameWork, | ||||
| 	rotate_commands: RotateCommandsWork, | ||||
| 	shutdown, | ||||
| 	unload_effect: UnloadEffectWork, | ||||
| 	unload_texture: UnloadTextureWork, | ||||
| 
 | ||||
| 	pub const CreateCommandsWork = struct { | ||||
| 		created: *coral.asyncio.Future(std.mem.Allocator.Error!*Commands), | ||||
| 	}; | ||||
| 
 | ||||
| 	pub const LoadEffectWork = struct { | ||||
| 		desc: handles.Effect.Desc, | ||||
| 		loaded: *coral.asyncio.Future(std.mem.Allocator.Error!handles.Effect), | ||||
| 	}; | ||||
| 
 | ||||
| 	pub const LoadTextureWork = struct { | ||||
| 		desc: handles.Texture.Desc, | ||||
| 		loaded: *coral.asyncio.Future(std.mem.Allocator.Error!handles.Texture), | ||||
| @ -369,6 +440,10 @@ pub const Work = union (enum) { | ||||
| 		finished: *std.Thread.ResetEvent, | ||||
| 	}; | ||||
| 
 | ||||
| 	pub const UnloadEffectWork = struct { | ||||
| 		handle: handles.Effect, | ||||
| 	}; | ||||
| 
 | ||||
| 	pub const UnloadTextureWork = struct { | ||||
| 		handle: handles.Texture, | ||||
| 	}; | ||||
| @ -395,62 +470,6 @@ const vertex_indices = .{ | ||||
| 	.instance = 1, | ||||
| }; | ||||
| 
 | ||||
| const vertex_layout_state = sokol.gfx.VertexLayoutState{ | ||||
| 	.attrs = get: { | ||||
| 		var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16; | ||||
| 
 | ||||
| 		attrs[0] = .{ | ||||
| 			.format = .FLOAT2, | ||||
| 			.buffer_index = vertex_indices.mesh, | ||||
| 		}; | ||||
| 
 | ||||
| 		attrs[1] = .{ | ||||
| 			.format = .FLOAT2, | ||||
| 			.buffer_index = vertex_indices.mesh, | ||||
| 		}; | ||||
| 
 | ||||
| 		attrs[2] = .{ | ||||
| 			.format = .FLOAT2, | ||||
| 			.buffer_index = vertex_indices.instance, | ||||
| 		}; | ||||
| 
 | ||||
| 		attrs[3] = .{ | ||||
| 			.format = .FLOAT2, | ||||
| 			.buffer_index = vertex_indices.instance, | ||||
| 		}; | ||||
| 
 | ||||
| 		attrs[4] = .{ | ||||
| 			.format = .FLOAT2, | ||||
| 			.buffer_index = vertex_indices.instance, | ||||
| 		}; | ||||
| 
 | ||||
| 		attrs[5] = .{ | ||||
| 			.format = .UBYTE4N, | ||||
| 			.buffer_index = vertex_indices.instance, | ||||
| 		}; | ||||
| 
 | ||||
| 		attrs[6] = .{ | ||||
| 			.format = .FLOAT, | ||||
| 			.buffer_index = vertex_indices.instance, | ||||
| 		}; | ||||
| 
 | ||||
| 		attrs[7] = .{ | ||||
| 			.format = .FLOAT4, | ||||
| 			.buffer_index = vertex_indices.instance, | ||||
| 		}; | ||||
| 
 | ||||
| 		break: get attrs; | ||||
| 	}, | ||||
| 
 | ||||
| 	.buffers = get: { | ||||
| 		var vertex_buffer_layouts = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8; | ||||
| 
 | ||||
| 		vertex_buffer_layouts[vertex_indices.instance].step_func = .PER_INSTANCE; | ||||
| 
 | ||||
| 		break: get vertex_buffer_layouts; | ||||
| 	}, | ||||
| }; | ||||
| 
 | ||||
| fn run(window: *ext.SDL_Window) !void { | ||||
| 	const context = configure_and_create: { | ||||
| 		var result = @as(c_int, 0); | ||||
| @ -530,21 +549,6 @@ fn run(window: *ext.SDL_Window) !void { | ||||
| 		.usage = .IMMUTABLE, | ||||
| 	}); | ||||
| 
 | ||||
| 	const shader_spirv = @embedFile("./shaders/2d_default.spv"); | ||||
| 	var spirv_unit = try spirv.Unit.init(coral.heap.allocator); | ||||
| 
 | ||||
| 	defer spirv_unit.deinit(); | ||||
| 
 | ||||
| 	try spirv_unit.compile(shader_spirv, .vertex); | ||||
| 	try spirv_unit.compile(shader_spirv, .fragment); | ||||
| 
 | ||||
| 	Frame.texture_batching_pipeline = sokol.gfx.makePipeline(.{ | ||||
| 		.label = "2D drawing pipeline", | ||||
| 		.layout = vertex_layout_state, | ||||
| 		.shader = sokol.gfx.makeShader(spirv_unit.shader_desc), | ||||
| 		.index_type = .UINT16, | ||||
| 	}); | ||||
| 
 | ||||
| 	var has_commands_head: ?*Commands = null; | ||||
| 	var has_commands_tail: ?*Commands = null; | ||||
| 
 | ||||
| @ -575,6 +579,14 @@ fn run(window: *ext.SDL_Window) !void { | ||||
| 				} | ||||
| 			}, | ||||
| 
 | ||||
| 			.load_effect => |load| { | ||||
| 				const effect = try pools.create_effect(load.desc); | ||||
| 
 | ||||
| 				if (!load.loaded.resolve(effect)) { | ||||
| 					std.debug.assert(pools.destroy_effect(effect)); | ||||
| 				} | ||||
| 			}, | ||||
| 
 | ||||
| 			.load_texture => |load| { | ||||
| 				const texture = try pools.create_texture(load.desc); | ||||
| 
 | ||||
| @ -650,6 +662,12 @@ fn run(window: *ext.SDL_Window) !void { | ||||
| 				break; | ||||
| 			}, | ||||
| 
 | ||||
| 			.unload_effect => |unload| { | ||||
| 				if (!pools.destroy_effect(unload.handle)) { | ||||
| 					@panic("Attempt to unload a non-existent effect"); | ||||
| 				} | ||||
| 			}, | ||||
| 
 | ||||
| 			.unload_texture => |unload| { | ||||
| 				if (!pools.destroy_texture(unload.handle)) { | ||||
| 					@panic("Attempt to unload a non-existent texture"); | ||||
|  | ||||
| @ -10,6 +10,140 @@ const spirv = @import("./spirv.zig"); | ||||
| 
 | ||||
| const std = @import("std"); | ||||
| 
 | ||||
| pub const Effect = struct { | ||||
| 	shader: sokol.gfx.Shader, | ||||
| 	pipeline: sokol.gfx.Pipeline, | ||||
| 	has_properties_buffer: ?sokol.gfx.Buffer = null, | ||||
| 
 | ||||
| 	pub fn deinit(self: *Effect) void { | ||||
| 		sokol.gfx.destroyPipeline(self.pipeline); | ||||
| 		sokol.gfx.destroyShader(self.shader); | ||||
| 
 | ||||
| 		if (self.has_properties_buffer) |parameters_buffer| { | ||||
| 			sokol.gfx.destroyBuffer(parameters_buffer); | ||||
| 		} | ||||
| 
 | ||||
| 		self.* = undefined; | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn init(desc: handles.Effect.Desc) !Effect { | ||||
| 		var spirv_unit = try spirv.Unit.init(coral.heap.allocator); | ||||
| 
 | ||||
| 		defer spirv_unit.deinit(); | ||||
| 
 | ||||
| 		try spirv_unit.compile(&spirv.to_ops(@embedFile("./shaders/2d_default.vert.spv")), .vertex); | ||||
| 		try spirv_unit.compile(desc.fragment_spirv, .fragment); | ||||
| 
 | ||||
| 		const shader = sokol.gfx.makeShader(spirv_unit.shader_desc()); | ||||
| 
 | ||||
| 		const pipeline = sokol.gfx.makePipeline(pipeline_desc: { | ||||
| 			var pipeline_desc = sokol.gfx.PipelineDesc{ | ||||
| 				.label = "Effect pipeline", | ||||
| 				.layout = vertex_layout_state, | ||||
| 				.shader = shader, | ||||
| 				.index_type = .UINT16, | ||||
| 				.blend_color = .{.r = 1.0, .g = 1.0, .b = 1.0, .a = 1.0}, | ||||
| 			}; | ||||
| 
 | ||||
| 			pipeline_desc.colors[0] = .{ | ||||
| 				.write_mask = .RGBA, | ||||
| 
 | ||||
| 				.blend = .{ | ||||
| 					.enabled = true, | ||||
| 					.src_factor_rgb = .SRC_ALPHA, | ||||
| 					.dst_factor_rgb = .ONE_MINUS_SRC_ALPHA, | ||||
| 				}, | ||||
| 			}; | ||||
| 
 | ||||
| 			break: pipeline_desc pipeline_desc; | ||||
| 		}); | ||||
| 
 | ||||
| 		errdefer { | ||||
| 			sokol.gfx.destroyPipeline(pipeline); | ||||
| 			sokol.gfx.destroyShader(shader); | ||||
| 		} | ||||
| 
 | ||||
| 		if (spirv_unit.shader_stage(.fragment).has_storage_buffer[0]) |storage_buffer| { | ||||
| 			const properties_buffer = sokol.gfx.makeBuffer(.{ | ||||
| 				.size = storage_buffer.minimum_size, | ||||
| 				.type = .STORAGEBUFFER, | ||||
| 				.usage = .STREAM, | ||||
| 			}); | ||||
| 
 | ||||
| 			errdefer { | ||||
| 				sokol.gfx.destroyBuffer(properties_buffer); | ||||
| 			} | ||||
| 
 | ||||
| 			return .{ | ||||
| 				.shader = shader, | ||||
| 				.pipeline = pipeline, | ||||
| 				.has_properties_buffer = properties_buffer, | ||||
| 			}; | ||||
| 		} else { | ||||
| 			return .{ | ||||
| 				.shader = shader, | ||||
| 				.pipeline = pipeline, | ||||
| 			}; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	const vertex_layout_state = sokol.gfx.VertexLayoutState{ | ||||
| 		.attrs = get: { | ||||
| 			var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16; | ||||
| 
 | ||||
| 			attrs[0] = .{ | ||||
| 				.format = .FLOAT2, | ||||
| 				.buffer_index = 0, | ||||
| 			}; | ||||
| 
 | ||||
| 			attrs[1] = .{ | ||||
| 				.format = .FLOAT2, | ||||
| 				.buffer_index = 0, | ||||
| 			}; | ||||
| 
 | ||||
| 			attrs[2] = .{ | ||||
| 				.format = .FLOAT2, | ||||
| 				.buffer_index = 1, | ||||
| 			}; | ||||
| 
 | ||||
| 			attrs[3] = .{ | ||||
| 				.format = .FLOAT2, | ||||
| 				.buffer_index = 1, | ||||
| 			}; | ||||
| 
 | ||||
| 			attrs[4] = .{ | ||||
| 				.format = .FLOAT2, | ||||
| 				.buffer_index = 1, | ||||
| 			}; | ||||
| 
 | ||||
| 			attrs[5] = .{ | ||||
| 				.format = .UBYTE4N, | ||||
| 				.buffer_index = 1, | ||||
| 			}; | ||||
| 
 | ||||
| 			attrs[6] = .{ | ||||
| 				.format = .FLOAT, | ||||
| 				.buffer_index = 1, | ||||
| 			}; | ||||
| 
 | ||||
| 			attrs[7] = .{ | ||||
| 				.format = .FLOAT4, | ||||
| 				.buffer_index = 1, | ||||
| 			}; | ||||
| 
 | ||||
| 			break: get attrs; | ||||
| 		}, | ||||
| 
 | ||||
| 		.buffers = get: { | ||||
| 			var vertex_buffer_layouts = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8; | ||||
| 
 | ||||
| 			vertex_buffer_layouts[1].step_func = .PER_INSTANCE; | ||||
| 
 | ||||
| 			break: get vertex_buffer_layouts; | ||||
| 		}, | ||||
| 	}; | ||||
| }; | ||||
| 
 | ||||
| pub const Texture = union (enum) { | ||||
| 	render: Render, | ||||
| 	static: Static, | ||||
|  | ||||
| @ -7,10 +7,6 @@ layout (location = 1) in vec2 uv; | ||||
| 
 | ||||
| layout (location = 0) out vec4 texel; | ||||
| 
 | ||||
| layout (binding = 0) readonly buffer View { | ||||
| 	mat4 projection_matrix; | ||||
| }; | ||||
| 
 | ||||
| void main() { | ||||
| 	texel = texture(sprite, uv) * color; | ||||
| 
 | ||||
|  | ||||
| @ -15,22 +15,225 @@ pub const Error = std.mem.Allocator.Error || error { | ||||
| 	UnsupportedBackend, | ||||
| }; | ||||
| 
 | ||||
| pub const Unit = struct { | ||||
| 	arena: std.heap.ArenaAllocator, | ||||
| 	context: ext.spvc_context, | ||||
| 	shader_desc: sokol.gfx.ShaderDesc, | ||||
| 	attrs_used: u32 = 0, | ||||
| pub const Shader = struct { | ||||
| 	decompiled_source: [:0]const u8 = "", | ||||
| 	has_storage_buffer: [max_storage_buffers]?StorageBuffer = [_]?StorageBuffer{null} ** max_storage_buffers, | ||||
| 	has_image: [max_images]?Image = [_]?Image{null} ** max_images, | ||||
|     has_sampler: [max_samplers]?Sampler = [_]?Sampler{null} ** max_samplers, | ||||
|     has_image_sampler_pair: [max_image_sampler_pairs]?ImageSamplerPair = [_]?ImageSamplerPair{null} ** max_image_sampler_pairs, | ||||
| 
 | ||||
| 	pub fn compile(self: *Unit, spirv_data: []const u8, stage: Stage) Error!void { | ||||
| 		if ((spirv_data.len % @alignOf(u32)) != 0) { | ||||
| 	pub const Image = struct { | ||||
| 		is_multisampled: bool, | ||||
| 	}; | ||||
| 
 | ||||
| 	pub const ImageSamplerPair = struct { | ||||
| 		image_slot: usize, | ||||
| 		sampler_slot: usize, | ||||
| 		name: [:0]const u8, | ||||
| 	}; | ||||
| 
 | ||||
| 	pub const Sampler = enum { | ||||
| 		filtering, | ||||
| 		non_filtering, | ||||
| 		comparison, | ||||
| 	}; | ||||
| 
 | ||||
| 	pub const StorageBuffer = struct { | ||||
| 		minimum_size: usize, | ||||
| 		is_readonly: bool, | ||||
| 	}; | ||||
| 
 | ||||
| 	pub const max_images = 12; | ||||
| 
 | ||||
| 	pub const max_image_sampler_pairs = 12; | ||||
| 
 | ||||
| 	pub const max_samplers = 8; | ||||
| 
 | ||||
| 	pub const max_storage_buffers = 8; | ||||
| 
 | ||||
| 	fn reflect_image_samplers(self: *Shader, _: Stage, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void { | ||||
| 		var sampled_images: []const ext.spvc_reflected_resource = &.{}; | ||||
| 
 | ||||
| 		try to_error(ext.spvc_resources_get_resource_list_for_type( | ||||
| 			resources, | ||||
| 			ext.SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, | ||||
| 			@ptrCast(&sampled_images.ptr), | ||||
| 			&sampled_images.len, | ||||
| 		)); | ||||
| 
 | ||||
| 		if (sampled_images.len > max_image_sampler_pairs) { | ||||
| 			return error.UnsupportedSPIRV; | ||||
| 		} | ||||
| 
 | ||||
| 		for (0 .. sampled_images.len, sampled_images) |i, sampled_image| { | ||||
| 			const sampled_image_type = ext.spvc_compiler_get_type_handle(compiler, sampled_image.type_id); | ||||
| 
 | ||||
| 			if (ext.spvc_type_get_basetype(sampled_image_type) != ext.SPVC_BASETYPE_SAMPLED_IMAGE) { | ||||
| 				return error.InvalidSPIRV; | ||||
| 			} | ||||
| 
 | ||||
| 			try switch (ext.spvc_type_get_image_dimension(sampled_image_type)) { | ||||
| 				ext.SpvDim2D => {}, | ||||
| 				else => error.InvalidSPIRV, | ||||
| 			}; | ||||
| 
 | ||||
| 			try switch (ext.spvc_type_get_basetype(ext.spvc_compiler_get_type_handle(compiler, ext.spvc_type_get_image_sampled_type(sampled_image_type)))) { | ||||
| 				ext.SPVC_BASETYPE_FP32 => {}, | ||||
| 				else => error.InvalidSPIRV, | ||||
| 			}; | ||||
| 
 | ||||
| 			self.has_image[i] = .{ | ||||
| 				.is_multisampled = ext.spvc_type_get_image_multisampled(sampled_image_type) != 0, | ||||
| 			}; | ||||
| 
 | ||||
| 			self.has_sampler[i] = .filtering; | ||||
| 
 | ||||
| 			self.has_image_sampler_pair[i] = .{ | ||||
| 				.name = std.mem.span(ext.spvc_compiler_get_name(compiler, sampled_image.id)), | ||||
| 				.image_slot = @intCast(i), | ||||
| 				.sampler_slot = @intCast(i), | ||||
| 			}; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	fn reflect_storage_buffers(self: *Shader, stage: Stage, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void { | ||||
| 		var storage_buffers: []const ext.spvc_reflected_resource = &.{}; | ||||
| 
 | ||||
| 		try to_error(ext.spvc_resources_get_resource_list_for_type( | ||||
| 			resources, | ||||
| 			ext.SPVC_RESOURCE_TYPE_STORAGE_BUFFER, | ||||
| 			@ptrCast(&storage_buffers.ptr), | ||||
| 			&storage_buffers.len, | ||||
| 		)); | ||||
| 
 | ||||
| 		const binding_offset: c_uint = if (stage == .fragment) max_storage_buffers else 0; | ||||
| 
 | ||||
| 		for (storage_buffers) |storage_buffer| { | ||||
| 			const binding = ext.spvc_compiler_get_decoration(compiler, storage_buffer.id, ext.SpvDecorationBinding); | ||||
| 
 | ||||
| 			if (binding >= max_storage_buffers) { | ||||
| 				return error.InvalidSPIRV; | ||||
| 			} | ||||
| 
 | ||||
| 			ext.spvc_compiler_set_decoration(compiler, storage_buffer.id, ext.SpvDecorationBinding, binding_offset + binding); | ||||
| 
 | ||||
| 			var block_decorations: []const ext.SpvDecoration = &.{}; | ||||
| 
 | ||||
| 			try to_error(ext.spvc_compiler_get_buffer_block_decorations( | ||||
| 				compiler, | ||||
| 				storage_buffer.id, | ||||
| 				@ptrCast(&block_decorations.ptr), | ||||
| 				&block_decorations.len, | ||||
| 			)); | ||||
| 
 | ||||
| 			var minimum_size: usize = 0; | ||||
| 
 | ||||
| 			try to_error(ext.spvc_compiler_get_declared_struct_size(compiler, ext.spvc_compiler_get_type_handle(compiler, storage_buffer.base_type_id), &minimum_size)); | ||||
| 
 | ||||
| 			self.has_storage_buffer[binding] = .{ | ||||
| 				.is_readonly = std.mem.indexOfScalar(ext.SpvDecoration, block_decorations, ext.SpvDecorationNonWritable) != null, | ||||
| 				.minimum_size = minimum_size, | ||||
| 			}; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	fn reflect_uniform_buffers(self: *Shader, _: Stage, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void { | ||||
| 		var uniform_buffers: []const ext.spvc_reflected_resource = &.{}; | ||||
| 
 | ||||
| 		try to_error(ext.spvc_resources_get_resource_list_for_type( | ||||
| 			resources, | ||||
| 			ext.SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, | ||||
| 			@ptrCast(&uniform_buffers.ptr), | ||||
| 			&uniform_buffers.len, | ||||
| 		)); | ||||
| 
 | ||||
| 		if (uniform_buffers.len != 0) { | ||||
| 			return error.InvalidSPIRV; | ||||
| 		} | ||||
| 
 | ||||
| 		const spirv_ops: []const u32 = @alignCast(std.mem.bytesAsSlice(u32, spirv_data)); | ||||
| 		// TODO: Support for older APIs? | ||||
| 		_ = self; | ||||
| 		_ = compiler; | ||||
| 	} | ||||
| 
 | ||||
| 		const execution_model, const stage_desc = switch (stage) { | ||||
| 			.vertex => .{ext.SpvExecutionModelVertex, &self.shader_desc.vs}, | ||||
| 			.fragment => .{ext.SpvExecutionModelFragment, &self.shader_desc.fs}, | ||||
| 	pub fn stage_desc(self: Shader) sokol.gfx.ShaderStageDesc { | ||||
| 		var stage = sokol.gfx.ShaderStageDesc{ | ||||
| 			.entry = "main", | ||||
| 			.source = self.decompiled_source, | ||||
| 		}; | ||||
| 
 | ||||
| 		for (0 .. max_storage_buffers) |slot| { | ||||
| 			const storage_buffer = &(self.has_storage_buffer[slot] orelse { | ||||
| 				continue; | ||||
| 			}); | ||||
| 
 | ||||
| 			stage.storage_buffers[slot] = .{ | ||||
| 				.readonly = storage_buffer.is_readonly, | ||||
| 				.used = true, | ||||
| 			}; | ||||
| 		} | ||||
| 
 | ||||
| 		for (0 .. max_images) |slot| { | ||||
| 			const image = &(self.has_image[slot] orelse { | ||||
| 				continue; | ||||
| 			}); | ||||
| 
 | ||||
| 			stage.images[slot] = .{ | ||||
| 				.multisampled = image.is_multisampled, | ||||
| 				.image_type = ._2D, | ||||
| 				.sample_type = .FLOAT, | ||||
| 				.used = true, | ||||
| 			}; | ||||
| 		} | ||||
| 
 | ||||
| 		for (0 .. max_samplers) |slot| { | ||||
| 			const sampler = &(self.has_sampler[slot] orelse { | ||||
| 				continue; | ||||
| 			}); | ||||
| 
 | ||||
| 			stage.samplers[slot] = .{ | ||||
| 				.sampler_type = switch (sampler.*) { | ||||
| 					.filtering => .FILTERING, | ||||
| 					.non_filtering => .NONFILTERING, | ||||
| 					.comparison => .COMPARISON, | ||||
| 				}, | ||||
| 
 | ||||
| 				.used = true, | ||||
| 			}; | ||||
| 		} | ||||
| 
 | ||||
| 		for (0 .. max_image_sampler_pairs) |slot| { | ||||
| 			const image_sampler_pair = &(self.has_image_sampler_pair[slot] orelse { | ||||
| 				continue; | ||||
| 			}); | ||||
| 
 | ||||
| 			stage.image_sampler_pairs[slot] = .{ | ||||
| 				.glsl_name = image_sampler_pair.name, | ||||
| 				.image_slot = @intCast(image_sampler_pair.image_slot), | ||||
| 				.sampler_slot = @intCast(image_sampler_pair.sampler_slot), | ||||
| 				.used = true, | ||||
| 			}; | ||||
| 		} | ||||
| 
 | ||||
| 		return stage; | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| pub const Stage = enum { | ||||
| 	vertex, | ||||
| 	fragment, | ||||
| }; | ||||
| 
 | ||||
| pub const Unit = struct { | ||||
| 	arena: std.heap.ArenaAllocator, | ||||
| 	context: ext.spvc_context, | ||||
| 	attrs_used: u32 = 0, | ||||
| 	stages: [std.enums.values(Stage).len]Shader = .{.{}, .{}}, | ||||
| 
 | ||||
| 	pub fn compile(self: *Unit, spirv_ops: []const u32, stage: Stage) Error!void { | ||||
| 		const execution_model, const shader = switch (stage) { | ||||
| 			.vertex => .{ext.SpvExecutionModelVertex, &self.stages[0]}, | ||||
| 			.fragment => .{ext.SpvExecutionModelFragment, &self.stages[1]}, | ||||
| 		}; | ||||
| 
 | ||||
| 		const Backend = struct { | ||||
| @ -73,8 +276,8 @@ pub const Unit = struct { | ||||
| 
 | ||||
| 			for (combined_image_samplers) |combined_image_sampler| { | ||||
| 				const name = try coral.utf8.alloc_formatted(arena_allocator, "{image_name}_{sampler_name}", .{ | ||||
| 					.image_name = std.mem.span(@as([*:0]const u8, ext.spvc_compiler_get_name(compiler, combined_image_sampler.image_id))), | ||||
| 					.sampler_name = std.mem.span(@as([*:0]const u8, ext.spvc_compiler_get_name(compiler, combined_image_sampler.sampler_id))), | ||||
| 					.image_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.image_id)), | ||||
| 					.sampler_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.sampler_id)), | ||||
| 				}); | ||||
| 
 | ||||
| 				ext.spvc_compiler_set_name(compiler, combined_image_sampler.combined_id, name); | ||||
| @ -86,7 +289,7 @@ pub const Unit = struct { | ||||
| 			break: parse_and_configure compiler; | ||||
| 		}; | ||||
| 
 | ||||
| 		try to_error(ext.spvc_compiler_set_entry_point(compiler, stage_desc.entry, @intCast(execution_model))); | ||||
| 		try to_error(ext.spvc_compiler_set_entry_point(compiler, "main", @intCast(execution_model))); | ||||
| 
 | ||||
| 		const resources = create: { | ||||
| 			var resources: ext.spvc_resources = null; | ||||
| @ -96,27 +299,29 @@ pub const Unit = struct { | ||||
| 			break: create resources; | ||||
| 		}; | ||||
| 
 | ||||
| 		try reflect_uniform_buffers(compiler, resources, stage_desc); | ||||
| 		try reflect_storage_buffers(compiler, resources, stage_desc); | ||||
| 		try reflect_image_samplers(compiler, resources, stage_desc); | ||||
| 		try shader.reflect_uniform_buffers(stage, compiler, resources); | ||||
| 		try shader.reflect_storage_buffers(stage, compiler, resources); | ||||
| 		try shader.reflect_image_samplers(stage, compiler, resources); | ||||
| 
 | ||||
| 		try to_error(ext.spvc_compiler_install_compiler_options(compiler, create: { | ||||
| 			var options: ext.spvc_compiler_options = null; | ||||
| 			var compiler_options: ext.spvc_compiler_options = null; | ||||
| 
 | ||||
| 			try to_error(ext.spvc_compiler_create_compiler_options(compiler, &options)); | ||||
| 			try to_error(ext.spvc_compiler_create_compiler_options(compiler, &compiler_options)); | ||||
| 
 | ||||
| 			for (backend.option_values) |option_value| { | ||||
| 				const entry, const value = option_value; | ||||
| 
 | ||||
| 				try to_error(ext.spvc_compiler_options_set_uint(options, entry, value)); | ||||
| 				try to_error(ext.spvc_compiler_options_set_uint(compiler_options, entry, value)); | ||||
| 			} | ||||
| 
 | ||||
| 			break: create options; | ||||
| 			break: create compiler_options; | ||||
| 		})); | ||||
| 
 | ||||
| 		try to_error(ext.spvc_compiler_compile(compiler, @ptrCast(&stage_desc.source))); | ||||
| 		try to_error(ext.spvc_compiler_compile(compiler, @ptrCast(&shader.decompiled_source.ptr))); | ||||
| 
 | ||||
| 		std.log.info("{s}", .{stage_desc.source}); | ||||
| 		shader.decompiled_source.len = std.mem.span(shader.decompiled_source.ptr).len; | ||||
| 
 | ||||
| 		std.log.info("{s}", .{shader.decompiled_source}); | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn deinit(self: *Unit) void { | ||||
| @ -140,18 +345,19 @@ pub const Unit = struct { | ||||
| 		return .{ | ||||
| 			.arena = std.heap.ArenaAllocator.init(allocator), | ||||
| 			.context = context, | ||||
| 
 | ||||
| 			.shader_desc = .{ | ||||
| 				.vs = .{.entry = "main"}, | ||||
| 				.fs = .{.entry = "main"}, | ||||
| 			}, | ||||
| 		}; | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| pub const Stage = enum { | ||||
| 	fragment, | ||||
| 	vertex, | ||||
| 	pub fn shader_desc(self: Unit) sokol.gfx.ShaderDesc { | ||||
| 		return .{ | ||||
| 			.vs = self.shader_stage(.vertex).stage_desc(), | ||||
| 			.fs = self.shader_stage(.fragment).stage_desc(), | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn shader_stage(self: *const Unit, stage: Stage) *const Shader { | ||||
| 		return &self.stages[@intFromEnum(stage)]; | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| fn log_context_errors(userdata: ?*anyopaque, error_message: [*c]const u8) callconv(.C) void { | ||||
| @ -159,109 +365,6 @@ fn log_context_errors(userdata: ?*anyopaque, error_message: [*c]const u8) callco | ||||
| 	std.log.err("{s}", .{error_message}); | ||||
| } | ||||
| 
 | ||||
| fn reflect_image_samplers(compiler: ext.spvc_compiler, resources: ext.spvc_resources, stage_desc: *sokol.gfx.ShaderStageDesc) Error!void { | ||||
| 	var sampled_images: []const ext.spvc_reflected_resource = &.{}; | ||||
| 
 | ||||
| 	try to_error(ext.spvc_resources_get_resource_list_for_type( | ||||
| 		resources, | ||||
| 		ext.SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, | ||||
| 		@ptrCast(&sampled_images.ptr), | ||||
| 		&sampled_images.len, | ||||
| 	)); | ||||
| 
 | ||||
| 	if (sampled_images.len > stage_desc.image_sampler_pairs.len) { | ||||
| 		return error.UnsupportedSPIRV; | ||||
| 	} | ||||
| 
 | ||||
| 	for (0 .. sampled_images.len, sampled_images) |i, sampled_image| { | ||||
| 		const sampled_image_type = ext.spvc_compiler_get_type_handle(compiler, sampled_image.type_id); | ||||
| 
 | ||||
| 		if (ext.spvc_type_get_basetype(sampled_image_type) != ext.SPVC_BASETYPE_SAMPLED_IMAGE) { | ||||
| 			return error.InvalidSPIRV; | ||||
| 		} | ||||
| 
 | ||||
| 		stage_desc.images[i] = .{ | ||||
| 			.multisampled = ext.spvc_type_get_image_multisampled(sampled_image_type) != 0, | ||||
| 
 | ||||
| 			.image_type = try switch (ext.spvc_type_get_image_dimension(sampled_image_type)) { | ||||
| 				ext.SpvDim2D => sokol.gfx.ImageType._2D, | ||||
| 				else => error.InvalidSPIRV, | ||||
| 			}, | ||||
| 
 | ||||
| 			.sample_type = try switch (ext.spvc_type_get_basetype(ext.spvc_compiler_get_type_handle(compiler, ext.spvc_type_get_image_sampled_type(sampled_image_type)))) { | ||||
| 				ext.SPVC_BASETYPE_FP32 => sokol.gfx.ImageSampleType.FLOAT, | ||||
| 				else => error.InvalidSPIRV, | ||||
| 			}, | ||||
| 
 | ||||
| 			.used = true, | ||||
| 		}; | ||||
| 
 | ||||
| 		stage_desc.samplers[i] = .{ | ||||
| 			.sampler_type = .DEFAULT, | ||||
| 			.used = true, | ||||
| 		}; | ||||
| 
 | ||||
| 		stage_desc.image_sampler_pairs[i] = .{ | ||||
| 			.glsl_name = ext.spvc_compiler_get_name(compiler, sampled_image.id), | ||||
| 			.image_slot = @intCast(i), | ||||
| 			.sampler_slot = @intCast(i), | ||||
| 			.used = true, | ||||
| 		}; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| fn reflect_storage_buffers(compiler: ext.spvc_compiler, resources: ext.spvc_resources, stage_desc: *sokol.gfx.ShaderStageDesc) Error!void { | ||||
| 	var storage_buffers: []const ext.spvc_reflected_resource = &.{}; | ||||
| 
 | ||||
| 	try to_error(ext.spvc_resources_get_resource_list_for_type( | ||||
| 		resources, | ||||
| 		ext.SPVC_RESOURCE_TYPE_STORAGE_BUFFER, | ||||
| 		@ptrCast(&storage_buffers.ptr), | ||||
| 		&storage_buffers.len, | ||||
| 	)); | ||||
| 
 | ||||
| 	for (storage_buffers) |storage_buffer| { | ||||
| 		const binding = ext.spvc_compiler_get_decoration(compiler, storage_buffer.id, ext.SpvDecorationBinding); | ||||
| 
 | ||||
| 		if (binding >= stage_desc.storage_buffers.len) { | ||||
| 			return error.InvalidSPIRV; | ||||
| 		} | ||||
| 
 | ||||
| 		var block_decorations: []const ext.SpvDecoration = &.{}; | ||||
| 
 | ||||
| 		try to_error(ext.spvc_compiler_get_buffer_block_decorations( | ||||
| 			compiler, | ||||
| 			storage_buffer.id, | ||||
| 			@ptrCast(&block_decorations.ptr), | ||||
| 			&block_decorations.len, | ||||
| 		)); | ||||
| 
 | ||||
| 		stage_desc.storage_buffers[binding] = .{ | ||||
| 			.readonly = std.mem.indexOfScalar(ext.SpvDecoration, block_decorations, ext.SpvDecorationNonWritable) != null, | ||||
| 			.used = true, | ||||
| 		}; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| fn reflect_uniform_buffers(compiler: ext.spvc_compiler, resources: ext.spvc_resources, stage_desc: *sokol.gfx.ShaderStageDesc) Error!void { | ||||
| 	var uniform_buffers: []const ext.spvc_reflected_resource = &.{}; | ||||
| 
 | ||||
| 	try to_error(ext.spvc_resources_get_resource_list_for_type( | ||||
| 		resources, | ||||
| 		ext.SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, | ||||
| 		@ptrCast(&uniform_buffers.ptr), | ||||
| 		&uniform_buffers.len, | ||||
| 	)); | ||||
| 
 | ||||
| 	if (uniform_buffers.len != 0) { | ||||
| 		return error.InvalidSPIRV; | ||||
| 	} | ||||
| 
 | ||||
| 	// TODO: Support for older APIs? | ||||
| 	_ = stage_desc; | ||||
| 	_ = compiler; | ||||
| } | ||||
| 
 | ||||
| fn to_error(result: ext.spvc_result) Error!void { | ||||
| 	return switch (result) { | ||||
| 		ext.SPVC_SUCCESS => {}, | ||||
| @ -272,3 +375,11 @@ fn to_error(result: ext.spvc_result) Error!void { | ||||
| 		else => unreachable, | ||||
| 	}; | ||||
| } | ||||
| 
 | ||||
| pub fn to_ops(raw: anytype) [raw.len / @alignOf(u32)]u32 { | ||||
| 	var ops: [raw.len / @alignOf(u32)]u32 = undefined; | ||||
| 
 | ||||
| 	@memcpy(std.mem.sliceAsBytes(&ops), raw); | ||||
| 
 | ||||
| 	return ops; | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user