Add vulkan video support

Implements #174.
This commit is contained in:
Robin Voetter
2025-03-15 02:42:05 +01:00
parent dcd538828c
commit dcb1d96c59
11 changed files with 544 additions and 321 deletions

View File

@@ -22,7 +22,7 @@ fn reportParseErrors(tree: std.zig.Ast) !void {
}
}
pub fn main() void {
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
@@ -34,6 +34,7 @@ pub fn main() void {
var maybe_xml_path: ?[]const u8 = null;
var maybe_out_path: ?[]const u8 = null;
var maybe_video_xml_path: ?[]const u8 = null;
var debug: bool = false;
var api = generator.Api.vulkan;
@@ -52,7 +53,9 @@ pub fn main() void {
\\Options:
\\-h --help show this message and exit.
\\-a --api <api> Generate API for 'vulkan' or 'vulkansc'. Defaults to 'vulkan'.
\\--debug Write out unformatted source if does not parse correctly.
\\--debug Write out unformatted source if does not parse correctly.
\\--video <path> Also gnerate Vulkan Video API bindings from video.xml
\\ registry at <path>.
\\
,
.{prog_name},
@@ -68,12 +71,16 @@ pub fn main() void {
api = std.meta.stringToEnum(generator.Api, api_str) orelse {
invalidUsage(prog_name, "invalid api '{s}'", .{api_str});
};
} else if (std.mem.eql(u8, arg, "--debug")) {
debug = true;
} else if (std.mem.eql(u8, arg, "--video")) {
maybe_video_xml_path = args.next() orelse {
invalidUsage(prog_name, "{s} expects argument <path>", .{arg});
};
} else if (maybe_xml_path == null) {
maybe_xml_path = arg;
} else if (maybe_out_path == null) {
maybe_out_path = arg;
} else if (std.mem.eql(u8, arg, "--debug")) {
debug = true;
} else {
invalidUsage(prog_name, "superficial argument '{s}'", .{arg});
}
@@ -93,8 +100,16 @@ pub fn main() void {
std.process.exit(1);
};
const maybe_video_xml_src = if (maybe_video_xml_path) |video_xml_path|
cwd.readFileAlloc(allocator, video_xml_path, std.math.maxInt(usize)) catch |err| {
std.log.err("failed to open input file '{s}' ({s})", .{ video_xml_path, @errorName(err) });
std.process.exit(1);
}
else
null;
var out_buffer = std.ArrayList(u8).init(allocator);
generator.generate(allocator, api, xml_src, out_buffer.writer()) catch |err| switch (err) {
generator.generate(allocator, api, xml_src, maybe_video_xml_src, 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", .{});

View File

@@ -90,10 +90,18 @@ pub const CTokenizer = struct {
const start = self.offset;
_ = self.consumeNoEof();
const hex = self.peek() == 'x';
if (hex) {
_ = self.consumeNoEof();
}
while (true) {
const c = self.peek() orelse break;
switch (c) {
switch (self.peek() orelse break) {
'0'...'9' => _ = self.consumeNoEof(),
'A'...'F', 'a'...'f' => {
if (!hex) break;
_ = self.consumeNoEof();
},
else => break,
}
}
@@ -164,7 +172,12 @@ pub const XmlCTokenizer = struct {
}
fn elemToToken(elem: *xml.Element) !?Token {
if (elem.children.len != 1 or elem.children[0] != .char_data) {
// Sometimes we encounter empty comment tags. Filter those out
// by early returning here, otherwise the next check will
// determine that the input is not valid XML.
if (mem.eql(u8, elem.tag, "comment")) {
return null;
} else if (elem.children.len != 1 or elem.children[0] != .char_data) {
return error.InvalidXml;
}
@@ -175,8 +188,6 @@ pub const XmlCTokenizer = struct {
return Token{ .kind = .enum_name, .text = text };
} else if (mem.eql(u8, elem.tag, "name")) {
return Token{ .kind = .name, .text = text };
} else if (mem.eql(u8, elem.tag, "comment")) {
return null;
} else {
return error.InvalidTag;
}
@@ -530,7 +541,10 @@ fn parseArrayDeclarator(xctok: *XmlCTokenizer) !?ArraySize {
error.InvalidCharacter => unreachable,
},
},
.enum_name => .{ .alias = size_tok.text },
// Sometimes, arrays are declared as `<type>T</type> <name>aa</name>[<enum>SIZE</enum>]`,
// and sometimes just as `<type>T</type> <name>aa</name>[SIZE]`, so we have to account
// for both `.enum_name` and `.id` here.
.enum_name, .id => .{ .alias = size_tok.text },
else => return error.InvalidSyntax,
};
@@ -538,7 +552,7 @@ fn parseArrayDeclarator(xctok: *XmlCTokenizer) !?ArraySize {
return size;
}
pub fn parseVersion(xctok: *XmlCTokenizer) ![4][]const u8 {
pub fn parseVersion(xctok: *XmlCTokenizer) !registry.ApiConstant.Value {
_ = try xctok.expect(.hash);
const define = try xctok.expect(.id);
if (!mem.eql(u8, define.text, "define")) {
@@ -547,12 +561,22 @@ pub fn parseVersion(xctok: *XmlCTokenizer) ![4][]const u8 {
_ = try xctok.expect(.name);
const vk_make_version = try xctok.expect(.type_name);
if (!mem.eql(u8, vk_make_version.text, "VK_MAKE_API_VERSION")) {
if (mem.eql(u8, vk_make_version.text, "VK_MAKE_API_VERSION")) {
return .{
.version = try parseVersionValues(xctok, 4),
};
} else if (mem.eql(u8, vk_make_version.text, "VK_MAKE_VIDEO_STD_VERSION")) {
return .{
.video_std_version = try parseVersionValues(xctok, 3),
};
} else {
return error.NotVersion;
}
}
fn parseVersionValues(xctok: *XmlCTokenizer, comptime count: usize) ![count][]const u8 {
_ = try xctok.expect(.lparen);
var version: [4][]const u8 = undefined;
var version: [count][]const u8 = undefined;
for (&version, 0..) |*part, i| {
if (i != 0) {
_ = try xctok.expect(.comma);

View File

@@ -10,11 +10,13 @@ const FeatureLevel = reg.FeatureLevel;
const EnumFieldMerger = struct {
const EnumExtensionMap = std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(reg.Enum.Field));
const ApiConstantMap = std.StringArrayHashMapUnmanaged(reg.ApiConstant);
const FieldSet = std.StringArrayHashMapUnmanaged(void);
arena: Allocator,
registry: *reg.Registry,
enum_extensions: EnumExtensionMap,
api_constants: ApiConstantMap,
field_set: FieldSet,
fn init(arena: Allocator, registry: *reg.Registry) EnumFieldMerger {
@@ -22,6 +24,7 @@ const EnumFieldMerger = struct {
.arena = arena,
.registry = registry,
.enum_extensions = .{},
.api_constants = .{},
.field_set = .{},
};
}
@@ -38,7 +41,17 @@ const EnumFieldMerger = struct {
fn addRequires(self: *EnumFieldMerger, reqs: []const reg.Require) !void {
for (reqs) |req| {
for (req.extends) |enum_ext| {
try self.putEnumExtension(enum_ext.extends, enum_ext.field);
switch (enum_ext.value) {
.field => try self.putEnumExtension(enum_ext.extends, enum_ext.value.field),
.new_api_constant_expr => |expr| try self.api_constants.put(
self.arena,
enum_ext.extends,
.{
.name = enum_ext.extends,
.value = .{ .expr = expr },
},
),
}
}
}
}
@@ -76,6 +89,10 @@ const EnumFieldMerger = struct {
}
fn merge(self: *EnumFieldMerger) !void {
for (self.registry.api_constants) |api_constant| {
try self.api_constants.put(self.arena, api_constant.name, api_constant);
}
for (self.registry.features) |feature| {
try self.addRequires(feature.requires);
}
@@ -91,6 +108,8 @@ const EnumFieldMerger = struct {
try self.mergeEnumFields(decl.name, &decl.decl_type.enumeration);
}
}
self.registry.api_constants = self.api_constants.values();
}
};
@@ -98,9 +117,10 @@ pub const Generator = struct {
arena: std.heap.ArenaAllocator,
registry: reg.Registry,
id_renderer: IdRenderer,
have_video: bool,
fn init(allocator: Allocator, spec: *xml.Element, api: reg.Api) !Generator {
const result = try parseXml(allocator, spec, api);
fn init(allocator: Allocator, spec: *xml.Element, maybe_video_spec: ?*xml.Element, api: reg.Api) !Generator {
const result = try parseXml(allocator, spec, maybe_video_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;
@@ -109,6 +129,7 @@ pub const Generator = struct {
.arena = result.arena,
.registry = result.registry,
.id_renderer = IdRenderer.init(allocator, tags),
.have_video = maybe_video_spec != null,
};
}
@@ -166,7 +187,7 @@ pub const Generator = struct {
}
fn render(self: *Generator, writer: anytype) !void {
try renderRegistry(writer, self.arena.allocator(), &self.registry, &self.id_renderer);
try renderRegistry(writer, self.arena.allocator(), &self.registry, &self.id_renderer, self.have_video);
}
};
@@ -178,7 +199,7 @@ pub const Api = reg.Api;
/// 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. `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 {
pub fn generate(allocator: Allocator, api: Api, spec_xml: []const u8, maybe_video_spec_xml: ?[]const u8, writer: anytype) !void {
const spec = xml.parse(allocator, spec_xml) catch |err| switch (err) {
error.InvalidDocument,
error.UnexpectedEof,
@@ -195,7 +216,26 @@ pub fn generate(allocator: Allocator, api: Api, spec_xml: []const u8, writer: an
};
defer spec.deinit();
var gen = Generator.init(allocator, spec.root, api) catch |err| switch (err) {
const maybe_video_spec_root = if (maybe_video_spec_xml) |video_spec_xml| blk: {
const video_spec = xml.parse(allocator, video_spec_xml) catch |err| switch (err) {
error.InvalidDocument,
error.UnexpectedEof,
error.UnexpectedCharacter,
error.IllegalCharacter,
error.InvalidEntity,
error.InvalidName,
error.InvalidStandaloneValue,
error.NonMatchingClosingTag,
error.UnclosedComment,
error.UnclosedValue,
=> return error.InvalidXml,
error.OutOfMemory => return error.OutOfMemory,
};
break :blk video_spec.root;
} else null;
var gen = Generator.init(allocator, spec.root, maybe_video_spec_root, api) catch |err| switch (err) {
error.InvalidXml,
error.InvalidCharacter,
error.Overflow,

View File

@@ -17,18 +17,43 @@ pub const ParseResult = struct {
}
};
pub fn parseXml(backing_allocator: Allocator, root: *xml.Element, api: registry.Api) !ParseResult {
pub fn parseXml(
backing_allocator: Allocator,
root: *xml.Element,
maybe_video_root: ?*xml.Element,
api: registry.Api,
) !ParseResult {
var arena = ArenaAllocator.init(backing_allocator);
errdefer arena.deinit();
const allocator = arena.allocator();
var decls: std.ArrayListUnmanaged(registry.Declaration) = .{};
var api_constants: std.ArrayListUnmanaged(registry.ApiConstant) = .{};
var tags: std.ArrayListUnmanaged(registry.Tag) = .{};
var features: std.ArrayListUnmanaged(registry.Feature) = .{};
var extensions: std.ArrayListUnmanaged(registry.Extension) = .{};
try parseDeclarations(allocator, root, api, &decls);
try parseApiConstants(allocator, root, api, &api_constants);
try parseTags(allocator, root, &tags);
try parseFeatures(allocator, root, api, &features);
try parseExtensions(allocator, root, api, &extensions);
if (maybe_video_root) |video_root| {
try parseDeclarations(allocator, video_root, api, &decls);
try parseApiConstants(allocator, video_root, api, &api_constants);
try parseTags(allocator, video_root, &tags);
try parseFeatures(allocator, video_root, api, &features);
try parseExtensions(allocator, video_root, api, &extensions);
}
const reg = registry.Registry{
.decls = try parseDeclarations(allocator, root, api),
.api_constants = try parseApiConstants(allocator, root, api),
.tags = try parseTags(allocator, root),
.features = try parseFeatures(allocator, root, api),
.extensions = try parseExtensions(allocator, root, api),
.decls = decls.items,
.api_constants = api_constants.items,
.tags = tags.items,
.features = features.items,
.extensions = extensions.items,
};
return ParseResult{
@@ -37,25 +62,33 @@ pub fn parseXml(backing_allocator: Allocator, root: *xml.Element, api: registry.
};
}
fn parseDeclarations(allocator: Allocator, root: *xml.Element, api: registry.Api) ![]registry.Declaration {
fn parseDeclarations(
allocator: Allocator,
root: *xml.Element,
api: registry.Api,
decls: *std.ArrayListUnmanaged(registry.Declaration),
) !void {
const types_elem = root.findChildByTag("types") orelse return error.InvalidRegistry;
const commands_elem = root.findChildByTag("commands") orelse return error.InvalidRegistry;
try decls.ensureUnusedCapacity(allocator, types_elem.children.len);
const decl_upper_bound = types_elem.children.len + commands_elem.children.len;
const decls = try allocator.alloc(registry.Declaration, decl_upper_bound);
try parseTypes(allocator, types_elem, api, decls);
try parseEnums(allocator, root, api, decls);
var count: usize = 0;
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];
if (root.findChildByTag("commands")) |commands_elem| {
try decls.ensureUnusedCapacity(allocator, commands_elem.children.len);
try parseCommands(allocator, commands_elem, api, decls);
}
}
fn parseTypes(allocator: Allocator, out: []registry.Declaration, types_elem: *xml.Element, api: registry.Api) !usize {
var i: usize = 0;
fn parseTypes(
allocator: Allocator,
types_elem: *xml.Element,
api: registry.Api,
decls: *std.ArrayListUnmanaged(registry.Declaration),
) !void {
var it = types_elem.findChildrenByTag("type");
while (it.next()) |ty| {
out[i] = blk: {
try decls.append(allocator, blk: {
if (!requiredByApi(ty, api))
continue;
@@ -80,12 +113,8 @@ fn parseTypes(allocator: Allocator, out: []registry.Declaration, types_elem: *xm
}
continue;
};
i += 1;
});
}
return i;
}
fn parseForeigntype(ty: *xml.Element) !registry.Declaration {
@@ -388,8 +417,7 @@ fn parseEnumAlias(elem: *xml.Element) !?registry.Declaration {
return null;
}
fn parseEnums(allocator: Allocator, out: []registry.Declaration, root: *xml.Element, api: registry.Api) !usize {
var i: usize = 0;
fn parseEnums(allocator: Allocator, root: *xml.Element, api: registry.Api, decls: *std.ArrayListUnmanaged(registry.Declaration)) !void {
var it = root.findChildrenByTag("enums");
while (it.next()) |enums| {
const name = enums.getAttribute("name") orelse return error.InvalidRegistry;
@@ -397,14 +425,11 @@ fn parseEnums(allocator: Allocator, out: []registry.Declaration, root: *xml.Elem
continue;
}
out[i] = .{
try decls.append(allocator, .{
.name = name,
.decl_type = .{ .enumeration = try parseEnumFields(allocator, enums, api) },
};
i += 1;
});
}
return i;
}
fn parseEnumFields(allocator: Allocator, elem: *xml.Element, api: registry.Api) !registry.Enum {
@@ -477,18 +502,19 @@ fn parseEnumField(field: *xml.Element) !registry.Enum.Field {
};
}
fn parseCommands(allocator: Allocator, out: []registry.Declaration, commands_elem: *xml.Element, api: registry.Api) !usize {
var i: usize = 0;
fn parseCommands(
allocator: Allocator,
commands_elem: *xml.Element,
api: registry.Api,
decls: *std.ArrayListUnmanaged(registry.Declaration),
) !void {
var it = commands_elem.findChildrenByTag("command");
while (it.next()) |elem| {
if (!requiredByApi(elem, api))
continue;
out[i] = try parseCommand(allocator, elem, api);
i += 1;
try decls.append(allocator, try parseCommand(allocator, elem, api));
}
return i;
}
fn splitCommaAlloc(allocator: Allocator, text: []const u8) ![][]const u8 {
@@ -587,8 +613,13 @@ fn parseCommand(allocator: Allocator, elem: *xml.Element, api: registry.Api) !re
};
}
fn parseApiConstants(allocator: Allocator, root: *xml.Element, api: registry.Api) ![]registry.ApiConstant {
var enums = blk: {
fn parseApiConstants(
allocator: Allocator,
root: *xml.Element,
api: registry.Api,
api_constants: *std.ArrayListUnmanaged(registry.ApiConstant),
) !void {
const maybe_enums = blk: {
var it = root.findChildrenByTag("enums");
while (it.next()) |child| {
const name = child.getAttribute("name") orelse continue;
@@ -597,52 +628,39 @@ fn parseApiConstants(allocator: Allocator, root: *xml.Element, api: registry.Api
}
}
return error.InvalidRegistry;
break :blk null;
};
var types = root.findChildByTag("types") orelse return error.InvalidRegistry;
const n_defines = blk: {
var n_defines: usize = 0;
var it = types.findChildrenByTag("type");
while (it.next()) |ty| {
if (ty.getAttribute("category")) |category| {
if (mem.eql(u8, category, "define")) {
n_defines += 1;
}
}
if (maybe_enums) |enums| {
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|
alias
else
return error.InvalidRegistry;
try api_constants.append(allocator, .{
.name = constant.getAttribute("name") orelse return error.InvalidRegistry,
.value = .{ .expr = expr },
});
}
break :blk n_defines;
};
const constants = try allocator.alloc(registry.ApiConstant, enums.children.len + n_defines);
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|
alias
else
return error.InvalidRegistry;
constants[i] = .{
.name = constant.getAttribute("name") orelse return error.InvalidRegistry,
.value = .{ .expr = expr },
};
i += 1;
}
i += try parseDefines(types, constants[i..], api);
return constants[0..i];
const types = root.findChildByTag("types") orelse return error.InvalidRegistry;
try parseDefines(allocator, types, api, api_constants);
}
fn parseDefines(types: *xml.Element, out: []registry.ApiConstant, api: registry.Api) !usize {
var i: usize = 0;
fn parseDefines(
allocator: Allocator,
types: *xml.Element,
api: registry.Api,
api_constants: *std.ArrayListUnmanaged(registry.ApiConstant),
) !void {
var it = types.findChildrenByTag("type");
while (it.next()) |ty| {
if (!requiredByApi(ty, api))
@@ -655,58 +673,45 @@ fn parseDefines(types: *xml.Element, out: []registry.ApiConstant, api: registry.
const name = ty.getCharData("name") orelse continue;
if (mem.eql(u8, name, "VK_HEADER_VERSION") or mem.eql(u8, name, "VKSC_API_VARIANT")) {
out[i] = .{
try api_constants.append(allocator, .{
.name = name,
.value = .{ .expr = mem.trim(u8, ty.children[2].char_data, " ") },
};
});
} else {
var xctok = cparse.XmlCTokenizer.init(ty);
out[i] = .{
try api_constants.append(allocator, .{
.name = name,
.value = .{ .version = cparse.parseVersion(&xctok) catch continue },
};
.value = cparse.parseVersion(&xctok) catch continue,
});
}
i += 1;
}
return i;
}
fn parseTags(allocator: Allocator, root: *xml.Element) ![]registry.Tag {
var tags_elem = root.findChildByTag("tags") orelse return error.InvalidRegistry;
const tags = try allocator.alloc(registry.Tag, tags_elem.children.len);
fn parseTags(
allocator: Allocator,
root: *xml.Element,
tags: *std.ArrayListUnmanaged(registry.Tag),
) !void {
var tags_elem = root.findChildByTag("tags") orelse return;
try tags.ensureUnusedCapacity(allocator, tags_elem.children.len);
var i: usize = 0;
var it = tags_elem.findChildrenByTag("tag");
while (it.next()) |tag| {
tags[i] = .{
tags.appendAssumeCapacity(.{
.name = tag.getAttribute("name") orelse return error.InvalidRegistry,
.author = tag.getAttribute("author") orelse return error.InvalidRegistry,
};
i += 1;
});
}
return tags[0..i];
}
fn parseFeatures(allocator: Allocator, root: *xml.Element, api: registry.Api) ![]registry.Feature {
fn parseFeatures(allocator: Allocator, root: *xml.Element, api: registry.Api, features: *std.ArrayListUnmanaged(registry.Feature)) !void {
var it = root.findChildrenByTag("feature");
var count: usize = 0;
while (it.next()) |_| count += 1;
const features = try allocator.alloc(registry.Feature, count);
var i: usize = 0;
it = root.findChildrenByTag("feature");
while (it.next()) |feature| {
if (!requiredByApi(feature, api))
continue;
features[i] = try parseFeature(allocator, feature, api);
i += 1;
try features.append(allocator, try parseFeature(allocator, feature, api));
}
return features[0..i];
}
fn parseFeature(allocator: Allocator, feature: *xml.Element, api: registry.Api) !registry.Feature {
@@ -736,11 +741,24 @@ fn parseFeature(allocator: Allocator, feature: *xml.Element, api: registry.Api)
fn parseEnumExtension(elem: *xml.Element, parent_extnumber: ?u31) !?registry.Require.EnumExtension {
// check for either _SPEC_VERSION or _EXTENSION_NAME
const extends = elem.getAttribute("extends") orelse return null;
const name = elem.getAttribute("name") orelse return error.InvalidRegistry;
if (std.mem.endsWith(u8, name, "_SPEC_VERSION") or std.mem.endsWith(u8, name, "_EXTENSION_NAME")) {
return null;
}
const extends = elem.getAttribute("extends") orelse {
const expr = elem.getAttribute("value") orelse return null;
// This adds a value to the 'API constants' set
return registry.Require.EnumExtension{
.extends = name,
.extnumber = null,
.value = .{ .new_api_constant_expr = expr },
};
};
if (elem.getAttribute("offset")) |offset_str| {
const offset = try std.fmt.parseInt(u31, offset_str, 10);
const name = elem.getAttribute("name") orelse return error.InvalidRegistry;
const extnumber = if (elem.getAttribute("extnumber")) |num|
try std.fmt.parseInt(u31, num, 10)
else
@@ -763,9 +781,11 @@ fn parseEnumExtension(elem: *xml.Element, parent_extnumber: ?u31) !?registry.Req
return registry.Require.EnumExtension{
.extends = extends,
.extnumber = actual_extnumber,
.field = .{
.name = name,
.value = .{ .int = value },
.value = .{
.field = .{
.name = name,
.value = .{ .int = value },
},
},
};
}
@@ -773,7 +793,7 @@ fn parseEnumExtension(elem: *xml.Element, parent_extnumber: ?u31) !?registry.Req
return registry.Require.EnumExtension{
.extends = extends,
.extnumber = parent_extnumber,
.field = try parseEnumField(elem),
.value = .{ .field = try parseEnumField(elem) },
};
}
@@ -844,11 +864,15 @@ fn parseRequire(allocator: Allocator, require: *xml.Element, extnumber: ?u31, ap
};
}
fn parseExtensions(allocator: Allocator, root: *xml.Element, api: registry.Api) ![]registry.Extension {
fn parseExtensions(
allocator: Allocator,
root: *xml.Element,
api: registry.Api,
extensions: *std.ArrayListUnmanaged(registry.Extension),
) !void {
const extensions_elem = root.findChildByTag("extensions") orelse return error.InvalidRegistry;
try extensions.ensureUnusedCapacity(allocator, extensions_elem.children.len);
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))
@@ -860,14 +884,11 @@ fn parseExtensions(allocator: Allocator, root: *xml.Element, api: registry.Api)
}
}
extensions[i] = try parseExtension(allocator, extension, api);
i += 1;
extensions.appendAssumeCapacity(try parseExtension(allocator, extension, api));
}
return extensions[0..i];
}
fn findExtVersion(extension: *xml.Element) !u32 {
fn findExtVersion(extension: *xml.Element) !registry.Extension.Version {
var req_it = extension.findChildrenByTag("require");
while (req_it.next()) |req| {
var enum_it = req.findChildrenByTag("enum");
@@ -875,17 +896,23 @@ fn findExtVersion(extension: *xml.Element) !u32 {
const name = e.getAttribute("name") orelse continue;
const value = e.getAttribute("value") orelse continue;
if (mem.endsWith(u8, name, "_SPEC_VERSION")) {
return try std.fmt.parseInt(u32, value, 10);
// Vulkan Video extensions are sometimes aliases.
// If we fail to parse it as integer, just assume that its an alias and return that.
const version = std.fmt.parseInt(u32, value, 10) catch return .{ .alias = value };
return .{ .int = version };
}
}
}
return error.InvalidRegistry;
return .unknown;
}
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 is_video = std.mem.startsWith(u8, name, "vulkan_video_");
const version = try findExtVersion(extension);
// For some reason there are two ways for an extension to state its required
@@ -907,11 +934,14 @@ fn parseExtension(allocator: Allocator, extension: *xml.Element, api: registry.A
};
const number = blk: {
// Vulkan Video extensions do not have numbers.
if (is_video) break :blk 0;
const number_str = extension.getAttribute("number") orelse return error.InvalidRegistry;
break :blk try std.fmt.parseInt(u31, number_str, 10);
};
const ext_type: ?registry.Extension.ExtensionType = blk: {
if (is_video) break :blk .video;
const ext_type_str = extension.getAttribute("type") orelse break :blk null;
if (mem.eql(u8, ext_type_str, "instance")) {
break :blk .instance;

View File

@@ -42,6 +42,7 @@ pub const ApiConstant = struct {
pub const Value = union(enum) {
expr: []const u8,
version: [4][]const u8,
video_std_version: [3][]const u8,
};
name: []const u8,
@@ -179,6 +180,7 @@ pub const Extension = struct {
pub const ExtensionType = enum {
instance,
device,
video,
};
pub const Promotion = union(enum) {
@@ -187,9 +189,15 @@ pub const Extension = struct {
extension: []const u8,
};
pub const Version = union(enum) {
int: u32,
alias: []const u8,
unknown,
};
name: []const u8,
number: u31,
version: u32,
version: Version,
extension_type: ?ExtensionType,
depends: []const []const u8, // Other extensions
promoted_to: Promotion,
@@ -200,9 +208,13 @@ pub const Extension = struct {
pub const Require = struct {
pub const EnumExtension = struct {
pub const Value = union(enum) {
field: Enum.Field,
new_api_constant_expr: []const u8,
};
extends: []const u8,
extnumber: ?u31,
field: Enum.Field,
value: Value,
};
extends: []EnumExtension,

View File

@@ -81,24 +81,18 @@ const preamble =
\\ }
\\ };
\\}
\\pub fn makeApiVersion(variant: u3, major: u7, minor: u10, patch: u12) u32 {
\\ return (@as(u32, variant) << 29) | (@as(u32, major) << 22) | (@as(u32, minor) << 12) | patch;
\\}
\\pub fn apiVersionVariant(version: u32) u3 {
\\ return @truncate(version >> 29);
\\}
\\pub fn apiVersionMajor(version: u32) u7 {
\\ return @truncate(version >> 22);
\\}
\\pub fn apiVersionMinor(version: u32) u10 {
\\ return @truncate(version >> 12);
\\}
\\pub fn apiVersionPatch(version: u32) u12 {
\\ return @truncate(version);
\\pub const Version = packed struct(u32) {
\\ patch: u12,
\\ minor: u10,
\\ major: u7,
\\ variant: u3,
\\};
\\pub fn makeApiVersion(variant: u3, major: u7, minor: u10, patch: u12) Version {
\\ return .{ .variant = variant, .major = major, .minor = minor, .patch = patch };
\\}
\\pub const ApiInfo = struct {
\\ name: [:0]const u8 = "custom",
\\ version: u32 = makeApiVersion(0, 0, 0, 0),
\\ version: Version = makeApiVersion(0, 0, 0, 0),
\\ base_commands: BaseCommandFlags = .{},
\\ instance_commands: InstanceCommandFlags = .{},
\\ device_commands: DeviceCommandFlags = .{},
@@ -375,25 +369,43 @@ fn Renderer(comptime WriterType: type) type {
allocator: Allocator,
registry: *const reg.Registry,
id_renderer: *IdRenderer,
declarations_by_name: std.StringHashMap(*const reg.DeclarationType),
decls_by_name: std.StringArrayHashMap(reg.DeclarationType),
structure_types: std.StringHashMap(void),
have_video: bool,
fn init(writer: WriterType, allocator: Allocator, registry: *const reg.Registry, id_renderer: *IdRenderer) !Self {
var declarations_by_name = std.StringHashMap(*const reg.DeclarationType).init(allocator);
errdefer declarations_by_name.deinit();
fn init(
writer: WriterType,
allocator: Allocator,
registry: *const reg.Registry,
id_renderer: *IdRenderer,
have_video: bool,
) !Self {
var decls_by_name = std.StringArrayHashMap(reg.DeclarationType).init(allocator);
errdefer decls_by_name.deinit();
for (registry.decls) |*decl| {
const result = try declarations_by_name.getOrPut(decl.name);
const result = try decls_by_name.getOrPut(decl.name);
if (result.found_existing) {
std.log.err("duplicate registry entry '{s}'", .{decl.name});
return error.InvalidRegistry;
// Allow overriding 'foreign' types. These are for example the Vulkan Video types
// declared as foreign type in the vk.xml, then defined in video.xml. Sometimes
// this also includes types like uint32_t, for these we don't really care.
// Just make sure to keep the non-foreign variant.
if (result.value_ptr.* == .foreign) {
result.value_ptr.* = decl.decl_type;
} else if (decl.decl_type == .foreign) {
// Foreign type trying to override a non-foreign one. Just keep the current
// one, and don't generate an error.
} else {
std.log.err("duplicate registry entry '{s}'", .{decl.name});
return error.InvalidRegistry;
}
} else {
result.value_ptr.* = decl.decl_type;
}
result.value_ptr.* = &decl.decl_type;
}
const vk_structure_type_decl = declarations_by_name.get("VkStructureType") orelse return error.InvalidRegistry;
const vk_structure_type = switch (vk_structure_type_decl.*) {
const vk_structure_type_decl = decls_by_name.get("VkStructureType") orelse return error.InvalidRegistry;
const vk_structure_type = switch (vk_structure_type_decl) {
.enumeration => |e| e,
else => return error.InvalidRegistry,
};
@@ -409,13 +421,14 @@ fn Renderer(comptime WriterType: type) type {
.allocator = allocator,
.registry = registry,
.id_renderer = id_renderer,
.declarations_by_name = declarations_by_name,
.decls_by_name = decls_by_name,
.structure_types = structure_types,
.have_video = have_video,
};
}
fn deinit(self: *Self) void {
self.declarations_by_name.deinit();
self.decls_by_name.deinit();
}
fn writeIdentifier(self: Self, id: []const u8) !void {
@@ -501,8 +514,8 @@ fn Renderer(comptime WriterType: type) type {
}
fn resolveDeclaration(self: Self, name: []const u8) ?reg.DeclarationType {
const decl = self.declarations_by_name.get(name) orelse return null;
return self.resolveAlias(decl.*) catch return null;
const decl = self.decls_by_name.get(name) orelse return null;
return self.resolveAlias(decl) catch return null;
}
fn resolveAlias(self: Self, start_decl: reg.DeclarationType) !reg.DeclarationType {
@@ -513,8 +526,7 @@ fn Renderer(comptime WriterType: type) type {
else => return decl,
};
const decl_ptr = self.declarations_by_name.get(name) orelse return error.InvalidRegistry;
decl = decl_ptr.*;
decl = self.decls_by_name.get(name) orelse return error.InvalidRegistry;
}
}
@@ -614,12 +626,17 @@ fn Renderer(comptime WriterType: type) type {
fn render(self: *Self) !void {
try self.writer.writeAll(preamble);
try self.writer.print("pub const have_vulkan_video = {};\n", .{self.have_video});
for (self.registry.api_constants) |api_constant| {
try self.renderApiConstant(api_constant);
}
for (self.registry.decls) |decl| {
try self.renderDecl(decl);
for (self.decls_by_name.keys(), self.decls_by_name.values()) |name, decl_type| {
try self.renderDecl(.{
.name = name,
.decl_type = decl_type,
});
}
try self.renderCommandPtrs();
@@ -636,8 +653,12 @@ fn Renderer(comptime WriterType: type) type {
switch (api_constant.value) {
.expr => |expr| try self.renderApiConstantExpr(expr),
.version => |version| {
inline .version, .video_std_version => |version, kind| {
try self.writer.writeAll("makeApiVersion(");
// For Vulkan Video, just re-use the API version and set the variant to 0.
if (kind == .video_std_version) {
try self.writer.writeAll("0, ");
}
for (version, 0..) |part, i| {
if (i != 0) {
try self.writer.writeAll(", ");
@@ -688,6 +709,7 @@ fn Renderer(comptime WriterType: type) type {
} else if (mem.eql(u8, suffix.text, "U")) {
try self.writer.print("@as(u32, {s})", .{tok.text});
} else {
std.debug.print("aaa {s}\n", .{suffix.text});
return error.InvalidApiConstant;
}
},
@@ -894,17 +916,6 @@ fn Renderer(comptime WriterType: type) type {
.{maybe_author orelse ""},
);
return true;
} else if (std.mem.eql(u8, basename, "VkClusterAccelerationStructureGeometryIndexAndGeometryFlags")) {
try self.writer.print(
\\packed struct(u32) {{
\\ geometry_index: u24,
\\ reserved: u5 = 0,
\\ geometry_flags: u3, // ClusterAccelerationStructureGeometryFlags{0s}
\\}};
,
.{maybe_author orelse ""},
);
return true;
} else if (std.mem.eql(u8, basename, "VkClusterAccelerationStructureBuildTriangleClusterInfo")) {
try self.writer.print(
\\extern struct {{
@@ -980,11 +991,50 @@ fn Renderer(comptime WriterType: type) type {
return false;
}
fn renderSimpleBitContainer(self: *Self, container: reg.Container) !bool {
var total_bits: usize = 0;
for (container.fields) |field| {
total_bits += field.bits orelse {
// C abi type - not a packed struct.
return false;
};
}
try self.writer.writeAll("packed struct(u32) {");
for (container.fields) |field| {
const bits = field.bits.?;
try self.writeIdentifierWithCase(.snake, field.name);
try self.writer.writeAll(": ");
// Default-zero fields that look like they are not used.
if (std.mem.eql(u8, field.name, "reserved")) {
try self.writer.print(" u{} = 0,\n", .{field.bits.?});
} else if (bits == 1) {
// Assume its a flag.
try self.writer.writeAll(" bool,\n");
} else {
try self.writer.print(" u{},\n", .{field.bits.?});
}
}
if (total_bits != 32) {
try self.writer.print("_reserved: u{} = 0,\n", .{32 - total_bits});
}
try self.writer.writeAll("};\n");
return true;
}
fn renderContainer(self: *Self, name: []const u8, container: reg.Container) !void {
try self.writer.writeAll("pub const ");
try self.renderName(name);
try self.writer.writeAll(" = ");
if (try self.renderSimpleBitContainer(container)) {
return;
}
if (try self.renderSpecialContainer(name)) {
return;
}
@@ -1231,7 +1281,9 @@ fn Renderer(comptime WriterType: type) type {
}
fn renderForeign(self: *Self, name: []const u8, foreign: reg.Foreign) !void {
if (mem.eql(u8, foreign.depends, "vk_platform")) {
if (mem.eql(u8, foreign.depends, "vk_platform") or
builtin_types.get(name) != null)
{
return; // Skip built-in types, they are handled differently
}
@@ -1262,18 +1314,18 @@ fn Renderer(comptime WriterType: type) type {
}
fn renderCommandPtrs(self: *Self) !void {
for (self.registry.decls) |decl| {
switch (decl.decl_type) {
for (self.decls_by_name.keys(), self.decls_by_name.values()) |name, decl_type| {
switch (decl_type) {
.command => {
try self.writer.writeAll("pub const ");
try self.renderCommandPtrName(decl.name);
try self.renderCommandPtrName(name);
try self.writer.writeAll(" = ");
try self.renderCommandPtr(decl.decl_type.command, false);
try self.renderCommandPtr(decl_type.command, false);
try self.writer.writeAll(";\n");
},
.alias => |alias| if (alias.target == .other_command) {
try self.writer.writeAll("pub const ");
try self.renderCommandPtrName(decl.name);
try self.renderCommandPtrName(name);
try self.writer.writeAll(" = ");
try self.renderCommandPtrName(alias.name);
try self.writer.writeAll(";\n");
@@ -1289,13 +1341,17 @@ fn Renderer(comptime WriterType: type) type {
\\
);
// The commands in a feature level are not pre-sorted based on if they are instance or device functions.
var base_commands = std.BufSet.init(self.allocator);
var base_commands = std.StringArrayHashMap(void).init(self.allocator);
defer base_commands.deinit();
var instance_commands = std.BufSet.init(self.allocator);
var instance_commands = std.StringArrayHashMap(void).init(self.allocator);
defer instance_commands.deinit();
var device_commands = std.BufSet.init(self.allocator);
var device_commands = std.StringArrayHashMap(void).init(self.allocator);
defer device_commands.deinit();
for (self.registry.features) |feature| {
base_commands.clearRetainingCapacity();
instance_commands.clearRetainingCapacity();
device_commands.clearRetainingCapacity();
try self.writer.writeAll("pub const ");
try self.writeIdentifierWithCase(.snake, trimVkNamespace(feature.name));
try self.writer.writeAll("= ApiInfo {\n");
@@ -1317,31 +1373,28 @@ fn Renderer(comptime WriterType: type) type {
};
const class = classifyCommandDispatch(command_name, command);
switch (class) {
.base => {
try base_commands.insert(command_name);
},
.instance => {
try instance_commands.insert(command_name);
},
.device => {
try device_commands.insert(command_name);
},
.base => try base_commands.put(command_name, {}),
.instance => try instance_commands.put(command_name, {}),
.device => try device_commands.put(command_name, {}),
}
}
}
// and write them out
// clear command lists for next iteration
try self.writer.writeAll(".base_commands = ");
try self.renderCommandFlags(&base_commands);
base_commands.hash_map.clearRetainingCapacity();
if (base_commands.count() != 0) {
try self.writer.writeAll(".base_commands = ");
try self.renderCommandFlags(base_commands.keys());
}
try self.writer.writeAll(".instance_commands = ");
try self.renderCommandFlags(&instance_commands);
instance_commands.hash_map.clearRetainingCapacity();
if (instance_commands.count() != 0) {
try self.writer.writeAll(".instance_commands = ");
try self.renderCommandFlags(instance_commands.keys());
}
try self.writer.writeAll(".device_commands = ");
try self.renderCommandFlags(&device_commands);
device_commands.hash_map.clearRetainingCapacity();
if (device_commands.count() != 0) {
try self.writer.writeAll(".device_commands = ");
try self.renderCommandFlags(device_commands.keys());
}
try self.writer.writeAll("};\n");
}
@@ -1355,17 +1408,35 @@ fn Renderer(comptime WriterType: type) type {
\\
);
// The commands in an extension are not pre-sorted based on if they are instance or device functions.
var base_commands = std.BufSet.init(self.allocator);
var base_commands = std.StringArrayHashMap(void).init(self.allocator);
defer base_commands.deinit();
var instance_commands = std.BufSet.init(self.allocator);
var instance_commands = std.StringArrayHashMap(void).init(self.allocator);
defer instance_commands.deinit();
var device_commands = std.BufSet.init(self.allocator);
var device_commands = std.StringArrayHashMap(void).init(self.allocator);
defer device_commands.deinit();
for (self.registry.extensions) |ext| {
base_commands.clearRetainingCapacity();
instance_commands.clearRetainingCapacity();
device_commands.clearRetainingCapacity();
try self.writer.writeAll("pub const ");
try self.writeIdentifierWithCase(.snake, trimVkNamespace(ext.name));
if (ext.extension_type == .video) {
// These are already in the right form, and the auto-casing style transformer
// is prone to messing up these names.
try self.writeIdentifier(trimVkNamespace(ext.name));
} else {
try self.writeIdentifierWithCase(.snake, trimVkNamespace(ext.name));
}
try self.writer.writeAll("= ApiInfo {\n");
try self.writer.print(".name = \"{s}\", .version = {},", .{ ext.name, ext.version });
try self.writer.print(".name = \"{s}\", .version = ", .{ext.name});
switch (ext.version) {
.int => |version| try self.writer.print("makeApiVersion(0, {}, 0, 0)", .{version}),
// This should be the same as in self.renderApiConstant.
// We assume that this is already a vk.Version type.
.alias => |alias| try self.renderName(alias),
.unknown => try self.writer.writeAll("makeApiVersion(0, 0, 0, 0)"),
}
try self.writer.writeByte(',');
// collect extension functions
for (ext.requires) |require| {
for (require.commands) |command_name| {
@@ -1379,42 +1450,38 @@ fn Renderer(comptime WriterType: type) type {
};
const class = classifyCommandDispatch(command_name, command);
switch (class) {
.base => {
try base_commands.insert(command_name);
},
.instance => {
try instance_commands.insert(command_name);
},
.device => {
try device_commands.insert(command_name);
},
.base => try base_commands.put(command_name, {}),
.instance => try instance_commands.put(command_name, {}),
.device => try device_commands.put(command_name, {}),
}
}
}
// and write them out
try self.writer.writeAll(".base_commands = ");
try self.renderCommandFlags(&base_commands);
base_commands.hash_map.clearRetainingCapacity();
if (base_commands.count() != 0) {
try self.writer.writeAll(".base_commands = ");
try self.renderCommandFlags(base_commands.keys());
}
try self.writer.writeAll(".instance_commands = ");
try self.renderCommandFlags(&instance_commands);
instance_commands.hash_map.clearRetainingCapacity();
if (instance_commands.count() != 0) {
try self.writer.writeAll(".instance_commands = ");
try self.renderCommandFlags(instance_commands.keys());
}
try self.writer.writeAll(".device_commands = ");
try self.renderCommandFlags(&device_commands);
device_commands.hash_map.clearRetainingCapacity();
if (device_commands.count() != 0) {
try self.writer.writeAll(".device_commands = ");
try self.renderCommandFlags(device_commands.keys());
}
try self.writer.writeAll("};\n");
}
try self.writer.writeAll("};\n");
}
fn renderCommandFlags(self: *Self, commands: *const std.BufSet) !void {
fn renderCommandFlags(self: *Self, commands: []const []const u8) !void {
try self.writer.writeAll(".{\n");
var iterator = commands.iterator();
while (iterator.next()) |command_name| {
for (commands) |command_name| {
try self.writer.writeAll(".");
try self.writeIdentifierWithCase(.camel, trimVkNamespace(command_name.*));
try self.writeIdentifierWithCase(.camel, trimVkNamespace(command_name));
try self.writer.writeAll(" = true, \n");
}
try self.writer.writeAll("},\n");
@@ -2226,8 +2293,14 @@ fn Renderer(comptime WriterType: type) type {
};
}
pub fn render(writer: anytype, allocator: Allocator, registry: *const reg.Registry, id_renderer: *IdRenderer) !void {
var renderer = try Renderer(@TypeOf(writer)).init(writer, allocator, registry, id_renderer);
pub fn render(
writer: anytype,
allocator: Allocator,
registry: *const reg.Registry,
id_renderer: *IdRenderer,
have_video: bool,
) !void {
var renderer = try Renderer(@TypeOf(writer)).init(writer, allocator, registry, id_renderer, have_video);
defer renderer.deinit();
try renderer.render();
}