diff --git a/build.zig b/build.zig index 376045f..c4629b8 100644 --- a/build.zig +++ b/build.zig @@ -33,7 +33,7 @@ pub fn build(b: *std.Build) void { }); } - // remaindure of the script is for local testing + // remainder of the script is for local testing const triangle_exe = b.addExecutable(.{ .name = "triangle", diff --git a/generator/main.zig b/generator/main.zig index 18f7213..93d20b5 100644 --- a/generator/main.zig +++ b/generator/main.zig @@ -1,7 +1,13 @@ const std = @import("std"); -const generate = @import("vulkan/generator.zig").generate; +const generator = @import("vulkan/generator.zig"); -const usage = "Usage: {s} [-h|--help] \n"; +const usage = + \\Usage: {s} [options] + \\Options: + \\-h --help show this message and exit. + \\-a --api Generate API for 'vulkan' or 'vulkansc'. Defaults to 'vulkan'. + \\ +; pub fn main() !void { const stderr = std.io.getStdErr(); @@ -15,6 +21,7 @@ pub fn main() !void { var maybe_xml_path: ?[]const u8 = null; var maybe_out_path: ?[]const u8 = null; + var api = generator.Api.vulkan; while (args.next()) |arg| { if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) { @@ -32,12 +39,22 @@ pub fn main() !void { .{prog_name}, ); return; + } else if (std.mem.eql(u8, arg, "-a") or std.mem.eql(u8, arg, "--api")) { + const api_str = args.next() orelse { + try stderr.writer().print("Error: {s} expects argument \n", .{arg}); + return; + }; + api = std.meta.stringToEnum(generator.Api, api_str) orelse { + try stderr.writer().print("Error: Invalid api '{s}'", .{api_str}); + return; + }; } else if (maybe_xml_path == null) { maybe_xml_path = arg; } else if (maybe_out_path == null) { maybe_out_path = arg; } else { try stderr.writer().print("Error: Superficial argument '{s}'\n", .{arg}); + return; } } @@ -58,7 +75,7 @@ pub fn main() !void { }; var out_buffer = std.ArrayList(u8).init(allocator); - try generate(allocator, xml_src, out_buffer.writer()); + try generator.generate(allocator, api, xml_src, out_buffer.writer()); try out_buffer.append(0); const src = out_buffer.items[0 .. out_buffer.items.len - 1 :0]; diff --git a/generator/vulkan/build_integration.zig b/generator/vulkan/build_integration.zig index bae6d1c..2cc55bb 100644 --- a/generator/vulkan/build_integration.zig +++ b/generator/vulkan/build_integration.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const generate = @import("generator.zig").generate; +const generator = @import("generator.zig"); const path = std.fs.path; const Build = std.Build; const Step = Build.Step; @@ -14,6 +14,10 @@ pub const GenerateStep = struct { generated_file: std.build.GeneratedFile, /// The path to vk.xml spec_path: []const u8, + /// The API to generate for. + /// Defaults to Vulkan. + // Note: VulkanSC is experimental. + api: generator.Api = .vulkan, /// Initialize a Vulkan generation step, for `builder`. `spec_path` is the path to /// vk.xml, relative to the project root. The generated bindings will be placed at @@ -44,6 +48,11 @@ pub const GenerateStep = struct { return create(builder, spec_path, output_name); } + /// Set the API to generate for. + pub fn setApi(self: *GenerateStep, api_to_generate: generator.Api) void { + self.api = api_to_generate; + } + /// Returns the module with the generated budings, with name `module_name`. pub fn getModule(self: *GenerateStep) *std.build.Module { return self.builder.createModule(.{ @@ -87,7 +96,7 @@ pub const GenerateStep = struct { } var out_buffer = std.ArrayList(u8).init(self.builder.allocator); - generate(self.builder.allocator, spec, out_buffer.writer()) catch |err| switch (err) { + generator.generate(self.builder.allocator, self.api, spec, out_buffer.writer()) catch |err| switch (err) { error.InvalidXml => { std.log.err("invalid vulkan registry - invalid xml", .{}); std.log.err("please check that the correct vk.xml file is passed", .{}); diff --git a/generator/vulkan/generator.zig b/generator/vulkan/generator.zig index 68db98b..702a899 100644 --- a/generator/vulkan/generator.zig +++ b/generator/vulkan/generator.zig @@ -99,8 +99,8 @@ pub const Generator = struct { registry: reg.Registry, id_renderer: IdRenderer, - fn init(allocator: Allocator, spec: *xml.Element) !Generator { - const result = try parseXml(allocator, spec); + fn init(allocator: Allocator, spec: *xml.Element, api: reg.Api) !Generator { + const result = try parseXml(allocator, spec, api); const tags = try allocator.alloc([]const u8, result.registry.tags.len); for (tags, result.registry.tags) |*tag, registry_tag| tag.* = registry_tag.name; @@ -170,11 +170,15 @@ pub const Generator = struct { } }; +/// The vulkan registry contains the specification for multiple APIs: Vulkan and VulkanSC. This enum +/// describes applicable APIs. +pub const Api = reg.Api; + /// Main function for generating the Vulkan bindings. vk.xml is to be provided via `spec_xml`, /// and the resulting binding is written to `writer`. `allocator` will be used to allocate temporary /// internal datastructures - mostly via an ArenaAllocator, but sometimes a hashmap uses this allocator -/// directly. -pub fn generate(allocator: Allocator, spec_xml: []const u8, writer: anytype) !void { +/// directly. `api` is the API to generate the bindings for, usually `.vulkan`. +pub fn generate(allocator: Allocator, api: Api, spec_xml: []const u8, writer: anytype) !void { const spec = xml.parse(allocator, spec_xml) catch |err| switch (err) { error.InvalidDocument, error.UnexpectedEof, @@ -191,7 +195,7 @@ pub fn generate(allocator: Allocator, spec_xml: []const u8, writer: anytype) !vo }; defer spec.deinit(); - var gen = Generator.init(allocator, spec.root) catch |err| switch (err) { + var gen = Generator.init(allocator, spec.root, api) catch |err| switch (err) { error.InvalidXml, error.InvalidCharacter, error.Overflow, diff --git a/generator/vulkan/parse.zig b/generator/vulkan/parse.zig index 46e8188..63b3f36 100644 --- a/generator/vulkan/parse.zig +++ b/generator/vulkan/parse.zig @@ -17,18 +17,18 @@ pub const ParseResult = struct { } }; -pub fn parseXml(backing_allocator: Allocator, root: *xml.Element) !ParseResult { +pub fn parseXml(backing_allocator: Allocator, root: *xml.Element, api: registry.Api) !ParseResult { var arena = ArenaAllocator.init(backing_allocator); errdefer arena.deinit(); const allocator = arena.allocator(); var reg = registry.Registry{ - .decls = try parseDeclarations(allocator, root), - .api_constants = try parseApiConstants(allocator, root), + .decls = try parseDeclarations(allocator, root, api), + .api_constants = try parseApiConstants(allocator, root, api), .tags = try parseTags(allocator, root), - .features = try parseFeatures(allocator, root), - .extensions = try parseExtensions(allocator, root), + .features = try parseFeatures(allocator, root, api), + .extensions = try parseExtensions(allocator, root, api), }; return ParseResult{ @@ -37,7 +37,7 @@ pub fn parseXml(backing_allocator: Allocator, root: *xml.Element) !ParseResult { }; } -fn parseDeclarations(allocator: Allocator, root: *xml.Element) ![]registry.Declaration { +fn parseDeclarations(allocator: Allocator, root: *xml.Element, api: registry.Api) ![]registry.Declaration { var types_elem = root.findChildByTag("types") orelse return error.InvalidRegistry; var commands_elem = root.findChildByTag("commands") orelse return error.InvalidRegistry; @@ -45,17 +45,20 @@ fn parseDeclarations(allocator: Allocator, root: *xml.Element) ![]registry.Decla const decls = try allocator.alloc(registry.Declaration, decl_upper_bound); var count: usize = 0; - count += try parseTypes(allocator, decls, types_elem); - count += try parseEnums(allocator, decls[count..], root); - count += try parseCommands(allocator, decls[count..], commands_elem); + count += try parseTypes(allocator, decls, types_elem, api); + count += try parseEnums(allocator, decls[count..], root, api); + count += try parseCommands(allocator, decls[count..], commands_elem, api); return decls[0..count]; } -fn parseTypes(allocator: Allocator, out: []registry.Declaration, types_elem: *xml.Element) !usize { +fn parseTypes(allocator: Allocator, out: []registry.Declaration, types_elem: *xml.Element, api: registry.Api) !usize { var i: usize = 0; var it = types_elem.findChildrenByTag("type"); while (it.next()) |ty| { out[i] = blk: { + if (!requiredByApi(ty, api)) + continue; + const category = ty.getAttribute("category") orelse { break :blk try parseForeigntype(ty); }; @@ -67,9 +70,9 @@ fn parseTypes(allocator: Allocator, out: []registry.Declaration, types_elem: *xm } else if (mem.eql(u8, category, "basetype")) { break :blk try parseBaseType(allocator, ty); } else if (mem.eql(u8, category, "struct")) { - break :blk try parseContainer(allocator, ty, false); + break :blk try parseContainer(allocator, ty, false, api); } else if (mem.eql(u8, category, "union")) { - break :blk try parseContainer(allocator, ty, true); + break :blk try parseContainer(allocator, ty, true, api); } else if (mem.eql(u8, category, "funcpointer")) { break :blk try parseFuncPointer(allocator, ty); } else if (mem.eql(u8, category, "enum")) { @@ -173,7 +176,7 @@ fn parseBaseType(allocator: Allocator, ty: *xml.Element) !registry.Declaration { } } -fn parseContainer(allocator: Allocator, ty: *xml.Element, is_union: bool) !registry.Declaration { +fn parseContainer(allocator: Allocator, ty: *xml.Element, is_union: bool, api: registry.Api) !registry.Declaration { const name = ty.getAttribute("name") orelse return error.InvalidRegistry; if (ty.getAttribute("alias")) |alias| { @@ -191,6 +194,9 @@ fn parseContainer(allocator: Allocator, ty: *xml.Element, is_union: bool) !regis var it = ty.findChildrenByTag("member"); var maybe_stype: ?[]const u8 = null; while (it.next()) |member| { + if (!requiredByApi(member, api)) + continue; + var xctok = cparse.XmlCTokenizer.init(member); members[i] = try cparse.parseMember(allocator, &xctok, false); if (mem.eql(u8, members[i].name, "sType")) { @@ -227,7 +233,10 @@ fn parseContainer(allocator: Allocator, ty: *xml.Element, is_union: bool) !regis it = ty.findChildrenByTag("member"); for (members) |*member| { - const member_elem = it.next().?; + const member_elem = while (it.next()) |elem| { + if (requiredByApi(elem, api)) break elem; + } else unreachable; + try parsePointerMeta(.{ .container = members }, &member.field_type, member_elem); // pNext isn't always properly marked as optional, so just manually override it, @@ -300,7 +309,7 @@ fn parsePointerMeta(fields: Fields, type_info: *registry.TypeInfo, elem: *xml.El const size_optional = lenToPointer(fields, len_str); current_type_info.pointer.is_optional = size_optional[1]; break :blk size_optional[0]; - } else .many; + } else .many; current_type_info.pointer.size = size; current_type_info = current_type_info.pointer.child; } @@ -308,6 +317,7 @@ fn parsePointerMeta(fields: Fields, type_info: *registry.TypeInfo, elem: *xml.El if (it.next()) |_| { // There are more elements in the `len` attribute than there are pointers // Something probably went wrong + std.log.err("len: {s}", .{lens}); return error.InvalidRegistry; } } @@ -342,18 +352,18 @@ fn parseEnumAlias(elem: *xml.Element) !?registry.Declaration { return null; } -fn parseEnums(allocator: Allocator, out: []registry.Declaration, root: *xml.Element) !usize { +fn parseEnums(allocator: Allocator, out: []registry.Declaration, root: *xml.Element, api: registry.Api) !usize { var i: usize = 0; var it = root.findChildrenByTag("enums"); while (it.next()) |enums| { const name = enums.getAttribute("name") orelse return error.InvalidRegistry; - if (mem.eql(u8, name, api_constants_name)) { + if (mem.eql(u8, name, api_constants_name) or !requiredByApi(enums, api)) { continue; } out[i] = .{ .name = name, - .decl_type = .{ .enumeration = try parseEnumFields(allocator, enums) }, + .decl_type = .{ .enumeration = try parseEnumFields(allocator, enums, api) }, }; i += 1; } @@ -361,7 +371,7 @@ fn parseEnums(allocator: Allocator, out: []registry.Declaration, root: *xml.Elem return i; } -fn parseEnumFields(allocator: Allocator, elem: *xml.Element) !registry.Enum { +fn parseEnumFields(allocator: Allocator, elem: *xml.Element, api: registry.Api) !registry.Enum { // TODO: `type` was added recently, fall back to checking endswith FlagBits for older versions? const enum_type = elem.getAttribute("type") orelse return error.InvalidRegistry; const is_bitmask = mem.eql(u8, enum_type, "bitmask"); @@ -379,6 +389,9 @@ fn parseEnumFields(allocator: Allocator, elem: *xml.Element) !registry.Enum { var i: usize = 0; var it = elem.findChildrenByTag("enum"); while (it.next()) |field| { + if (!requiredByApi(field, api)) + continue; + fields[i] = try parseEnumField(field); i += 1; } @@ -428,11 +441,14 @@ fn parseEnumField(field: *xml.Element) !registry.Enum.Field { }; } -fn parseCommands(allocator: Allocator, out: []registry.Declaration, commands_elem: *xml.Element) !usize { +fn parseCommands(allocator: Allocator, out: []registry.Declaration, commands_elem: *xml.Element, api: registry.Api) !usize { var i: usize = 0; var it = commands_elem.findChildrenByTag("command"); while (it.next()) |elem| { - out[i] = try parseCommand(allocator, elem); + if (!requiredByApi(elem, api)) + continue; + + out[i] = try parseCommand(allocator, elem, api); i += 1; } @@ -454,7 +470,7 @@ fn splitCommaAlloc(allocator: Allocator, text: []const u8) ![][]const u8 { return codes; } -fn parseCommand(allocator: Allocator, elem: *xml.Element) !registry.Declaration { +fn parseCommand(allocator: Allocator, elem: *xml.Element, api: registry.Api) !registry.Declaration { if (elem.getAttribute("alias")) |alias| { const name = elem.getAttribute("name") orelse return error.InvalidRegistry; return registry.Declaration{ @@ -474,6 +490,9 @@ fn parseCommand(allocator: Allocator, elem: *xml.Element) !registry.Declaration var i: usize = 0; var it = elem.findChildrenByTag("param"); while (it.next()) |param| { + if (!requiredByApi(param, api)) + continue; + var xctok = cparse.XmlCTokenizer.init(param); const decl = try cparse.parseParamOrProto(allocator, &xctok, false); params[i] = .{ @@ -501,7 +520,10 @@ fn parseCommand(allocator: Allocator, elem: *xml.Element) !registry.Declaration it = elem.findChildrenByTag("param"); for (params) |*param| { - const param_elem = it.next().?; + const param_elem = while (it.next()) |param_elem| { + if (requiredByApi(param_elem, api)) break param_elem; + } else unreachable; + try parsePointerMeta(.{ .command = params }, ¶m.param_type, param_elem); } @@ -518,7 +540,7 @@ fn parseCommand(allocator: Allocator, elem: *xml.Element) !registry.Declaration }; } -fn parseApiConstants(allocator: Allocator, root: *xml.Element) ![]registry.ApiConstant { +fn parseApiConstants(allocator: Allocator, root: *xml.Element, api: registry.Api) ![]registry.ApiConstant { var enums = blk: { var it = root.findChildrenByTag("enums"); while (it.next()) |child| { @@ -550,6 +572,9 @@ fn parseApiConstants(allocator: Allocator, root: *xml.Element) ![]registry.ApiCo var i: usize = 0; var it = enums.findChildrenByTag("enum"); while (it.next()) |constant| { + if (!requiredByApi(constant, api)) + continue; + const expr = if (constant.getAttribute("value")) |expr| expr else if (constant.getAttribute("alias")) |alias| @@ -565,21 +590,24 @@ fn parseApiConstants(allocator: Allocator, root: *xml.Element) ![]registry.ApiCo i += 1; } - i += try parseDefines(types, constants[i..]); + i += try parseDefines(types, constants[i..], api); return constants[0..i]; } -fn parseDefines(types: *xml.Element, out: []registry.ApiConstant) !usize { +fn parseDefines(types: *xml.Element, out: []registry.ApiConstant, api: registry.Api) !usize { var i: usize = 0; var it = types.findChildrenByTag("type"); while (it.next()) |ty| { + if (!requiredByApi(ty, api)) + continue; + const category = ty.getAttribute("category") orelse continue; if (!mem.eql(u8, category, "define")) { continue; } const name = ty.getCharData("name") orelse continue; - if (mem.eql(u8, name, "VK_HEADER_VERSION")) { + if (mem.eql(u8, name, "VK_HEADER_VERSION") or mem.eql(u8, name, "VKSC_API_VARIANT")) { out[i] = .{ .name = name, .value = .{ .expr = mem.trim(u8, ty.children[2].char_data, " ") }, @@ -615,7 +643,7 @@ fn parseTags(allocator: Allocator, root: *xml.Element) ![]registry.Tag { return tags[0..i]; } -fn parseFeatures(allocator: Allocator, root: *xml.Element) ![]registry.Feature { +fn parseFeatures(allocator: Allocator, root: *xml.Element, api: registry.Api) ![]registry.Feature { var it = root.findChildrenByTag("feature"); var count: usize = 0; while (it.next()) |_| count += 1; @@ -624,14 +652,17 @@ fn parseFeatures(allocator: Allocator, root: *xml.Element) ![]registry.Feature { var i: usize = 0; it = root.findChildrenByTag("feature"); while (it.next()) |feature| { - features[i] = try parseFeature(allocator, feature); + if (!requiredByApi(feature, api)) + continue; + + features[i] = try parseFeature(allocator, feature, api); i += 1; } - return features; + return features[0..i]; } -fn parseFeature(allocator: Allocator, feature: *xml.Element) !registry.Feature { +fn parseFeature(allocator: Allocator, feature: *xml.Element, api: registry.Api) !registry.Feature { const name = feature.getAttribute("name") orelse return error.InvalidRegistry; const feature_level = blk: { const number = feature.getAttribute("number") orelse return error.InvalidRegistry; @@ -642,7 +673,10 @@ fn parseFeature(allocator: Allocator, feature: *xml.Element) !registry.Feature { var i: usize = 0; var it = feature.findChildrenByTag("require"); while (it.next()) |require| { - requires[i] = try parseRequire(allocator, require, null); + if (!requiredByApi(require, api)) + continue; + + requires[i] = try parseRequire(allocator, require, null, api); i += 1; } @@ -702,7 +736,7 @@ fn enumExtOffsetToValue(extnumber: u31, offset: u31) u31 { return extension_value_base + (extnumber - 1) * extension_block + offset; } -fn parseRequire(allocator: Allocator, require: *xml.Element, extnumber: ?u31) !registry.Require { +fn parseRequire(allocator: Allocator, require: *xml.Element, extnumber: ?u31, api: registry.Api) !registry.Require { var n_extends: usize = 0; var n_types: usize = 0; var n_commands: usize = 0; @@ -728,6 +762,9 @@ fn parseRequire(allocator: Allocator, require: *xml.Element, extnumber: ?u31) !r it = require.elements(); while (it.next()) |elem| { + if (!requiredByApi(elem, api)) + continue; + if (mem.eql(u8, elem.tag, "enum")) { if (try parseEnumExtension(elem, extnumber)) |ext| { extends[i_extends] = ext; @@ -753,20 +790,22 @@ fn parseRequire(allocator: Allocator, require: *xml.Element, extnumber: ?u31) !r return registry.Require{ .extends = extends[0..i_extends], - .types = types, - .commands = commands, + .types = types[0..i_types], + .commands = commands[0..i_commands], .required_feature_level = required_feature_level, .required_extension = require.getAttribute("extension"), }; } -fn parseExtensions(allocator: Allocator, root: *xml.Element) ![]registry.Extension { +fn parseExtensions(allocator: Allocator, root: *xml.Element, api: registry.Api) ![]registry.Extension { const extensions_elem = root.findChildByTag("extensions") orelse return error.InvalidRegistry; const extensions = try allocator.alloc(registry.Extension, extensions_elem.children.len); var i: usize = 0; var it = extensions_elem.findChildrenByTag("extension"); while (it.next()) |extension| { + if (!requiredByApi(extension, api)) + continue; // Some extensions (in particular 94) are disabled, so just skip them if (extension.getAttribute("supported")) |supported| { if (mem.eql(u8, supported, "disabled")) { @@ -774,7 +813,7 @@ fn parseExtensions(allocator: Allocator, root: *xml.Element) ![]registry.Extensi } } - extensions[i] = try parseExtension(allocator, extension); + extensions[i] = try parseExtension(allocator, extension, api); i += 1; } @@ -797,7 +836,7 @@ fn findExtVersion(extension: *xml.Element) !u32 { return error.InvalidRegistry; } -fn parseExtension(allocator: Allocator, extension: *xml.Element) !registry.Extension { +fn parseExtension(allocator: Allocator, extension: *xml.Element, api: registry.Api) !registry.Extension { const name = extension.getAttribute("name") orelse return error.InvalidRegistry; const platform = extension.getAttribute("platform"); const version = try findExtVersion(extension); @@ -845,7 +884,9 @@ fn parseExtension(allocator: Allocator, extension: *xml.Element) !registry.Exten var i: usize = 0; var it = extension.findChildrenByTag("require"); while (it.next()) |require| { - requires[i] = try parseRequire(allocator, require, number); + if (!requiredByApi(require, api)) + continue; + requires[i] = try parseRequire(allocator, require, number, api); i += 1; } @@ -876,3 +917,14 @@ fn splitFeatureLevel(ver: []const u8, split: []const u8) !registry.FeatureLevel .minor = try std.fmt.parseInt(u32, minor, 10), }; } + +fn requiredByApi(elem: *xml.Element, api: registry.Api) bool { + const apis = elem.getAttribute("api") orelse return true; // If the 'api' element is not present, assume required. + + var it = mem.split(u8, apis, ","); + while (it.next()) |required_by_api| { + if (std.mem.eql(u8, @tagName(api), required_by_api)) return true; + } + + return false; +} diff --git a/generator/vulkan/registry.zig b/generator/vulkan/registry.zig index dfaac2c..b443574 100644 --- a/generator/vulkan/registry.zig +++ b/generator/vulkan/registry.zig @@ -1,3 +1,8 @@ +pub const Api = enum { + vulkan, + vulkansc, +}; + pub const Registry = struct { decls: []Declaration, api_constants: []ApiConstant, diff --git a/generator/vulkan/render.zig b/generator/vulkan/render.zig index 1e7ca43..3ea1228 100644 --- a/generator/vulkan/render.zig +++ b/generator/vulkan/render.zig @@ -192,6 +192,7 @@ fn Renderer(comptime WriterType: type) type { for (registry.decls) |*decl| { const result = try declarations_by_name.getOrPut(decl.name); if (result.found_existing) { + std.log.err("duplicate registry entry '{s}'", .{decl.name}); return error.InvalidRegistry; }