Compare commits
	
		
			2 Commits
		
	
	
		
			c47b334742
			...
			56342d9d8e
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 56342d9d8e | |||
| f3efb60c05 | 
@ -7,7 +7,7 @@ layout (location = 1) in vec2 uv;
 | 
			
		||||
 | 
			
		||||
layout (location = 0) out vec4 texel;
 | 
			
		||||
 | 
			
		||||
layout (binding = 0) readonly buffer Effect {
 | 
			
		||||
layout (binding = 0) uniform Effect {
 | 
			
		||||
	float effect_magnitude;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,7 @@ Ona is also the Catalan word for "wave".
 | 
			
		||||
Ona currently depends the following third-party tools to build it:
 | 
			
		||||
 | 
			
		||||
  * Platform support for SDL2 at version 2.0.20 or above.
 | 
			
		||||
  * SPIR-V shader compilation system utilities, namely `glslangValidator` and `spirv-link`.
 | 
			
		||||
  * SPIR-V shader compilation system utilities, namely `glslangValidator`.
 | 
			
		||||
  * Zig compiler toolchain.
 | 
			
		||||
 | 
			
		||||
As the project evolves, dependencies on libraries external to the project codebase will be minimized or removed outright to meet the goals of the project as closely as possible.
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,8 @@ const std = @import("std");
 | 
			
		||||
const ona = @import("ona");
 | 
			
		||||
 | 
			
		||||
const ChromaticAberration = extern struct {
 | 
			
		||||
	magnitude: f32,
 | 
			
		||||
	effect_magnitude: f32,
 | 
			
		||||
	padding: [12]u8 = undefined,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const Actors = struct {
 | 
			
		||||
@ -53,7 +54,7 @@ fn exit(actors: ona.Write(Actors)) void {
 | 
			
		||||
 | 
			
		||||
fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors)) !void {
 | 
			
		||||
	try commands.set_effect(actors.res.ca_effect, ChromaticAberration{
 | 
			
		||||
		.magnitude = 15.0,
 | 
			
		||||
		.effect_magnitude = 15.0,
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	for (actors.res.instances.values) |instance| {
 | 
			
		||||
 | 
			
		||||
@ -73,20 +73,22 @@ pub const Assets = struct {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const fragment_file_stat = try storage.stat(path);
 | 
			
		||||
		const fragment_spirv = try coral.heap.allocator.alloc(u32, fragment_file_stat.size / @alignOf(u32));
 | 
			
		||||
		const fragment_spirv_ops = try coral.heap.allocator.alloc(u32, fragment_file_stat.size / @alignOf(u32));
 | 
			
		||||
 | 
			
		||||
		defer {
 | 
			
		||||
			coral.heap.allocator.free(fragment_spirv);
 | 
			
		||||
			coral.heap.allocator.free(fragment_spirv_ops);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		_ = try storage.read_all(path, std.mem.sliceAsBytes(fragment_spirv), .{});
 | 
			
		||||
		const bytes_read = try storage.read_all(path, std.mem.sliceAsBytes(fragment_spirv_ops), .{});
 | 
			
		||||
 | 
			
		||||
		std.debug.assert(bytes_read.len == fragment_file_stat.size);
 | 
			
		||||
 | 
			
		||||
		var loaded = coral.asyncio.Future(std.mem.Allocator.Error!Effect){};
 | 
			
		||||
 | 
			
		||||
		rendering.enqueue_work(.{
 | 
			
		||||
			.load_effect = .{
 | 
			
		||||
				.desc = .{
 | 
			
		||||
					.fragment_spirv = fragment_spirv,
 | 
			
		||||
					.fragment_spirv_ops = fragment_spirv_ops,
 | 
			
		||||
				},
 | 
			
		||||
 | 
			
		||||
				.loaded = &loaded,
 | 
			
		||||
@ -127,20 +129,6 @@ pub const Assets = struct {
 | 
			
		||||
		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;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -213,13 +201,6 @@ pub const Input = union (enum) {
 | 
			
		||||
 | 
			
		||||
pub const Effect = handles.Effect;
 | 
			
		||||
 | 
			
		||||
pub const Rect = struct {
 | 
			
		||||
	left: f32,
 | 
			
		||||
	top: f32,
 | 
			
		||||
	right: f32,
 | 
			
		||||
	bottom: f32,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const Texture = handles.Texture;
 | 
			
		||||
 | 
			
		||||
pub fn poll(app: flow.Write(App), inputs: msg.Send(Input)) !void {
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@ const coral = @import("coral");
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
 | 
			
		||||
pub const Effect = Handle(struct {
 | 
			
		||||
	fragment_spirv: []const u32,
 | 
			
		||||
	fragment_spirv_ops: []const u32,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
fn Handle(comptime HandleDesc: type) type {
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
const gfx = @import("../gfx.zig");
 | 
			
		||||
 | 
			
		||||
pub const Color = @Vector(4, f32);
 | 
			
		||||
 | 
			
		||||
pub fn EvenOrderMatrix(comptime n: usize, comptime Element: type) type {
 | 
			
		||||
@ -16,7 +14,14 @@ pub const Transform2D = extern struct {
 | 
			
		||||
 | 
			
		||||
pub const ProjectionMatrix = EvenOrderMatrix(4, f32);
 | 
			
		||||
 | 
			
		||||
pub fn orthographic_projection(near: f32, far: f32, viewport: gfx.Rect) EvenOrderMatrix(4, f32) {
 | 
			
		||||
pub const Rect = struct {
 | 
			
		||||
	left: f32,
 | 
			
		||||
	top: f32,
 | 
			
		||||
	right: f32,
 | 
			
		||||
	bottom: f32,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub fn orthographic_projection(near: f32, far: f32, viewport: Rect) EvenOrderMatrix(4, f32) {
 | 
			
		||||
	const width = viewport.right - viewport.left;
 | 
			
		||||
	const height = viewport.bottom - viewport.top;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -185,7 +185,7 @@ const Frame = struct {
 | 
			
		||||
 | 
			
		||||
		bindings.vertex_buffers[vertex_indices.mesh] = quad_vertex_buffer;
 | 
			
		||||
 | 
			
		||||
		switch (pools.textures.get(@intFromEnum(self.current_source_texture)).?.*) {
 | 
			
		||||
		switch (pools.textures.get(@intFromEnum(self.current_source_texture)).?.access) {
 | 
			
		||||
			.render => |render| {
 | 
			
		||||
				bindings.fs.images[0] = render.color_image;
 | 
			
		||||
				bindings.fs.samplers[0] = render.sampler;
 | 
			
		||||
@ -197,22 +197,30 @@ const Frame = struct {
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (self.current_target_texture) |target_texture| {
 | 
			
		||||
			const target_view_buffer = pools.textures.get(@intFromEnum(target_texture)).?.render.view_buffer;
 | 
			
		||||
 | 
			
		||||
			bindings.vs.storage_buffers[0] = target_view_buffer;
 | 
			
		||||
		} else {
 | 
			
		||||
			bindings.vs.storage_buffers[0] = view_buffer;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		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;
 | 
			
		||||
		if (self.current_target_texture) |target_texture| {
 | 
			
		||||
			const texture = pools.textures.get(@intFromEnum(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(.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;
 | 
			
		||||
@ -238,10 +246,8 @@ const Frame = struct {
 | 
			
		||||
 | 
			
		||||
		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));
 | 
			
		||||
			}
 | 
			
		||||
		if (pools.effects.get(@intFromEnum(self.current_effect))) |effect| {
 | 
			
		||||
			@memcpy(effect.properties, command.properties);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -275,7 +281,7 @@ const Frame = struct {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (command.texture) |texture| {
 | 
			
		||||
			pass.attachments = switch (pools.textures.get(@intFromEnum(texture)).?.*) {
 | 
			
		||||
			pass.attachments = switch (pools.textures.get(@intFromEnum(texture)).?.access) {
 | 
			
		||||
				.static => @panic("Cannot render to static textures"),
 | 
			
		||||
				.render => |render| render.attachments,
 | 
			
		||||
			};
 | 
			
		||||
@ -376,7 +382,7 @@ const Pools = struct {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		_ = try pools.create_effect(.{
 | 
			
		||||
			.fragment_spirv = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")),
 | 
			
		||||
			.fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")),
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		const default_texture_data = [_]u32{
 | 
			
		||||
@ -463,8 +469,6 @@ var quad_index_buffer: sokol.gfx.Buffer = undefined;
 | 
			
		||||
 | 
			
		||||
var quad_vertex_buffer: sokol.gfx.Buffer = undefined;
 | 
			
		||||
 | 
			
		||||
var view_buffer: sokol.gfx.Buffer = undefined;
 | 
			
		||||
 | 
			
		||||
const vertex_indices = .{
 | 
			
		||||
	.mesh = 0,
 | 
			
		||||
	.instance = 1,
 | 
			
		||||
@ -537,18 +541,6 @@ fn run(window: *ext.SDL_Window) !void {
 | 
			
		||||
		.type = .VERTEXBUFFER,
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	view_buffer = sokol.gfx.makeBuffer(.{
 | 
			
		||||
		.data = sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{
 | 
			
		||||
			.left = 0,
 | 
			
		||||
			.top = 0,
 | 
			
		||||
			.right = @floatFromInt(1280),
 | 
			
		||||
			.bottom = @floatFromInt(720),
 | 
			
		||||
		})),
 | 
			
		||||
 | 
			
		||||
		.type = .STORAGEBUFFER,
 | 
			
		||||
		.usage = .IMMUTABLE,
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	var has_commands_head: ?*Commands = null;
 | 
			
		||||
	var has_commands_tail: ?*Commands = null;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -13,29 +13,50 @@ const std = @import("std");
 | 
			
		||||
pub const Effect = struct {
 | 
			
		||||
	shader: sokol.gfx.Shader,
 | 
			
		||||
	pipeline: sokol.gfx.Pipeline,
 | 
			
		||||
	has_properties_buffer: ?sokol.gfx.Buffer = null,
 | 
			
		||||
	properties: []coral.io.Byte,
 | 
			
		||||
 | 
			
		||||
	pub fn deinit(self: *Effect) void {
 | 
			
		||||
		coral.heap.allocator.free(self.properties);
 | 
			
		||||
		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);
 | 
			
		||||
	pub fn init(desc: handles.Effect.Desc) spirv.Error!Effect {
 | 
			
		||||
		var spirv_arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
 | 
			
		||||
 | 
			
		||||
		defer spirv_unit.deinit();
 | 
			
		||||
		defer {
 | 
			
		||||
			spirv_arena.deinit();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		try spirv_unit.compile(&spirv.to_ops(@embedFile("./shaders/2d_default.vert.spv")), .vertex);
 | 
			
		||||
		try spirv_unit.compile(desc.fragment_spirv, .fragment);
 | 
			
		||||
		const spirv_program = try spirv.analyze(&spirv_arena, .{
 | 
			
		||||
			.target = try switch (sokol.gfx.queryBackend()) {
 | 
			
		||||
				.GLCORE => spirv.Target.glsl,
 | 
			
		||||
				else => error.InvalidSPIRV,
 | 
			
		||||
			},
 | 
			
		||||
 | 
			
		||||
		const shader = sokol.gfx.makeShader(spirv_unit.shader_desc());
 | 
			
		||||
			.vertex_source = .{
 | 
			
		||||
				.ops = &spirv.to_ops(@embedFile("./shaders/2d_default.vert.spv")),
 | 
			
		||||
			},
 | 
			
		||||
 | 
			
		||||
			.fragment_source = .{
 | 
			
		||||
				.ops = desc.fragment_spirv_ops,
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		const shader = sokol.gfx.makeShader(shader_desc: {
 | 
			
		||||
			const shader_desc = sokol.gfx.ShaderDesc{
 | 
			
		||||
				.vs = stage_desc(spirv_program.vertex_stage),
 | 
			
		||||
				.fs = stage_desc(spirv_program.fragment_stage),
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			// TODO: Vertex attributes, for some reason they aren't needed?
 | 
			
		||||
 | 
			
		||||
			break: shader_desc shader_desc;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		// TODO: Review blending rules.
 | 
			
		||||
		const pipeline = sokol.gfx.makePipeline(pipeline_desc: {
 | 
			
		||||
			var pipeline_desc = sokol.gfx.PipelineDesc{
 | 
			
		||||
				.label = "Effect pipeline",
 | 
			
		||||
@ -58,33 +79,106 @@ pub const Effect = struct {
 | 
			
		||||
			break: pipeline_desc pipeline_desc;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		const properties = try coral.heap.allocator.alloc(
 | 
			
		||||
			coral.io.Byte,
 | 
			
		||||
			if (spirv_program.fragment_stage.has_uniform_blocks[0]) |uniform_block| uniform_block.size() else 0,
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		errdefer {
 | 
			
		||||
			coral.heap.allocator.free(properties);
 | 
			
		||||
			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,
 | 
			
		||||
		return .{
 | 
			
		||||
			.shader = shader,
 | 
			
		||||
			.pipeline = pipeline,
 | 
			
		||||
			.properties = properties,
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn stage_desc(spirv_stage: spirv.Stage) sokol.gfx.ShaderStageDesc {
 | 
			
		||||
		var stage = sokol.gfx.ShaderStageDesc{
 | 
			
		||||
			.entry = spirv_stage.entry_point,
 | 
			
		||||
			.source = spirv_stage.source,
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		for (0 .. spirv.Stage.max_uniform_blocks) |slot| {
 | 
			
		||||
			const uniform_block = &(spirv_stage.has_uniform_blocks[slot] orelse {
 | 
			
		||||
				continue;
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			errdefer {
 | 
			
		||||
				sokol.gfx.destroyBuffer(properties_buffer);
 | 
			
		||||
			const stage_uniform_block = &stage.uniform_blocks[slot];
 | 
			
		||||
 | 
			
		||||
			stage_uniform_block.layout = switch (uniform_block.layout) {
 | 
			
		||||
				.std140 => .STD140,
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			stage_uniform_block.size = uniform_block.size();
 | 
			
		||||
 | 
			
		||||
			for (stage_uniform_block.uniforms[0 .. uniform_block.uniforms.len], uniform_block.uniforms) |*stage_uniform, uniform| {
 | 
			
		||||
				stage_uniform.* = .{
 | 
			
		||||
					.type = switch (uniform.type) {
 | 
			
		||||
						.float => .FLOAT,
 | 
			
		||||
						.float2 => .FLOAT2,
 | 
			
		||||
						.float3 => .FLOAT3,
 | 
			
		||||
						.float4 => .FLOAT4,
 | 
			
		||||
						.int => .INT,
 | 
			
		||||
						.int2 => .INT2,
 | 
			
		||||
						.int3 => .INT3,
 | 
			
		||||
						.int4 => .INT4,
 | 
			
		||||
						.mat4 => .MAT4,
 | 
			
		||||
					},
 | 
			
		||||
 | 
			
		||||
					.name = uniform.name,
 | 
			
		||||
					.array_count = uniform.len,
 | 
			
		||||
				};
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
			return .{
 | 
			
		||||
				.shader = shader,
 | 
			
		||||
				.pipeline = pipeline,
 | 
			
		||||
				.has_properties_buffer = properties_buffer,
 | 
			
		||||
			};
 | 
			
		||||
		} else {
 | 
			
		||||
			return .{
 | 
			
		||||
				.shader = shader,
 | 
			
		||||
				.pipeline = pipeline,
 | 
			
		||||
		for (0 .. spirv.Stage.max_images) |slot| {
 | 
			
		||||
			const image = &(spirv_stage.has_images[slot] orelse {
 | 
			
		||||
				continue;
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			stage.images[slot] = .{
 | 
			
		||||
				.multisampled = image.is_multisampled,
 | 
			
		||||
				.image_type = ._2D,
 | 
			
		||||
				.sample_type = .FLOAT,
 | 
			
		||||
				.used = true,
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for (0 .. spirv.Stage.max_samplers) |slot| {
 | 
			
		||||
			const sampler = &(spirv_stage.has_samplers[slot] orelse {
 | 
			
		||||
				continue;
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			stage.samplers[slot] = .{
 | 
			
		||||
				.sampler_type = switch (sampler.*) {
 | 
			
		||||
					.filtering => .FILTERING,
 | 
			
		||||
					.non_filtering => .NONFILTERING,
 | 
			
		||||
					.comparison => .COMPARISON,
 | 
			
		||||
				},
 | 
			
		||||
 | 
			
		||||
				.used = true,
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for (0 .. spirv.Stage.max_image_sampler_pairs) |slot| {
 | 
			
		||||
			const image_sampler_pair = &(spirv_stage.has_image_sampler_pairs[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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const vertex_layout_state = sokol.gfx.VertexLayoutState{
 | 
			
		||||
@ -144,113 +238,114 @@ pub const Effect = struct {
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const Texture = union (enum) {
 | 
			
		||||
	render: Render,
 | 
			
		||||
	static: Static,
 | 
			
		||||
pub const Texture = struct {
 | 
			
		||||
	width: u16,
 | 
			
		||||
	height: u16,
 | 
			
		||||
	access: Access,
 | 
			
		||||
 | 
			
		||||
	const Render = struct {
 | 
			
		||||
	pub const Access = union (enum) {
 | 
			
		||||
		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,
 | 
			
		||||
		view_buffer: sokol.gfx.Buffer,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
		fn deinit(self: *Render) void {
 | 
			
		||||
			sokol.gfx.destroyImage(self.color_image);
 | 
			
		||||
			sokol.gfx.destroyImage(self.depth_image);
 | 
			
		||||
			sokol.gfx.destroySampler(self.sampler);
 | 
			
		||||
			sokol.gfx.destroyAttachments(self.attachments);
 | 
			
		||||
			sokol.gfx.destroyBuffer(self.view_buffer);
 | 
			
		||||
	pub const StaticAccess = struct {
 | 
			
		||||
		sampler: sokol.gfx.Sampler,
 | 
			
		||||
		image: sokol.gfx.Image,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub fn deinit(self: *Texture) void {
 | 
			
		||||
		switch (self.access) {
 | 
			
		||||
			.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);
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		self.* = undefined;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		fn init(format: handles.Texture.Desc.Format, access: handles.Texture.Desc.Access.Render) std.mem.Allocator.Error!Render {
 | 
			
		||||
			const color_image = sokol.gfx.makeImage(.{
 | 
			
		||||
				.pixel_format = switch (format) {
 | 
			
		||||
	pub fn init(desc: handles.Texture.Desc) std.mem.Allocator.Error!Texture {
 | 
			
		||||
		const pixel_format = switch (desc.format) {
 | 
			
		||||
			.rgba8 => sokol.gfx.PixelFormat.RGBA8,
 | 
			
		||||
			.bgra8 => sokol.gfx.PixelFormat.BGRA8,
 | 
			
		||||
				},
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
				.width = access.width,
 | 
			
		||||
				.height = access.height,
 | 
			
		||||
		switch (desc.access) {
 | 
			
		||||
			.render => |render| {
 | 
			
		||||
				const color_image = sokol.gfx.makeImage(.{
 | 
			
		||||
					.pixel_format = pixel_format,
 | 
			
		||||
					.width = render.width,
 | 
			
		||||
					.height = render.height,
 | 
			
		||||
					.render_target = true,
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
				const depth_image = sokol.gfx.makeImage(.{
 | 
			
		||||
				.width = access.width,
 | 
			
		||||
				.height = access.height,
 | 
			
		||||
					.width = render.width,
 | 
			
		||||
					.height = render.height,
 | 
			
		||||
					.render_target = true,
 | 
			
		||||
					.pixel_format = .DEPTH_STENCIL,
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
				const attachments = sokol.gfx.makeAttachments(attachments_desc: {
 | 
			
		||||
				var desc = sokol.gfx.AttachmentsDesc{
 | 
			
		||||
					var attachments_desc = sokol.gfx.AttachmentsDesc{
 | 
			
		||||
						.depth_stencil = .{
 | 
			
		||||
							.image = depth_image,
 | 
			
		||||
						},
 | 
			
		||||
					};
 | 
			
		||||
 | 
			
		||||
				desc.colors[0] = .{
 | 
			
		||||
					attachments_desc.colors[0] = .{
 | 
			
		||||
						.image = color_image,
 | 
			
		||||
					};
 | 
			
		||||
 | 
			
		||||
				break: attachments_desc desc;
 | 
			
		||||
					break: attachments_desc attachments_desc;
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
				const sampler = sokol.gfx.makeSampler(.{});
 | 
			
		||||
 | 
			
		||||
			const view_buffer = sokol.gfx.makeBuffer(.{
 | 
			
		||||
				.data = sokol.gfx.asRange(&lina.orthographic_projection(-1.0, 1.0, .{
 | 
			
		||||
					.left = 0,
 | 
			
		||||
					.top = 0,
 | 
			
		||||
					.right = @floatFromInt(access.width),
 | 
			
		||||
					.bottom = @floatFromInt(access.height),
 | 
			
		||||
				})),
 | 
			
		||||
 | 
			
		||||
				.type = .STORAGEBUFFER,
 | 
			
		||||
				.usage = .IMMUTABLE,
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
				return .{
 | 
			
		||||
					.width = render.width,
 | 
			
		||||
					.height = render.height,
 | 
			
		||||
 | 
			
		||||
					.access = .{
 | 
			
		||||
						.render = .{
 | 
			
		||||
							.attachments = attachments,
 | 
			
		||||
							.sampler = sampler,
 | 
			
		||||
							.color_image = color_image,
 | 
			
		||||
							.depth_image = depth_image,
 | 
			
		||||
				.view_buffer = view_buffer,
 | 
			
		||||
			};
 | 
			
		||||
						},
 | 
			
		||||
					}
 | 
			
		||||
				};
 | 
			
		||||
			},
 | 
			
		||||
 | 
			
		||||
	const Static = struct {
 | 
			
		||||
		image: sokol.gfx.Image,
 | 
			
		||||
		sampler: sokol.gfx.Sampler,
 | 
			
		||||
 | 
			
		||||
		fn deinit(self: *Static) void {
 | 
			
		||||
			sokol.gfx.destroyImage(self.image);
 | 
			
		||||
			sokol.gfx.destroySampler(self.sampler);
 | 
			
		||||
 | 
			
		||||
			self.* = undefined;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fn init(format: handles.Texture.Desc.Format, access: handles.Texture.Desc.Access.Static) std.mem.Allocator.Error!Static {
 | 
			
		||||
			const image = sokol.gfx.makeImage(image_desc: {
 | 
			
		||||
				var desc = sokol.gfx.ImageDesc{
 | 
			
		||||
					.height = std.math.cast(i32, access.data.len / (access.width * format.byte_size())) orelse {
 | 
			
		||||
			.static => |static| {
 | 
			
		||||
				const height = std.math.cast(u16, static.data.len / (static.width * desc.format.byte_size())) orelse {
 | 
			
		||||
					return error.OutOfMemory;
 | 
			
		||||
					},
 | 
			
		||||
 | 
			
		||||
					.pixel_format = switch (format) {
 | 
			
		||||
						.rgba8 => sokol.gfx.PixelFormat.RGBA8,
 | 
			
		||||
						.bgra8 => sokol.gfx.PixelFormat.BGRA8,
 | 
			
		||||
					},
 | 
			
		||||
 | 
			
		||||
					.width = access.width,
 | 
			
		||||
				};
 | 
			
		||||
 | 
			
		||||
				desc.data.subimage[0][0] = sokol.gfx.asRange(access.data);
 | 
			
		||||
				const image = sokol.gfx.makeImage(image_desc: {
 | 
			
		||||
					var image_desc = sokol.gfx.ImageDesc{
 | 
			
		||||
						.height = height,
 | 
			
		||||
						.pixel_format = pixel_format,
 | 
			
		||||
						.width = static.width,
 | 
			
		||||
					};
 | 
			
		||||
 | 
			
		||||
				break: image_desc desc;
 | 
			
		||||
					image_desc.data.subimage[0][0] = sokol.gfx.asRange(static.data);
 | 
			
		||||
 | 
			
		||||
					break: image_desc image_desc;
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
				const sampler = sokol.gfx.makeSampler(.{});
 | 
			
		||||
@ -261,23 +356,17 @@ pub const Texture = union (enum) {
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				return .{
 | 
			
		||||
					.width = static.width,
 | 
			
		||||
					.height = height,
 | 
			
		||||
 | 
			
		||||
					.access = .{
 | 
			
		||||
						.static = .{
 | 
			
		||||
							.image = image,
 | 
			
		||||
							.sampler = sampler,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				};
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub fn deinit(self: *Texture) void {
 | 
			
		||||
		switch (self.*) {
 | 
			
		||||
			.static => |*static| static.deinit(),
 | 
			
		||||
			.render => |*render| render.deinit(),
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn init(desc: handles.Texture.Desc) std.mem.Allocator.Error!Texture {
 | 
			
		||||
		return switch (desc.access) {
 | 
			
		||||
			.static => |static| .{.static = try Static.init(desc.format, static)},
 | 
			
		||||
			.render => |render| .{.render = try Render.init(desc.format, render)},
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,7 @@ layout (location = 7) in vec4 instance_rect;
 | 
			
		||||
layout (location = 0) out vec4 color;
 | 
			
		||||
layout (location = 1) out vec2 uv;
 | 
			
		||||
 | 
			
		||||
layout (binding = 0) readonly buffer View {
 | 
			
		||||
layout (binding = 0) uniform View {
 | 
			
		||||
	mat4 projection_matrix;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -9,18 +9,33 @@ const sokol = @import("sokol");
 | 
			
		||||
const std = @import("std");
 | 
			
		||||
 | 
			
		||||
pub const Error = std.mem.Allocator.Error || error {
 | 
			
		||||
	UnsupportedTarget,
 | 
			
		||||
	InvalidSPIRV,
 | 
			
		||||
	UnsupportedSPIRV,
 | 
			
		||||
	UnsupportedBackend,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
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 const Options = struct {
 | 
			
		||||
	target: Target,
 | 
			
		||||
	vertex_source: Source,
 | 
			
		||||
	fragment_source: Source,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const Program = struct {
 | 
			
		||||
	vertex_stage: Stage,
 | 
			
		||||
	fragment_stage: Stage,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const Source = struct {
 | 
			
		||||
	entry_point: []const u8 = "main",
 | 
			
		||||
	ops: []const u32,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const Stage = struct {
 | 
			
		||||
	entry_point: [:0]const u8,
 | 
			
		||||
	source: [:0]const u8,
 | 
			
		||||
	has_image_sampler_pairs: [max_image_sampler_pairs]?ImageSamplerPair = [_]?ImageSamplerPair{null} ** max_image_sampler_pairs,
 | 
			
		||||
	has_images: [max_images]?Image = [_]?Image{null} ** max_images,
 | 
			
		||||
    has_samplers: [max_samplers]?Sampler = [_]?Sampler{null} ** max_samplers,
 | 
			
		||||
	has_uniform_blocks: [max_uniform_blocks]?UniformBlock = [_]?UniformBlock{null} ** max_uniform_blocks,
 | 
			
		||||
 | 
			
		||||
	pub const Image = struct {
 | 
			
		||||
		is_multisampled: bool,
 | 
			
		||||
@ -32,15 +47,65 @@ pub const Shader = struct {
 | 
			
		||||
		name: [:0]const u8,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub const Layout = enum {
 | 
			
		||||
		std140,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub const Sampler = enum {
 | 
			
		||||
		filtering,
 | 
			
		||||
		non_filtering,
 | 
			
		||||
		comparison,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub const StorageBuffer = struct {
 | 
			
		||||
		minimum_size: usize,
 | 
			
		||||
		is_readonly: bool,
 | 
			
		||||
	pub const Uniform = struct {
 | 
			
		||||
		name: [:0]const u8,
 | 
			
		||||
		type: Type,
 | 
			
		||||
		len: u16,
 | 
			
		||||
 | 
			
		||||
		pub const Type = enum {
 | 
			
		||||
			float,
 | 
			
		||||
			float2,
 | 
			
		||||
			float3,
 | 
			
		||||
			float4,
 | 
			
		||||
			int,
 | 
			
		||||
			int2,
 | 
			
		||||
			int3,
 | 
			
		||||
			int4,
 | 
			
		||||
			mat4,
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub const UniformBlock = struct {
 | 
			
		||||
		layout: Layout,
 | 
			
		||||
		uniforms: []const Uniform,
 | 
			
		||||
 | 
			
		||||
		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) {
 | 
			
		||||
					.float => 4,
 | 
			
		||||
					.float2 => 8,
 | 
			
		||||
					.float3 => 12,
 | 
			
		||||
					.float4 => 16,
 | 
			
		||||
					.int => 4,
 | 
			
		||||
					.int2 => 8,
 | 
			
		||||
					.int3 => 12,
 | 
			
		||||
					.int4 => 16,
 | 
			
		||||
					.mat4 => 64,
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
				accumulated_size += (type_size + (alignment - 1)) & ~(alignment - 1);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return accumulated_size;
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	pub const max_images = 12;
 | 
			
		||||
@ -51,7 +116,10 @@ pub const Shader = struct {
 | 
			
		||||
 | 
			
		||||
	pub const max_storage_buffers = 8;
 | 
			
		||||
 | 
			
		||||
	fn reflect_image_samplers(self: *Shader, _: Stage, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void {
 | 
			
		||||
	pub const max_uniform_blocks = 8;
 | 
			
		||||
 | 
			
		||||
	fn reflect_images_samplers(self: *Stage, arena: *std.heap.ArenaAllocator, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void {
 | 
			
		||||
		const arena_allocator = arena.allocator();
 | 
			
		||||
		var sampled_images: []const ext.spvc_reflected_resource = &.{};
 | 
			
		||||
 | 
			
		||||
		try to_error(ext.spvc_resources_get_resource_list_for_type(
 | 
			
		||||
@ -82,196 +150,208 @@ pub const Shader = struct {
 | 
			
		||||
				else => error.InvalidSPIRV,
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			self.has_image[i] = .{
 | 
			
		||||
			self.has_images[i] = .{
 | 
			
		||||
				.is_multisampled = ext.spvc_type_get_image_multisampled(sampled_image_type) != 0,
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			self.has_sampler[i] = .filtering;
 | 
			
		||||
			self.has_samplers[i] = .filtering;
 | 
			
		||||
 | 
			
		||||
			self.has_image_sampler_pair[i] = .{
 | 
			
		||||
				.name = std.mem.span(ext.spvc_compiler_get_name(compiler, sampled_image.id)),
 | 
			
		||||
			self.has_image_sampler_pairs[i] = .{
 | 
			
		||||
				.name = try arena_allocator.dupeZ(u8, 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 = &.{};
 | 
			
		||||
	fn reflect_uniform_blocks(self: *Stage, arena: *std.heap.ArenaAllocator, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void {
 | 
			
		||||
		const arena_allocator = arena.allocator();
 | 
			
		||||
		var reflected_resources: []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,
 | 
			
		||||
			@ptrCast(&reflected_resources.ptr),
 | 
			
		||||
			&reflected_resources.len,
 | 
			
		||||
		));
 | 
			
		||||
 | 
			
		||||
		if (uniform_buffers.len != 0) {
 | 
			
		||||
		if (reflected_resources.len > max_uniform_blocks) {
 | 
			
		||||
			return error.InvalidSPIRV;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// TODO: Support for older APIs?
 | 
			
		||||
		_ = self;
 | 
			
		||||
		_ = compiler;
 | 
			
		||||
		for (0 .. reflected_resources.len, reflected_resources) |i, reflected_resource| {
 | 
			
		||||
			const type_handle = ext.spvc_compiler_get_type_handle(compiler, reflected_resource.base_type_id);
 | 
			
		||||
			const is_std430 = ext.spvc_compiler_has_decoration(compiler, reflected_resource.id, ext.SpvDecorationBufferBlock) == ext.SPVC_TRUE;
 | 
			
		||||
 | 
			
		||||
			if (is_std430) {
 | 
			
		||||
				return error.UnsupportedSPIRV;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
	pub fn stage_desc(self: Shader) sokol.gfx.ShaderStageDesc {
 | 
			
		||||
		var stage = sokol.gfx.ShaderStageDesc{
 | 
			
		||||
			.entry = "main",
 | 
			
		||||
			.source = self.decompiled_source,
 | 
			
		||||
		};
 | 
			
		||||
			const uniform_count = ext.spvc_type_get_num_member_types(type_handle);
 | 
			
		||||
 | 
			
		||||
		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,
 | 
			
		||||
			};
 | 
			
		||||
			if (uniform_count > UniformBlock.max_uniforms) {
 | 
			
		||||
				return error.UnsupportedSPIRV;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		for (0 .. max_images) |slot| {
 | 
			
		||||
			const image = &(self.has_image[slot] orelse {
 | 
			
		||||
				continue;
 | 
			
		||||
			});
 | 
			
		||||
			const uniforms = try arena_allocator.alloc(Uniform, uniform_count);
 | 
			
		||||
 | 
			
		||||
			stage.images[slot] = .{
 | 
			
		||||
				.multisampled = image.is_multisampled,
 | 
			
		||||
				.image_type = ._2D,
 | 
			
		||||
				.sample_type = .FLOAT,
 | 
			
		||||
				.used = true,
 | 
			
		||||
			};
 | 
			
		||||
			for (uniforms, 0 .. uniform_count) |*uniform, j| {
 | 
			
		||||
				const member_index: c_uint = @intCast(j);
 | 
			
		||||
				const member_type_handle = ext.spvc_compiler_get_type_handle(compiler, ext.spvc_type_get_member_type(type_handle, member_index));
 | 
			
		||||
 | 
			
		||||
				if (ext.spvc_type_get_num_array_dimensions(member_type_handle) > 1) {
 | 
			
		||||
					return error.UnsupportedSPIRV;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
		for (0 .. max_samplers) |slot| {
 | 
			
		||||
			const sampler = &(self.has_sampler[slot] orelse {
 | 
			
		||||
				continue;
 | 
			
		||||
			});
 | 
			
		||||
				uniform.* = .{
 | 
			
		||||
					.name = try coral.utf8.alloc_formatted(arena_allocator, "_{id}.{member_name}", .{
 | 
			
		||||
						.id = reflected_resource.id,
 | 
			
		||||
						.member_name = std.mem.span(ext.spvc_compiler_get_member_name(compiler, reflected_resource.base_type_id, member_index)),
 | 
			
		||||
					}),
 | 
			
		||||
 | 
			
		||||
			stage.samplers[slot] = .{
 | 
			
		||||
				.sampler_type = switch (sampler.*) {
 | 
			
		||||
					.filtering => .FILTERING,
 | 
			
		||||
					.non_filtering => .NONFILTERING,
 | 
			
		||||
					.comparison => .COMPARISON,
 | 
			
		||||
					.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,
 | 
			
		||||
								1 => Uniform.Type.float4,
 | 
			
		||||
								else => error.UnsupportedSPIRV,
 | 
			
		||||
							},
 | 
			
		||||
 | 
			
		||||
				.used = true,
 | 
			
		||||
							1 => Uniform.Type.float,
 | 
			
		||||
							2 => Uniform.Type.float2,
 | 
			
		||||
							3 => Uniform.Type.float3,
 | 
			
		||||
							else => error.UnsupportedSPIRV,
 | 
			
		||||
						},
 | 
			
		||||
 | 
			
		||||
						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,
 | 
			
		||||
							else => error.UnsupportedSPIRV,
 | 
			
		||||
						},
 | 
			
		||||
 | 
			
		||||
						else => error.UnsupportedSPIRV,
 | 
			
		||||
					},
 | 
			
		||||
 | 
			
		||||
					.len = std.math.cast(u16, ext.spvc_type_get_array_dimension(member_type_handle, 0)) orelse {
 | 
			
		||||
						return error.UnsupportedSPIRV;
 | 
			
		||||
					},
 | 
			
		||||
				};
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		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,
 | 
			
		||||
			self.has_uniform_blocks[i] = .{
 | 
			
		||||
				.uniforms = uniforms,
 | 
			
		||||
				.layout = .std140,
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return stage;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub const Stage = enum {
 | 
			
		||||
	vertex,
 | 
			
		||||
	fragment,
 | 
			
		||||
pub const Target = enum {
 | 
			
		||||
	glsl,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
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 analyze(arena: *std.heap.ArenaAllocator, options: Options) Error!Program {
 | 
			
		||||
	var context: ext.spvc_context = null;
 | 
			
		||||
 | 
			
		||||
	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]},
 | 
			
		||||
	if (ext.spvc_context_create(&context) != ext.SPVC_SUCCESS) {
 | 
			
		||||
		return error.OutOfMemory;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer {
 | 
			
		||||
		ext.spvc_context_destroy(context);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ext.spvc_context_set_error_callback(context, log_errors, null);
 | 
			
		||||
 | 
			
		||||
	const arena_allocator = arena.allocator();
 | 
			
		||||
 | 
			
		||||
	return .{
 | 
			
		||||
		.vertex_stage = vertex_stage: {
 | 
			
		||||
			const compiler = try parse(arena, context, options.target, options.vertex_source);
 | 
			
		||||
 | 
			
		||||
			var stage = Stage{
 | 
			
		||||
				.entry_point = try arena_allocator.dupeZ(u8, options.vertex_source.entry_point),
 | 
			
		||||
				.source = try compile(arena, compiler),
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
		const Backend = struct {
 | 
			
		||||
			target: ext.spvc_backend,
 | 
			
		||||
			option_values: []const struct {ext.spvc_compiler_option, c_uint},
 | 
			
		||||
			const resources = create: {
 | 
			
		||||
				var resources: ext.spvc_resources = null;
 | 
			
		||||
 | 
			
		||||
				try to_error(ext.spvc_compiler_create_shader_resources(compiler, &resources));
 | 
			
		||||
 | 
			
		||||
				break: create resources;
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
		const backend: Backend = try switch (sokol.gfx.queryBackend()) {
 | 
			
		||||
			.GLCORE => .{
 | 
			
		||||
				.target = ext.SPVC_BACKEND_GLSL,
 | 
			
		||||
			try stage.reflect_images_samplers(arena, compiler, resources);
 | 
			
		||||
			try stage.reflect_uniform_blocks(arena, compiler, resources);
 | 
			
		||||
 | 
			
		||||
				.option_values = &.{
 | 
			
		||||
					.{ext.SPVC_COMPILER_OPTION_GLSL_VERSION, 430},
 | 
			
		||||
					.{ext.SPVC_COMPILER_OPTION_GLSL_ES, @intFromBool(false)},
 | 
			
		||||
					.{ext.SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, @intFromBool(false)},
 | 
			
		||||
					.{ext.SPVC_COMPILER_OPTION_GLSL_EMIT_UNIFORM_BUFFER_AS_PLAIN_UNIFORMS, @intFromBool(true)},
 | 
			
		||||
					.{ext.SPVC_COMPILER_OPTION_FLIP_VERTEX_Y, @intFromBool(true)},
 | 
			
		||||
				},
 | 
			
		||||
			std.log.info("{s}", .{stage.source});
 | 
			
		||||
 | 
			
		||||
			break: vertex_stage stage;
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
			else => error.UnsupportedBackend,
 | 
			
		||||
		.fragment_stage = fragment_stage: {
 | 
			
		||||
			const compiler = try parse(arena, context, options.target, options.fragment_source);
 | 
			
		||||
 | 
			
		||||
			var stage = Stage{
 | 
			
		||||
				.entry_point = try arena_allocator.dupeZ(u8, options.fragment_source.entry_point),
 | 
			
		||||
				.source = try compile(arena, compiler),
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
		const compiler = parse_and_configure: {
 | 
			
		||||
			const resources = create: {
 | 
			
		||||
				var resources: ext.spvc_resources = null;
 | 
			
		||||
 | 
			
		||||
				try to_error(ext.spvc_compiler_create_shader_resources(compiler, &resources));
 | 
			
		||||
 | 
			
		||||
				break: create resources;
 | 
			
		||||
			};
 | 
			
		||||
 | 
			
		||||
			try stage.reflect_images_samplers(arena, compiler, resources);
 | 
			
		||||
			try stage.reflect_uniform_blocks(arena, compiler, resources);
 | 
			
		||||
 | 
			
		||||
			std.log.info("{s}", .{stage.source});
 | 
			
		||||
 | 
			
		||||
			break: fragment_stage stage;
 | 
			
		||||
		},
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn compile(arena: *std.heap.ArenaAllocator, compiler: ext.spvc_compiler) Error![:0]const u8 {
 | 
			
		||||
	const arena_allocator = arena.allocator();
 | 
			
		||||
	var source: [*:0]const u8 = "";
 | 
			
		||||
 | 
			
		||||
	try to_error(ext.spvc_compiler_compile(compiler, @ptrCast(&source)));
 | 
			
		||||
 | 
			
		||||
	return arena_allocator.dupeZ(u8, std.mem.span(source));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn log_errors(userdata: ?*anyopaque, error_message: [*c]const u8) callconv(.C) void {
 | 
			
		||||
	std.debug.assert(userdata == null);
 | 
			
		||||
	std.log.err("{s}", .{error_message});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn parse(arena: *std.heap.ArenaAllocator, context: ext.spvc_context, target: Target, source: Source) Error!ext.spvc_compiler {
 | 
			
		||||
	var parsed_ir: ext.spvc_parsed_ir = null;
 | 
			
		||||
 | 
			
		||||
			try to_error(ext.spvc_context_parse_spirv(self.context, spirv_ops.ptr, spirv_ops.len, &parsed_ir));
 | 
			
		||||
	try to_error(ext.spvc_context_parse_spirv(context, source.ops.ptr, source.ops.len, &parsed_ir));
 | 
			
		||||
 | 
			
		||||
	var compiler: ext.spvc_compiler = null;
 | 
			
		||||
 | 
			
		||||
			try to_error(ext.spvc_context_create_compiler(self.context, backend.target, parsed_ir, ext.SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler));
 | 
			
		||||
	const spvc_target = switch (target) {
 | 
			
		||||
		.glsl => ext.SPVC_BACKEND_GLSL,
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	try to_error(ext.spvc_context_create_compiler(context, spvc_target, parsed_ir, ext.SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler));
 | 
			
		||||
 | 
			
		||||
	try to_error(ext.spvc_compiler_build_combined_image_samplers(compiler));
 | 
			
		||||
 | 
			
		||||
	var combined_image_samplers: []const ext.spvc_combined_image_sampler = &.{};
 | 
			
		||||
 | 
			
		||||
	try to_error(ext.spvc_compiler_get_combined_image_samplers(compiler, @ptrCast(&combined_image_samplers.ptr), &combined_image_samplers.len));
 | 
			
		||||
 | 
			
		||||
			const arena_allocator = self.arena.allocator();
 | 
			
		||||
	const arena_allocator = arena.allocator();
 | 
			
		||||
	var binding: u32 = 0;
 | 
			
		||||
 | 
			
		||||
	for (combined_image_samplers) |combined_image_sampler| {
 | 
			
		||||
@ -286,83 +366,29 @@ pub const Unit = struct {
 | 
			
		||||
		binding += 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
			break: parse_and_configure compiler;
 | 
			
		||||
	const option_values: []const struct {ext.spvc_compiler_option, c_uint} = switch (target) {
 | 
			
		||||
		.glsl => &.{
 | 
			
		||||
			.{ext.SPVC_COMPILER_OPTION_GLSL_VERSION, 430},
 | 
			
		||||
			.{ext.SPVC_COMPILER_OPTION_GLSL_ES, @intFromBool(false)},
 | 
			
		||||
			.{ext.SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, @intFromBool(false)},
 | 
			
		||||
			.{ext.SPVC_COMPILER_OPTION_GLSL_EMIT_UNIFORM_BUFFER_AS_PLAIN_UNIFORMS, @intFromBool(true)},
 | 
			
		||||
			.{ext.SPVC_COMPILER_OPTION_FLIP_VERTEX_Y, @intFromBool(true)},
 | 
			
		||||
		},
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
		try to_error(ext.spvc_compiler_set_entry_point(compiler, "main", @intCast(execution_model)));
 | 
			
		||||
 | 
			
		||||
		const resources = create: {
 | 
			
		||||
			var resources: ext.spvc_resources = null;
 | 
			
		||||
 | 
			
		||||
			try to_error(ext.spvc_compiler_create_shader_resources(compiler, &resources));
 | 
			
		||||
 | 
			
		||||
			break: create resources;
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		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 compiler_options: ext.spvc_compiler_options = null;
 | 
			
		||||
 | 
			
		||||
	try to_error(ext.spvc_compiler_create_compiler_options(compiler, &compiler_options));
 | 
			
		||||
 | 
			
		||||
			for (backend.option_values) |option_value| {
 | 
			
		||||
	for (option_values) |option_value| {
 | 
			
		||||
		const entry, const value = option_value;
 | 
			
		||||
 | 
			
		||||
		try to_error(ext.spvc_compiler_options_set_uint(compiler_options, entry, value));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
			break: create compiler_options;
 | 
			
		||||
		}));
 | 
			
		||||
	try to_error(ext.spvc_compiler_install_compiler_options(compiler, compiler_options));
 | 
			
		||||
 | 
			
		||||
		try to_error(ext.spvc_compiler_compile(compiler, @ptrCast(&shader.decompiled_source.ptr)));
 | 
			
		||||
 | 
			
		||||
		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 {
 | 
			
		||||
		ext.spvc_context_destroy(self.context);
 | 
			
		||||
		self.arena.deinit();
 | 
			
		||||
 | 
			
		||||
		self.* = undefined;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pub fn init(allocator: std.mem.Allocator) std.mem.Allocator.Error!Unit {
 | 
			
		||||
		var context: ext.spvc_context = null;
 | 
			
		||||
 | 
			
		||||
		if (ext.spvc_context_create(&context) != ext.SPVC_SUCCESS) {
 | 
			
		||||
			return error.OutOfMemory;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		errdefer ext.spvc_context_destroy(context);
 | 
			
		||||
 | 
			
		||||
		ext.spvc_context_set_error_callback(context, log_context_errors, null);
 | 
			
		||||
 | 
			
		||||
		return .{
 | 
			
		||||
			.arena = std.heap.ArenaAllocator.init(allocator),
 | 
			
		||||
			.context = context,
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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 {
 | 
			
		||||
	std.debug.assert(userdata == null);
 | 
			
		||||
	std.log.err("{s}", .{error_message});
 | 
			
		||||
	return compiler;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn to_error(result: ext.spvc_result) Error!void {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user