Implement CRT shader effect support
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is passing
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			This commit is contained in:
		
							parent
							
								
									56342d9d8e
								
							
						
					
					
						commit
						67dee07f8e
					
				
							
								
								
									
										52
									
								
								debug/crt.frag
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								debug/crt.frag
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | ||||
| #version 430 | ||||
| 
 | ||||
| layout (binding = 0) uniform sampler2D sprite; | ||||
| 
 | ||||
| layout (location = 0) in vec4 color; | ||||
| layout (location = 1) in vec2 uv; | ||||
| 
 | ||||
| layout (location = 0) out vec4 texel; | ||||
| 
 | ||||
| layout (binding = 0) uniform Effect { | ||||
| 	float screen_width; | ||||
| 	float screen_height; | ||||
| 	float time; | ||||
| }; | ||||
| 
 | ||||
| vec3 scanline(vec2 coord, vec3 screen) | ||||
| { | ||||
| 	screen.rgb -= sin((coord.y + (time * 29.0))) * 0.02; | ||||
| 	return screen; | ||||
| } | ||||
| 
 | ||||
| vec2 crt(vec2 coord, float bend) | ||||
| { | ||||
| 	// put in symmetrical coords | ||||
| 	coord = (coord - 0.5) * 2.0; | ||||
| 
 | ||||
| 	coord *= 1.0; | ||||
| 
 | ||||
| 	// deform coords | ||||
| 	coord.x *= 1.0 + pow((abs(coord.y) / bend), 2.0); | ||||
| 	coord.y *= 1.0 + pow((abs(coord.x) / bend), 2.0); | ||||
| 
 | ||||
| 	// transform back to 0.0 - 1.0 space | ||||
| 	coord  = (coord / 2.0) + 0.5; | ||||
| 
 | ||||
| 	return coord; | ||||
| } | ||||
| 
 | ||||
| void main() | ||||
| { | ||||
| 	vec2 crtCoords = crt(uv, 4.8); | ||||
| 
 | ||||
| 	// Split the color channels | ||||
| 	texel.rgb = texture(sprite, crtCoords).rgb; | ||||
| 	texel.a = 1; | ||||
| 
 | ||||
| 	// HACK: this bend produces a shitty moire pattern. | ||||
| 	// Up the bend for the scanline | ||||
| 	vec2 screenSpace = crtCoords * vec2(screen_width, screen_height); | ||||
| 	texel.rgb = scanline(screenSpace, texel.rgb); | ||||
| } | ||||
| 
 | ||||
							
								
								
									
										60
									
								
								src/main.zig
									
									
									
									
									
								
							
							
						
						
									
										60
									
								
								src/main.zig
									
									
									
									
									
								
							| @ -9,11 +9,20 @@ const ChromaticAberration = extern struct { | ||||
| 	padding: [12]u8 = undefined, | ||||
| }; | ||||
| 
 | ||||
| const CRT = extern struct { | ||||
| 	width: f32, | ||||
| 	height: f32, | ||||
| 	time: f32, | ||||
| 	padding: [4]u8 = undefined, | ||||
| }; | ||||
| 
 | ||||
| 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, | ||||
| 	crt_effect: ona.gfx.Effect = .default, | ||||
| 	staging_texture: ona.gfx.Texture = .default, | ||||
| }; | ||||
| 
 | ||||
| const Player = struct { | ||||
| @ -44,6 +53,7 @@ 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"); | ||||
| 	actors.res.crt_effect = try assets.res.load_effect_file(coral.files.bundle, "./crt.frag.spv"); | ||||
| 
 | ||||
| 	try actors.res.instances.push_grow(.{0, 0}); | ||||
| } | ||||
| @ -52,22 +62,46 @@ fn exit(actors: ona.Write(Actors)) void { | ||||
| 	actors.res.instances.deinit(); | ||||
| } | ||||
| 
 | ||||
| fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors)) !void { | ||||
| 	try commands.set_effect(actors.res.ca_effect, ChromaticAberration{ | ||||
| 		.effect_magnitude = 15.0, | ||||
| fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors), app: ona.Read(ona.App)) !void { | ||||
| 	try commands.set_target(.{ | ||||
| 		.texture = actors.res.render_texture, | ||||
| 		.clear_color = ona.gfx.colors.black, | ||||
| 		.clear_depth = 0, | ||||
| 		.clear_stencil = 0, | ||||
| 	}); | ||||
| 
 | ||||
| 	for (actors.res.instances.values) |instance| { | ||||
| 		try commands.draw_texture(.{ | ||||
| 			.texture = actors.res.body_texture, | ||||
| 	try commands.draw_texture(.{ | ||||
| 		.texture = .default, | ||||
| 
 | ||||
| 			.transform = .{ | ||||
| 				.origin = instance, | ||||
| 				.xbasis = .{64, 0}, | ||||
| 				.ybasis = .{0, 64}, | ||||
| 			}, | ||||
| 		}); | ||||
| 	} | ||||
| 		.transform = .{ | ||||
| 			.origin = .{1280 / 2, 720 / 2}, | ||||
| 			.xbasis = .{1280, 0}, | ||||
| 			.ybasis = .{0, 720}, | ||||
| 		}, | ||||
| 	}); | ||||
| 
 | ||||
| 	try commands.set_effect(actors.res.crt_effect, CRT{ | ||||
| 		.width = 1280, | ||||
| 		.height = 720, | ||||
| 		.time = @floatCast(app.res.elapsed_time), | ||||
| 	}); | ||||
| 
 | ||||
| 	try commands.set_target(.{ | ||||
| 		.texture = .backbuffer, | ||||
| 		.clear_color = null, | ||||
| 		.clear_depth = null, | ||||
| 		.clear_stencil = null, | ||||
| 	}); | ||||
| 
 | ||||
| 	try commands.draw_texture(.{ | ||||
| 		.texture = actors.res.render_texture, | ||||
| 
 | ||||
| 		.transform = .{ | ||||
| 			.origin = .{1280 / 2, 720 / 2}, | ||||
| 			.xbasis = .{1280, 0}, | ||||
| 			.ybasis = .{0, 720}, | ||||
| 		}, | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| fn update(player: ona.Read(Player), actors: ona.Write(Actors), mapping: ona.Read(ona.act.Mapping)) !void { | ||||
|  | ||||
| @ -4,6 +4,7 @@ const flow = @import("flow"); | ||||
| 
 | ||||
| events: *const Events, | ||||
| target_frame_time: f64, | ||||
| elapsed_time: f64, | ||||
| is_running: bool, | ||||
| 
 | ||||
| pub const Events = struct { | ||||
|  | ||||
| @ -17,33 +17,40 @@ fn Handle(comptime HandleDesc: type) type { | ||||
| 	}; | ||||
| } | ||||
| 
 | ||||
| pub const Texture = Handle(struct { | ||||
| 	format: Format, | ||||
| 	access: Access, | ||||
| pub const Texture = enum (u32) { | ||||
| 	default, | ||||
| 	backbuffer, | ||||
| 	_, | ||||
| 
 | ||||
| 	pub const Access = union (enum) { | ||||
| 		static: Static, | ||||
| 		render: Render, | ||||
| 	pub const Desc = struct { | ||||
| 		format: Format, | ||||
| 		access: Access, | ||||
| 
 | ||||
| 		pub const Static = struct { | ||||
| 			width: u16, | ||||
| 			data: []const coral.io.Byte, | ||||
| 		}; | ||||
| 		pub const Access = union (enum) { | ||||
| 			static: Static, | ||||
| 			render: Render, | ||||
| 
 | ||||
| 		pub const Render = struct { | ||||
| 			width: u16, | ||||
| 			height: u16, | ||||
| 		}; | ||||
| 	}; | ||||
| 
 | ||||
| 	pub const Format = enum { | ||||
| 		rgba8, | ||||
| 		bgra8, | ||||
| 
 | ||||
| 		pub fn byte_size(self: Format) usize { | ||||
| 			return switch (self) { | ||||
| 				.rgba8, .bgra8 => 4, | ||||
| 			pub const Static = struct { | ||||
| 				width: u16, | ||||
| 				data: []const coral.io.Byte, | ||||
| 			}; | ||||
| 		} | ||||
| 
 | ||||
| 			pub const Render = struct { | ||||
| 				width: u16, | ||||
| 				height: u16, | ||||
| 			}; | ||||
| 		}; | ||||
| 
 | ||||
| 		pub const Format = enum { | ||||
| 			rgba8, | ||||
| 			bgra8, | ||||
| 
 | ||||
| 			pub fn byte_size(self: Format) usize { | ||||
| 				return switch (self) { | ||||
| 					.rgba8, .bgra8 => 4, | ||||
| 				}; | ||||
| 			} | ||||
| 		}; | ||||
| 	}; | ||||
| }); | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -30,7 +30,7 @@ pub const Command = union (enum) { | ||||
| 	}; | ||||
| 
 | ||||
| 	pub const SetTarget = struct { | ||||
| 		texture: ?handles.Texture = null, | ||||
| 		texture: handles.Texture, | ||||
| 		clear_color: ?lina.Color, | ||||
| 		clear_depth: ?f32, | ||||
| 		clear_stencil: ?u8, | ||||
| @ -135,7 +135,7 @@ const Frame = struct { | ||||
| 	drawn_count: usize = 0, | ||||
| 	flushed_count: usize = 0, | ||||
| 	current_source_texture: handles.Texture = .default, | ||||
| 	current_target_texture: ?handles.Texture = null, | ||||
| 	current_target_texture: handles.Texture = .backbuffer, | ||||
| 	current_effect: handles.Effect = .default, | ||||
| 
 | ||||
| 	const DrawTexture = extern struct { | ||||
| @ -185,42 +185,39 @@ const Frame = struct { | ||||
| 
 | ||||
| 		bindings.vertex_buffers[vertex_indices.mesh] = quad_vertex_buffer; | ||||
| 
 | ||||
| 		switch (pools.textures.get(@intFromEnum(self.current_source_texture)).?.access) { | ||||
| 		switch (pools.get_texture(self.current_source_texture).?.access) { | ||||
| 			.render => |render| { | ||||
| 				bindings.fs.images[0] = render.color_image; | ||||
| 				bindings.fs.samplers[0] = render.sampler; | ||||
| 				bindings.fs.samplers[0] = default_sampler; | ||||
| 			}, | ||||
| 
 | ||||
| 			.static => |static| { | ||||
| 				bindings.fs.images[0] = static.image; | ||||
| 				bindings.fs.samplers[0] = static.sampler; | ||||
| 				bindings.fs.samplers[0] = default_sampler; | ||||
| 			}, | ||||
| 
 | ||||
| 			.empty => { | ||||
| 				@panic("Cannot render empty textures"); | ||||
| 			}, | ||||
| 		} | ||||
| 
 | ||||
| 		const effect = pools.effects.get(@intFromEnum(self.current_effect)).?; | ||||
| 		const effect = pools.get_effect(self.current_effect).?; | ||||
| 
 | ||||
| 		sokol.gfx.applyPipeline(effect.pipeline); | ||||
| 
 | ||||
| 		if (self.current_target_texture) |target_texture| { | ||||
| 			const texture = pools.textures.get(@intFromEnum(target_texture)).?; | ||||
| 		const texture = pools.get_texture(self.current_target_texture).?; | ||||
| 
 | ||||
| 			sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{ | ||||
| 				.left = 0, | ||||
| 				.top = 0, | ||||
| 				.right = @floatFromInt(texture.width), | ||||
| 				.bottom = @floatFromInt(texture.height), | ||||
| 			}))); | ||||
| 		} else { | ||||
| 			sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{ | ||||
| 				.left = 0, | ||||
| 				.top = 0, | ||||
| 				.right = @floatFromInt(self.swapchain.width), | ||||
| 				.bottom = @floatFromInt(self.swapchain.height), | ||||
| 			}))); | ||||
| 		sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{ | ||||
| 			.left = 0, | ||||
| 			.top = 0, | ||||
| 			.right = @floatFromInt(texture.width), | ||||
| 			.bottom = @floatFromInt(texture.height), | ||||
| 		}))); | ||||
| 
 | ||||
| 		if (effect.properties.len != 0) { | ||||
| 			sokol.gfx.applyUniforms(.FS, 0, sokol.gfx.asRange(effect.properties)); | ||||
| 		} | ||||
| 
 | ||||
| 		sokol.gfx.applyUniforms(.FS, 0, sokol.gfx.asRange(effect.properties)); | ||||
| 
 | ||||
| 		while (true) { | ||||
| 			const buffer_index = self.flushed_count / batches_per_buffer; | ||||
| 			const buffer_offset = self.flushed_count % batches_per_buffer; | ||||
| @ -246,7 +243,7 @@ const Frame = struct { | ||||
| 
 | ||||
| 		self.current_effect = command.effect; | ||||
| 
 | ||||
| 		if (pools.effects.get(@intFromEnum(self.current_effect))) |effect| { | ||||
| 		if (pools.get_effect(self.current_effect)) |effect| { | ||||
| 			@memcpy(effect.properties, command.properties); | ||||
| 		} | ||||
| 	} | ||||
| @ -280,17 +277,13 @@ const Frame = struct { | ||||
| 			pass.action.depth = .{.load_action = .LOAD}; | ||||
| 		} | ||||
| 
 | ||||
| 		if (command.texture) |texture| { | ||||
| 			pass.attachments = switch (pools.textures.get(@intFromEnum(texture)).?.access) { | ||||
| 				.static => @panic("Cannot render to static textures"), | ||||
| 				.render => |render| render.attachments, | ||||
| 			}; | ||||
| 		pass.attachments = switch (pools.get_texture(self.current_target_texture).?.access) { | ||||
| 			.static => @panic("Cannot render to static textures"), | ||||
| 			.empty => @panic("Cannot render to empty textures"), | ||||
| 			.render => |render| render.attachments, | ||||
| 		}; | ||||
| 
 | ||||
| 			self.current_target_texture = command.texture; | ||||
| 		} else { | ||||
| 			pass.swapchain = self.swapchain; | ||||
| 			self.current_target_texture = null; | ||||
| 		} | ||||
| 		self.current_target_texture = command.texture; | ||||
| 
 | ||||
| 		sokol.gfx.beginPass(pass); | ||||
| 	} | ||||
| @ -313,7 +306,7 @@ const Pools = struct { | ||||
| 
 | ||||
| 	const TexturePool = coral.Pool(resources.Texture); | ||||
| 
 | ||||
| 	fn create_effect(self: *Pools, desc: handles.Effect.Desc) !handles.Effect { | ||||
| 	pub fn create_effect(self: *Pools, desc: handles.Effect.Desc) !handles.Effect { | ||||
| 		var effect = try resources.Effect.init(desc); | ||||
| 
 | ||||
| 		errdefer effect.deinit(); | ||||
| @ -321,7 +314,7 @@ const Pools = struct { | ||||
| 		return @enumFromInt(try self.effects.insert(effect)); | ||||
| 	} | ||||
| 
 | ||||
| 	fn create_texture(self: *Pools, desc: handles.Texture.Desc) !handles.Texture { | ||||
| 	pub fn create_texture(self: *Pools, desc: handles.Texture.Desc) !handles.Texture { | ||||
| 		var texture = try resources.Texture.init(desc); | ||||
| 
 | ||||
| 		errdefer texture.deinit(); | ||||
| @ -329,7 +322,7 @@ const Pools = struct { | ||||
| 		return @enumFromInt(try self.textures.insert(texture)); | ||||
| 	} | ||||
| 
 | ||||
| 	fn deinit(self: *Pools) void { | ||||
| 	pub fn deinit(self: *Pools) void { | ||||
| 		var textures = self.textures.values(); | ||||
| 
 | ||||
| 		while (textures.next()) |texture| { | ||||
| @ -339,7 +332,7 @@ const Pools = struct { | ||||
| 		self.textures.deinit(); | ||||
| 	} | ||||
| 
 | ||||
| 	fn destroy_effect(self: *Pools, handle: handles.Effect) bool { | ||||
| 	pub fn destroy_effect(self: *Pools, handle: handles.Effect) bool { | ||||
| 		switch (handle) { | ||||
| 			.default => {}, | ||||
| 
 | ||||
| @ -355,7 +348,7 @@ const Pools = struct { | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	fn destroy_texture(self: *Pools, handle: handles.Texture) bool { | ||||
| 	pub fn destroy_texture(self: *Pools, handle: handles.Texture) bool { | ||||
| 		switch (handle) { | ||||
| 			.default => {}, | ||||
| 
 | ||||
| @ -371,7 +364,15 @@ const Pools = struct { | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	fn init(allocator: std.mem.Allocator) !Pools { | ||||
| 	pub fn get_effect(self: *Pools, handle: handles.Effect) ?*resources.Effect { | ||||
| 		return self.effects.get(@intFromEnum(handle)); | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn get_texture(self: *Pools, handle: handles.Texture) ?*resources.Texture { | ||||
| 		return self.textures.get(@intFromEnum(handle)); | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn init(allocator: std.mem.Allocator) !Pools { | ||||
| 		var pools = Pools{ | ||||
| 			.effects = EffectPool.init(allocator), | ||||
| 			.textures = TexturePool.init(allocator), | ||||
| @ -381,31 +382,47 @@ const Pools = struct { | ||||
| 			pools.deinit(); | ||||
| 		} | ||||
| 
 | ||||
| 		_ = try pools.create_effect(.{ | ||||
| 			.fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")), | ||||
| 		}); | ||||
| 
 | ||||
| 		const default_texture_data = [_]u32{ | ||||
| 			0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, | ||||
| 			0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, | ||||
| 			0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, | ||||
| 			0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, | ||||
| 			0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, | ||||
| 			0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, | ||||
| 			0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, | ||||
| 			0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, 0x800080FF, 0x000000FF, | ||||
| 		const assert = struct { | ||||
| 			fn is_handle(expected: anytype, actual: @TypeOf(expected)) void { | ||||
| 				std.debug.assert(actual == expected); | ||||
| 			} | ||||
| 		}; | ||||
| 
 | ||||
| 		_ = try pools.create_texture(.{ | ||||
| 		assert.is_handle(handles.Effect.default, try pools.create_effect(.{ | ||||
| 			.fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")), | ||||
| 		})); | ||||
| 
 | ||||
| 		assert.is_handle(handles.Texture.default, try pools.create_texture(.{ | ||||
| 			.format = .rgba8, | ||||
| 
 | ||||
| 			.access = .{ | ||||
| 				.static = .{ | ||||
| 					.data = std.mem.asBytes(&default_texture_data), | ||||
| 					.data = std.mem.asBytes(&[_]u32{ | ||||
| 						0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, | ||||
| 						0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, | ||||
| 						0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, | ||||
| 						0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, | ||||
| 						0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, | ||||
| 						0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, | ||||
| 						0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, | ||||
| 						0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, | ||||
| 					}), | ||||
| 
 | ||||
| 					.width = 8, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}); | ||||
| 		})); | ||||
| 
 | ||||
| 		assert.is_handle(handles.Texture.backbuffer, try pools.create_texture(.{ | ||||
| 			.format = .rgba8, | ||||
| 
 | ||||
| 			.access = .{ | ||||
| 				.render = .{ | ||||
| 					.width = 0, | ||||
| 					.height = 0, | ||||
| 				}, | ||||
| 			} | ||||
| 		})); | ||||
| 
 | ||||
| 		return pools; | ||||
| 	} | ||||
| @ -457,6 +474,8 @@ pub const Work = union (enum) { | ||||
| 	var pending: coral.asyncio.BlockingQueue(1024, Work) = .{}; | ||||
| }; | ||||
| 
 | ||||
| var default_sampler: sokol.gfx.Sampler = undefined; | ||||
| 
 | ||||
| pub fn enqueue_work(work: Work) void { | ||||
| 	Work.pending.enqueue(work); | ||||
| } | ||||
| @ -541,6 +560,8 @@ fn run(window: *ext.SDL_Window) !void { | ||||
| 		.type = .VERTEXBUFFER, | ||||
| 	}); | ||||
| 
 | ||||
| 	default_sampler = sokol.gfx.makeSampler(.{}); | ||||
| 
 | ||||
| 	var has_commands_head: ?*Commands = null; | ||||
| 	var has_commands_tail: ?*Commands = null; | ||||
| 
 | ||||
| @ -588,6 +609,23 @@ fn run(window: *ext.SDL_Window) !void { | ||||
| 			}, | ||||
| 
 | ||||
| 			.render_frame => |render_frame| { | ||||
| 				const backbuffer = pools.get_texture(.backbuffer).?; | ||||
| 
 | ||||
| 				if (backbuffer.width != render_frame.width or backbuffer.height != render_frame.height) { | ||||
| 					backbuffer.deinit(); | ||||
| 
 | ||||
| 					backbuffer.* = try resources.Texture.init(.{ | ||||
| 						.format = .rgba8, | ||||
| 
 | ||||
| 						.access = .{ | ||||
| 							.render = .{ | ||||
| 								.width = render_frame.width, | ||||
| 								.height = render_frame.height, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}); | ||||
| 				} | ||||
| 
 | ||||
| 				var frame = Frame{ | ||||
| 					.swapchain = .{ | ||||
| 						.width = render_frame.width, | ||||
| @ -599,6 +637,51 @@ fn run(window: *ext.SDL_Window) !void { | ||||
| 					} | ||||
| 				}; | ||||
| 
 | ||||
| 				sokol.gfx.beginPass(pass: { | ||||
| 					var pass = sokol.gfx.Pass{ | ||||
| 						.action = .{ | ||||
| 							.stencil = .{ | ||||
| 								.load_action = .CLEAR, | ||||
| 							}, | ||||
| 
 | ||||
| 							.depth = .{ | ||||
| 								.load_action = .CLEAR, | ||||
| 								.clear_value = 0, | ||||
| 							} | ||||
| 						}, | ||||
| 					}; | ||||
| 
 | ||||
| 					pass.action.colors[0] = .{ | ||||
| 						.load_action = .CLEAR, | ||||
| 						.clear_value = @bitCast(render_frame.clear_color), | ||||
| 					}; | ||||
| 
 | ||||
| 					pass.attachments = pools.get_texture(.backbuffer).?.access.render.attachments; | ||||
| 
 | ||||
| 					break: pass pass; | ||||
| 				}); | ||||
| 
 | ||||
| 				var has_commands = has_commands_head; | ||||
| 
 | ||||
| 				while (has_commands) |commands| : (has_commands = commands.has_next) { | ||||
| 					for (commands.submitted_commands()) |command| { | ||||
| 						try command.process(&pools, &frame); | ||||
| 					} | ||||
| 
 | ||||
| 					frame.flush(&pools); | ||||
| 
 | ||||
| 					if (frame.current_target_texture != .backbuffer) { | ||||
| 						frame.set_target(&pools, .{ | ||||
| 							.texture = .backbuffer, | ||||
| 							.clear_color = null, | ||||
| 							.clear_depth = null, | ||||
| 							.clear_stencil = null, | ||||
| 						}); | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| 				sokol.gfx.endPass(); | ||||
| 
 | ||||
| 				sokol.gfx.beginPass(swapchain_pass: { | ||||
| 					var pass = sokol.gfx.Pass{ | ||||
| 						.swapchain = frame.swapchain, | ||||
| @ -609,29 +692,20 @@ fn run(window: *ext.SDL_Window) !void { | ||||
| 						}, | ||||
| 					}; | ||||
| 
 | ||||
| 					pass.action.colors[0] = .{ | ||||
| 						.clear_value = @bitCast(render_frame.clear_color), | ||||
| 						.load_action = .CLEAR, | ||||
| 					}; | ||||
| 					pass.action.colors[0] = .{.load_action = .CLEAR}; | ||||
| 
 | ||||
| 					break: swapchain_pass pass; | ||||
| 				}); | ||||
| 
 | ||||
| 				var has_commands = has_commands_head; | ||||
| 				try frame.draw_texture(&pools, .{ | ||||
| 					.texture = .backbuffer, | ||||
| 
 | ||||
| 				while (has_commands) |commands| : (has_commands = commands.has_next) { | ||||
| 					for (commands.submitted_commands()) |command| { | ||||
| 						try command.process(&pools, &frame); | ||||
| 					} | ||||
| 
 | ||||
| 					if (frame.current_target_texture != .default) { | ||||
| 						frame.set_target(&pools, .{ | ||||
| 							.clear_color = null, | ||||
| 							.clear_depth = null, | ||||
| 							.clear_stencil = null, | ||||
| 						}); | ||||
| 					} | ||||
| 				} | ||||
| 					.transform = .{ | ||||
| 						.origin = .{@as(f32, @floatFromInt(render_frame.width)) / 2, @as(f32, @floatFromInt(render_frame.height)) / 2}, | ||||
| 						.xbasis = .{@floatFromInt(render_frame.width), 0}, | ||||
| 						.ybasis = .{0, @floatFromInt(render_frame.height)}, | ||||
| 					}, | ||||
| 				}); | ||||
| 
 | ||||
| 				frame.flush(&pools); | ||||
| 				sokol.gfx.endPass(); | ||||
|  | ||||
| @ -123,11 +123,11 @@ pub const Effect = struct { | ||||
| 						.float2 => .FLOAT2, | ||||
| 						.float3 => .FLOAT3, | ||||
| 						.float4 => .FLOAT4, | ||||
| 						.int => .INT, | ||||
| 						.int2 => .INT2, | ||||
| 						.int3 => .INT3, | ||||
| 						.int4 => .INT4, | ||||
| 						.mat4 => .MAT4, | ||||
| 						.integer => .INT, | ||||
| 						.integer2 => .INT2, | ||||
| 						.integer3 => .INT3, | ||||
| 						.integer4 => .INT4, | ||||
| 						.matrix4 => .MAT4, | ||||
| 					}, | ||||
| 
 | ||||
| 					.name = uniform.name, | ||||
| @ -244,19 +244,18 @@ pub const Texture = struct { | ||||
| 	access: Access, | ||||
| 
 | ||||
| 	pub const Access = union (enum) { | ||||
| 		empty, | ||||
| 		render: RenderAccess, | ||||
| 		static: StaticAccess, | ||||
| 	}; | ||||
| 
 | ||||
| 	pub const RenderAccess = struct { | ||||
| 		sampler: sokol.gfx.Sampler, | ||||
| 		color_image: sokol.gfx.Image, | ||||
| 		depth_image: sokol.gfx.Image, | ||||
| 		attachments: sokol.gfx.Attachments, | ||||
| 	}; | ||||
| 
 | ||||
| 	pub const StaticAccess = struct { | ||||
| 		sampler: sokol.gfx.Sampler, | ||||
| 		image: sokol.gfx.Image, | ||||
| 	}; | ||||
| 
 | ||||
| @ -265,14 +264,14 @@ pub const Texture = struct { | ||||
| 			.render => |render| { | ||||
| 				sokol.gfx.destroyImage(render.color_image); | ||||
| 				sokol.gfx.destroyImage(render.depth_image); | ||||
| 				sokol.gfx.destroySampler(render.sampler); | ||||
| 				sokol.gfx.destroyAttachments(render.attachments); | ||||
| 			}, | ||||
| 
 | ||||
| 			.static => |static| { | ||||
| 				sokol.gfx.destroyImage(static.image); | ||||
| 				sokol.gfx.destroySampler(static.sampler); | ||||
| 			}, | ||||
| 
 | ||||
| 			.empty => {}, | ||||
| 		} | ||||
| 
 | ||||
| 		self.* = undefined; | ||||
| @ -286,6 +285,14 @@ pub const Texture = struct { | ||||
| 
 | ||||
| 		switch (desc.access) { | ||||
| 			.render => |render| { | ||||
| 				if (render.width == 0 or render.height == 0) { | ||||
| 					return .{ | ||||
| 						.width = render.width, | ||||
| 						.height = render.height, | ||||
| 						.access = .empty, | ||||
| 					}; | ||||
| 				} | ||||
| 
 | ||||
| 				const color_image = sokol.gfx.makeImage(.{ | ||||
| 					.pixel_format = pixel_format, | ||||
| 					.width = render.width, | ||||
| @ -314,8 +321,6 @@ pub const Texture = struct { | ||||
| 					break: attachments_desc attachments_desc; | ||||
| 				}); | ||||
| 
 | ||||
| 				const sampler = sokol.gfx.makeSampler(.{}); | ||||
| 
 | ||||
| 				return .{ | ||||
| 					.width = render.width, | ||||
| 					.height = render.height, | ||||
| @ -323,11 +328,10 @@ pub const Texture = struct { | ||||
| 					.access = .{ | ||||
| 						.render = .{ | ||||
| 							.attachments = attachments, | ||||
| 							.sampler = sampler, | ||||
| 							.color_image = color_image, | ||||
| 							.depth_image = depth_image, | ||||
| 						}, | ||||
| 					} | ||||
| 					}, | ||||
| 				}; | ||||
| 			}, | ||||
| 
 | ||||
| @ -336,6 +340,14 @@ pub const Texture = struct { | ||||
| 					return error.OutOfMemory; | ||||
| 				}; | ||||
| 
 | ||||
| 				if (static.width == 0 or height == 0) { | ||||
| 					return .{ | ||||
| 						.width = static.width, | ||||
| 						.height = height, | ||||
| 						.access = .empty, | ||||
| 					}; | ||||
| 				} | ||||
| 
 | ||||
| 				const image = sokol.gfx.makeImage(image_desc: { | ||||
| 					var image_desc = sokol.gfx.ImageDesc{ | ||||
| 						.height = height, | ||||
| @ -348,10 +360,7 @@ pub const Texture = struct { | ||||
| 					break: image_desc image_desc; | ||||
| 				}); | ||||
| 
 | ||||
| 				const sampler = sokol.gfx.makeSampler(.{}); | ||||
| 
 | ||||
| 				errdefer { | ||||
| 					sokol.gfx.destroySampler(sampler); | ||||
| 					sokol.gfx.destroyImage(image); | ||||
| 				} | ||||
| 
 | ||||
| @ -362,7 +371,6 @@ pub const Texture = struct { | ||||
| 					.access = .{ | ||||
| 						.static = .{ | ||||
| 							.image = image, | ||||
| 							.sampler = sampler, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}; | ||||
|  | ||||
| @ -21,8 +21,9 @@ void main() { | ||||
| 	const vec2 world_position = instance_origin + mesh_xy.x * instance_xbasis + mesh_xy.y * instance_ybasis; | ||||
| 	const vec2 projected_position = (projection_matrix * vec4(world_position, 0, 1)).xy; | ||||
| 	const vec2 rect_size = instance_rect.zw - instance_rect.xy; | ||||
| 	const vec4 screen_position = vec4(projected_position, instance_depth, 1.0); | ||||
| 
 | ||||
| 	gl_Position = vec4(projected_position, instance_depth, 1.0); | ||||
| 	gl_Position = screen_position; | ||||
| 	color = instance_tint; | ||||
| 	uv = instance_rect.xy + (mesh_uv * rect_size); | ||||
| } | ||||
|  | ||||
| @ -67,11 +67,11 @@ pub const Stage = struct { | ||||
| 			float2, | ||||
| 			float3, | ||||
| 			float4, | ||||
| 			int, | ||||
| 			int2, | ||||
| 			int3, | ||||
| 			int4, | ||||
| 			mat4, | ||||
| 			integer, | ||||
| 			integer2, | ||||
| 			integer3, | ||||
| 			integer4, | ||||
| 			matrix4, | ||||
| 		}; | ||||
| 	}; | ||||
| 
 | ||||
| @ -82,29 +82,27 @@ pub const Stage = struct { | ||||
| 		pub const max_uniforms = 16; | ||||
| 
 | ||||
| 		pub fn size(self: UniformBlock) usize { | ||||
| 			const alignment: usize = switch (self.layout) { | ||||
| 				.std140 => 16, | ||||
| 			}; | ||||
| 
 | ||||
| 			var accumulated_size: usize = 0; | ||||
| 
 | ||||
| 			for (self.uniforms) |uniform| { | ||||
| 				const type_size = @max(1, uniform.len) * @as(usize, switch (uniform.type) { | ||||
| 				accumulated_size += @max(1, uniform.len) * @as(usize, switch (uniform.type) { | ||||
| 					.float => 4, | ||||
| 					.float2 => 8, | ||||
| 					.float3 => 12, | ||||
| 					.float4 => 16, | ||||
| 					.int => 4, | ||||
| 					.int2 => 8, | ||||
| 					.int3 => 12, | ||||
| 					.int4 => 16, | ||||
| 					.mat4 => 64, | ||||
| 					.integer => 4, | ||||
| 					.integer2 => 8, | ||||
| 					.integer3 => 12, | ||||
| 					.integer4 => 16, | ||||
| 					.matrix4 => 64, | ||||
| 				}); | ||||
| 
 | ||||
| 				accumulated_size += (type_size + (alignment - 1)) & ~(alignment - 1); | ||||
| 			} | ||||
| 
 | ||||
| 			return accumulated_size; | ||||
| 			const alignment: usize = switch (self.layout) { | ||||
| 				.std140 => 16, | ||||
| 			}; | ||||
| 
 | ||||
| 			return (accumulated_size + (alignment - 1)) & ~(alignment - 1); | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
| @ -212,7 +210,7 @@ pub const Stage = struct { | ||||
| 					.type = try switch (ext.spvc_type_get_basetype(member_type_handle)) { | ||||
| 						ext.SPVC_BASETYPE_FP32 => switch (ext.spvc_type_get_vector_size(member_type_handle)) { | ||||
| 							4 => switch (ext.spvc_type_get_columns(member_type_handle)) { | ||||
| 								4 => Uniform.Type.mat4, | ||||
| 								4 => Uniform.Type.matrix4, | ||||
| 								1 => Uniform.Type.float4, | ||||
| 								else => error.UnsupportedSPIRV, | ||||
| 							}, | ||||
| @ -224,10 +222,10 @@ pub const Stage = struct { | ||||
| 						}, | ||||
| 
 | ||||
| 						ext.SPVC_BASETYPE_INT32 => try switch (ext.spvc_type_get_vector_size(member_type_handle)) { | ||||
| 							1 => Uniform.Type.int, | ||||
| 							2 => Uniform.Type.int2, | ||||
| 							3 => Uniform.Type.int3, | ||||
| 							4 => Uniform.Type.int4, | ||||
| 							1 => Uniform.Type.integer, | ||||
| 							2 => Uniform.Type.integer2, | ||||
| 							3 => Uniform.Type.integer3, | ||||
| 							4 => Uniform.Type.integer4, | ||||
| 							else => error.UnsupportedSPIRV, | ||||
| 						}, | ||||
| 
 | ||||
|  | ||||
| @ -75,6 +75,7 @@ pub fn start_app(setup: Setup, options: Options) anyerror!void { | ||||
| 	const app = try world.set_get_resource(App{ | ||||
| 		.events = &events, | ||||
| 		.target_frame_time = 1.0 / @as(f64, @floatFromInt(options.tick_rate)), | ||||
| 		.elapsed_time = 0, | ||||
| 		.is_running = true, | ||||
| 	}); | ||||
| 
 | ||||
| @ -85,7 +86,8 @@ pub fn start_app(setup: Setup, options: Options) anyerror!void { | ||||
| 	try setup(&world, events); | ||||
| 	try world.run_event(events.load); | ||||
| 
 | ||||
| 	var ticks_previous = std.time.milliTimestamp(); | ||||
| 	const ticks_initial = std.time.milliTimestamp(); | ||||
| 	var ticks_previous = ticks_initial; | ||||
| 	var accumulated_time = @as(f64, 0); | ||||
| 
 | ||||
| 	while (app.is_running) { | ||||
| @ -93,6 +95,7 @@ pub fn start_app(setup: Setup, options: Options) anyerror!void { | ||||
| 		const milliseconds_per_second = 1000.0; | ||||
| 		const delta_time = @as(f64, @floatFromInt(ticks_current - ticks_previous)) / milliseconds_per_second; | ||||
| 
 | ||||
| 		app.elapsed_time = @as(f64, @floatFromInt(ticks_current - ticks_initial)) / milliseconds_per_second; | ||||
| 		ticks_previous = ticks_current; | ||||
| 		accumulated_time += delta_time; | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user