472 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Zig
		
	
	
	
	
	
			
		
		
	
	
			472 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Zig
		
	
	
	
	
	
const Resources = @import("./Resources.zig");
 | 
						|
 | 
						|
const gfx = @import("./gfx.zig");
 | 
						|
 | 
						|
const ext = @cImport({
 | 
						|
	@cInclude("SDL2/SDL.h");
 | 
						|
});
 | 
						|
 | 
						|
const ona = @import("ona");
 | 
						|
 | 
						|
const sokol = @import("sokol");
 | 
						|
 | 
						|
const spirv = @import("./spirv.zig");
 | 
						|
 | 
						|
const std = @import("std");
 | 
						|
 | 
						|
const Frame = struct {
 | 
						|
	texture_batch_buffers: ona.stack.Sequential(sokol.gfx.Buffer),
 | 
						|
	quad_index_buffer: sokol.gfx.Buffer,
 | 
						|
	quad_vertex_buffer: sokol.gfx.Buffer,
 | 
						|
	drawn_count: usize = 0,
 | 
						|
	flushed_count: usize = 0,
 | 
						|
	current_source_texture: gfx.Texture = .default,
 | 
						|
	current_target_texture: ?gfx.Texture = null,
 | 
						|
	current_effect: gfx.Effect = .default,
 | 
						|
	transform_matrix: Matrix = identity_matrix,
 | 
						|
	width: u16 = 0,
 | 
						|
	height: u16 = 0,
 | 
						|
 | 
						|
	const DrawTexture = extern struct {
 | 
						|
		rotation: f32,
 | 
						|
		depth: f32,
 | 
						|
		position: gfx.Vector2,
 | 
						|
		size: gfx.Vector2,
 | 
						|
		anchor: gfx.Vector2,
 | 
						|
		source_clip: gfx.Rect,
 | 
						|
		tint: gfx.Color,
 | 
						|
	};
 | 
						|
 | 
						|
	const View = extern struct {
 | 
						|
		projection_matrix: Matrix,
 | 
						|
		transform_matrix: Matrix,
 | 
						|
	};
 | 
						|
 | 
						|
	const batches_per_buffer = 512;
 | 
						|
 | 
						|
	pub fn deinit(self: *Frame) void {
 | 
						|
		for (self.texture_batch_buffers.values) |buffer| {
 | 
						|
			sokol.gfx.destroyBuffer(buffer);
 | 
						|
		}
 | 
						|
 | 
						|
		self.texture_batch_buffers.deinit();
 | 
						|
 | 
						|
		self.* = undefined;
 | 
						|
	}
 | 
						|
 | 
						|
	pub fn init() !Frame {
 | 
						|
		const Vertex = struct {
 | 
						|
			xy: @Vector(2, f32),
 | 
						|
			uv: @Vector(2, f32),
 | 
						|
		};
 | 
						|
 | 
						|
		const quad_index_buffer = sokol.gfx.makeBuffer(.{
 | 
						|
			.data = sokol.gfx.asRange(&[_]u16{0, 1, 2, 0, 2, 3}),
 | 
						|
			.type = .INDEXBUFFER,
 | 
						|
		});
 | 
						|
 | 
						|
		const quad_vertex_buffer = sokol.gfx.makeBuffer(.{
 | 
						|
			.data = sokol.gfx.asRange(&[_]Vertex{
 | 
						|
				.{.xy = .{-0.5, -0.5}, .uv = .{0, 1}},
 | 
						|
				.{.xy = .{0.5, -0.5}, .uv = .{1, 1}},
 | 
						|
				.{.xy = .{0.5, 0.5}, .uv = .{1, 0}},
 | 
						|
				.{.xy = .{-0.5, 0.5},  .uv = .{0, 0}},
 | 
						|
			}),
 | 
						|
 | 
						|
			.type = .VERTEXBUFFER,
 | 
						|
		});
 | 
						|
 | 
						|
		return .{
 | 
						|
			.texture_batch_buffers = .{},
 | 
						|
			.quad_index_buffer = quad_index_buffer,
 | 
						|
			.quad_vertex_buffer = quad_vertex_buffer,
 | 
						|
		};
 | 
						|
	}
 | 
						|
 | 
						|
	pub fn draw_font(self: *Frame, resources: *Resources, command: gfx.Command.DrawFont) !void {
 | 
						|
		_ = self;
 | 
						|
		_ = resources;
 | 
						|
		_ = command;
 | 
						|
	}
 | 
						|
 | 
						|
	pub fn draw_texture(self: *Frame, resources: *Resources, command: gfx.Command.DrawTexture) !void {
 | 
						|
		if (self.current_source_texture != command.texture) {
 | 
						|
			self.flush(resources);
 | 
						|
		}
 | 
						|
 | 
						|
		self.current_source_texture = command.texture;
 | 
						|
 | 
						|
		const has_filled_current_buffer = (self.drawn_count % batches_per_buffer) == 0;
 | 
						|
		const buffer_count = self.drawn_count / batches_per_buffer;
 | 
						|
 | 
						|
		if (has_filled_current_buffer and buffer_count == self.texture_batch_buffers.len()) {
 | 
						|
			const instance_buffer = sokol.gfx.makeBuffer(.{
 | 
						|
				.size = @sizeOf(DrawTexture) * batches_per_buffer,
 | 
						|
				.usage = .STREAM,
 | 
						|
			});
 | 
						|
 | 
						|
			errdefer sokol.gfx.destroyBuffer(instance_buffer);
 | 
						|
 | 
						|
			try self.texture_batch_buffers.push_grow(instance_buffer);
 | 
						|
		}
 | 
						|
 | 
						|
		const texture = resources.get_texture(command.texture).?;
 | 
						|
 | 
						|
		_ = sokol.gfx.appendBuffer(self.texture_batch_buffers.get().?.*, sokol.gfx.asRange(&DrawTexture{
 | 
						|
			.size = command.size orelse .{@floatFromInt(texture.width), @floatFromInt(texture.height)},
 | 
						|
			.anchor = command.anchor,
 | 
						|
			.rotation = command.rotation,
 | 
						|
			.depth = command.depth,
 | 
						|
			.source_clip = command.source,
 | 
						|
			.tint = command.tint,
 | 
						|
			.position = command.position,
 | 
						|
		}));
 | 
						|
 | 
						|
		self.drawn_count += 1;
 | 
						|
	}
 | 
						|
 | 
						|
	pub fn finish(self: *Frame, resources: *Resources) void {
 | 
						|
		self.flush(resources);
 | 
						|
		sokol.gfx.endPass();
 | 
						|
		sokol.gfx.commit();
 | 
						|
 | 
						|
		self.drawn_count = 0;
 | 
						|
		self.flushed_count = 0;
 | 
						|
		self.current_source_texture = .default;
 | 
						|
		self.current_target_texture = null;
 | 
						|
		self.current_effect = .default;
 | 
						|
	}
 | 
						|
 | 
						|
	pub fn flush(self: *Frame, resources: *Resources) void {
 | 
						|
		if (self.flushed_count == self.drawn_count) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		var bindings = sokol.gfx.Bindings{
 | 
						|
			.index_buffer = self.quad_index_buffer,
 | 
						|
		};
 | 
						|
 | 
						|
		bindings.vertex_buffers[vertex_indices.mesh] = self.quad_vertex_buffer;
 | 
						|
 | 
						|
		switch (resources.get_texture(self.current_source_texture).?.access) {
 | 
						|
			.render => |render| {
 | 
						|
				bindings.fs.images[0] = render.color_image;
 | 
						|
				bindings.fs.samplers[0] = default_sampler;
 | 
						|
			},
 | 
						|
 | 
						|
			.static => |static| {
 | 
						|
				bindings.fs.images[0] = static.image;
 | 
						|
				bindings.fs.samplers[0] = default_sampler;
 | 
						|
			},
 | 
						|
		}
 | 
						|
 | 
						|
		const effect = resources.get_effect(self.current_effect).?;
 | 
						|
 | 
						|
		sokol.gfx.applyPipeline(effect.pipeline);
 | 
						|
 | 
						|
		if (self.current_target_texture) |target_texture| {
 | 
						|
			const texture = resources.get_texture(target_texture).?;
 | 
						|
 | 
						|
			sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&View{
 | 
						|
				.projection_matrix = orthographic_projection(-1.0, 1.0, .{
 | 
						|
					.left = 0,
 | 
						|
					.top = 0,
 | 
						|
					.right = @floatFromInt(texture.width),
 | 
						|
					.bottom = @floatFromInt(texture.height),
 | 
						|
				}),
 | 
						|
 | 
						|
				.transform_matrix = self.transform_matrix,
 | 
						|
			}));
 | 
						|
		} else {
 | 
						|
			sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&View{
 | 
						|
				.projection_matrix = orthographic_projection(-1.0, 1.0, .{
 | 
						|
					.left = 0,
 | 
						|
					.top = 0,
 | 
						|
					.right = @floatFromInt(self.width),
 | 
						|
					.bottom = @floatFromInt(self.height),
 | 
						|
				}),
 | 
						|
 | 
						|
				.transform_matrix = self.transform_matrix,
 | 
						|
			}));
 | 
						|
		}
 | 
						|
 | 
						|
		if (effect.properties.len != 0) {
 | 
						|
			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;
 | 
						|
			const instances_to_flush = @min(batches_per_buffer - buffer_offset, self.drawn_count - self.flushed_count);
 | 
						|
 | 
						|
			self.flushed_count += instances_to_flush;
 | 
						|
			bindings.vertex_buffers[vertex_indices.instance] = self.texture_batch_buffers.values[buffer_index];
 | 
						|
			bindings.vertex_buffer_offsets[vertex_indices.instance] = @intCast(@sizeOf(DrawTexture) * buffer_offset);
 | 
						|
 | 
						|
			sokol.gfx.applyBindings(bindings);
 | 
						|
			sokol.gfx.draw(0, 6, @intCast(instances_to_flush));
 | 
						|
 | 
						|
			if (self.flushed_count == self.drawn_count) {
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	pub fn set_effect(self: *Frame, resources: *Resources, command: gfx.Command.SetEffect) void {
 | 
						|
		if (command.effect != self.current_effect) {
 | 
						|
			self.flush(resources);
 | 
						|
		}
 | 
						|
 | 
						|
		self.current_effect = command.effect;
 | 
						|
 | 
						|
		if (resources.get_effect(self.current_effect)) |effect| {
 | 
						|
			@memcpy(effect.properties, command.properties);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	pub fn set_scissor_rect(self: *Frame, resources: *Resources, has_rect: ?gfx.Rect) void {
 | 
						|
		self.flush(resources);
 | 
						|
 | 
						|
		if (has_rect) |rect| {
 | 
						|
			const width, const height = rect.size();
 | 
						|
 | 
						|
			sokol.gfx.applyScissorRect(
 | 
						|
				@intFromFloat(rect.left),
 | 
						|
				@intFromFloat(rect.top),
 | 
						|
				@intFromFloat(width),
 | 
						|
				@intFromFloat(height),
 | 
						|
				true,
 | 
						|
			);
 | 
						|
		} else {
 | 
						|
			sokol.gfx.applyScissorRect(0, 0, self.width, self.height, true);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	pub fn set_target(self: *Frame, resources: *Resources, command: gfx.Command.SetTarget) void {
 | 
						|
		sokol.gfx.endPass();
 | 
						|
 | 
						|
		var pass = sokol.gfx.Pass{
 | 
						|
			.action = .{
 | 
						|
				.stencil = .{
 | 
						|
					.load_action = .CLEAR,
 | 
						|
				},
 | 
						|
			},
 | 
						|
		};
 | 
						|
 | 
						|
		if (command.clear_color) |color| {
 | 
						|
			pass.action.colors[0] = .{
 | 
						|
				.load_action = .CLEAR,
 | 
						|
				.clear_value = @bitCast(color),
 | 
						|
			};
 | 
						|
		} else {
 | 
						|
			pass.action.colors[0] = .{.load_action = .LOAD};
 | 
						|
		}
 | 
						|
 | 
						|
		if (command.clear_depth) |depth| {
 | 
						|
			pass.action.depth = .{
 | 
						|
				.load_action = .CLEAR,
 | 
						|
				.clear_value = depth,
 | 
						|
			};
 | 
						|
		} else {
 | 
						|
			pass.action.depth = .{.load_action = .LOAD};
 | 
						|
		}
 | 
						|
 | 
						|
		self.current_target_texture = command.texture;
 | 
						|
 | 
						|
		if (self.current_target_texture) |target_texture| {
 | 
						|
			pass.attachments = switch (resources.get_texture(target_texture).?.access) {
 | 
						|
				.static => @panic("Cannot render to static textures"),
 | 
						|
				.render => |render| render.attachments,
 | 
						|
			};
 | 
						|
		} else {
 | 
						|
			pass.swapchain = .{
 | 
						|
				.width = self.width,
 | 
						|
				.height = self.height,
 | 
						|
				.sample_count = 1,
 | 
						|
				.color_format = .RGBA8,
 | 
						|
				.depth_format = .DEPTH_STENCIL,
 | 
						|
				.gl = .{.framebuffer = 0},
 | 
						|
			};
 | 
						|
		}
 | 
						|
 | 
						|
		sokol.gfx.beginPass(pass);
 | 
						|
	}
 | 
						|
 | 
						|
	pub fn start(self: *Frame, width: u16, height: u16) void {
 | 
						|
		self.width = width;
 | 
						|
		self.height = height;
 | 
						|
 | 
						|
		sokol.gfx.beginPass(pass: {
 | 
						|
			var pass = sokol.gfx.Pass{
 | 
						|
				.swapchain = .{
 | 
						|
					.width = width,
 | 
						|
					.height = height,
 | 
						|
					.sample_count = 1,
 | 
						|
					.color_format = .RGBA8,
 | 
						|
					.depth_format = .DEPTH_STENCIL,
 | 
						|
					.gl = .{.framebuffer = 0},
 | 
						|
				},
 | 
						|
 | 
						|
				.action = .{
 | 
						|
					.stencil = .{.load_action = .CLEAR},
 | 
						|
					.depth = .{.load_action = .CLEAR},
 | 
						|
				},
 | 
						|
			};
 | 
						|
 | 
						|
			pass.action.colors[0] = .{.load_action = .CLEAR};
 | 
						|
 | 
						|
			break: pass pass;
 | 
						|
		});
 | 
						|
	}
 | 
						|
};
 | 
						|
 | 
						|
const Matrix = [4]@Vector(4, f32);
 | 
						|
 | 
						|
var default_sampler: sokol.gfx.Sampler = undefined;
 | 
						|
 | 
						|
const identity_matrix = Matrix{
 | 
						|
	.{1, 0, 0, 0},
 | 
						|
	.{0, 1, 0, 0},
 | 
						|
	.{0, 0, 1, 0},
 | 
						|
	.{0, 0, 0, 1},
 | 
						|
};
 | 
						|
 | 
						|
const vertex_indices = .{
 | 
						|
	.mesh = 0,
 | 
						|
	.instance = 1,
 | 
						|
};
 | 
						|
 | 
						|
fn orthographic_projection(near: f32, far: f32, viewport: gfx.Rect) Matrix {
 | 
						|
	const width = viewport.right - viewport.left;
 | 
						|
	const height = viewport.bottom - viewport.top;
 | 
						|
 | 
						|
	return .{
 | 
						|
		.{2 / width, 0, 0, 0},
 | 
						|
		.{0, 2 / height, 0, 0},
 | 
						|
		.{0, 0, 1 / (far - near), 0},
 | 
						|
		.{-((viewport.left + viewport.right) / width), -((viewport.top + viewport.bottom) / height), near / (near - far), 1},
 | 
						|
	};
 | 
						|
}
 | 
						|
 | 
						|
pub fn process_work(pending_work: *gfx.Assets.WorkQueue, window: *ext.SDL_Window) !void {
 | 
						|
	const context = configure_and_create: {
 | 
						|
		var result = @as(c_int, 0);
 | 
						|
 | 
						|
		result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_FLAGS, ext.SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
 | 
						|
		result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_PROFILE_MASK, ext.SDL_GL_CONTEXT_PROFILE_CORE);
 | 
						|
		result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MAJOR_VERSION, 3);
 | 
						|
		result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MINOR_VERSION, 3);
 | 
						|
		result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_DOUBLEBUFFER, 1);
 | 
						|
 | 
						|
		if (result != 0) {
 | 
						|
			return error.Unsupported;
 | 
						|
		}
 | 
						|
 | 
						|
		break: configure_and_create ext.SDL_GL_CreateContext(window);
 | 
						|
	};
 | 
						|
 | 
						|
	defer ext.SDL_GL_DeleteContext(context);
 | 
						|
 | 
						|
	sokol.gfx.setup(.{
 | 
						|
		.environment = .{
 | 
						|
			.defaults = .{
 | 
						|
				.color_format = .RGBA8,
 | 
						|
				.depth_format = .DEPTH_STENCIL,
 | 
						|
				.sample_count = 1,
 | 
						|
			},
 | 
						|
		},
 | 
						|
 | 
						|
		.logger = .{
 | 
						|
			.func = sokol.log.func,
 | 
						|
		},
 | 
						|
	});
 | 
						|
 | 
						|
	defer {
 | 
						|
		sokol.gfx.shutdown();
 | 
						|
	}
 | 
						|
 | 
						|
	var resources = try Resources.init();
 | 
						|
 | 
						|
	defer {
 | 
						|
		resources.deinit();
 | 
						|
	}
 | 
						|
 | 
						|
	var frame = try Frame.init();
 | 
						|
 | 
						|
	defer {
 | 
						|
		frame.deinit();
 | 
						|
	}
 | 
						|
 | 
						|
	default_sampler = sokol.gfx.makeSampler(.{});
 | 
						|
 | 
						|
	while (true) {
 | 
						|
		switch (pending_work.dequeue()) {
 | 
						|
			.load_effect => |load| {
 | 
						|
				const effect = try resources.create_effect(load.desc);
 | 
						|
 | 
						|
				if (!load.loaded.resolve(effect)) {
 | 
						|
					std.debug.assert(resources.destroy_effect(effect));
 | 
						|
				}
 | 
						|
			},
 | 
						|
 | 
						|
			.load_texture => |load| {
 | 
						|
				const texture = try resources.create_texture(load.desc);
 | 
						|
 | 
						|
				if (!load.loaded.resolve(texture)) {
 | 
						|
					std.debug.assert(resources.destroy_texture(texture));
 | 
						|
				}
 | 
						|
			},
 | 
						|
 | 
						|
			.render_frame => |*render_frame| {
 | 
						|
				frame.start(render_frame.width, render_frame.height);
 | 
						|
 | 
						|
				var has_commands = render_frame.has_commands;
 | 
						|
 | 
						|
				while (has_commands) |commands| : (has_commands = commands.has_next) {
 | 
						|
					for (commands.param.submitted_commands()) |command| {
 | 
						|
						try switch (command) {
 | 
						|
							.draw_font => |draw_font| frame.draw_font(&resources, draw_font),
 | 
						|
							.draw_texture => |draw_texture| frame.draw_texture(&resources, draw_texture),
 | 
						|
							.set_effect => |set_effect| frame.set_effect(&resources, set_effect),
 | 
						|
							.set_scissor_rect => |has_rect| frame.set_scissor_rect(&resources, has_rect),
 | 
						|
							.set_target => |set_target| frame.set_target(&resources, set_target),
 | 
						|
						};
 | 
						|
					}
 | 
						|
 | 
						|
					frame.flush(&resources);
 | 
						|
 | 
						|
					if (frame.current_target_texture != null) {
 | 
						|
						frame.set_target(&resources, .{
 | 
						|
							.clear_color = null,
 | 
						|
							.clear_depth = null,
 | 
						|
							.clear_stencil = null,
 | 
						|
						});
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				frame.finish(&resources);
 | 
						|
				ext.SDL_GL_SwapWindow(window);
 | 
						|
				render_frame.finished.set();
 | 
						|
			},
 | 
						|
 | 
						|
			.shutdown => {
 | 
						|
				break;
 | 
						|
			},
 | 
						|
 | 
						|
			.unload_effect => |unload| {
 | 
						|
				if (!resources.destroy_effect(unload.handle)) {
 | 
						|
					@panic("Attempt to unload a non-existent effect");
 | 
						|
				}
 | 
						|
			},
 | 
						|
 | 
						|
			.unload_texture => |unload| {
 | 
						|
				if (!resources.destroy_texture(unload.handle)) {
 | 
						|
					@panic("Attempt to unload a non-existent texture");
 | 
						|
				}
 | 
						|
			},
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
var work_thread: std.Thread = undefined;
 |