From 393b550c74ecd873942e954a5cea84325523b8bb Mon Sep 17 00:00:00 2001 From: peterino2 Date: Sun, 17 Aug 2025 18:36:40 -0700 Subject: [PATCH] created spvreflect2 --- lib/sdl3/spvreflect/build.zig | 25 + lib/sdl3/spvreflect/rect.vert.zig | 64 +++ lib/sdl3/spvreflect/reflected.zig | 9 + lib/sdl3/spvreflect/spvReflect2.zig | 719 ++++++++++++++++++++++++++++ 4 files changed, 817 insertions(+) create mode 100644 lib/sdl3/spvreflect/build.zig create mode 100644 lib/sdl3/spvreflect/rect.vert.zig create mode 100644 lib/sdl3/spvreflect/spvReflect2.zig diff --git a/lib/sdl3/spvreflect/build.zig b/lib/sdl3/spvreflect/build.zig new file mode 100644 index 0000000..aae5446 --- /dev/null +++ b/lib/sdl3/spvreflect/build.zig @@ -0,0 +1,25 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const exe = b.addExecutable(.{ + .name = "spvReflect2", + .root_source_file = b.path("spvReflect2.zig"), + .target = target, + .optimize = optimize, + }); + + b.installArtifact(exe); + + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step("run", "Run the JSON parser"); + run_step.dependOn(&run_cmd.step); +} diff --git a/lib/sdl3/spvreflect/rect.vert.zig b/lib/sdl3/spvreflect/rect.vert.zig new file mode 100644 index 0000000..6f66e41 --- /dev/null +++ b/lib/sdl3/spvreflect/rect.vert.zig @@ -0,0 +1,64 @@ +pub const shaderTypes = @import("shaderTypes"); + +pub const int = shaderTypes.int; +pub const uint = shaderTypes.uint; + +pub const vec2 = shaderTypes.vec2; +pub const u8vec4 = shaderTypes.u8vec4; +pub const vec3 = shaderTypes.vec3; +pub const vec4 = shaderTypes.vec4; +pub const mat4 = shaderTypes.mat4; +pub const float = shaderTypes.float; + +pub const BufferInfo = shaderTypes.BufferInfo; +pub const Scene = extern struct { + imagePosition: vec2, + imageSize: vec2, + anchorPoint: vec2, + scale: vec2, + alpha: float, + borderWidth: float, + flags: uint, + pad0: [4]c_char = undefined, + baseColor: vec4, + rounding: vec4, + edgeColor: vec4, + + pub const Buffer: BufferInfo = .{ .storage = 0 }; + + pub const FieldDetails: []shaderTypes.FieldDetail = &.{ + .{ .name = "imagePosition", .offset = 0, .size = 8 }, + .{ .name = "imageSize", .offset = 8, .size = 8 }, + .{ .name = "anchorPoint", .offset = 16, .size = 8 }, + .{ .name = "scale", .offset = 24, .size = 8 }, + .{ .name = "alpha", .offset = 32, .size = 4 }, + .{ .name = "borderWidth", .offset = 36, .size = 4 }, + .{ .name = "flags", .offset = 40, .size = 4 }, + .{ .name = "baseColor", .offset = 48, .size = 16 }, + .{ .name = "rounding", .offset = 64, .size = 16 }, + .{ .name = "edgeColor", .offset = 80, .size = 16 }, + }; +}; + +comptime { + shaderTypes.ValidateGeneratedStruct(Scene); +} +pub const Uniforms = extern struct { + Extents: vec2, + + pub const Buffer: BufferInfo = .{ .uniform = 0 }; + + pub const FieldDetails: []shaderTypes.FieldDetail = &.{ + .{ .name = "Extents", .offset = 0, .size = 8 }, + }; +}; + +comptime { + shaderTypes.ValidateGeneratedStruct(Uniforms); +} +pub const LoadArgs = shaderTypes.ShaderLoadArgs{ + .num_samplers = 0, + .num_storage_textures = 0, + .num_storage_buffers = 1, + .num_uniform_buffers = 1, +}; diff --git a/lib/sdl3/spvreflect/reflected.zig b/lib/sdl3/spvreflect/reflected.zig index daed2f3..5eb9371 100644 --- a/lib/sdl3/spvreflect/reflected.zig +++ b/lib/sdl3/spvreflect/reflected.zig @@ -14,9 +14,18 @@ pub const BufferInfo = shaderTypes.BufferInfo; pub const Uniforms = struct { time: float, + pub const FieldDetails: []shaderTypes.FieldDetail = &.{ + .{ .name = "time", .size = 4, .offset = 0 }, + }; + pub const Buffer: BufferInfo = .{ .uniform = 0 }; }; +// Compile-time validation check +comptime { + shaderTypes.ValidateGeneratedStruct(Uniforms); +} + pub const LoadArgs = shaderTypes.ShaderLoadArgs{ .num_samplers = 1, // The number of samplers defined in the shader. .num_storage_textures = 0, // The number of storage textures defined in the shader. diff --git a/lib/sdl3/spvreflect/spvReflect2.zig b/lib/sdl3/spvreflect/spvReflect2.zig new file mode 100644 index 0000000..edfbe30 --- /dev/null +++ b/lib/sdl3/spvreflect/spvReflect2.zig @@ -0,0 +1,719 @@ +const std = @import("std"); + +fn printLog(comptime fmt: []const u8, args: anytype) void { + //_ = fmt; + // _ = args; + std.debug.print(fmt ++ "\n", args); +} + +fn printErr(comptime fmt: []const u8, args: anytype) void { + std.debug.print(fmt ++ "\n", args); +} + +const FieldEntry = struct { + name: []const u8, + typeName: []const u8, + offset: u32 = 0, + typeIndex: ?usize = null, // this is only set if it's a reference to a type in the typemap +}; + +const TypeEntry = struct { + name: []u8, + size: u32 = 0, + subType: ?usize = null, // if set, this is a list or a uniform, and the underlying type is the real type that we want to generate code for. + stride: u32 = 0, + alignment: u32 = 0, + fields: std.ArrayListUnmanaged(FieldEntry) = .{}, + fieldsArena: std.heap.ArenaAllocator, + + pub fn init(allocator: std.mem.Allocator, name: []const u8) !@This() { + var arena = std.heap.ArenaAllocator.init(allocator); + + const new: @This() = .{ + .name = try arena.allocator().dupe(u8, name), + .fieldsArena = arena, + }; + + return new; + } + + pub fn fieldsAllocator(self: *@This()) std.mem.Allocator { + return self.fieldsArena.allocator(); + } + + pub fn setName(self: *@This(), name: []const u8) !void { + self.fieldsAllocator().free(self.name); + self.name = try self.fieldsAllocator().dupe(u8, name); + } + + pub fn pushField(self: *@This(), field: FieldEntry) !void { + try self.fields.append(self.fieldsAllocator(), field); + } + + pub fn deinit(self: *@This()) void { + self.fields.deinit(self.fieldsAllocator()); + self.fieldsArena.deinit(); + } +}; + +var stringsArena: std.heap.ArenaAllocator = undefined; + +const BuiltinType = struct { + name: []const u8, + size: u32 = 0, + alignment: u32 = 0, +}; + +const builtinTypes: []const BuiltinType = &.{ + .{ .name = "int", .size = 4, .alignment = 4 }, + .{ .name = "uint", .size = 4, .alignment = 4 }, + .{ .name = "float", .size = 4, .alignment = 4 }, + .{ .name = "vec2", .size = 8, .alignment = 8 }, + .{ .name = "vec3", .size = 12, .alignment = 16 }, // vec3 has complex alignment rules + .{ .name = "vec4", .size = 16, .alignment = 16 }, + .{ .name = "u8vec4", .size = 4, .alignment = 1 }, + .{ .name = "mat4", .size = 64, .alignment = 16 }, // mat4 alignment depends on layout +}; + +const TypeMap = struct { + types: std.StringHashMapUnmanaged(usize) = .{}, + typeList: std.ArrayListUnmanaged(TypeEntry) = .{}, + allocator: std.mem.Allocator, + + pub fn init(allocator: std.mem.Allocator) @This() { + return .{ + .allocator = allocator, + }; + } + + pub fn pushType(self: *@This(), typeName: []const u8, entry: TypeEntry) !void { + try self.types.put(self.allocator, try stringsArena.allocator().dupe(u8, typeName), self.typeList.items.len); + try self.typeList.append(self.allocator, entry); + } + + fn isBuiltinType(tag: []const u8) bool { + const builtinTypeTags: []const []const u8 = &.{ + "vec2", + "int", + "uint", + "u8vec4", + "vec3", + "vec4", + "mat4", + "float", + }; + + for (builtinTypeTags) |cmp| { + if (std.mem.eql(u8, cmp, tag)) { + return true; + } + } + + return false; + } + + pub fn parseTypes(self: *@This(), parsed: std.json.Parsed(std.json.Value)) !void { + const root = parsed.value.object; + const typesObj = root.get("types") orelse return; + + var typesIterator = typesObj.object.iterator(); + while (typesIterator.next()) |typeEntry| { + // const typeId = typeEntry.key_ptr.*; + const typeData = typeEntry.value_ptr.*.object; + + const typeName = typeData.get("name").?.string; + var entry = try TypeEntry.init(self.allocator, typeName); + + // Parse members if they exist + if (typeData.get("members")) |membersValue| { + const members = membersValue.array; + for (members.items) |member| { + const memberObj = member.object; + const fieldName = memberObj.get("name").?.string; + const fieldTypeTag = memberObj.get("type").?.string; + const fieldOffset = @as(u32, @intCast(memberObj.get("offset").?.integer)); + + var field: FieldEntry = .{ + .name = fieldName, + .typeName = fieldTypeTag, + .offset = fieldOffset, + }; + + if (!isBuiltinType(fieldTypeTag)) { + if (typesObj.object.get(fieldTypeTag)) |*typeRefObj| { + if (typeRefObj.object.get("name")) |nameObj| { + const fieldType = nameObj.string; + + field = FieldEntry{ + .name = try entry.fieldsAllocator().dupe(u8, fieldName), + .typeName = try entry.fieldsAllocator().dupe(u8, fieldType), + }; + } else { + printErr("No field 'name' in type '{s}'", .{fieldTypeTag}); + } + } else { + printErr("unable to find type object '{s}' in json", .{fieldTypeTag}); + } + } + + try entry.pushField(field); + } + } + + try self.pushType(typeName, entry); + } + + // elaborate type indices + for (self.typeList.items) |*t| { + for (t.fields.items) |*f| { + if (self.types.get(f.typeName)) |typeIndex| { + f.typeIndex = typeIndex; + } + } + } + + // resolve structured buffer subtypes + for (self.typeList.items) |*t| { + const structuredBufferStr = "type.StructuredBuffer."; + if (std.mem.startsWith(u8, t.name, structuredBufferStr)) { + if (t.fields.items.len == 0) { + printErr("structuredBuffer type {s} has no subfields? that's odd..", .{t.name}); + continue; + } + const subName = t.fields.items[0].typeName; + + t.subType = self.types.get(subName); + if (t.subType == null) { + printErr("structuredBuffer type {s} refers to child type {s} which is not found in the json", .{ t.name, subName }); + } + } + } + + // update field sizes + for (self.typeList.items) |*t| { + var lastOffset: u32 = 0; + for (t.fields.items) |field| { + std.debug.assert(lastOffset <= field.offset); + t.size = field.offset + self.getFieldSize(field); + lastOffset = field.offset; + } + } + } + + pub fn getFieldSize(self: *const @This(), field: FieldEntry) u32 { + // First check if it's a builtin type + for (builtinTypes) |builtinType| { + if (std.mem.eql(u8, builtinType.name, field.typeName)) { + return builtinType.size; + } + } + + // If it's a reference to another type in the typemap + if (field.typeIndex) |typeIndex| { + if (typeIndex < self.typeList.items.len) { + return self.typeList.items[typeIndex].size; + } + } + + // Unknown type, return 0 + printErr("Unknown field type '{s}', returning size 0", .{field.typeName}); + return 0; + } + + pub fn printAll(self: *@This()) void { + printLog("TypeMap contains {} types:", .{self.typeList.items.len}); + + var typesIterator = self.types.iterator(); + while (typesIterator.next()) |entry| { + const typeId = entry.key_ptr.*; + const typeIndex = entry.value_ptr.*; + const typeEntry = &self.typeList.items[typeIndex]; + + printLog(" Type ID: '{s}' -> Name: '{s}' index: {d}", .{ typeId, typeEntry.name, typeIndex }); + printLog(" Size: {}, Stride: {}, Alignment: {}", .{ typeEntry.size, typeEntry.stride, typeEntry.alignment }); + + if (typeEntry.fields.items.len > 0) { + printLog(" Fields ({}):", .{typeEntry.fields.items.len}); + for (typeEntry.fields.items) |field| { + printLog(" - {s}: {s}", .{ field.name, field.typeName }); + } + } else { + printLog(" No fields", .{}); + } + printLog("", .{}); + } + } + + pub fn deinit(self: *@This()) void { + for (self.typeList.items) |*t| { + t.deinit(); + } + + self.typeList.deinit(self.allocator); + self.types.deinit(self.allocator); + } +}; + +const UboEntry = struct { + name: []u8, + typeIndex: usize, + set: u32, + binding: u32, + block_size: u32, +}; + +const UboList = struct { + list: std.ArrayListUnmanaged(UboEntry) = .{}, + allocator: std.mem.Allocator, + + pub fn init(allocator: std.mem.Allocator) @This() { + return .{ + .allocator = allocator, + }; + } + + pub fn parseFromJson(self: *@This(), parsed: std.json.Parsed(std.json.Value), map: *const TypeMap) !void { + const root = parsed.value.object; + const list = root.get("ubos") orelse { + printErr("missing ubos entry", .{}); + return; + }; + + if (list != .array) { + printErr("invalid type for list 'ubos' in root, skipping", .{}); + return; + } + + for (list.array.items) |i| { + if (i != .object) { + printErr("invalid type in ubos json list, expected an object", .{}); + continue; + } + + const typeField = root.get("types").?.object.get(i.object.get("type").?.string).?.object.get("name").?.string; + const nameField = i.object.get("name").?.string; + const blockSizeField = @as(u32, @intCast(i.object.get("block_size").?.integer)); + const setField = @as(u32, @intCast(i.object.get("set").?.integer)); + const bindingField = @as(u32, @intCast(i.object.get("binding").?.integer)); + + var entry = UboEntry{ + .name = try stringsArena.allocator().dupe(u8, nameField), + .typeIndex = undefined, + .block_size = blockSizeField, + .set = setField, + .binding = bindingField, + }; + + if (map.types.get(typeField)) |typeIndex| { + entry.typeIndex = typeIndex; + } else { + printErr("Type for UBO '{s}' of type '{s}' not found in provided typemap", .{ nameField, typeField }); + return error.BadUniformBuffer; + } + + try self.list.append(self.allocator, entry); + } + } + + pub fn printAll(self: *@This(), typeMap: *const TypeMap) void { + printLog("UboList contains {} UBOs:", .{self.list.items.len}); + + for (self.list.items, 0..) |ubo, i| { + printLog(" UBO {}: '{s}'", .{ i, ubo.name }); + printLog(" Type Index: {}", .{ubo.typeIndex}); + if (ubo.typeIndex < typeMap.typeList.items.len) { + const typeEntry = &typeMap.typeList.items[ubo.typeIndex]; + printLog(" Type Name: '{s}'", .{typeEntry.name}); + } + printLog(" Set: {}, Binding: {}", .{ ubo.set, ubo.binding }); + printLog(" Block Size: {}", .{ubo.block_size}); + printLog("", .{}); + } + } + + pub fn deinit(self: *@This()) void { + self.list.deinit(self.allocator); + } +}; + +const SsboEntry = struct { + name: []u8, + typeIndex: usize, + readOnly: bool, + set: u32, + binding: u32, + block_size: u32, +}; + +const SsboList = struct { + list: std.ArrayListUnmanaged(SsboEntry) = .{}, + allocator: std.mem.Allocator, + + pub fn init(allocator: std.mem.Allocator) @This() { + return .{ + .allocator = allocator, + }; + } + + pub fn parseFromJson(self: *@This(), parsed: std.json.Parsed(std.json.Value), map: *const TypeMap) !void { + const root = parsed.value.object; + const list = root.get("ssbos") orelse { + printErr("missing ssbos entry", .{}); + return; + }; + + if (list != .array) { + printErr("invalid type for list 'ssbos' in root, skipping", .{}); + return; + } + + for (list.array.items) |i| { + if (i != .object) { + printErr("invalid type in ssbos json list, expected an object", .{}); + continue; + } + + const typeField = root.get("types").?.object.get(i.object.get("type").?.string).?.object.get("name").?.string; + const nameField = i.object.get("name").?.string; + const readOnlyField = i.object.get("readonly").?.bool; + const blockSizeField = @as(u32, @intCast(i.object.get("block_size").?.integer)); + const setField = @as(u32, @intCast(i.object.get("set").?.integer)); + const bindingField = @as(u32, @intCast(i.object.get("binding").?.integer)); + + var entry = SsboEntry{ + .name = try stringsArena.allocator().dupe(u8, nameField), + .typeIndex = undefined, + .readOnly = readOnlyField, + .block_size = blockSizeField, + .set = setField, + .binding = bindingField, + }; + + if (map.types.get(typeField)) |typeIndex| { + entry.typeIndex = typeIndex; + if (map.typeList.items[typeIndex].subType) |subType| { + entry.typeIndex = subType; + } + } else { + printErr("Type for SSBO '{s}' of type '{s}' not found in provided typemap", .{ nameField, typeField }); + return error.BadStructuredBuffer; + } + + try self.list.append(self.allocator, entry); + } + } + + pub fn printAll(self: *@This(), typeMap: *const TypeMap) void { + printLog("SsboList contains {} SSBOs:", .{self.list.items.len}); + + for (self.list.items, 0..) |ssbo, i| { + printLog(" SSBO {}: '{s}'", .{ i, ssbo.name }); + printLog(" Type Index: {}", .{ssbo.typeIndex}); + if (ssbo.typeIndex < typeMap.typeList.items.len) { + const typeEntry = &typeMap.typeList.items[ssbo.typeIndex]; + printLog(" Type Name: '{s}'", .{typeEntry.name}); + } + printLog(" Read Only: {}", .{ssbo.readOnly}); + printLog(" Set: {}, Binding: {}", .{ ssbo.set, ssbo.binding }); + printLog(" Block Size: {}", .{ssbo.block_size}); + printLog("", .{}); + } + } + + pub fn deinit(self: *@This()) void { + self.list.deinit(self.allocator); + } +}; + +const SamplersEntry = struct { + name: []const u8, + set: u32, + binding: u32, +}; + +const SamplersList = struct { + list: std.ArrayListUnmanaged(SamplersEntry) = .{}, + allocator: std.mem.Allocator, + + pub fn init(allocator: std.mem.Allocator) @This() { + return .{ + .allocator = allocator, + }; + } + + pub fn parseSamplers(self: *@This(), parsed: std.json.Parsed(std.json.Value)) !void { + const root = parsed.value.object; + const samplersArray = root.get("separate_samplers") orelse return; + + for (samplersArray.array.items) |samplerItem| { + const samplerObj = samplerItem.object; + + const name = try stringsArena.allocator().dupe(u8, samplerObj.get("name").?.string); + const set = @as(u32, @intCast(samplerObj.get("set").?.integer)); + const binding = @as(u32, @intCast(samplerObj.get("binding").?.integer)); + + const entry = SamplersEntry{ + .name = name, + .set = set, + .binding = binding, + }; + + try self.list.append(self.allocator, entry); + } + } + + pub fn printAll(self: *@This()) void { + printLog("SamplersList contains {} samplers:", .{self.list.items.len}); + + for (self.list.items, 0..) |sampler, i| { + printLog(" Sampler {}: '{s}'", .{ i, sampler.name }); + printLog(" Set: {}, Binding: {}", .{ sampler.set, sampler.binding }); + printLog("", .{}); + } + } + + pub fn deinit(self: *@This()) void { + self.list.deinit(self.allocator); + } +}; + +const OutputFile = struct { + output: std.ArrayList(u8), + typeMap: *const TypeMap, + ssbos: *const SsboList, + ubos: *const UboList, + samplers: *const SamplersList, + + pub fn init( + allocator: std.mem.Allocator, + typeMap: *const TypeMap, + ssbos: *const SsboList, + ubos: *const UboList, + samplers: *const SamplersList, + ) !@This() { + return .{ + .output = std.ArrayList(u8).init(allocator), + .typeMap = typeMap, + .ssbos = ssbos, + .ubos = ubos, + .samplers = samplers, + }; + } + + pub fn finalize(self: *@This()) !void { + try self.output.append(0); + } + + pub fn writeOut(self: *@This(), path: []const u8) !void { + const file = try std.fs.cwd().createFile(path, .{}); + defer file.close(); + + var ast = try std.zig.Ast.parse( + self.output.allocator, + @as([:0]const u8, @ptrCast(self.output.items[0 .. self.output.items.len - 1])), + .zig, + ); + // std.debug.print("output=\n{s}", .{content.items[0 .. content.items.len - 1]}); + defer ast.deinit(self.output.allocator); + const out = try ast.render(self.output.allocator); + + try file.writeAll(out); + } + + pub fn generatePreamble(self: *@This()) !void { + var writer = self.output.writer(); + try writer.print( + \\pub const shaderTypes = @import("shaderTypes"); + \\ + \\pub const int = shaderTypes.int; + \\pub const uint = shaderTypes.uint; + \\ + \\pub const vec2 = shaderTypes.vec2; + \\pub const u8vec4 = shaderTypes.u8vec4; + \\pub const vec3 = shaderTypes.vec3; + \\pub const vec4 = shaderTypes.vec4; + \\pub const mat4 = shaderTypes.mat4; + \\pub const float = shaderTypes.float; + \\ + \\pub const BufferInfo = shaderTypes.BufferInfo; + \\ + , .{}); + } + + pub fn generateSsbos(self: *@This()) !void { + var writer = self.output.writer(); + for (self.ssbos.list.items) |ssbo| { + const ssboType = self.typeMap.typeList.items[ssbo.typeIndex]; + try writer.print("pub const {s} = extern struct {{\n", .{ssboType.name}); + + var lastFieldEnd: u32 = 0; + var paddingCount: u32 = 0; + for (ssboType.fields.items) |field| { + if (lastFieldEnd != field.offset) { + printErr("WARNING: Field {s} in ssbo {s} generated padding, expected offset {d} offset + struct size = {d}", .{ field.name, ssbo.name, field.offset, lastFieldEnd }); + try writer.print(" pad{d}: [{d}]c_char = undefined,\n", .{ paddingCount, field.offset - lastFieldEnd }); + paddingCount += 1; + } + try writer.print(" {s}: {s},\n", .{ field.name, field.typeName }); + lastFieldEnd = field.offset + self.typeMap.getFieldSize(field); + } + + // generate buffer info + + try writer.print("\n pub const Buffer: BufferInfo = .{{ .storage = {d} }};\n", .{ssbo.binding}); + + try writer.print("\n pub const FieldDetails: []shaderTypes.FieldDetail = &.{{ \n", .{}); + for (ssboType.fields.items) |field| { + try writer.print(" " ** 8, .{}); + try writer.print(".{{ .name = \"{s}\", .offset = {d}, .size = {d} }},\n", .{ field.name, field.offset, self.typeMap.getFieldSize(field) }); + } + try writer.print(" }};\n", .{}); + + try writer.print("}};\n", .{}); + } + + try writer.print("\ncomptime {{\n", .{}); + for (self.ssbos.list.items) |ssbo| { + const ssboType = self.typeMap.typeList.items[ssbo.typeIndex]; + try writer.print(" shaderTypes.ValidateGeneratedStruct({s});\n", .{ssboType.name}); + } + try writer.print("}}\n", .{}); + } + + pub fn generateUbos(self: *@This()) !void { + var writer = self.output.writer(); + for (self.ubos.list.items) |ubo| { + const uboType = self.typeMap.typeList.items[ubo.typeIndex]; + try writer.print("pub const Uniforms = extern struct {{\n", .{}); + + var lastFieldEnd: u32 = 0; + var paddingCount: u32 = 0; + for (uboType.fields.items) |field| { + if (lastFieldEnd != field.offset) { + printErr("WARNING: Field {s} in ubo {s} generated padding, expected offset {d} offset + struct size = {d}", .{ field.name, ubo.name, field.offset, lastFieldEnd }); + try writer.print(" pad{d}: [{d}]c_char = undefined,\n", .{ paddingCount, field.offset - lastFieldEnd }); + paddingCount += 1; + } + try writer.print(" {s}: {s},\n", .{ field.name, field.typeName }); + lastFieldEnd = field.offset + self.typeMap.getFieldSize(field); + } + + // generate buffer info using .uniform instead of .storage + try writer.print("\n pub const Buffer: BufferInfo = .{{ .uniform = {d} }};\n", .{ubo.binding}); + + try writer.print("\n pub const FieldDetails: []shaderTypes.FieldDetail = &.{{ \n", .{}); + for (uboType.fields.items) |field| { + try writer.print(" " ** 8, .{}); + try writer.print(".{{ .name = \"{s}\", .offset = {d}, .size = {d} }},\n", .{ field.name, field.offset, self.typeMap.getFieldSize(field) }); + } + try writer.print(" }};\n", .{}); + + try writer.print("}};\n", .{}); + } + + try writer.print("\ncomptime {{\n", .{}); + for (self.ubos.list.items) |ubo| { + const uboType = self.typeMap.typeList.items[ubo.typeIndex]; + _ = uboType; + try writer.print(" shaderTypes.ValidateGeneratedStruct({s});\n", .{"Uniforms"}); + } + try writer.print("}}\n", .{}); + } + + pub fn generateLoadArguments(self: *@This()) !void { + var writer = self.output.writer(); + + try writer.print("pub const LoadArgs = shaderTypes.ShaderLoadArgs {{\n", .{}); + + try writer.print( + \\.num_samplers = {d}, + \\.num_storage_textures = {d}, + \\.num_storage_buffers = {d}, + \\.num_uniform_buffers = {d}, + , .{ + self.samplers.list.items.len, + 0, // storage textures to be added later + self.ssbos.list.items.len, + self.ubos.list.items.len, + }); + try writer.print("}};\n", .{}); + } + + pub fn deinit(self: *@This()) void { + self.output.deinit(); + } +}; + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + + var arena = std.heap.ArenaAllocator.init(gpa.allocator()); + defer arena.deinit(); + + const allocator = arena.allocator(); + stringsArena = std.heap.ArenaAllocator.init(gpa.allocator()); + defer stringsArena.deinit(); + + const args = try std.process.argsAlloc(allocator); + defer std.process.argsFree(allocator, args); + + if (args.len < 3) { + printLog("Usage: {s} ", .{args[0]}); + return; + } + + const jsonFilePath = args[1]; + + const file = std.fs.cwd().openFile(jsonFilePath, .{}) catch |err| { + printErr("Error opening file '{s}': {}", .{ jsonFilePath, err }); + return; + }; + defer file.close(); + + const fileSize = try file.getEndPos(); + const jsonContent = try allocator.alloc(u8, fileSize); + _ = try file.readAll(jsonContent); + + var parsed = std.json.parseFromSlice(std.json.Value, allocator, jsonContent, .{}) catch |err| { + printErr("Error parsing JSON: {}", .{err}); + return; + }; + defer parsed.deinit(); + + var types: TypeMap = TypeMap.init(allocator); + try types.parseTypes(parsed); + defer types.deinit(); + + types.printAll(); + + // create ssbolist and parse out the ssbolist from the typeslist + var ssbos = SsboList.init(allocator); + try ssbos.parseFromJson(parsed, &types); + defer ssbos.deinit(); + ssbos.printAll(&types); + + // create ubolist and parse out the ubolist from the typeslist + var ubos = UboList.init(allocator); + try ubos.parseFromJson(parsed, &types); + defer ubos.deinit(); + + ubos.printAll(&types); + + // parse out the samplers + var samplers = SamplersList.init(allocator); + try samplers.parseSamplers(parsed); + defer samplers.deinit(); + + var outputfile = try OutputFile.init(allocator, &types, &ssbos, &ubos, &samplers); + try outputfile.generatePreamble(); + try outputfile.generateSsbos(); + try outputfile.generateUbos(); + try outputfile.generateLoadArguments(); + printLog("{s}", .{outputfile.output.items}); + try outputfile.finalize(); + try outputfile.writeOut(args[2]); + defer outputfile.deinit(); + + printLog("Successfully parsed JSON from: {s}", .{jsonFilePath}); +}