diff --git a/.vscode/launch.json b/.vscode/launch.json index 89d52ec..e0928e0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "target": "${workspaceRoot}/zig-out/bin/main", "cwd": "${workspaceRoot}/debug/", - "valuesFormatting": "parseText", + "valuesFormatting": "prettyPrinters", "preLaunchTask": "Build All" }, ] diff --git a/src/coral/ascii.zig b/src/coral/ascii.zig index 2d4e26a..0339e7d 100644 --- a/src/coral/ascii.zig +++ b/src/coral/ascii.zig @@ -115,7 +115,7 @@ pub const DecimalFormat = struct { } } - pub fn print(self: DecimalFormat, writer: io.Writer, value: anytype) io.PrintError!void { + pub fn format(self: DecimalFormat, writer: io.Writer, value: anytype) io.PrintError!void { if (value == 0) { return io.print(writer, switch (self.positive_prefix) { .none => "0", @@ -178,13 +178,13 @@ pub const HexadecimalFormat = struct { positive_prefix: enum {none, plus, space} = .none, casing: enum {lower, upper} = .lower, - const default = HexadecimalFormat{ + pub const default = HexadecimalFormat{ .delimiter = "", .positive_prefix = .none, .casing = .lower, }; - pub fn print(self: HexadecimalFormat, writer: io.Writer, value: anytype) ?usize { + pub fn format(self: HexadecimalFormat, writer: io.Writer, value: anytype) io.Error!void { // TODO: Implement. _ = self; _ = writer; diff --git a/src/coral/io.zig b/src/coral/io.zig index 383825e..545b64f 100644 --- a/src/coral/io.zig +++ b/src/coral/io.zig @@ -108,13 +108,23 @@ pub fn Generator(comptime Output: type, comptime input_types: []const type) type }; } -pub const PrintError = Error || error { - IncompleteWrite, +pub const NullWritable = struct { + written: usize = 0, + + pub fn write(self: *NullWritable, buffer: []const Byte) Error!usize { + self.written += buffer.len; + + return buffer.len; + } + + pub fn writer(self: *NullWritable) Writer { + return Writer.bind(NullWritable, self, write); + } }; -pub const Reader = Generator(Error!usize, &.{[]coral.Byte}); +pub const Reader = Generator(Error!usize, &.{[]Byte}); -pub const Writer = Generator(Error!usize, &.{[]const coral.Byte}); +pub const Writer = Generator(Error!usize, &.{[]const Byte}); pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral.Byte { const buffer = coral.Stack(coral.Byte){.allocator = allocator}; @@ -138,12 +148,6 @@ pub fn bytes_of(value: anytype) []const Byte { }; } -pub fn print(writer: Writer, utf8: []const u8) PrintError!void { - if (try writer.yield(.{utf8}) != utf8.len) { - return error.IncompleteWrite; - } -} - pub fn skip_n(input: Reader, distance: u64) Error!void { var buffer = @as([512]coral.Byte, undefined); var remaining = distance; @@ -159,7 +163,7 @@ pub fn skip_n(input: Reader, distance: u64) Error!void { } } -pub fn slice_sentineled(comptime sen: anytype, ptr: [*:sen]const @TypeOf(sen)) [:sen]const @TypeOf(sen) { +pub fn slice_sentineled(comptime Sentinel: type, comptime sen: Sentinel, ptr: [*:sen]const Sentinel) [:sen]const Sentinel { var len = @as(usize, 0); while (ptr[len] != sen) { @@ -206,3 +210,9 @@ pub fn stream_n(input: Reader, output: Writer, limit: usize) Error!usize { remaining -= read; } } + +pub fn write_all(writer: Writer, utf8: []const u8) Error!void { + if (try writer.yield(.{utf8}) != utf8.len) { + return error.UnavailableResource; + } +} diff --git a/src/coral/utf8.zig b/src/coral/utf8.zig index 2d02b5c..4992107 100644 --- a/src/coral/utf8.zig +++ b/src/coral/utf8.zig @@ -19,15 +19,54 @@ pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8 return buffer.to_allocation(formatted_len, 0); } -fn count_formatted(comptime format: []const u8, args: anytype) usize { - var count = io.defaultWritable{}; +pub fn count_formatted(comptime format: []const u8, args: anytype) usize { + var count = io.NullWritable{}; - print_formatted(count.writer(), format, args) catch unreachable; + write_formatted(count.writer(), format, args) catch unreachable; return count.written; } -pub fn print_formatted(writer: io.Writer, comptime format: []const u8, args: anytype) io.PrintError!void { +pub fn print_formatted(buffer: [:0]coral.io.Byte, comptime format: []const u8, args: anytype) io.Error![:0]u8 { + const Seekable = struct { + buffer: []coral.io.Byte, + cursor: usize, + + const Self = @This(); + + fn write(self: *Self, input: []const coral.io.Byte) io.Error!usize { + const range = @min(input.len, self.buffer.len - self.cursor); + const tail = self.cursor + range; + + @memcpy(self.buffer[self.cursor .. tail], input); + + self.cursor = tail; + + return range; + } + }; + + const len = count_formatted(format, args); + + if (len > buffer.len) { + return error.UnavailableResource; + } + + var seekable = Seekable{ + .buffer = buffer, + .cursor = 0, + }; + + try write_formatted(coral.io.Writer.bind(Seekable, &seekable, Seekable.write), format, args); + + if (buffer.len < len) { + buffer[len] = 0; + } + + return buffer[0 .. len:0]; +} + +pub fn write_formatted(writer: io.Writer, comptime format: []const u8, args: anytype) io.Error!void { switch (@typeInfo(@TypeOf(args))) { .Struct => |arguments_struct| { comptime var arg_index = 0; @@ -67,7 +106,7 @@ pub fn print_formatted(writer: io.Writer, comptime format: []const u8, args: any @compileError("format specifiers cannot be named when using a tuple struct"); } - try io.print(writer, format[head .. (tail - 1)]); + try io.write_all(writer, format[head .. (tail - 1)]); head = tail; tail += 1; @@ -93,14 +132,14 @@ pub fn print_formatted(writer: io.Writer, comptime format: []const u8, args: any } } - try io.print(writer, format[head .. ]); + try io.write_all(writer, format[head .. ]); }, else => @compileError("`arguments` must be a struct type"), } } -noinline fn print_formatted_value(writer: io.Writer, value: anytype) io.PrintError!void { +noinline fn print_formatted_value(writer: io.Writer, value: anytype) io.Error!void { const Value = @TypeOf(value); return switch (@typeInfo(Value)) { @@ -109,9 +148,9 @@ noinline fn print_formatted_value(writer: io.Writer, value: anytype) io.PrintErr .Enum => io.print(writer, @tagName(value)), .Pointer => |pointer| switch (pointer.size) { - .Many, .C => ascii.HexadecimalFormat.default.print(writer, @intFromPtr(value)), - .One => if (pointer.child == []const u8) io.print(writer, *value) else ascii.HexadecimalFormat.default.print(writer, @intFromPtr(value)), - .Slice => if (pointer.child == u8) io.print(writer, value) else @compileError(unformattableMessage(Value)), + .Many, .C => ascii.HexadecimalFormat.default.format(writer, @intFromPtr(value)), + .One => if (pointer.child == []const u8) io.write_all(writer, *value) else ascii.HexadecimalFormat.default.print(writer, @intFromPtr(value)), + .Slice => if (pointer.child == u8) io.write_all(writer, value) else @compileError(unformattableMessage(Value)), }, else => @compileError(unformattableMessage(Value)), diff --git a/src/ona/gfx/device.zig b/src/ona/gfx/device.zig index c43619b..60a2f73 100644 --- a/src/ona/gfx/device.zig +++ b/src/ona/gfx/device.zig @@ -380,7 +380,7 @@ const Loop = struct { ext.SDL_GL_DeleteContext(context); } - var rendering_2d = try Rendering2D.init(coral.heap.allocator); + var rendering_2d = try Rendering2D.init(); defer rendering_2d.deinit(); @@ -535,6 +535,12 @@ const Rendering2D = struct { const mesh_2d = resource_cast(frame.mesh.?).payload.mesh_2d; + const texture_image, const texture_sampler = switch (resource_cast(frame.texture.?).payload) { + .texture => |texture| .{texture.image, texture.sampler}, + .render_target => |render_target| .{render_target.color_image, render_target.sampler}, + else => unreachable, + }; + var bindings = sokol.gfx.Bindings{ .vertex_buffers = get: { var buffers = [_]sokol.gfx.Buffer{.{}} ** 8; @@ -546,44 +552,40 @@ const Rendering2D = struct { .index_buffer = mesh_2d.index_buffer, - .fs = switch (resource_cast(frame.texture.?).payload) { - .texture => |texture| .{ - .images = get: { - var images = [_]sokol.gfx.Image{.{}} ** 12; + .fs = .{ + .images = get: { + var images = [_]sokol.gfx.Image{.{}} ** 12; - images[0] = texture.image; + images[0] = texture_image; - break: get images; - }, - - .samplers = get: { - var samplers = [_]sokol.gfx.Sampler{.{}} ** 8; - - samplers[0] = texture.sampler; - - break: get samplers; - }, + break: get images; }, - .render_target => |render_target| .{ - .images = get: { - var images = [_]sokol.gfx.Image{.{}} ** 12; + .samplers = get: { + var samplers = [_]sokol.gfx.Sampler{.{}} ** 8; - images[0] = render_target.color_image; + samplers[0] = texture_sampler; - break: get images; - }, + break: get samplers; + }, + }, - .samplers = get: { - var samplers = [_]sokol.gfx.Sampler{.{}} ** 8; + .vs = .{ + .images = get: { + var images = [_]sokol.gfx.Image{.{}} ** 12; - samplers[0] = render_target.sampler; + images[0] = texture_image; - break: get samplers; - }, + break: get images; }, - else => unreachable, + .samplers = get: { + var samplers = [_]sokol.gfx.Sampler{.{}} ** 8; + + samplers[0] = texture_sampler; + + break: get samplers; + }, }, }; @@ -602,7 +604,7 @@ const Rendering2D = struct { } } - fn init(allocator: std.mem.Allocator) spirv.CompileError!Rendering2D { + fn init() spirv.Error!Rendering2D { sokol.gfx.setup(.{ .environment = .{ .defaults = .{ @@ -617,17 +619,15 @@ const Rendering2D = struct { }, }); - var arena = std.heap.ArenaAllocator.init(allocator); + var spirv_unit = try spirv.Unit.init(); - defer arena.deinit(); + defer spirv_unit.deinit(); - const shader_desc = try spirv.compile(&arena, .{ - .fragment_spirv = shader_spirv[0 ..], - .vertex_spirv = shader_spirv[0 ..], - }); + try spirv_unit.compile(shader_spirv[0 ..], .vertex); + try spirv_unit.compile(shader_spirv[0 ..], .fragment); - std.log.info("{s}\n", .{shader_desc.fs.source}); - std.log.info("{s}", .{shader_desc.vs.source}); + std.log.info("{s}\n", .{spirv_unit.shader_desc.fs.source}); + std.log.info("{s}", .{spirv_unit.shader_desc.vs.source}); return .{ .batching_pipeline = sokol.gfx.makePipeline(.{ @@ -689,7 +689,7 @@ const Rendering2D = struct { }, }, - .shader = sokol.gfx.makeShader(shader_desc), + .shader = sokol.gfx.makeShader(spirv_unit.shader_desc), .index_type = .UINT16, }), diff --git a/src/ona/gfx/spirv.zig b/src/ona/gfx/spirv.zig index 857f4c2..9794360 100644 --- a/src/ona/gfx/spirv.zig +++ b/src/ona/gfx/spirv.zig @@ -8,123 +8,285 @@ const sokol = @import("sokol"); const std = @import("std"); -pub const CompileError = std.mem.Allocator.Error || error { +pub const Error = std.mem.Allocator.Error || error { UnsupportedTarget, - InvalidSpirV, - UnsupportedSpirv, - UnknownFailure, + InvalidSPIRV, + UnsupportedSPIRV, }; -pub const Sources = struct { - vertex_spirv: []const u32, - fragment_spirv: []const u32, +pub const Unit = struct { + context: ext.spvc_context, + shader_desc: sokol.gfx.ShaderDesc, + attrs_used: u32 = 0, + + pub fn compile(self: *Unit, spirv: []const u32, stage: Stage) Error!void { + const execution_model, const stage_desc = switch (stage) { + .vertex => .{ext.SpvExecutionModelVertex, &self.shader_desc.vs}, + .fragment => .{ext.SpvExecutionModelFragment, &self.shader_desc.fs}, + }; + + const Backend = struct { + target: ext.spvc_backend, + option_values: []const struct {ext.spvc_compiler_option, c_uint}, + }; + + const backend: Backend = switch (sokol.gfx.queryBackend()) { + .GLCORE => .{ + .target = ext.SPVC_BACKEND_GLSL, + + .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)}, + }, + }, + + else => @panic("Unimplemented"), + }; + + const compiler = parse_and_configure: { + var parsed_ir: ext.spvc_parsed_ir = null; + + try to_error(ext.spvc_context_parse_spirv(self.context, spirv.ptr, spirv.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)); + 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)); + + var binding: u32 = 0; + + for (combined_image_samplers) |combined_image_sampler| { + var name_buffer = [_:0]u8{0} ** 255; + + const name = coral.utf8.print_formatted(&name_buffer, "{image_name}_{sampler_name}", .{ + .image_name = coral.io.slice_sentineled(u8, 0, ext.spvc_compiler_get_name(compiler, combined_image_sampler.image_id)), + .sampler_name = coral.io.slice_sentineled(u8, 0, ext.spvc_compiler_get_name(compiler, combined_image_sampler.sampler_id)), + }) catch { + return error.InvalidSPIRV; + }; + + ext.spvc_compiler_set_name(compiler, combined_image_sampler.combined_id, name); + ext.spvc_compiler_set_decoration(compiler, combined_image_sampler.combined_id, ext.SpvDecorationBinding, binding); + + binding += 1; + } + + break: parse_and_configure compiler; + }; + + try to_error(ext.spvc_compiler_set_entry_point(compiler, stage_desc.entry, @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 reflect_uniform_blocks(compiler, resources, stage_desc); + try reflect_image_samplers(compiler, resources, stage_desc); + + try to_error(ext.spvc_compiler_install_compiler_options(compiler, create: { + var options: ext.spvc_compiler_options = null; + + try to_error(ext.spvc_compiler_create_compiler_options(compiler, &options)); + + for (backend.option_values) |option_value| { + const entry, const value = option_value; + + try to_error(ext.spvc_compiler_options_set_uint(options, entry, value)); + } + + break: create options; + })); + + try to_error(ext.spvc_compiler_compile(compiler, @ptrCast(&stage_desc.source))); + } + + pub fn deinit(self: *Unit) void { + ext.spvc_context_destroy(self.context); + + self.* = undefined; + } + + pub fn init() 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 .{ + .context = context, + + .shader_desc = .{ + .vs = .{.entry = "main"}, + .fs = .{.entry = "main"}, + }, + }; + } }; -pub fn compile(arena: *std.heap.ArenaAllocator, sources: Sources) CompileError!sokol.gfx.ShaderDesc { - var context = @as(ext.spvc_context, null); +pub const Stage = enum { + fragment, + vertex, +}; - try switch (ext.spvc_context_create(&context)) { - ext.SPVC_ERROR_OUT_OF_MEMORY => error.OutOfMemory, - ext.SPVC_SUCCESS => {}, - else => error.UnknownFailure, - }; - - ext.spvc_context_set_error_callback(context, log_errors, null); - - defer ext.spvc_context_destroy(context); - - const arena_allocator = arena.allocator(); - const shader_source_sentinel = @as(u8, 0); - - return .{ - .vs = .{ - .source = try arena_allocator.dupeZ(u8, coral.io.slice_sentineled( - shader_source_sentinel, - @ptrCast(try compile_shader(context, ext.SpvExecutionModelVertex, sources.vertex_spirv)))), - }, - - .fs = .{ - .source = try arena_allocator.dupeZ(u8, coral.io.slice_sentineled( - shader_source_sentinel, - @ptrCast(try compile_shader(context, ext.SpvExecutionModelFragment, sources.fragment_spirv)))), - }, - }; -} - -fn compile_shader(context: ext.spvc_context, model: ext.SpvExecutionModel, spirv: []const u32) CompileError![*]const u8 { - var parsed_ir = @as(ext.spvc_parsed_ir, null); - - try switch (ext.spvc_context_parse_spirv(context, spirv.ptr, spirv.len, &parsed_ir)) { - ext.SPVC_ERROR_OUT_OF_MEMORY => error.OutOfMemory, - ext.SPVC_ERROR_INVALID_SPIRV => error.InvalidSpirV, - ext.SPVC_SUCCESS => {}, - else => error.UnknownFailure, - }; - - var compiler = @as(ext.spvc_compiler, null); - - try switch (ext.spvc_context_create_compiler(context, ext.SPVC_BACKEND_GLSL, parsed_ir, ext.SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler)) { - ext.SPVC_ERROR_OUT_OF_MEMORY => error.OutOfMemory, - ext.SPVC_ERROR_INVALID_ARGUMENT => error.UnsupportedTarget, - ext.SPVC_SUCCESS => {}, - else => error.UnknownFailure, - }; - - try switch (ext.spvc_compiler_build_combined_image_samplers(compiler)) { - ext.SPVC_ERROR_UNSUPPORTED_SPIRV => error.UnsupportedSpirv, - ext.SPVC_SUCCESS => {}, - else => error.UnknownFailure, - }; - - try switch (ext.spvc_compiler_set_entry_point(compiler, "main", model)) { - ext.SPVC_SUCCESS => {}, - else => error.UnknownFailure, - }; - - var options = @as(ext.spvc_compiler_options, null); - - try switch (ext.spvc_compiler_create_compiler_options(compiler, &options)) { - ext.SPVC_ERROR_OUT_OF_MEMORY => error.OutOfMemory, - ext.SPVC_SUCCESS => {}, - else => error.UnknownFailure, - }; - - try switch (ext.spvc_compiler_options_set_uint(options, ext.SPVC_COMPILER_OPTION_GLSL_VERSION, 430)) { - ext.SPVC_ERROR_INVALID_ARGUMENT => error.InvalidSpirV, - ext.SPVC_SUCCESS => {}, - else => error.UnknownFailure, - }; - - try switch (ext.spvc_compiler_options_set_bool(options, ext.SPVC_COMPILER_OPTION_GLSL_ES, @intFromBool(false))) { - ext.SPVC_ERROR_INVALID_ARGUMENT => error.InvalidSpirV, - ext.SPVC_SUCCESS => {}, - else => error.UnknownFailure, - }; - - try switch (ext.spvc_compiler_options_set_bool(options, ext.SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, @intFromBool(false))) { - ext.SPVC_ERROR_INVALID_ARGUMENT => error.InvalidSpirV, - ext.SPVC_SUCCESS => {}, - else => error.UnknownFailure, - }; - - try switch (ext.spvc_compiler_install_compiler_options(compiler, options)) { - ext.SPVC_SUCCESS => {}, - else => error.UnknownFailure, - }; - - var source = @as([*]const u8, undefined); - - try switch (ext.spvc_compiler_compile(compiler, @ptrCast(&source))) { - ext.SPVC_ERROR_OUT_OF_MEMORY => error.OutOfMemory, - ext.SPVC_ERROR_UNSUPPORTED_SPIRV => error.UnsupportedSpirv, - ext.SPVC_SUCCESS => {}, - else => error.UnknownFailure, - }; - - return source; -} - -fn log_errors(userdata: ?*anyopaque, error_message: [*c]const u8) callconv(.C) void { +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}); } + +fn reflect_image_samplers(compiler: ext.spvc_compiler, resources: ext.spvc_resources, stage: *sokol.gfx.ShaderStageDesc) Error!void { + var reflected_sampled_images: []const ext.spvc_reflected_resource = &.{}; + + try to_error(ext.spvc_resources_get_resource_list_for_type( + resources, + ext.SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, + @ptrCast(&reflected_sampled_images.ptr), + &reflected_sampled_images.len, + )); + + if (reflected_sampled_images.len > stage.image_sampler_pairs.len) { + return error.UnsupportedSPIRV; + } + + for (0 .. reflected_sampled_images.len, reflected_sampled_images) |i, reflected_sampled_image| { + const sampled_image_type = ext.spvc_compiler_get_type_handle(compiler, reflected_sampled_image.type_id); + + if (ext.spvc_type_get_basetype(sampled_image_type) != ext.SPVC_BASETYPE_SAMPLED_IMAGE) { + return error.InvalidSPIRV; + } + + stage.images[i] = .{ + .multisampled = ext.spvc_type_get_image_multisampled(sampled_image_type) != 0, + + .image_type = try switch (ext.spvc_type_get_image_dimension(sampled_image_type)) { + ext.SpvDim2D => sokol.gfx.ImageType._2D, + else => error.InvalidSPIRV, + }, + + .sample_type = try switch (ext.spvc_type_get_basetype(ext.spvc_compiler_get_type_handle(compiler, ext.spvc_type_get_image_sampled_type(sampled_image_type)))) { + ext.SPVC_BASETYPE_FP32 => sokol.gfx.ImageSampleType.FLOAT, + else => error.InvalidSPIRV, + }, + + .used = true, + }; + + stage.samplers[i] = .{ + .sampler_type = .DEFAULT, + .used = true, + }; + + stage.image_sampler_pairs[i] = .{ + .glsl_name = ext.spvc_compiler_get_name(compiler, reflected_sampled_image.id), + .image_slot = @intCast(i), + .sampler_slot = @intCast(i), + .used = true, + }; + } +} + +fn reflect_uniform_blocks(compiler: ext.spvc_compiler, resources: ext.spvc_resources, stage: *sokol.gfx.ShaderStageDesc) Error!void { + var reflected_uniform_buffers: []const ext.spvc_reflected_resource = &.{}; + + try to_error(ext.spvc_resources_get_resource_list_for_type( + resources, + ext.SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, + @ptrCast(&reflected_uniform_buffers.ptr), + &reflected_uniform_buffers.len, + )); + + if (reflected_uniform_buffers.len > stage.uniform_blocks.len) { + return error.UnsupportedSPIRV; + } + + for (stage.uniform_blocks[0 .. reflected_uniform_buffers.len], reflected_uniform_buffers) |*uniform_block, reflected_uniform_buffer| { + const uniform_buffer_type = ext.spvc_compiler_get_type_handle(compiler, reflected_uniform_buffer.type_id); + + if (ext.spvc_type_get_basetype(uniform_buffer_type) != ext.SPVC_BASETYPE_STRUCT) { + return error.InvalidSPIRV; + } + + const member_count = ext.spvc_type_get_num_member_types(uniform_buffer_type); + + if (member_count > uniform_block.uniforms.len) { + return error.UnsupportedSPIRV; + } + + try to_error(ext.spvc_compiler_get_declared_struct_size(compiler, uniform_buffer_type, &uniform_block.size)); + + var uniform_blocks_used: u32 = 0; + + while (uniform_blocks_used < member_count) : (uniform_blocks_used += 1) { + const member_type_id = ext.spvc_type_get_member_type(uniform_buffer_type, uniform_blocks_used); + const member_type = ext.spvc_compiler_get_type_handle(compiler, member_type_id); + + uniform_block.uniforms[uniform_blocks_used] = .{ + // .name = ext.spvc_compiler_get_member_name(compiler, ext.spvc_type_get_base_type_id(uniform_buffer_type), uniform_blocks_used), + + .array_count = @intCast(try switch (ext.spvc_type_get_num_array_dimensions(member_type)) { + 0 => 0, + + 1 => switch (ext.spvc_type_array_dimension_is_literal(member_type, 1) != 0) { + true => ext.spvc_type_get_array_dimension(member_type, 1), + false => error.InvalidSPIRV, + }, + + else => error.InvalidSPIRV, + }), + + .type = try switch (ext.spvc_type_get_basetype(member_type)) { + ext.SPVC_BASETYPE_INT32 => switch (ext.spvc_type_get_vector_size(member_type)) { + 1 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.INT else error.InvalidSPIRV, + 2 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.INT2 else error.InvalidSPIRV, + 3 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.INT3 else error.InvalidSPIRV, + 4 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.INT4 else error.InvalidSPIRV, + else => error.InvalidSPIRV, + }, + + ext.SPVC_BASETYPE_FP32 => switch (ext.spvc_type_get_vector_size(member_type)) { + 1 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.FLOAT else error.InvalidSPIRV, + 2 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.FLOAT2 else error.InvalidSPIRV, + 3 => if (ext.spvc_type_get_columns(member_type) == 1) sokol.gfx.UniformType.FLOAT3 else error.InvalidSPIRV, + + 4 => switch (ext.spvc_type_get_columns(member_type)) { + 1 => sokol.gfx.UniformType.FLOAT4, + 4 => sokol.gfx.UniformType.MAT4, + else => error.InvalidSPIRV, + }, + + else => error.InvalidSPIRV, + }, + + else => error.InvalidSPIRV, + }, + }; + + // uniform_block.uniforms[uniform_blocks_used].name = ext.spvc_compiler_get_member_name(compiler, ext.spvc_type_get_base_type_id(uniform_buffer_type), uniform_blocks_used); + } + } +} + +fn to_error(result: ext.spvc_result) Error!void { + return switch (result) { + ext.SPVC_SUCCESS => {}, + ext.SPVC_ERROR_INVALID_SPIRV => error.InvalidSPIRV, + ext.SPVC_ERROR_UNSUPPORTED_SPIRV => error.UnsupportedSPIRV, + ext.SPVC_ERROR_OUT_OF_MEMORY => error.OutOfMemory, + ext.SPVC_ERROR_INVALID_ARGUMENT, ext.SPVC_ERROR_INT_MAX => unreachable, + else => unreachable, + }; +}