4 Commits

Author SHA1 Message Date
Robin Voetter
5559cccea6 add missing vulkan video type in ref_all_decls.zig 2024-11-24 21:00:45 +01:00
Robin Voetter
84b9622f92 Merge pull request #165 from moomba42/master
Bring zig-0.13-compat branch up to date with main
2024-10-12 21:28:44 +02:00
Aleksander Długosz
a6e228cd9f Update ref_all_decls.zig 2024-10-12 02:07:38 +02:00
Aleksander Długosz
fc700b44c0 Update render.zig 2024-10-12 02:06:48 +02:00
21 changed files with 2165 additions and 2450 deletions

View File

@@ -16,7 +16,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Zig - name: Setup Zig
uses: mlugg/setup-zig@v2 uses: mlugg/setup-zig@v1
with: with:
version: master version: master
@@ -31,30 +31,20 @@ jobs:
sudo apt install shaderc libglfw3 libglfw3-dev sudo apt install shaderc libglfw3 libglfw3-dev
- name: Fetch latest vk.xml - name: Fetch latest vk.xml
run: | run: wget https://raw.githubusercontent.com/KhronosGroup/Vulkan-Docs/main/xml/vk.xml
wget https://raw.githubusercontent.com/KhronosGroup/Vulkan-Docs/main/xml/vk.xml
wget https://raw.githubusercontent.com/KhronosGroup/Vulkan-Docs/main/xml/video.xml
- name: Test and install with latest zig & latest vk.xml - name: Test and install with latest zig & latest vk.xml
run: zig build test install -Dregistry=$(pwd)/vk.xml run: zig build test install -Dregistry=$(pwd)/vk.xml
- name: Test and install with latest zig & latest vk.xml & latest video.xml
run: zig build test install -p zig-out-video -Dregistry=$(pwd)/vk.xml -Dvideo=$(pwd)/video.xml
- name: Build example with latest zig & vk.xml from dependency - name: Build example with latest zig & vk.xml from dependency
run: zig build --build-file $(pwd)/examples/build.zig run: zig build --build-file $(pwd)/examples/build.zig
- name: Build example with latest zig & latest vk.xml - name: Build example with latest zig & latest vk.xml
run: zig build --build-file $(pwd)/examples/build.zig -Doverride-registry=$(pwd)/vk.xml run: zig build --build-file $(pwd)/examples/build.zig -Doverride-registry=$(pwd)/vk.xml
- name: Build example with latest zig & vk.xml from dependency & use zig shaders
run: zig build --build-file $(pwd)/examples/build.zig -Dzig-shader
- name: Archive vk.zig - name: Archive vk.zig
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: vk.zig name: vk.zig
path: | path: zig-out/src/vk.zig
zig-out/src/vk.zig
zig-out-video/src/vk.zig
if-no-files-found: error if-no-files-found: error

View File

@@ -1,4 +1,4 @@
Copyright © Robin Voetter Copyright © 2020-2022 Robin Voetter
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

122
README.md
View File

@@ -50,35 +50,14 @@ There is also support for adding this project as a dependency through zig packag
``` ```
And then in your build.zig file, you'll need to add a line like this to your build function: And then in your build.zig file, you'll need to add a line like this to your build function:
```zig ```zig
const vulkan = b.dependency("vulkan_zig", .{ const vkzig_dep = b.dependency("vulkan_zig", .{
.registry = b.path("path/to/vk.xml"), .registry = @as([]const u8, b.pathFromRoot("path/to/vk.xml")),
}).module("vulkan-zig"); });
exe.root_module.addImport("vulkan", vulkan); const vkzig_bindings = vkzig_dep.module("vulkan-zig");
exe.root_module.addImport("vulkan", vkzig_bindings);
``` ```
That will allow you to `@import("vulkan")` in your executable's source. That will allow you to `@import("vulkan")` in your executable's source.
#### Generating bindings directly from Vulkan-Headers
Bindings can be generated directly from the Vulkan-Headers repository by adding Vulkan-Headers as a dependency, and then passing the path to `vk.xml` from that dependency:
```zig
.{
// -- snip --
.dependencies = .{
// -- snip --
.vulkan_headers = .{
.url = "https://github.com/KhronosGroup/Vulkan-Headers/archive/v1.3.283.tar.gz",
.hash = "<dependency hash>",
},
},
}
```
```zig
const vulkan = b.dependency("vulkan_zig", .{
.registry = b.dependency("vulkan_headers", .{}).path("registry/vk.xml"),
}).module("vulkan-zig");
exe.root_module.addImport("vulkan", vulkan);
```
### Manual generation with the package manager from build.zig ### Manual generation with the package manager from build.zig
Bindings can also be generated by invoking the generator directly. This may be useful is some special cases, for example, it integrates particularly well with fetching the registry via the package manager. This can be done by adding the Vulkan-Headers repository to your dependencies, and then passing the `vk.xml` inside it to vulkan-zig-generator: Bindings can also be generated by invoking the generator directly. This may be useful is some special cases, for example, it integrates particularly well with fetching the registry via the package manager. This can be done by adding the Vulkan-Headers repository to your dependencies, and then passing the `vk.xml` inside it to vulkan-zig-generator:
@@ -104,7 +83,7 @@ const vk_gen = b.dependency("vulkan_zig", .{}).artifact("vulkan-zig-generator");
// Set up a run step to generate the bindings // Set up a run step to generate the bindings
const vk_generate_cmd = b.addRunArtifact(vk_gen); const vk_generate_cmd = b.addRunArtifact(vk_gen);
// Pass the registry to the generator // Pass the registry to the generator
vk_generate_cmd.addFileArg(registry); generate_cmd.addArg(registry);
// Create a module from the generator's output... // Create a module from the generator's output...
const vulkan_zig = b.addModule("vulkan-zig", .{ const vulkan_zig = b.addModule("vulkan-zig", .{
.root_source_file = vk_generate_cmd.addOutputFileArg("vk.zig"), .root_source_file = vk_generate_cmd.addOutputFileArg("vk.zig"),
@@ -127,9 +106,9 @@ Functions and fields are renamed to be more or less in line with [Zig's standard
* Container fields and function parameter names are generated in (lower) snake case in a similar manner: `ppEnabledLayerNames` becomes `pp_enabled_layer_names`. * Container fields and function parameter names are generated in (lower) snake case in a similar manner: `ppEnabledLayerNames` becomes `pp_enabled_layer_names`.
* Any name which is either an illegal Zig name or a reserved identifier is rendered using `@"name"` syntax. For example, `VK_IMAGE_TYPE_2D` is translated to `@"2d"`. * Any name which is either an illegal Zig name or a reserved identifier is rendered using `@"name"` syntax. For example, `VK_IMAGE_TYPE_2D` is translated to `@"2d"`.
### Dispatch Tables ### Function pointers & Wrappers
Vulkan-zig provides no integration for statically linking libvulkan, and these symbols are not generated at all. Instead, vulkan functions are to be loaded dynamically. For each Vulkan function, a function pointer type is generated using the exact parameters and return types as defined by the Vulkan specification: vulkan-zig provides no integration for statically linking libvulkan, and these symbols are not generated at all. Instead, vulkan functions are to be loaded dynamically. For each Vulkan function, a function pointer type is generated using the exact parameters and return types as defined by the Vulkan specification:
```zig ```zig
pub const PfnCreateInstance = fn ( pub const PfnCreateInstance = fn (
p_create_info: *const InstanceCreateInfo, p_create_info: *const InstanceCreateInfo,
@@ -138,21 +117,39 @@ pub const PfnCreateInstance = fn (
) callconv(vulkan_call_conv) Result; ) callconv(vulkan_call_conv) Result;
``` ```
A set of _dispatch table_ structures is generated. A dispatch table simply contains a set of (optional) function pointers to Vulkan API functions, and not much else. Function pointers grouped by the nature of the function as follows: For each function, a wrapper is generated into one of three structs:
* Vulkan functions which are loaded by `vkGetInstanceProcAddr` without the need for passing an instance are placed in `BaseDispatch`. * BaseWrapper. This contains wrappers for functions which are loaded by `vkGetInstanceProcAddr` without an instance, such as `vkCreateInstance`, `vkEnumerateInstanceVersion`, etc.
* Vulkan functions which are loaded by `vkGetInstanceProcAddr` but do need an instance are placed in `InstanceDispatch`. * InstanceWrapper. This contains wrappers for functions which are otherwise loaded by `vkGetInstanceProcAddr`.
* Vulkan functions which are loaded by `vkGetDeviceProcAddr` are placed in `DeviceDispatch`. * DeviceWrapper. This contains wrappers for functions which are loaded by `vkGetDeviceProcAddr`.
### Wrappers To create a wrapper type, an "api specification" should be passed to it. This is a list of `ApiInfo` structs, which allows one to specify the functions that should be made available. An `ApiInfo` structure is initialized 3 optional fields, `base_commands`, `instance_commands`, and `device_commands`. Each of these takes a set of the vulkan functions that should be made available for that category, for example, setting `.createInstance = true` in `base_commands` makes the `createInstance` function available (loaded from `vkCreateInstance`). An entire feature level or extension can be pulled in at once too, for example, `vk.features.version_1_0` contains all functions for Vulkan 1.0. `vk.extensions.khr_surface` contains all functions for the `VK_KHR_surface` extension.
To provide more interesting functionality, a set of _wrapper_ types is also generated, one for each dispatch table type. These contain the Zig-versions of each Vulkan API function, along with corresponding error set definitions, return type definitions, etc, where appropriate.
```zig
const vk = @import("vulkan");
/// To construct base, instance and device wrappers for vulkan-zig, you need to pass a list of 'apis' to it.
const apis: []const vk.ApiInfo = &.{
// You can either add invidiual functions by manually creating an 'api'
.{
.base_commands = .{
.createInstance = true,
},
.instance_commands = .{
.createDevice = true,
},
},
// Or you can add entire feature sets or extensions
vk.features.version_1_0,
vk.extensions.khr_surface,
vk.extensions.khr_swapchain,
};
const BaseDispatch = vk.BaseWrapper(apis);
```
The wrapper struct then provides wrapper functions for each function pointer in the dispatch struct: The wrapper struct then provides wrapper functions for each function pointer in the dispatch struct:
```zig ```zig
pub const BaseWrapper = struct { pub const BaseWrapper(comptime cmds: anytype) type {
const Self = @This(); ...
const Dispatch = CreateDispatchStruct(cmds); const Dispatch = CreateDispatchStruct(cmds);
return struct {
dispatch: Dispatch, dispatch: Dispatch,
pub const CreateInstanceError = error{ pub const CreateInstanceError = error{
@@ -170,7 +167,7 @@ pub const BaseWrapper = struct {
p_allocator: ?*const AllocationCallbacks, p_allocator: ?*const AllocationCallbacks,
) CreateInstanceError!Instance { ) CreateInstanceError!Instance {
var instance: Instance = undefined; var instance: Instance = undefined;
const result = self.dispatch.vkCreateInstance.?( const result = self.dispatch.vkCreateInstance(
&create_info, &create_info,
p_allocator, p_allocator,
&instance, &instance,
@@ -189,7 +186,8 @@ pub const BaseWrapper = struct {
} }
... ...
}; }
}
``` ```
Wrappers are generated according to the following rules: Wrappers are generated according to the following rules:
* The return type is determined from the original return type and the parameters. * The return type is determined from the original return type and the parameters.
@@ -200,12 +198,10 @@ Wrappers are generated according to the following rules:
* Error codes are translated into Zig errors. * Error codes are translated into Zig errors.
* As of yet, there is no specific handling of enumeration style commands or other commands which accept slices. * As of yet, there is no specific handling of enumeration style commands or other commands which accept slices.
#### Initializing Wrappers Furthermore, each wrapper contains a function to load each function pointer member when passed either `PfnGetInstanceProcAddr` or `PfnGetDeviceProcAddr`, which attempts to load each member as function pointer and casts it to the appropriate type. These functions are loaded literally, and any wrongly named member or member with a wrong function pointer type will result in problems.
* For `BaseWrapper`, this function has signature `fn load(loader: anytype) error{CommandFailure}!Self`, where the type of `loader` must resemble `PfnGetInstanceProcAddr` (with optionally having a different calling convention).
Wrapper types are initialized by the `load` function, which must be passed a _loader_: A function which loads a function pointer by name. * For `InstanceWrapper`, this function has signature `fn load(instance: Instance, loader: anytype) error{CommandFailure}!Self`, where the type of `loader` must resemble `PfnGetInstanceProcAddr`.
* For `BaseWrapper`, this function has signature `fn load(loader: anytype) Self`, where the type of `loader` must resemble `PfnGetInstanceProcAddr` (with optionally having a different calling convention). * For `DeviceWrapper`, this function has signature `fn load(device: Device, loader: anytype) error{CommandFailure}!Self`, where the type of `loader` must resemble `PfnGetDeviceProcAddr`.
* For `InstanceWrapper`, this function has signature `fn load(instance: Instance, loader: anytype) Self`, where the type of `loader` must resemble `PfnGetInstanceProcAddr`.
* For `DeviceWrapper`, this function has signature `fn load(device: Device, loader: anytype) Self`, where the type of `loader` must resemble `PfnGetDeviceProcAddr`.
Note that these functions accepts a loader with the signature of `anytype` instead of `PfnGetInstanceProcAddr`. This is because it is valid for `vkGetInstanceProcAddr` to load itself, in which case the returned function is to be called with the vulkan calling convention. This calling convention is not required for loading vulkan-zig itself, though, and a loader to be called with any calling convention with the target architecture may be passed in. This is particularly useful when interacting with C libraries that provide `vkGetInstanceProcAddr`. Note that these functions accepts a loader with the signature of `anytype` instead of `PfnGetInstanceProcAddr`. This is because it is valid for `vkGetInstanceProcAddr` to load itself, in which case the returned function is to be called with the vulkan calling convention. This calling convention is not required for loading vulkan-zig itself, though, and a loader to be called with any calling convention with the target architecture may be passed in. This is particularly useful when interacting with C libraries that provide `vkGetInstanceProcAddr`.
@@ -221,33 +217,32 @@ fn customGetInstanceProcAddress(instance: vk.Instance, procname: [*:0]const u8)
... ...
} }
// Both calls are valid. // Both calls are valid, even
const vkb = BaseWrapper.load(glfwGetInstanceProcAddress); const vkb = try BaseDispatch.load(glfwGetInstanceProcAddress);
const vkb = BaseWrapper.load(customGetInstanceProcAddress); const vkb = try BaseDispatch.load(customGetInstanceProcAddress);
``` ```
The `load` function tries to load all function pointers unconditionally, regardless of enabled extensions or platform. If a function pointer could not be loaded, its entry in the dispatch table is set to `null`. When invoking a function on a wrapper table, the function pointer is checked for null, and there will be a crash or undefined behavior if it was not loaded properly. That means that **it is up to the programmer to ensure that a function pointer is valid for the platform before calling it**, either by checking whether the associated extension or Vulkan version is supported or simply by checking whether the function pointer is non-null. By default, wrapper `load` functions return `error.CommandLoadFailure` if a call to the loader resulted in `null`. If this behaviour is not desired, one can use `loadNoFail`. This function accepts the same parameters as `load`, but does not return an error any function pointer fails to load and sets its value to `undefined` instead. It is at the programmer's discretion not to invoke invalid functions, which can be tested for by checking whether the required core and extension versions the function requires are supported.
One can access the underlying unwrapped C functions by doing `wrapper.dispatch.vkFuncYouWant.?(..)`. One can access the underlying unwrapped C functions by doing `wrapper.dispatch.vkFuncYouWant(..)`.
#### Proxying Wrappers #### Proxying Wrappers
Proxying wrappers wrap a wrapper and a pointer to the associated handle in a single struct, and automatically passes this handle to commands as appropriate. Besides the proxying wrappers for instances and devices, there are also proxying wrappers for queues and command buffers. Proxying wrapper type are constructed in the same way as a regular wrapper, by passing an api specification to them. To initialize a proxying wrapper, it must be passed a handle and a pointer to an appropriate wrapper. For queue and command buffer proxying wrappers, a pointer to a device wrapper must be passed. Proxying wrappers wrap a wrapper and a pointer to the associated handle in a single struct, and automatically passes this handle to commands as appropriate. Besides the proxying wrappers for instances and devices, there are also proxying wrappers for queues and command buffers. Proxying wrapper type are constructed in the same way as a regular wrapper, by passing an api specification to them. To initialize a proxying wrapper, it must be passed a handle and a pointer to an appropriate wrapper. For queue and command buffer proxying wrappers, a pointer to a device wrapper must be passed.
```zig ```zig
const InstanceWrapper = vk.InstanceWrapper; // Create the dispatch tables
const Instance = vk.InstanceProxy; const InstanceDispatch = vk.InstanceWrapper(apis);
const Instance = vk.InstanceProxy(apis);
const instance_handle = try vkb.createInstance(...); const instance_handle = try vkb.createInstance(...);
const vki = try InstanceWrapper.load(instance_handle, vkb.dispatch.vkGetInstanceProcAddr.?); const vki = try InstanceDispatch.load(instance_handle, vkb.vkGetInstanceProcAddr);
const instance = Instance.load(instance_handle, &vki); const instance = Instance.load(instance_handle, &vki);
defer instance.destroyInstance(null); defer instance.destroyInstance(null);
``` ```
For queue and command buffer proxying wrappers, the `queue` and `cmd` prefix is removed for functions where appropriate. Note that the device proxying wrappers also have the queue and command buffer functions made available for convenience, but there the prefix is not stripped. For queue and command buffer proxying wrappers, the `queue` and `cmd` prefix is removed for functions where appropriate. Note that the device proxying wrappers also have the queue and command buffer functions made available for convenience, but there the prefix is not stripped.
Note that the proxy must be passed a _pointer_ to a wrapper. This is because there was a limitation with LLVM in the past, where a struct with an object pointer and its associated function pointers wouldn't be optimized properly. By using a separate function pointer, LLVM knows that the "vtable" dispatch struct can never be modified and so it can subject each call to vtable optimizations.
### Bitflags ### Bitflags
Packed structs of bools are used for bit flags in vulkan-zig, instead of both a `FlagBits` and `Flags` variant. Places where either of these variants are used are both replaced by this packed struct instead. This means that even in places where just one flag would normally be accepted, the packed struct is accepted. The programmer is responsible for only enabling a single bit. Packed structs of bools are used for bit flags in vulkan-zig, instead of both a `FlagBits` and `Flags` variant. Places where either of these variants are used are both replaced by this packed struct instead. This means that even in places where just one flag would normally be accepted, the packed struct is accepted. The programmer is responsible for only enabling a single bit.
@@ -366,19 +361,6 @@ See [examples/build.zig](examples/build.zig) for a working example.
For more advanced shader compiler usage, one may consider a library such as [shader_compiler](https://github.com/Games-by-Mason/shader_compiler). For more advanced shader compiler usage, one may consider a library such as [shader_compiler](https://github.com/Games-by-Mason/shader_compiler).
### Vulkan Video
Vulkan-zig also supports generating Vulkan Video bindings. To do this, one additionally pass `--video <video.xml>` to the generator, or pass `-Dvideo=<video.xml>` to build.zig. If using vulkan-zig via the Zig package manager, the following also works:
```zig
const vulkan_headers = b.dependency("vulkan_headers");
const vulkan = b.dependency("vulkan_zig", .{
.registry = vulkan_headers.path("registry/vk.xml"),
.video = vulkan_headers.path("registery/video.xml"),
}).module("vulkan-zig");
```
The Vulkan Video bindings are not generated by default. In this case, the relevant definitions must be supplied by the user. See [platform types](#platform-types) for how this is done.
## Limitations ## Limitations
* vulkan-zig has as of yet no functionality for selecting feature levels and extensions when generating bindings. This is because when an extension is promoted to Vulkan core, its fields and commands are renamed to lose the extensions author tag (for example, VkSemaphoreWaitFlagsKHR was renamed to VkSemaphoreWaitFlags when it was promoted from an extension to Vulkan 1.2 core). This leads to inconsistencies when only items from up to a certain feature level is included, as these promoted items then need to re-gain a tag. * vulkan-zig has as of yet no functionality for selecting feature levels and extensions when generating bindings. This is because when an extension is promoted to Vulkan core, its fields and commands are renamed to lose the extensions author tag (for example, VkSemaphoreWaitFlagsKHR was renamed to VkSemaphoreWaitFlags when it was promoted from an extension to Vulkan 1.2 core). This leads to inconsistencies when only items from up to a certain feature level is included, as these promoted items then need to re-gain a tag.

View File

@@ -3,23 +3,18 @@ const std = @import("std");
pub fn build(b: *std.Build) void { pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{}); const optimize = b.standardOptimizeOption(.{});
const maybe_registry = b.option(std.Build.LazyPath, "registry", "Set the path to the Vulkan registry (vk.xml)"); const maybe_registry: ?[]const u8 = b.option([]const u8, "registry", "Set the path to the Vulkan registry (vk.xml)");
const maybe_video = b.option(std.Build.LazyPath, "video", "Set the path to the Vulkan Video registry (video.xml)");
const test_step = b.step("test", "Run all the tests"); const test_step = b.step("test", "Run all the tests");
const root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// Using the package manager, this artifact can be obtained by the user // Using the package manager, this artifact can be obtained by the user
// through `b.dependency(<name in build.zig.zon>, .{}).artifact("vulkan-zig-generator")`. // through `b.dependency(<name in build.zig.zon>, .{}).artifact("vulkan-zig-generator")`.
// with that, the user need only `.addArg("path/to/vk.xml")`, and then obtain // with that, the user need only `.addArg("path/to/vk.xml")`, and then obtain
// a file source to the generated code with `.addOutputArg("vk.zig")` // a file source to the generated code with `.addOutputArg("vk.zig")`
const generator_exe = b.addExecutable(.{ const generator_exe = b.addExecutable(.{
.name = "vulkan-zig-generator", .name = "vulkan-zig-generator",
.root_module = root_module, .root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
}); });
b.installArtifact(generator_exe); b.installArtifact(generator_exe);
@@ -28,12 +23,7 @@ pub fn build(b: *std.Build) void {
if (maybe_registry) |registry| { if (maybe_registry) |registry| {
const vk_generate_cmd = b.addRunArtifact(generator_exe); const vk_generate_cmd = b.addRunArtifact(generator_exe);
if (maybe_video) |video| { vk_generate_cmd.addArg(registry);
vk_generate_cmd.addArg("--video");
vk_generate_cmd.addFileArg(video);
}
vk_generate_cmd.addFileArg(registry);
const vk_zig = vk_generate_cmd.addOutputFileArg("vk.zig"); const vk_zig = vk_generate_cmd.addOutputFileArg("vk.zig");
const vk_zig_module = b.addModule("vulkan-zig", .{ const vk_zig_module = b.addModule("vulkan-zig", .{
@@ -51,16 +41,16 @@ pub fn build(b: *std.Build) void {
// It does not need to run anyway. // It does not need to run anyway.
const ref_all_decls_test = b.addObject(.{ const ref_all_decls_test = b.addObject(.{
.name = "ref-all-decls-test", .name = "ref-all-decls-test",
.root_module = b.createModule(.{
.root_source_file = b.path("test/ref_all_decls.zig"), .root_source_file = b.path("test/ref_all_decls.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}),
}); });
ref_all_decls_test.root_module.addImport("vulkan", vk_zig_module); ref_all_decls_test.root_module.addImport("vulkan", vk_zig_module);
test_step.dependOn(&ref_all_decls_test.step); test_step.dependOn(&ref_all_decls_test.step);
} }
const test_target = b.addTest(.{ .root_module = root_module }); const test_target = b.addTest(.{
.root_source_file = b.path("src/main.zig"),
});
test_step.dependOn(&b.addRunArtifact(test_target).step); test_step.dependOn(&b.addRunArtifact(test_target).step);
} }

View File

@@ -1,8 +1,7 @@
.{ .{
.name = .vulkan, .name = "vulkan",
.fingerprint = 0xbe155a03c72db6af,
.version = "0.0.0", .version = "0.0.0",
.minimum_zig_version = "0.15.0-dev.1518+749f10af4", .minimum_zig_version = "0.14.0-dev.1359+e9a00ba7f",
.paths = .{ .paths = .{
"build.zig", "build.zig",
"LICENSE", "LICENSE",

View File

@@ -6,69 +6,32 @@ pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{}); const optimize = b.standardOptimizeOption(.{});
const maybe_override_registry = b.option([]const u8, "override-registry", "Override the path to the Vulkan registry used for the examples"); const maybe_override_registry = b.option([]const u8, "override-registry", "Override the path to the Vulkan registry used for the examples");
const use_zig_shaders = b.option(bool, "zig-shader", "Use Zig shaders instead of GLSL") orelse false;
const registry = b.dependency("vulkan_headers", .{}).path("registry/vk.xml"); const registry = b.dependency("vulkan_headers", .{}).path("registry/vk.xml");
const triangle_exe = b.addExecutable(.{ const triangle_exe = b.addExecutable(.{
.name = "triangle", .name = "triangle",
.root_module = b.createModule(.{
.root_source_file = b.path("triangle.zig"), .root_source_file = b.path("triangle.zig"),
.target = target, .target = target,
.link_libc = true, .link_libc = true,
.optimize = optimize, .optimize = optimize,
}),
// TODO: Remove this once x86_64 is stable
.use_llvm = true,
}); });
b.installArtifact(triangle_exe); b.installArtifact(triangle_exe);
triangle_exe.linkSystemLibrary("glfw"); triangle_exe.linkSystemLibrary("glfw");
const registry_path: std.Build.LazyPath = if (maybe_override_registry) |override_registry| const vk_gen = b.dependency("vulkan_zig", .{}).artifact("vulkan-zig-generator");
.{ .cwd_relative = override_registry } const vk_generate_cmd = b.addRunArtifact(vk_gen);
else
registry;
const vulkan = b.dependency("vulkan_zig", .{ if (maybe_override_registry) |override_registry| {
.registry = registry_path, vk_generate_cmd.addFileArg(.{ .cwd_relative = override_registry });
}).module("vulkan-zig");
triangle_exe.root_module.addImport("vulkan", vulkan);
if (use_zig_shaders) {
const spirv_target = b.resolveTargetQuery(.{
.cpu_arch = .spirv32,
.os_tag = .vulkan,
.cpu_model = .{ .explicit = &std.Target.spirv.cpu.vulkan_v1_2 },
.ofmt = .spirv,
});
const vert_spv = b.addObject(.{
.name = "vertex_shader",
.root_module = b.createModule(.{
.root_source_file = b.path("shaders/vertex.zig"),
.target = spirv_target,
}),
.use_llvm = false,
});
triangle_exe.root_module.addAnonymousImport(
"vertex_shader",
.{ .root_source_file = vert_spv.getEmittedBin() },
);
const frag_spv = b.addObject(.{
.name = "fragment_shader",
.root_module = b.createModule(.{
.root_source_file = b.path("shaders/fragment.zig"),
.target = spirv_target,
}),
.use_llvm = false,
});
triangle_exe.root_module.addAnonymousImport(
"fragment_shader",
.{ .root_source_file = frag_spv.getEmittedBin() },
);
} else { } else {
vk_generate_cmd.addFileArg(registry);
}
triangle_exe.root_module.addAnonymousImport("vulkan", .{
.root_source_file = vk_generate_cmd.addOutputFileArg("vk.zig"),
});
const vert_cmd = b.addSystemCommand(&.{ const vert_cmd = b.addSystemCommand(&.{
"glslc", "glslc",
"--target-env=vulkan1.2", "--target-env=vulkan1.2",
@@ -90,7 +53,6 @@ pub fn build(b: *std.Build) void {
triangle_exe.root_module.addAnonymousImport("fragment_shader", .{ triangle_exe.root_module.addAnonymousImport("fragment_shader", .{
.root_source_file = frag_spv, .root_source_file = frag_spv,
}); });
}
const triangle_run_cmd = b.addRunArtifact(triangle_exe); const triangle_run_cmd = b.addRunArtifact(triangle_exe);
triangle_run_cmd.step.dependOn(b.getInstallStep()); triangle_run_cmd.step.dependOn(b.getInstallStep());

View File

@@ -1,6 +1,5 @@
.{ .{
.name = .vulkan_zig_examples, .name = "vulkan-zig-examples",
.fingerprint = 0x60508bcca14cfc6d,
.version = "0.1.0", .version = "0.1.0",
.dependencies = .{ .dependencies = .{
.vulkan_zig = .{ .vulkan_zig = .{
@@ -8,7 +7,7 @@
}, },
.vulkan_headers = .{ .vulkan_headers = .{
.url = "https://github.com/KhronosGroup/Vulkan-Headers/archive/v1.3.283.tar.gz", .url = "https://github.com/KhronosGroup/Vulkan-Headers/archive/v1.3.283.tar.gz",
.hash = "N-V-__8AAAkkoQGn5z1yoNVrwqZfnYmZp8AZ5CJgoHRMQI0c", .hash = "1220a7e73d72a0d56bc2a65f9d8999a7c019e42260a0744c408d1cded111bc205e10",
}, },
}, },
.paths = .{""}, .paths = .{""},

View File

@@ -5,34 +5,38 @@ const Allocator = std.mem.Allocator;
const required_device_extensions = [_][*:0]const u8{vk.extensions.khr_swapchain.name}; const required_device_extensions = [_][*:0]const u8{vk.extensions.khr_swapchain.name};
/// There are 3 levels of bindings in vulkan-zig: /// To construct base, instance and device wrappers for vulkan-zig, you need to pass a list of 'apis' to it.
/// - The Dispatch types (vk.BaseDispatch, vk.InstanceDispatch, vk.DeviceDispatch) const apis: []const vk.ApiInfo = &.{
/// are "plain" structs which just contain the function pointers for a particular // You can either add invidiual functions by manually creating an 'api'
/// object. .{
/// - The Wrapper types (vk.Basewrapper, vk.InstanceWrapper, vk.DeviceWrapper) contains .base_commands = .{
/// the Dispatch type, as well as Ziggified Vulkan functions - these return Zig errors, .createInstance = true,
/// etc. },
/// - The Proxy types (vk.InstanceProxy, vk.DeviceProxy, vk.CommandBufferProxy, .instance_commands = .{
/// vk.QueueProxy) contain a pointer to a Wrapper and also contain the object's handle. .createDevice = true,
/// Calling Ziggified functions on these types automatically passes the handle as },
/// the first parameter of each function. Note that this type accepts a pointer to },
/// a wrapper struct as there is a problem with LLVM where embedding function pointers // Or you can add entire feature sets or extensions
/// and object pointer in the same struct leads to missed optimizations. If the wrapper vk.features.version_1_0,
/// member is a pointer, LLVM will try to optimize it as any other vtable. vk.extensions.khr_surface,
/// The wrappers contain vk.extensions.khr_swapchain,
const BaseWrapper = vk.BaseWrapper; };
const InstanceWrapper = vk.InstanceWrapper;
const DeviceWrapper = vk.DeviceWrapper;
const Instance = vk.InstanceProxy; /// Next, pass the `apis` to the wrappers to create dispatch tables.
const Device = vk.DeviceProxy; const BaseDispatch = vk.BaseWrapper(apis);
const InstanceDispatch = vk.InstanceWrapper(apis);
const DeviceDispatch = vk.DeviceWrapper(apis);
// Also create some proxying wrappers, which also have the respective handles
const Instance = vk.InstanceProxy(apis);
const Device = vk.DeviceProxy(apis);
pub const GraphicsContext = struct { pub const GraphicsContext = struct {
pub const CommandBuffer = vk.CommandBufferProxy; pub const CommandBuffer = vk.CommandBufferProxy(apis);
allocator: Allocator, allocator: Allocator,
vkb: BaseWrapper, vkb: BaseDispatch,
instance: Instance, instance: Instance,
surface: vk.SurfaceKHR, surface: vk.SurfaceKHR,
@@ -47,37 +51,28 @@ pub const GraphicsContext = struct {
pub fn init(allocator: Allocator, app_name: [*:0]const u8, window: *c.GLFWwindow) !GraphicsContext { pub fn init(allocator: Allocator, app_name: [*:0]const u8, window: *c.GLFWwindow) !GraphicsContext {
var self: GraphicsContext = undefined; var self: GraphicsContext = undefined;
self.allocator = allocator; self.allocator = allocator;
self.vkb = BaseWrapper.load(c.glfwGetInstanceProcAddress); self.vkb = try BaseDispatch.load(c.glfwGetInstanceProcAddress);
var extension_names: std.ArrayList([*:0]const u8) = .empty;
defer extension_names.deinit(allocator);
// these extensions are to support vulkan in mac os
// see https://github.com/glfw/glfw/issues/2335
try extension_names.append(allocator, "VK_KHR_portability_enumeration");
try extension_names.append(allocator, "VK_KHR_get_physical_device_properties2");
var glfw_exts_count: u32 = 0; var glfw_exts_count: u32 = 0;
const glfw_exts = c.glfwGetRequiredInstanceExtensions(&glfw_exts_count); const glfw_exts = c.glfwGetRequiredInstanceExtensions(&glfw_exts_count);
try extension_names.appendSlice(allocator, @ptrCast(glfw_exts[0..glfw_exts_count]));
const app_info = vk.ApplicationInfo{
.p_application_name = app_name,
.application_version = vk.makeApiVersion(0, 0, 0, 0),
.p_engine_name = app_name,
.engine_version = vk.makeApiVersion(0, 0, 0, 0),
.api_version = vk.API_VERSION_1_2,
};
const instance = try self.vkb.createInstance(&.{ const instance = try self.vkb.createInstance(&.{
.p_application_info = &.{ .p_application_info = &app_info,
.p_application_name = app_name, .enabled_extension_count = glfw_exts_count,
.application_version = @bitCast(vk.makeApiVersion(0, 0, 0, 0)), .pp_enabled_extension_names = @ptrCast(glfw_exts),
.p_engine_name = app_name,
.engine_version = @bitCast(vk.makeApiVersion(0, 0, 0, 0)),
.api_version = @bitCast(vk.API_VERSION_1_2),
},
.enabled_extension_count = @intCast(extension_names.items.len),
.pp_enabled_extension_names = extension_names.items.ptr,
// enumerate_portability_bit_khr to support vulkan in mac os
// see https://github.com/glfw/glfw/issues/2335
.flags = .{ .enumerate_portability_bit_khr = true },
}, null); }, null);
const vki = try allocator.create(InstanceWrapper); const vki = try allocator.create(InstanceDispatch);
errdefer allocator.destroy(vki); errdefer allocator.destroy(vki);
vki.* = InstanceWrapper.load(instance, self.vkb.dispatch.vkGetInstanceProcAddr.?); vki.* = try InstanceDispatch.load(instance, self.vkb.dispatch.vkGetInstanceProcAddr);
self.instance = Instance.init(instance, vki); self.instance = Instance.init(instance, vki);
errdefer self.instance.destroyInstance(null); errdefer self.instance.destroyInstance(null);
@@ -90,9 +85,9 @@ pub const GraphicsContext = struct {
const dev = try initializeCandidate(self.instance, candidate); const dev = try initializeCandidate(self.instance, candidate);
const vkd = try allocator.create(DeviceWrapper); const vkd = try allocator.create(DeviceDispatch);
errdefer allocator.destroy(vkd); errdefer allocator.destroy(vkd);
vkd.* = DeviceWrapper.load(dev, self.instance.wrapper.dispatch.vkGetDeviceProcAddr.?); vkd.* = try DeviceDispatch.load(dev, self.instance.wrapper.dispatch.vkGetDeviceProcAddr);
self.dev = Device.init(dev, vkd); self.dev = Device.init(dev, vkd);
errdefer self.dev.destroyDevice(null); errdefer self.dev.destroyDevice(null);
@@ -253,7 +248,7 @@ fn allocateQueues(instance: Instance, pdev: vk.PhysicalDevice, allocator: Alloca
graphics_family = family; graphics_family = family;
} }
if (present_family == null and (try instance.getPhysicalDeviceSurfaceSupportKHR(pdev, family, surface)) == .true) { if (present_family == null and (try instance.getPhysicalDeviceSurfaceSupportKHR(pdev, family, surface)) == vk.TRUE) {
present_family = family; present_family = family;
} }
} }

View File

@@ -1,12 +0,0 @@
const std = @import("std");
const gpu = std.gpu;
extern const v_color: @Vector(3, f32) addrspace(.input);
extern var f_color: @Vector(4, f32) addrspace(.output);
export fn main() callconv(.spirv_fragment) void {
gpu.location(&v_color, 0);
gpu.location(&f_color, 0);
f_color = .{ v_color[0], v_color[1], v_color[2], 1.0 };
}

View File

@@ -1,16 +0,0 @@
const std = @import("std");
const gpu = std.gpu;
extern const a_pos: @Vector(2, f32) addrspace(.input);
extern const a_color: @Vector(3, f32) addrspace(.input);
extern var v_color: @Vector(3, f32) addrspace(.output);
export fn main() callconv(.spirv_vertex) void {
gpu.location(&a_pos, 0);
gpu.location(&a_color, 1);
gpu.location(&v_color, 0);
gpu.position_out.* = .{ a_pos[0], a_pos[1], 0.0, 1.0 };
v_color = a_color;
}

View File

@@ -46,7 +46,7 @@ pub const Swapchain = struct {
else else
.exclusive; .exclusive;
const handle = gc.dev.createSwapchainKHR(&.{ const handle = try gc.dev.createSwapchainKHR(&.{
.surface = gc.surface, .surface = gc.surface,
.min_image_count = image_count, .min_image_count = image_count,
.image_format = surface_format.format, .image_format = surface_format.format,
@@ -60,11 +60,9 @@ pub const Swapchain = struct {
.pre_transform = caps.current_transform, .pre_transform = caps.current_transform,
.composite_alpha = .{ .opaque_bit_khr = true }, .composite_alpha = .{ .opaque_bit_khr = true },
.present_mode = present_mode, .present_mode = present_mode,
.clipped = .true, .clipped = vk.TRUE,
.old_swapchain = old_handle, .old_swapchain = old_handle,
}, null) catch { }, null);
return error.SwapchainCreationFailed;
};
errdefer gc.dev.destroySwapchainKHR(handle, null); errdefer gc.dev.destroySwapchainKHR(handle, null);
if (old_handle != .null_handle) { if (old_handle != .null_handle) {
@@ -82,11 +80,7 @@ pub const Swapchain = struct {
errdefer gc.dev.destroySemaphore(next_image_acquired, null); errdefer gc.dev.destroySemaphore(next_image_acquired, null);
const result = try gc.dev.acquireNextImageKHR(handle, std.math.maxInt(u64), next_image_acquired, .null_handle); const result = try gc.dev.acquireNextImageKHR(handle, std.math.maxInt(u64), next_image_acquired, .null_handle);
// event with a .suboptimal_khr we can still go on to present if (result.result != .success) {
// if we error even for .suboptimal_khr the example will crash and segfault
// on resize, since even the recreated swapchain can be suboptimal during a
// resize.
if (result.result == .not_ready or result.result == .timeout) {
return error.ImageAcquireFailed; return error.ImageAcquireFailed;
} }
@@ -115,8 +109,6 @@ pub const Swapchain = struct {
} }
pub fn deinit(self: Swapchain) void { pub fn deinit(self: Swapchain) void {
// if we have no swapchain none of these should exist and we can just return
if (self.handle == .null_handle) return;
self.deinitExceptSwapchain(); self.deinitExceptSwapchain();
self.gc.dev.destroySwapchainKHR(self.handle, null); self.gc.dev.destroySwapchainKHR(self.handle, null);
} }
@@ -126,18 +118,7 @@ pub const Swapchain = struct {
const allocator = self.allocator; const allocator = self.allocator;
const old_handle = self.handle; const old_handle = self.handle;
self.deinitExceptSwapchain(); self.deinitExceptSwapchain();
// set current handle to NULL_HANDLE to signal that the current swapchain does no longer need to be self.* = try initRecycle(gc, allocator, new_extent, old_handle);
// de-initialized if we fail to recreate it.
self.handle = .null_handle;
self.* = initRecycle(gc, allocator, new_extent, old_handle) catch |err| switch (err) {
error.SwapchainCreationFailed => {
// we failed while recreating so our current handle still exists,
// but we won't destroy it in the deferred deinit of this object.
gc.dev.destroySwapchainKHR(old_handle, null);
return err;
},
else => return err,
};
} }
pub fn currentImage(self: Swapchain) vk.Image { pub fn currentImage(self: Swapchain) vk.Image {
@@ -261,7 +242,7 @@ const SwapImage = struct {
} }
fn waitForFence(self: SwapImage, gc: *const GraphicsContext) !void { fn waitForFence(self: SwapImage, gc: *const GraphicsContext) !void {
_ = try gc.dev.waitForFences(1, @ptrCast(&self.frame_fence), .true, std.math.maxInt(u64)); _ = try gc.dev.waitForFences(1, @ptrCast(&self.frame_fence), vk.TRUE, std.math.maxInt(u64));
} }
}; };

View File

@@ -63,23 +63,6 @@ pub fn main() !void {
) orelse return error.WindowInitFailed; ) orelse return error.WindowInitFailed;
defer c.glfwDestroyWindow(window); defer c.glfwDestroyWindow(window);
// According to the GLFW docs:
//
// > Window systems put limits on window sizes. Very large or very small window dimensions
// > may be overridden by the window system on creation. Check the actual size after creation.
// -- https://www.glfw.org/docs/3.3/group__window.html#ga3555a418df92ad53f917597fe2f64aeb
//
// This happens in practice, for example, when using Wayland with a scaling factor that is not a
// divisor of the initial window size (see https://github.com/Snektron/vulkan-zig/pull/192).
// To fix it, just fetch the actual size here, after the windowing system has had the time to
// update the window.
extent.width, extent.height = blk: {
var w: c_int = undefined;
var h: c_int = undefined;
c.glfwGetFramebufferSize(window, &w, &h);
break :blk .{ @intCast(w), @intCast(h) };
};
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit(); defer _ = gpa.deinit();
const allocator = gpa.allocator(); const allocator = gpa.allocator();
@@ -140,7 +123,6 @@ pub fn main() !void {
); );
defer destroyCommandBuffers(&gc, pool, allocator, cmdbufs); defer destroyCommandBuffers(&gc, pool, allocator, cmdbufs);
var state: Swapchain.PresentState = .optimal;
while (c.glfwWindowShouldClose(window) == c.GLFW_FALSE) { while (c.glfwWindowShouldClose(window) == c.GLFW_FALSE) {
var w: c_int = undefined; var w: c_int = undefined;
var h: c_int = undefined; var h: c_int = undefined;
@@ -154,6 +136,11 @@ pub fn main() !void {
const cmdbuf = cmdbufs[swapchain.image_index]; const cmdbuf = cmdbufs[swapchain.image_index];
const state = swapchain.present(cmdbuf) catch |err| switch (err) {
error.OutOfDateKHR => Swapchain.PresentState.suboptimal,
else => |narrow| return narrow,
};
if (state == .suboptimal or extent.width != @as(u32, @intCast(w)) or extent.height != @as(u32, @intCast(h))) { if (state == .suboptimal or extent.width != @as(u32, @intCast(w)) or extent.height != @as(u32, @intCast(h))) {
extent.width = @intCast(w); extent.width = @intCast(w);
extent.height = @intCast(h); extent.height = @intCast(h);
@@ -174,10 +161,6 @@ pub fn main() !void {
framebuffers, framebuffers,
); );
} }
state = swapchain.present(cmdbuf) catch |err| switch (err) {
error.OutOfDateKHR => Swapchain.PresentState.suboptimal,
else => |narrow| return narrow,
};
c.glfwPollEvents(); c.glfwPollEvents();
} }
@@ -414,7 +397,7 @@ fn createPipeline(
const piasci = vk.PipelineInputAssemblyStateCreateInfo{ const piasci = vk.PipelineInputAssemblyStateCreateInfo{
.topology = .triangle_list, .topology = .triangle_list,
.primitive_restart_enable = .false, .primitive_restart_enable = vk.FALSE,
}; };
const pvsci = vk.PipelineViewportStateCreateInfo{ const pvsci = vk.PipelineViewportStateCreateInfo{
@@ -425,12 +408,12 @@ fn createPipeline(
}; };
const prsci = vk.PipelineRasterizationStateCreateInfo{ const prsci = vk.PipelineRasterizationStateCreateInfo{
.depth_clamp_enable = .false, .depth_clamp_enable = vk.FALSE,
.rasterizer_discard_enable = .false, .rasterizer_discard_enable = vk.FALSE,
.polygon_mode = .fill, .polygon_mode = .fill,
.cull_mode = .{ .back_bit = true }, .cull_mode = .{ .back_bit = true },
.front_face = .clockwise, .front_face = .clockwise,
.depth_bias_enable = .false, .depth_bias_enable = vk.FALSE,
.depth_bias_constant_factor = 0, .depth_bias_constant_factor = 0,
.depth_bias_clamp = 0, .depth_bias_clamp = 0,
.depth_bias_slope_factor = 0, .depth_bias_slope_factor = 0,
@@ -439,14 +422,14 @@ fn createPipeline(
const pmsci = vk.PipelineMultisampleStateCreateInfo{ const pmsci = vk.PipelineMultisampleStateCreateInfo{
.rasterization_samples = .{ .@"1_bit" = true }, .rasterization_samples = .{ .@"1_bit" = true },
.sample_shading_enable = .false, .sample_shading_enable = vk.FALSE,
.min_sample_shading = 1, .min_sample_shading = 1,
.alpha_to_coverage_enable = .false, .alpha_to_coverage_enable = vk.FALSE,
.alpha_to_one_enable = .false, .alpha_to_one_enable = vk.FALSE,
}; };
const pcbas = vk.PipelineColorBlendAttachmentState{ const pcbas = vk.PipelineColorBlendAttachmentState{
.blend_enable = .false, .blend_enable = vk.FALSE,
.src_color_blend_factor = .one, .src_color_blend_factor = .one,
.dst_color_blend_factor = .zero, .dst_color_blend_factor = .zero,
.color_blend_op = .add, .color_blend_op = .add,
@@ -457,7 +440,7 @@ fn createPipeline(
}; };
const pcbsci = vk.PipelineColorBlendStateCreateInfo{ const pcbsci = vk.PipelineColorBlendStateCreateInfo{
.logic_op_enable = .false, .logic_op_enable = vk.FALSE,
.logic_op = .copy, .logic_op = .copy,
.attachment_count = 1, .attachment_count = 1,
.p_attachments = @ptrCast(&pcbas), .p_attachments = @ptrCast(&pcbas),

View File

@@ -52,8 +52,13 @@ pub fn isZigPrimitiveType(name: []const u8) bool {
return false; return false;
} }
pub fn writeIdentifier(w: *std.io.Writer, id: []const u8) !void { pub fn writeIdentifier(writer: anytype, id: []const u8) !void {
try w.print("{f}", .{std.zig.fmtId(id)}); // https://github.com/ziglang/zig/issues/2897
if (isZigPrimitiveType(id)) {
try writer.print("@\"{}\"", .{std.zig.fmtEscapes(id)});
} else {
try writer.print("{}", .{std.zig.fmtId(id)});
}
} }
pub const CaseStyle = enum { pub const CaseStyle = enum {
@@ -121,12 +126,12 @@ pub const SegmentIterator = struct {
pub const IdRenderer = struct { pub const IdRenderer = struct {
tags: []const []const u8, tags: []const []const u8,
text_cache: std.io.Writer.Allocating, text_cache: std.ArrayList(u8),
pub fn init(allocator: Allocator, tags: []const []const u8) IdRenderer { pub fn init(allocator: Allocator, tags: []const []const u8) IdRenderer {
return .{ return .{
.tags = tags, .tags = tags,
.text_cache = .init(allocator), .text_cache = std.ArrayList(u8).init(allocator),
}; };
} }
@@ -142,19 +147,19 @@ pub const IdRenderer = struct {
if (first) { if (first) {
first = false; first = false;
} else { } else {
try self.text_cache.writer.writeByte('_'); try self.text_cache.append('_');
} }
for (segment) |c| { for (segment) |c| {
try self.text_cache.writer.writeByte(if (screaming) std.ascii.toUpper(c) else std.ascii.toLower(c)); try self.text_cache.append(if (screaming) std.ascii.toUpper(c) else std.ascii.toLower(c));
} }
} }
if (tag) |name| { if (tag) |name| {
try self.text_cache.writer.writeByte('_'); try self.text_cache.append('_');
for (name) |c| { for (name) |c| {
try self.text_cache.writer.writeByte(if (screaming) std.ascii.toUpper(c) else std.ascii.toLower(c)); try self.text_cache.append(if (screaming) std.ascii.toUpper(c) else std.ascii.toLower(c));
} }
} }
} }
@@ -166,7 +171,7 @@ pub const IdRenderer = struct {
while (it.next()) |segment| { while (it.next()) |segment| {
var i: usize = 0; var i: usize = 0;
while (i < segment.len and std.ascii.isDigit(segment[i])) { while (i < segment.len and std.ascii.isDigit(segment[i])) {
try self.text_cache.writer.writeByte(segment[i]); try self.text_cache.append(segment[i]);
i += 1; i += 1;
} }
@@ -175,34 +180,34 @@ pub const IdRenderer = struct {
} }
if (i == 0 and lower_first) { if (i == 0 and lower_first) {
try self.text_cache.writer.writeByte(std.ascii.toLower(segment[i])); try self.text_cache.append(std.ascii.toLower(segment[i]));
} else { } else {
try self.text_cache.writer.writeByte(std.ascii.toUpper(segment[i])); try self.text_cache.append(std.ascii.toUpper(segment[i]));
} }
lower_first = false; lower_first = false;
for (segment[i + 1 ..]) |c| { for (segment[i + 1 ..]) |c| {
try self.text_cache.writer.writeByte(std.ascii.toLower(c)); try self.text_cache.append(std.ascii.toLower(c));
} }
} }
if (tag) |name| { if (tag) |name| {
try self.text_cache.writer.writeAll(name); try self.text_cache.appendSlice(name);
} }
} }
pub fn renderFmt(self: *IdRenderer, out: *std.Io.Writer, comptime fmt: []const u8, args: anytype) !void { pub fn renderFmt(self: *IdRenderer, out: anytype, comptime fmt: []const u8, args: anytype) !void {
_ = self.text_cache.writer.consumeAll(); self.text_cache.items.len = 0;
try self.text_cache.writer.print(fmt, args); try std.fmt.format(self.text_cache.writer(), fmt, args);
try writeIdentifier(out, self.text_cache.writer.buffered()); try writeIdentifier(out, self.text_cache.items);
} }
pub fn renderWithCase(self: *IdRenderer, out: *std.Io.Writer, case_style: CaseStyle, id: []const u8) !void { pub fn renderWithCase(self: *IdRenderer, out: anytype, case_style: CaseStyle, id: []const u8) !void {
const tag = self.getAuthorTag(id); const tag = self.getAuthorTag(id);
// The trailing underscore doesn't need to be removed here as its removed by the SegmentIterator. // The trailing underscore doesn't need to be removed here as its removed by the SegmentIterator.
const adjusted_id = if (tag) |name| id[0 .. id.len - name.len] else id; const adjusted_id = if (tag) |name| id[0 .. id.len - name.len] else id;
_ = self.text_cache.writer.consumeAll(); self.text_cache.items.len = 0;
switch (case_style) { switch (case_style) {
.snake => try self.renderSnake(false, adjusted_id, tag), .snake => try self.renderSnake(false, adjusted_id, tag),
@@ -211,7 +216,7 @@ pub const IdRenderer = struct {
.camel => try self.renderCamel(false, adjusted_id, tag), .camel => try self.renderCamel(false, adjusted_id, tag),
} }
try writeIdentifier(out, self.text_cache.writer.buffered()); try writeIdentifier(out, self.text_cache.items);
} }
pub fn getAuthorTag(self: IdRenderer, id: []const u8) ?[]const u8 { pub fn getAuthorTag(self: IdRenderer, id: []const u8) ?[]const u8 {

View File

@@ -1,5 +1,4 @@
const std = @import("std"); const std = @import("std");
const generator = @import("vulkan/generator.zig"); const generator = @import("vulkan/generator.zig");
fn invalidUsage(prog_name: []const u8, comptime fmt: []const u8, args: anytype) noreturn { fn invalidUsage(prog_name: []const u8, comptime fmt: []const u8, args: anytype) noreturn {
@@ -9,47 +8,39 @@ fn invalidUsage(prog_name: []const u8, comptime fmt: []const u8, args: anytype)
} }
fn reportParseErrors(tree: std.zig.Ast) !void { fn reportParseErrors(tree: std.zig.Ast) !void {
var buf: [1024]u8 = undefined; const stderr = std.io.getStdErr().writer();
var stderr = std.fs.File.stderr().writer(&buf);
const w = &stderr.interface;
for (tree.errors) |err| { for (tree.errors) |err| {
const loc = tree.tokenLocation(0, err.token); const loc = tree.tokenLocation(0, err.token);
try w.print("(vulkan-zig error):{}:{}: error: ", .{ loc.line + 1, loc.column + 1 }); try stderr.print("(vulkan-zig error):{}:{}: error: ", .{ loc.line + 1, loc.column + 1 });
try tree.renderError(err, w); try tree.renderError(err, stderr);
try w.print("\n{s}\n", .{tree.source[loc.line_start..loc.line_end]}); try stderr.print("\n{s}\n", .{tree.source[loc.line_start..loc.line_end]});
for (0..loc.column) |_| { for (0..loc.column) |_| {
try w.writeAll(" "); try stderr.writeAll(" ");
} }
try w.writeAll("^\n"); try stderr.writeAll("^\n");
} }
} }
fn oomPanic() noreturn { pub fn main() void {
@panic("Out of memory");
}
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit(); defer arena.deinit();
const allocator = arena.allocator(); const allocator = arena.allocator();
var args = std.process.argsWithAllocator(allocator) catch |err| switch (err) { var args = std.process.argsWithAllocator(allocator) catch |err| switch (err) {
error.OutOfMemory => oomPanic(), error.OutOfMemory => @panic("OOM"),
}; };
const prog_name = args.next() orelse "vulkan-zig-generator"; const prog_name = args.next() orelse "vulkan-zig-generator";
var maybe_xml_path: ?[]const u8 = null; var maybe_xml_path: ?[]const u8 = null;
var maybe_out_path: ?[]const u8 = null; var maybe_out_path: ?[]const u8 = null;
var maybe_video_xml_path: ?[]const u8 = null;
var debug: bool = false; var debug: bool = false;
var api = generator.Api.vulkan; var api = generator.Api.vulkan;
while (args.next()) |arg| { while (args.next()) |arg| {
if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) { if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
@setEvalBranchQuota(2000); @setEvalBranchQuota(2000);
var buf: [1024]u8 = undefined; std.io.getStdOut().writer().print(
var w = std.fs.File.stdout().writer(&buf);
w.interface.print(
\\Utility to generate a Zig binding from the Vulkan XML API registry. \\Utility to generate a Zig binding from the Vulkan XML API registry.
\\ \\
\\The most recent Vulkan XML API registry can be obtained from \\The most recent Vulkan XML API registry can be obtained from
@@ -62,13 +53,12 @@ pub fn main() !void {
\\-h --help show this message and exit. \\-h --help show this message and exit.
\\-a --api <api> Generate API for 'vulkan' or 'vulkansc'. Defaults to 'vulkan'. \\-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}, .{prog_name},
) catch |err| { ) catch |err| {
std.process.fatal("failed to write to stdout: {s}", .{@errorName(err)}); std.log.err("failed to write to stdout: {s}", .{@errorName(err)});
std.process.exit(1);
}; };
return; return;
} else if (std.mem.eql(u8, arg, "-a") or std.mem.eql(u8, arg, "--api")) { } else if (std.mem.eql(u8, arg, "-a") or std.mem.eql(u8, arg, "--api")) {
@@ -78,16 +68,12 @@ pub fn main() !void {
api = std.meta.stringToEnum(generator.Api, api_str) orelse { api = std.meta.stringToEnum(generator.Api, api_str) orelse {
invalidUsage(prog_name, "invalid api '{s}'", .{api_str}); 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) { } else if (maybe_xml_path == null) {
maybe_xml_path = arg; maybe_xml_path = arg;
} else if (maybe_out_path == null) { } else if (maybe_out_path == null) {
maybe_out_path = arg; maybe_out_path = arg;
} else if (std.mem.eql(u8, arg, "--debug")) {
debug = true;
} else { } else {
invalidUsage(prog_name, "superficial argument '{s}'", .{arg}); invalidUsage(prog_name, "superficial argument '{s}'", .{arg});
} }
@@ -103,23 +89,12 @@ pub fn main() !void {
const cwd = std.fs.cwd(); const cwd = std.fs.cwd();
const xml_src = cwd.readFileAlloc(allocator, xml_path, std.math.maxInt(usize)) catch |err| { const xml_src = cwd.readFileAlloc(allocator, xml_path, std.math.maxInt(usize)) catch |err| {
std.process.fatal("failed to open input file '{s}' ({s})", .{ xml_path, @errorName(err) }); std.log.err("failed to open input file '{s}' ({s})", .{ xml_path, @errorName(err) });
std.process.exit(1);
}; };
const maybe_video_xml_src = if (maybe_video_xml_path) |video_xml_path| var out_buffer = std.ArrayList(u8).init(allocator);
cwd.readFileAlloc(allocator, video_xml_path, std.math.maxInt(usize)) catch |err| { generator.generate(allocator, api, xml_src, out_buffer.writer()) catch |err| switch (err) {
std.process.fatal("failed to open input file '{s}' ({s})", .{ video_xml_path, @errorName(err) });
}
else
null;
var aw: std.io.Writer.Allocating = .init(allocator);
generator.generate(allocator, api, xml_src, maybe_video_xml_src, &aw.writer) catch |err| {
if (debug) {
return err;
}
switch (err) {
error.InvalidXml => { error.InvalidXml => {
std.log.err("invalid vulkan registry - invalid xml", .{}); std.log.err("invalid vulkan registry - invalid xml", .{});
std.log.err("please check that the correct vk.xml file is passed", .{}); std.log.err("please check that the correct vk.xml file is passed", .{});
@@ -136,16 +111,14 @@ pub fn main() !void {
std.log.err("please make a bug report at https://github.com/Snektron/vulkan-zig/issues/", .{}); std.log.err("please make a bug report at https://github.com/Snektron/vulkan-zig/issues/", .{});
std.process.exit(1); std.process.exit(1);
}, },
error.OutOfMemory, error.WriteFailed => oomPanic(), error.OutOfMemory => @panic("oom"),
}
}; };
aw.writer.writeByte(0) catch oomPanic(); out_buffer.append(0) catch @panic("oom");
const buffered = aw.writer.buffered(); const src = out_buffer.items[0 .. out_buffer.items.len - 1 :0];
const src = buffered[0 .. buffered.len - 1 :0];
const tree = std.zig.Ast.parse(allocator, src, .zig) catch |err| switch (err) { const tree = std.zig.Ast.parse(allocator, src, .zig) catch |err| switch (err) {
error.OutOfMemory => oomPanic(), error.OutOfMemory => @panic("oom"),
}; };
const formatted = if (tree.errors.len > 0) blk: { const formatted = if (tree.errors.len > 0) blk: {
@@ -155,20 +128,22 @@ pub fn main() !void {
std.log.err("or run with --debug to write out unformatted source", .{}); std.log.err("or run with --debug to write out unformatted source", .{});
reportParseErrors(tree) catch |err| { reportParseErrors(tree) catch |err| {
std.process.fatal("failed to dump ast errors: {s}", .{@errorName(err)}); std.log.err("failed to dump ast errors: {s}", .{@errorName(err)});
std.process.exit(1);
}; };
if (debug) { if (debug) {
break :blk src; break :blk src;
} }
std.process.exit(1); std.process.exit(1);
} else tree.renderAlloc(allocator) catch |err| switch (err) { } else tree.render(allocator) catch |err| switch (err) {
error.OutOfMemory => oomPanic(), error.OutOfMemory => @panic("oom"),
}; };
if (std.fs.path.dirname(out_path)) |dir| { if (std.fs.path.dirname(out_path)) |dir| {
cwd.makePath(dir) catch |err| { cwd.makePath(dir) catch |err| {
std.process.fatal("failed to create output directory '{s}' ({s})", .{ dir, @errorName(err) }); std.log.err("failed to create output directory '{s}' ({s})", .{ dir, @errorName(err) });
std.process.exit(1);
}; };
} }
@@ -176,7 +151,8 @@ pub fn main() !void {
.sub_path = out_path, .sub_path = out_path,
.data = formatted, .data = formatted,
}) catch |err| { }) catch |err| {
std.process.fatal("failed to write to output file '{s}' ({s})", .{ out_path, @errorName(err) }); std.log.err("failed to write to output file '{s}' ({s})", .{ out_path, @errorName(err) });
std.process.exit(1);
}; };
} }

View File

@@ -90,18 +90,10 @@ pub const CTokenizer = struct {
const start = self.offset; const start = self.offset;
_ = self.consumeNoEof(); _ = self.consumeNoEof();
const hex = self.peek() == 'x';
if (hex) {
_ = self.consumeNoEof();
}
while (true) { while (true) {
switch (self.peek() orelse break) { const c = self.peek() orelse break;
switch (c) {
'0'...'9' => _ = self.consumeNoEof(), '0'...'9' => _ = self.consumeNoEof(),
'A'...'F', 'a'...'f' => {
if (!hex) break;
_ = self.consumeNoEof();
},
else => break, else => break,
} }
} }
@@ -172,12 +164,7 @@ pub const XmlCTokenizer = struct {
} }
fn elemToToken(elem: *xml.Element) !?Token { fn elemToToken(elem: *xml.Element) !?Token {
// Sometimes we encounter empty comment tags. Filter those out if (elem.children.len != 1 or elem.children[0] != .char_data) {
// 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; return error.InvalidXml;
} }
@@ -188,6 +175,8 @@ pub const XmlCTokenizer = struct {
return Token{ .kind = .enum_name, .text = text }; return Token{ .kind = .enum_name, .text = text };
} else if (mem.eql(u8, elem.tag, "name")) { } else if (mem.eql(u8, elem.tag, "name")) {
return Token{ .kind = .name, .text = text }; return Token{ .kind = .name, .text = text };
} else if (mem.eql(u8, elem.tag, "comment")) {
return null;
} else { } else {
return error.InvalidTag; return error.InvalidTag;
} }
@@ -457,8 +446,8 @@ fn parseFnPtrSuffix(allocator: Allocator, xctok: *XmlCTokenizer, return_type: Ty
// There is no good way to estimate the number of parameters beforehand. // There is no good way to estimate the number of parameters beforehand.
// Fortunately, there are usually a relatively low number of parameters to a function pointer, // Fortunately, there are usually a relatively low number of parameters to a function pointer,
// so an ArrayList backed by an arena allocator is good enough. // so an ArrayList backed by an arena allocator is good enough.
var params: std.ArrayList(registry.Command.Param) = .empty; var params = std.ArrayList(registry.Command.Param).init(allocator);
try params.append(allocator, .{ try params.append(.{
.name = first_param.name.?, .name = first_param.name.?,
.param_type = first_param.decl_type, .param_type = first_param.decl_type,
.is_buffer_len = false, .is_buffer_len = false,
@@ -473,7 +462,7 @@ fn parseFnPtrSuffix(allocator: Allocator, xctok: *XmlCTokenizer, return_type: Ty
} }
const decl = try parseDeclaration(allocator, xctok, ptrs_optional); const decl = try parseDeclaration(allocator, xctok, ptrs_optional);
try params.append(allocator, .{ try params.append(.{
.name = decl.name orelse return error.MissingTypeIdentifier, .name = decl.name orelse return error.MissingTypeIdentifier,
.param_type = decl.decl_type, .param_type = decl.decl_type,
.is_buffer_len = false, .is_buffer_len = false,
@@ -482,7 +471,7 @@ fn parseFnPtrSuffix(allocator: Allocator, xctok: *XmlCTokenizer, return_type: Ty
} }
_ = try xctok.nextNoEof(); _ = try xctok.nextNoEof();
command_ptr.decl_type.command_ptr.params = try params.toOwnedSlice(allocator); command_ptr.decl_type.command_ptr.params = try params.toOwnedSlice();
return command_ptr; return command_ptr;
} }
@@ -541,10 +530,7 @@ fn parseArrayDeclarator(xctok: *XmlCTokenizer) !?ArraySize {
error.InvalidCharacter => unreachable, error.InvalidCharacter => unreachable,
}, },
}, },
// Sometimes, arrays are declared as `<type>T</type> <name>aa</name>[<enum>SIZE</enum>]`, .enum_name => .{ .alias = size_tok.text },
// 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, else => return error.InvalidSyntax,
}; };
@@ -552,7 +538,7 @@ fn parseArrayDeclarator(xctok: *XmlCTokenizer) !?ArraySize {
return size; return size;
} }
pub fn parseVersion(xctok: *XmlCTokenizer) !registry.ApiConstant.Value { pub fn parseVersion(xctok: *XmlCTokenizer) ![4][]const u8 {
_ = try xctok.expect(.hash); _ = try xctok.expect(.hash);
const define = try xctok.expect(.id); const define = try xctok.expect(.id);
if (!mem.eql(u8, define.text, "define")) { if (!mem.eql(u8, define.text, "define")) {
@@ -561,22 +547,12 @@ pub fn parseVersion(xctok: *XmlCTokenizer) !registry.ApiConstant.Value {
_ = try xctok.expect(.name); _ = try xctok.expect(.name);
const vk_make_version = try xctok.expect(.type_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; return error.NotVersion;
} }
}
fn parseVersionValues(xctok: *XmlCTokenizer, comptime count: usize) ![count][]const u8 {
_ = try xctok.expect(.lparen); _ = try xctok.expect(.lparen);
var version: [count][]const u8 = undefined; var version: [4][]const u8 = undefined;
for (&version, 0..) |*part, i| { for (&version, 0..) |*part, i| {
if (i != 0) { if (i != 0) {
_ = try xctok.expect(.comma); _ = try xctok.expect(.comma);

View File

@@ -10,13 +10,11 @@ const FeatureLevel = reg.FeatureLevel;
const EnumFieldMerger = struct { const EnumFieldMerger = struct {
const EnumExtensionMap = std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(reg.Enum.Field)); const EnumExtensionMap = std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(reg.Enum.Field));
const ApiConstantMap = std.StringArrayHashMapUnmanaged(reg.ApiConstant);
const FieldSet = std.StringArrayHashMapUnmanaged(void); const FieldSet = std.StringArrayHashMapUnmanaged(void);
arena: Allocator, arena: Allocator,
registry: *reg.Registry, registry: *reg.Registry,
enum_extensions: EnumExtensionMap, enum_extensions: EnumExtensionMap,
api_constants: ApiConstantMap,
field_set: FieldSet, field_set: FieldSet,
fn init(arena: Allocator, registry: *reg.Registry) EnumFieldMerger { fn init(arena: Allocator, registry: *reg.Registry) EnumFieldMerger {
@@ -24,7 +22,6 @@ const EnumFieldMerger = struct {
.arena = arena, .arena = arena,
.registry = registry, .registry = registry,
.enum_extensions = .{}, .enum_extensions = .{},
.api_constants = .{},
.field_set = .{}, .field_set = .{},
}; };
} }
@@ -32,7 +29,7 @@ const EnumFieldMerger = struct {
fn putEnumExtension(self: *EnumFieldMerger, enum_name: []const u8, field: reg.Enum.Field) !void { fn putEnumExtension(self: *EnumFieldMerger, enum_name: []const u8, field: reg.Enum.Field) !void {
const res = try self.enum_extensions.getOrPut(self.arena, enum_name); const res = try self.enum_extensions.getOrPut(self.arena, enum_name);
if (!res.found_existing) { if (!res.found_existing) {
res.value_ptr.* = .empty; res.value_ptr.* = std.ArrayListUnmanaged(reg.Enum.Field){};
} }
try res.value_ptr.append(self.arena, field); try res.value_ptr.append(self.arena, field);
@@ -41,17 +38,7 @@ const EnumFieldMerger = struct {
fn addRequires(self: *EnumFieldMerger, reqs: []const reg.Require) !void { fn addRequires(self: *EnumFieldMerger, reqs: []const reg.Require) !void {
for (reqs) |req| { for (reqs) |req| {
for (req.extends) |enum_ext| { for (req.extends) |enum_ext| {
switch (enum_ext.value) { try self.putEnumExtension(enum_ext.extends, enum_ext.field);
.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 },
},
),
}
} }
} }
} }
@@ -89,10 +76,6 @@ const EnumFieldMerger = struct {
} }
fn merge(self: *EnumFieldMerger) !void { 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| { for (self.registry.features) |feature| {
try self.addRequires(feature.requires); try self.addRequires(feature.requires);
} }
@@ -108,8 +91,6 @@ const EnumFieldMerger = struct {
try self.mergeEnumFields(decl.name, &decl.decl_type.enumeration); try self.mergeEnumFields(decl.name, &decl.decl_type.enumeration);
} }
} }
self.registry.api_constants = self.api_constants.values();
} }
}; };
@@ -117,10 +98,9 @@ pub const Generator = struct {
arena: std.heap.ArenaAllocator, arena: std.heap.ArenaAllocator,
registry: reg.Registry, registry: reg.Registry,
id_renderer: IdRenderer, id_renderer: IdRenderer,
have_video: bool,
fn init(allocator: Allocator, spec: *xml.Element, maybe_video_spec: ?*xml.Element, api: reg.Api) !Generator { fn init(allocator: Allocator, spec: *xml.Element, api: reg.Api) !Generator {
const result = try parseXml(allocator, spec, maybe_video_spec, api); const result = try parseXml(allocator, spec, api);
const tags = try allocator.alloc([]const u8, result.registry.tags.len); const tags = try allocator.alloc([]const u8, result.registry.tags.len);
for (tags, result.registry.tags) |*tag, registry_tag| tag.* = registry_tag.name; for (tags, result.registry.tags) |*tag, registry_tag| tag.* = registry_tag.name;
@@ -129,7 +109,6 @@ pub const Generator = struct {
.arena = result.arena, .arena = result.arena,
.registry = result.registry, .registry = result.registry,
.id_renderer = IdRenderer.init(allocator, tags), .id_renderer = IdRenderer.init(allocator, tags),
.have_video = maybe_video_spec != null,
}; };
} }
@@ -186,8 +165,8 @@ pub const Generator = struct {
self.registry.decls.len = i; self.registry.decls.len = i;
} }
fn render(self: *Generator, writer: *std.Io.Writer) !void { fn render(self: *Generator, writer: anytype) !void {
try renderRegistry(writer, self.arena.allocator(), &self.registry, &self.id_renderer, self.have_video); try renderRegistry(writer, self.arena.allocator(), &self.registry, &self.id_renderer);
} }
}; };
@@ -199,13 +178,7 @@ pub const Api = reg.Api;
/// and the resulting binding is written to `writer`. `allocator` will be used to allocate temporary /// 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 /// 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`. /// directly. `api` is the API to generate the bindings for, usually `.vulkan`.
pub fn generate( pub fn generate(allocator: Allocator, api: Api, spec_xml: []const u8, writer: anytype) !void {
allocator: Allocator,
api: Api,
spec_xml: []const u8,
maybe_video_spec_xml: ?[]const u8,
writer: *std.Io.Writer,
) !void {
const spec = xml.parse(allocator, spec_xml) catch |err| switch (err) { const spec = xml.parse(allocator, spec_xml) catch |err| switch (err) {
error.InvalidDocument, error.InvalidDocument,
error.UnexpectedEof, error.UnexpectedEof,
@@ -222,26 +195,7 @@ pub fn generate(
}; };
defer spec.deinit(); defer spec.deinit();
const maybe_video_spec_root = if (maybe_video_spec_xml) |video_spec_xml| blk: { var gen = Generator.init(allocator, spec.root, api) catch |err| switch (err) {
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.InvalidXml,
error.InvalidCharacter, error.InvalidCharacter,
error.Overflow, error.Overflow,

View File

@@ -17,43 +17,18 @@ pub const ParseResult = struct {
} }
}; };
pub fn parseXml( pub fn parseXml(backing_allocator: Allocator, root: *xml.Element, api: registry.Api) !ParseResult {
backing_allocator: Allocator,
root: *xml.Element,
maybe_video_root: ?*xml.Element,
api: registry.Api,
) !ParseResult {
var arena = ArenaAllocator.init(backing_allocator); var arena = ArenaAllocator.init(backing_allocator);
errdefer arena.deinit(); errdefer arena.deinit();
const allocator = arena.allocator(); const allocator = arena.allocator();
var decls: std.ArrayList(registry.Declaration) = .empty;
var api_constants: std.ArrayList(registry.ApiConstant) = .empty;
var tags: std.ArrayList(registry.Tag) = .empty;
var features: std.ArrayList(registry.Feature) = .empty;
var extensions: std.ArrayList(registry.Extension) = .empty;
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{ const reg = registry.Registry{
.decls = decls.items, .decls = try parseDeclarations(allocator, root, api),
.api_constants = api_constants.items, .api_constants = try parseApiConstants(allocator, root, api),
.tags = tags.items, .tags = try parseTags(allocator, root),
.features = features.items, .features = try parseFeatures(allocator, root, api),
.extensions = extensions.items, .extensions = try parseExtensions(allocator, root, api),
}; };
return ParseResult{ return ParseResult{
@@ -62,33 +37,25 @@ pub fn parseXml(
}; };
} }
fn parseDeclarations( fn parseDeclarations(allocator: Allocator, root: *xml.Element, api: registry.Api) ![]registry.Declaration {
allocator: Allocator,
root: *xml.Element,
api: registry.Api,
decls: *std.ArrayList(registry.Declaration),
) !void {
const types_elem = root.findChildByTag("types") orelse return error.InvalidRegistry; const types_elem = root.findChildByTag("types") orelse return error.InvalidRegistry;
try decls.ensureUnusedCapacity(allocator, types_elem.children.len); const commands_elem = root.findChildByTag("commands") orelse return error.InvalidRegistry;
try parseTypes(allocator, types_elem, api, decls); const decl_upper_bound = types_elem.children.len + commands_elem.children.len;
try parseEnums(allocator, root, api, decls); const decls = try allocator.alloc(registry.Declaration, decl_upper_bound);
if (root.findChildByTag("commands")) |commands_elem| { var count: usize = 0;
try decls.ensureUnusedCapacity(allocator, commands_elem.children.len); count += try parseTypes(allocator, decls, types_elem, api);
try parseCommands(allocator, commands_elem, api, decls); count += try parseEnums(allocator, decls[count..], root, api);
} count += try parseCommands(allocator, decls[count..], commands_elem, api);
return decls[0..count];
} }
fn parseTypes( fn parseTypes(allocator: Allocator, out: []registry.Declaration, types_elem: *xml.Element, api: registry.Api) !usize {
allocator: Allocator, var i: usize = 0;
types_elem: *xml.Element,
api: registry.Api,
decls: *std.ArrayList(registry.Declaration),
) !void {
var it = types_elem.findChildrenByTag("type"); var it = types_elem.findChildrenByTag("type");
while (it.next()) |ty| { while (it.next()) |ty| {
try decls.append(allocator, blk: { out[i] = blk: {
if (!requiredByApi(ty, api)) if (!requiredByApi(ty, api))
continue; continue;
@@ -113,8 +80,12 @@ fn parseTypes(
} }
continue; continue;
}); };
i += 1;
} }
return i;
} }
fn parseForeigntype(ty: *xml.Element) !registry.Declaration { fn parseForeigntype(ty: *xml.Element) !registry.Declaration {
@@ -365,18 +336,10 @@ fn parsePointerMeta(fields: Fields, type_info: *registry.TypeInfo, elem: *xml.El
else => break, else => break,
}; };
if (it.next()) |_| ignore: { if (it.next()) |_| {
// There are more elements in the `len` attribute than there are pointers // There are more elements in the `len` attribute than there are pointers
// Something probably went wrong // Something probably went wrong
switch (current_type_info.*) { std.log.err("len: {s}", .{lens});
.name => |name| if (std.mem.eql(u8, name, "StdVideoH265SubLayerHrdParameters")) {
// Known issue: https://github.com/KhronosGroup/Vulkan-Docs/issues/2557
break :ignore;
},
else => {},
}
std.log.err("excessive pointer lengths: {s}", .{lens});
return error.InvalidRegistry; return error.InvalidRegistry;
} }
} }
@@ -425,12 +388,8 @@ fn parseEnumAlias(elem: *xml.Element) !?registry.Declaration {
return null; return null;
} }
fn parseEnums( fn parseEnums(allocator: Allocator, out: []registry.Declaration, root: *xml.Element, api: registry.Api) !usize {
allocator: Allocator, var i: usize = 0;
root: *xml.Element,
api: registry.Api,
decls: *std.ArrayList(registry.Declaration),
) !void {
var it = root.findChildrenByTag("enums"); var it = root.findChildrenByTag("enums");
while (it.next()) |enums| { while (it.next()) |enums| {
const name = enums.getAttribute("name") orelse return error.InvalidRegistry; const name = enums.getAttribute("name") orelse return error.InvalidRegistry;
@@ -438,11 +397,14 @@ fn parseEnums(
continue; continue;
} }
try decls.append(allocator, .{ out[i] = .{
.name = name, .name = name,
.decl_type = .{ .enumeration = try parseEnumFields(allocator, enums, api) }, .decl_type = .{ .enumeration = try parseEnumFields(allocator, enums, api) },
}); };
i += 1;
} }
return i;
} }
fn parseEnumFields(allocator: Allocator, elem: *xml.Element, api: registry.Api) !registry.Enum { fn parseEnumFields(allocator: Allocator, elem: *xml.Element, api: registry.Api) !registry.Enum {
@@ -515,19 +477,18 @@ fn parseEnumField(field: *xml.Element) !registry.Enum.Field {
}; };
} }
fn parseCommands( fn parseCommands(allocator: Allocator, out: []registry.Declaration, commands_elem: *xml.Element, api: registry.Api) !usize {
allocator: Allocator, var i: usize = 0;
commands_elem: *xml.Element,
api: registry.Api,
decls: *std.ArrayList(registry.Declaration),
) !void {
var it = commands_elem.findChildrenByTag("command"); var it = commands_elem.findChildrenByTag("command");
while (it.next()) |elem| { while (it.next()) |elem| {
if (!requiredByApi(elem, api)) if (!requiredByApi(elem, api))
continue; continue;
try decls.append(allocator, try parseCommand(allocator, elem, api)); out[i] = try parseCommand(allocator, elem, api);
i += 1;
} }
return i;
} }
fn splitCommaAlloc(allocator: Allocator, text: []const u8) ![][]const u8 { fn splitCommaAlloc(allocator: Allocator, text: []const u8) ![][]const u8 {
@@ -626,13 +587,8 @@ fn parseCommand(allocator: Allocator, elem: *xml.Element, api: registry.Api) !re
}; };
} }
fn parseApiConstants( fn parseApiConstants(allocator: Allocator, root: *xml.Element, api: registry.Api) ![]registry.ApiConstant {
allocator: Allocator, var enums = blk: {
root: *xml.Element,
api: registry.Api,
api_constants: *std.ArrayList(registry.ApiConstant),
) !void {
const maybe_enums = blk: {
var it = root.findChildrenByTag("enums"); var it = root.findChildrenByTag("enums");
while (it.next()) |child| { while (it.next()) |child| {
const name = child.getAttribute("name") orelse continue; const name = child.getAttribute("name") orelse continue;
@@ -641,10 +597,26 @@ fn parseApiConstants(
} }
} }
break :blk null; return error.InvalidRegistry;
}; };
if (maybe_enums) |enums| { 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;
}
}
}
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"); var it = enums.findChildrenByTag("enum");
while (it.next()) |constant| { while (it.next()) |constant| {
if (!requiredByApi(constant, api)) if (!requiredByApi(constant, api))
@@ -657,23 +629,20 @@ fn parseApiConstants(
else else
return error.InvalidRegistry; return error.InvalidRegistry;
try api_constants.append(allocator, .{ constants[i] = .{
.name = constant.getAttribute("name") orelse return error.InvalidRegistry, .name = constant.getAttribute("name") orelse return error.InvalidRegistry,
.value = .{ .expr = expr }, .value = .{ .expr = expr },
}); };
}
i += 1;
} }
const types = root.findChildByTag("types") orelse return error.InvalidRegistry; i += try parseDefines(types, constants[i..], api);
try parseDefines(allocator, types, api, api_constants); return constants[0..i];
} }
fn parseDefines( fn parseDefines(types: *xml.Element, out: []registry.ApiConstant, api: registry.Api) !usize {
allocator: Allocator, var i: usize = 0;
types: *xml.Element,
api: registry.Api,
api_constants: *std.ArrayList(registry.ApiConstant),
) !void {
var it = types.findChildrenByTag("type"); var it = types.findChildrenByTag("type");
while (it.next()) |ty| { while (it.next()) |ty| {
if (!requiredByApi(ty, api)) if (!requiredByApi(ty, api))
@@ -686,45 +655,58 @@ fn parseDefines(
const name = ty.getCharData("name") orelse continue; const name = ty.getCharData("name") orelse continue;
if (mem.eql(u8, name, "VK_HEADER_VERSION") or mem.eql(u8, name, "VKSC_API_VARIANT")) { if (mem.eql(u8, name, "VK_HEADER_VERSION") or mem.eql(u8, name, "VKSC_API_VARIANT")) {
try api_constants.append(allocator, .{ out[i] = .{
.name = name, .name = name,
.value = .{ .expr = mem.trim(u8, ty.children[2].char_data, " ") }, .value = .{ .expr = mem.trim(u8, ty.children[2].char_data, " ") },
}); };
} else { } else {
var xctok = cparse.XmlCTokenizer.init(ty); var xctok = cparse.XmlCTokenizer.init(ty);
try api_constants.append(allocator, .{ out[i] = .{
.name = name, .name = name,
.value = cparse.parseVersion(&xctok) catch continue, .value = .{ .version = cparse.parseVersion(&xctok) catch continue },
}); };
}
} }
i += 1;
} }
fn parseTags( return i;
allocator: Allocator, }
root: *xml.Element,
tags: *std.ArrayList(registry.Tag),
) !void {
var tags_elem = root.findChildByTag("tags") orelse return;
try tags.ensureUnusedCapacity(allocator, tags_elem.children.len);
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);
var i: usize = 0;
var it = tags_elem.findChildrenByTag("tag"); var it = tags_elem.findChildrenByTag("tag");
while (it.next()) |tag| { while (it.next()) |tag| {
tags.appendAssumeCapacity(.{ tags[i] = .{
.name = tag.getAttribute("name") orelse return error.InvalidRegistry, .name = tag.getAttribute("name") orelse return error.InvalidRegistry,
.author = tag.getAttribute("author") orelse return error.InvalidRegistry, .author = tag.getAttribute("author") orelse return error.InvalidRegistry,
}); };
}
i += 1;
} }
fn parseFeatures(allocator: Allocator, root: *xml.Element, api: registry.Api, features: *std.ArrayList(registry.Feature)) !void { return tags[0..i];
}
fn parseFeatures(allocator: Allocator, root: *xml.Element, api: registry.Api) ![]registry.Feature {
var it = root.findChildrenByTag("feature"); 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| { while (it.next()) |feature| {
if (!requiredByApi(feature, api)) if (!requiredByApi(feature, api))
continue; continue;
try features.append(allocator, try parseFeature(allocator, feature, api)); features[i] = try parseFeature(allocator, feature, api);
i += 1;
} }
return features[0..i];
} }
fn parseFeature(allocator: Allocator, feature: *xml.Element, api: registry.Api) !registry.Feature { fn parseFeature(allocator: Allocator, feature: *xml.Element, api: registry.Api) !registry.Feature {
@@ -754,24 +736,11 @@ fn parseFeature(allocator: Allocator, feature: *xml.Element, api: registry.Api)
fn parseEnumExtension(elem: *xml.Element, parent_extnumber: ?u31) !?registry.Require.EnumExtension { fn parseEnumExtension(elem: *xml.Element, parent_extnumber: ?u31) !?registry.Require.EnumExtension {
// check for either _SPEC_VERSION or _EXTENSION_NAME // check for either _SPEC_VERSION or _EXTENSION_NAME
const name = elem.getAttribute("name") orelse return error.InvalidRegistry; const extends = elem.getAttribute("extends") orelse return null;
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| { if (elem.getAttribute("offset")) |offset_str| {
const offset = try std.fmt.parseInt(u31, offset_str, 10); 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| const extnumber = if (elem.getAttribute("extnumber")) |num|
try std.fmt.parseInt(u31, num, 10) try std.fmt.parseInt(u31, num, 10)
else else
@@ -794,19 +763,17 @@ fn parseEnumExtension(elem: *xml.Element, parent_extnumber: ?u31) !?registry.Req
return registry.Require.EnumExtension{ return registry.Require.EnumExtension{
.extends = extends, .extends = extends,
.extnumber = actual_extnumber, .extnumber = actual_extnumber,
.value = .{
.field = .{ .field = .{
.name = name, .name = name,
.value = .{ .int = value }, .value = .{ .int = value },
}, },
},
}; };
} }
return registry.Require.EnumExtension{ return registry.Require.EnumExtension{
.extends = extends, .extends = extends,
.extnumber = parent_extnumber, .extnumber = parent_extnumber,
.value = .{ .field = try parseEnumField(elem) }, .field = try parseEnumField(elem),
}; };
} }
@@ -877,15 +844,11 @@ fn parseRequire(allocator: Allocator, require: *xml.Element, extnumber: ?u31, ap
}; };
} }
fn parseExtensions( fn parseExtensions(allocator: Allocator, root: *xml.Element, api: registry.Api) ![]registry.Extension {
allocator: Allocator,
root: *xml.Element,
api: registry.Api,
extensions: *std.ArrayList(registry.Extension),
) !void {
const extensions_elem = root.findChildByTag("extensions") orelse return error.InvalidRegistry; 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"); var it = extensions_elem.findChildrenByTag("extension");
while (it.next()) |extension| { while (it.next()) |extension| {
if (!requiredByApi(extension, api)) if (!requiredByApi(extension, api))
@@ -897,11 +860,14 @@ fn parseExtensions(
} }
} }
extensions.appendAssumeCapacity(try parseExtension(allocator, extension, api)); extensions[i] = try parseExtension(allocator, extension, api);
} i += 1;
} }
fn findExtVersion(extension: *xml.Element) !registry.Extension.Version { return extensions[0..i];
}
fn findExtVersion(extension: *xml.Element) !u32 {
var req_it = extension.findChildrenByTag("require"); var req_it = extension.findChildrenByTag("require");
while (req_it.next()) |req| { while (req_it.next()) |req| {
var enum_it = req.findChildrenByTag("enum"); var enum_it = req.findChildrenByTag("enum");
@@ -909,23 +875,17 @@ fn findExtVersion(extension: *xml.Element) !registry.Extension.Version {
const name = e.getAttribute("name") orelse continue; const name = e.getAttribute("name") orelse continue;
const value = e.getAttribute("value") orelse continue; const value = e.getAttribute("value") orelse continue;
if (mem.endsWith(u8, name, "_SPEC_VERSION")) { if (mem.endsWith(u8, name, "_SPEC_VERSION")) {
// Vulkan Video extensions are sometimes aliases. return try std.fmt.parseInt(u32, value, 10);
// 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 .unknown; return error.InvalidRegistry;
} }
fn parseExtension(allocator: Allocator, extension: *xml.Element, api: registry.Api) !registry.Extension { fn parseExtension(allocator: Allocator, extension: *xml.Element, api: registry.Api) !registry.Extension {
const name = extension.getAttribute("name") orelse return error.InvalidRegistry; const name = extension.getAttribute("name") orelse return error.InvalidRegistry;
const platform = extension.getAttribute("platform"); const platform = extension.getAttribute("platform");
const is_video = std.mem.startsWith(u8, name, "vulkan_video_");
const version = try findExtVersion(extension); const version = try findExtVersion(extension);
// For some reason there are two ways for an extension to state its required // For some reason there are two ways for an extension to state its required
@@ -947,14 +907,11 @@ fn parseExtension(allocator: Allocator, extension: *xml.Element, api: registry.A
}; };
const number = blk: { 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; const number_str = extension.getAttribute("number") orelse return error.InvalidRegistry;
break :blk try std.fmt.parseInt(u31, number_str, 10); break :blk try std.fmt.parseInt(u31, number_str, 10);
}; };
const ext_type: ?registry.Extension.ExtensionType = blk: { const ext_type: ?registry.Extension.ExtensionType = blk: {
if (is_video) break :blk .video;
const ext_type_str = extension.getAttribute("type") orelse break :blk null; const ext_type_str = extension.getAttribute("type") orelse break :blk null;
if (mem.eql(u8, ext_type_str, "instance")) { if (mem.eql(u8, ext_type_str, "instance")) {
break :blk .instance; break :blk .instance;

View File

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

View File

@@ -1,12 +1,11 @@
const std = @import("std"); const std = @import("std");
const reg = @import("registry.zig");
const id_render = @import("../id_render.zig");
const cparse = @import("c_parse.zig");
const mem = std.mem; const mem = std.mem;
const Allocator = mem.Allocator; const Allocator = mem.Allocator;
const id_render = @import("../id_render.zig");
const CaseStyle = id_render.CaseStyle; const CaseStyle = id_render.CaseStyle;
const IdRenderer = id_render.IdRenderer; const IdRenderer = id_render.IdRenderer;
const cparse = @import("c_parse.zig");
const reg = @import("registry.zig");
const preamble = const preamble =
\\// This file is generated from the Khronos Vulkan XML API registry by vulkan-zig. \\// This file is generated from the Khronos Vulkan XML API registry by vulkan-zig.
@@ -18,19 +17,19 @@ const preamble =
\\const Allocator = std.mem.Allocator; \\const Allocator = std.mem.Allocator;
\\ \\
\\pub const vulkan_call_conv: std.builtin.CallingConvention = if (builtin.os.tag == .windows and builtin.cpu.arch == .x86) \\pub const vulkan_call_conv: std.builtin.CallingConvention = if (builtin.os.tag == .windows and builtin.cpu.arch == .x86)
\\ .winapi \\ .Stdcall
\\ else if (builtin.abi == .android and (builtin.cpu.arch.isArm() or builtin.cpu.arch.isThumb()) and std.Target.arm.featureSetHas(builtin.cpu.features, .has_v7) and builtin.cpu.arch.ptrBitWidth() == 32) \\ else if (builtin.abi == .android and (builtin.cpu.arch.isARM() or builtin.cpu.arch.isThumb()) and std.Target.arm.featureSetHas(builtin.cpu.features, .has_v7) and builtin.cpu.arch.ptrBitWidth() == 32)
\\ // On Android 32-bit ARM targets, Vulkan functions use the "hardfloat" \\ // On Android 32-bit ARM targets, Vulkan functions use the "hardfloat"
\\ // calling convention, i.e. float parameters are passed in registers. This \\ // calling convention, i.e. float parameters are passed in registers. This
\\ // is true even if the rest of the application passes floats on the stack, \\ // is true even if the rest of the application passes floats on the stack,
\\ // as it does by default when compiling for the armeabi-v7a NDK ABI. \\ // as it does by default when compiling for the armeabi-v7a NDK ABI.
\\ .arm_aapcs_vfp \\ .AAPCSVFP
\\ else \\ else
\\ .c; \\ .C;
// Note: Keep in sync with flag_functions // Note: Keep in sync with flag_functions
\\pub fn FlagsMixin(comptime FlagsType: type) type { \\pub fn FlagsMixin(comptime FlagsType: type) type {
\\ return struct { \\ return struct {
\\ pub const IntType = @typeInfo(FlagsType).@"struct".backing_integer.?; \\ pub const IntType = @typeInfo(FlagsType).Struct.backing_integer.?;
\\ pub fn toInt(self: FlagsType) IntType { \\ pub fn toInt(self: FlagsType) IntType {
\\ return @bitCast(self); \\ return @bitCast(self);
\\ } \\ }
@@ -59,6 +58,8 @@ const preamble =
\\ return struct { \\ return struct {
\\ pub fn format( \\ pub fn format(
\\ self: FlagsType, \\ self: FlagsType,
\\ comptime _: []const u8,
\\ _: std.fmt.FormatOptions,
\\ writer: anytype, \\ writer: anytype,
\\ ) !void { \\ ) !void {
\\ try writer.writeAll(@typeName(FlagsType) ++ "{"); \\ try writer.writeAll(@typeName(FlagsType) ++ "{");
@@ -80,18 +81,27 @@ const preamble =
\\ } \\ }
\\ }; \\ };
\\} \\}
\\pub const Version = packed struct(u32) { \\pub fn makeApiVersion(variant: u3, major: u7, minor: u10, patch: u12) u32 {
\\ patch: u12, \\ return (@as(u32, variant) << 29) | (@as(u32, major) << 22) | (@as(u32, minor) << 12) | patch;
\\ minor: u10, \\}
\\ major: u7, \\pub fn apiVersionVariant(version: u32) u3 {
\\ variant: u3, \\ return @truncate(version >> 29);
\\}; \\}
\\pub fn makeApiVersion(variant: u3, major: u7, minor: u10, patch: u12) Version { \\pub fn apiVersionMajor(version: u32) u7 {
\\ return .{ .variant = variant, .major = major, .minor = minor, .patch = patch }; \\ 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 ApiInfo = struct { \\pub const ApiInfo = struct {
\\ name: [:0]const u8 = "custom", \\ name: [:0]const u8 = "custom",
\\ version: Version = makeApiVersion(0, 0, 0, 0), \\ version: u32 = makeApiVersion(0, 0, 0, 0),
\\ base_commands: BaseCommandFlags = .{},
\\ instance_commands: InstanceCommandFlags = .{},
\\ device_commands: DeviceCommandFlags = .{},
\\}; \\};
; ;
@@ -113,7 +123,7 @@ const command_flags_mixin =
\\ pub fn merge(lhs: CommandFlags, rhs: CommandFlags) CommandFlags { \\ pub fn merge(lhs: CommandFlags, rhs: CommandFlags) CommandFlags {
\\ var result: CommandFlags = .{}; \\ var result: CommandFlags = .{};
\\ @setEvalBranchQuota(10_000); \\ @setEvalBranchQuota(10_000);
\\ inline for (@typeInfo(CommandFlags).@"struct".fields) |field| { \\ inline for (@typeInfo(CommandFlags).Struct.fields) |field| {
\\ @field(result, field.name) = @field(lhs, field.name) or @field(rhs, field.name); \\ @field(result, field.name) = @field(lhs, field.name) or @field(rhs, field.name);
\\ } \\ }
\\ return result; \\ return result;
@@ -121,7 +131,7 @@ const command_flags_mixin =
\\ pub fn intersect(lhs: CommandFlags, rhs: CommandFlags) CommandFlags { \\ pub fn intersect(lhs: CommandFlags, rhs: CommandFlags) CommandFlags {
\\ var result: CommandFlags = .{}; \\ var result: CommandFlags = .{};
\\ @setEvalBranchQuota(10_000); \\ @setEvalBranchQuota(10_000);
\\ inline for (@typeInfo(CommandFlags).@"struct".fields) |field| { \\ inline for (@typeInfo(CommandFlags).Struct.fields) |field| {
\\ @field(result, field.name) = @field(lhs, field.name) and @field(rhs, field.name); \\ @field(result, field.name) = @field(lhs, field.name) and @field(rhs, field.name);
\\ } \\ }
\\ return result; \\ return result;
@@ -129,7 +139,7 @@ const command_flags_mixin =
\\ pub fn complement(self: CommandFlags) CommandFlags { \\ pub fn complement(self: CommandFlags) CommandFlags {
\\ var result: CommandFlags = .{}; \\ var result: CommandFlags = .{};
\\ @setEvalBranchQuota(10_000); \\ @setEvalBranchQuota(10_000);
\\ inline for (@typeInfo(CommandFlags).@"struct".fields) |field| { \\ inline for (@typeInfo(CommandFlags).Struct.fields) |field| {
\\ @field(result, field.name) = !@field(self, field.name); \\ @field(result, field.name) = !@field(self, field.name);
\\ } \\ }
\\ return result; \\ return result;
@@ -137,14 +147,14 @@ const command_flags_mixin =
\\ pub fn subtract(lhs: CommandFlags, rhs: CommandFlags) CommandFlags { \\ pub fn subtract(lhs: CommandFlags, rhs: CommandFlags) CommandFlags {
\\ var result: CommandFlags = .{}; \\ var result: CommandFlags = .{};
\\ @setEvalBranchQuota(10_000); \\ @setEvalBranchQuota(10_000);
\\ inline for (@typeInfo(CommandFlags).@"struct".fields) |field| { \\ inline for (@typeInfo(CommandFlags).Struct.fields) |field| {
\\ @field(result, field.name) = @field(lhs, field.name) and !@field(rhs, field.name); \\ @field(result, field.name) = @field(lhs, field.name) and !@field(rhs, field.name);
\\ } \\ }
\\ return result; \\ return result;
\\ } \\ }
\\ pub fn contains(lhs: CommandFlags, rhs: CommandFlags) bool { \\ pub fn contains(lhs: CommandFlags, rhs: CommandFlags) bool {
\\ @setEvalBranchQuota(10_000); \\ @setEvalBranchQuota(10_000);
\\ inline for (@typeInfo(CommandFlags).@"struct".fields) |field| { \\ inline for (@typeInfo(CommandFlags).Struct.fields) |field| {
\\ if (!@field(lhs, field.name) and @field(rhs, field.name)) { \\ if (!@field(lhs, field.name) and @field(rhs, field.name)) {
\\ return false; \\ return false;
\\ } \\ }
@@ -204,18 +214,6 @@ const foreign_types = std.StaticStringMap([]const u8).initComptime(.{
.{ "_screen_window", "opaque {}" }, .{ "_screen_window", "opaque {}" },
.{ "IDirectFB", "opaque {}" }, .{ "IDirectFB", "opaque {}" },
.{ "IDirectFBSurface", "opaque {}" }, .{ "IDirectFBSurface", "opaque {}" },
.{ "NvSciSyncAttrList", "*opaque{}" },
.{ "NvSciSyncObj", "*opaque{}" },
.{ "NvSciSyncFence", "*opaque{}" },
.{ "NvSciBufAttrList", "*opaque{}" },
.{ "NvSciBufObj", "*opaque{}" },
// We don't know the true size of these but whatever Stadia is dead anyway.
.{ "GgpStreamDescriptor", "*opaque{}" },
.{ "GgpFrameToken", "*opaque{}" },
// The Vulkan Video tokens cannot be "opaque {}" and have to be handled
// separately.
.{ "StdVideoVP9Profile", "u32" },
.{ "StdVideoVP9Level", "u32" },
}); });
const CommandDispatchType = enum { const CommandDispatchType = enum {
@@ -335,9 +333,11 @@ pub fn trimVkNamespace(id: []const u8) []const u8 {
return id; return id;
} }
const Renderer = struct { fn Renderer(comptime WriterType: type) type {
return struct {
const Self = @This(); const Self = @This();
const RenderTypeInfoError = std.Io.Writer.Error || std.fmt.ParseIntError || error{ OutOfMemory, InvalidRegistry }; const WriteError = WriterType.Error;
const RenderTypeInfoError = WriteError || std.fmt.ParseIntError || error{ OutOfMemory, InvalidRegistry };
const BitflagName = struct { const BitflagName = struct {
/// Name without FlagBits, so VkSurfaceTransformFlagBitsKHR /// Name without FlagBits, so VkSurfaceTransformFlagBitsKHR
@@ -371,47 +371,29 @@ const Renderer = struct {
}, },
}; };
writer: *std.Io.Writer, writer: WriterType,
allocator: Allocator, allocator: Allocator,
registry: *const reg.Registry, registry: *const reg.Registry,
id_renderer: *IdRenderer, id_renderer: *IdRenderer,
decls_by_name: std.StringArrayHashMap(reg.DeclarationType), declarations_by_name: std.StringHashMap(*const reg.DeclarationType),
structure_types: std.StringHashMap(void), structure_types: std.StringHashMap(void),
have_video: bool,
fn init( fn init(writer: WriterType, allocator: Allocator, registry: *const reg.Registry, id_renderer: *IdRenderer) !Self {
writer: *std.Io.Writer, var declarations_by_name = std.StringHashMap(*const reg.DeclarationType).init(allocator);
allocator: Allocator, errdefer declarations_by_name.deinit();
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| { for (registry.decls) |*decl| {
const result = try decls_by_name.getOrPut(decl.name); const result = try declarations_by_name.getOrPut(decl.name);
if (result.found_existing) { if (result.found_existing) {
// 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}); std.log.err("duplicate registry entry '{s}'", .{decl.name});
return error.InvalidRegistry; return error.InvalidRegistry;
} }
} else {
result.value_ptr.* = decl.decl_type; result.value_ptr.* = &decl.decl_type;
}
} }
const vk_structure_type_decl = decls_by_name.get("VkStructureType") orelse return error.InvalidRegistry; 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 = switch (vk_structure_type_decl.*) {
.enumeration => |e| e, .enumeration => |e| e,
else => return error.InvalidRegistry, else => return error.InvalidRegistry,
}; };
@@ -427,14 +409,13 @@ const Renderer = struct {
.allocator = allocator, .allocator = allocator,
.registry = registry, .registry = registry,
.id_renderer = id_renderer, .id_renderer = id_renderer,
.decls_by_name = decls_by_name, .declarations_by_name = declarations_by_name,
.structure_types = structure_types, .structure_types = structure_types,
.have_video = have_video,
}; };
} }
fn deinit(self: *Self) void { fn deinit(self: *Self) void {
self.decls_by_name.deinit(); self.declarations_by_name.deinit();
} }
fn writeIdentifier(self: Self, id: []const u8) !void { fn writeIdentifier(self: Self, id: []const u8) !void {
@@ -520,8 +501,8 @@ const Renderer = struct {
} }
fn resolveDeclaration(self: Self, name: []const u8) ?reg.DeclarationType { fn resolveDeclaration(self: Self, name: []const u8) ?reg.DeclarationType {
const decl = self.decls_by_name.get(name) orelse return null; const decl = self.declarations_by_name.get(name) orelse return null;
return self.resolveAlias(decl) catch return null; return self.resolveAlias(decl.*) catch return null;
} }
fn resolveAlias(self: Self, start_decl: reg.DeclarationType) !reg.DeclarationType { fn resolveAlias(self: Self, start_decl: reg.DeclarationType) !reg.DeclarationType {
@@ -532,7 +513,8 @@ const Renderer = struct {
else => return decl, else => return decl,
}; };
decl = self.decls_by_name.get(name) orelse return error.InvalidRegistry; const decl_ptr = self.declarations_by_name.get(name) orelse return error.InvalidRegistry;
decl = decl_ptr.*;
} }
} }
@@ -632,23 +614,17 @@ const Renderer = struct {
fn render(self: *Self) !void { fn render(self: *Self) !void {
try self.writer.writeAll(preamble); 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| { for (self.registry.api_constants) |api_constant| {
try self.renderApiConstant(api_constant); try self.renderApiConstant(api_constant);
} }
for (self.decls_by_name.keys(), self.decls_by_name.values()) |name, decl_type| { for (self.registry.decls) |decl| {
try self.renderDecl(.{ try self.renderDecl(decl);
.name = name,
.decl_type = decl_type,
});
} }
try self.renderCommandPtrs(); try self.renderCommandPtrs();
try self.renderFeatureInfo(); try self.renderFeatureInfo();
try self.renderExtensionInfo(); try self.renderExtensionInfo();
try self.renderDispatchTables();
try self.renderWrappers(); try self.renderWrappers();
try self.renderProxies(); try self.renderProxies();
} }
@@ -660,12 +636,8 @@ const Renderer = struct {
switch (api_constant.value) { switch (api_constant.value) {
.expr => |expr| try self.renderApiConstantExpr(expr), .expr => |expr| try self.renderApiConstantExpr(expr),
inline .version, .video_std_version => |version, kind| { .version => |version| {
try self.writer.writeAll("makeApiVersion("); 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| { for (version, 0..) |part, i| {
if (i != 0) { if (i != 0) {
try self.writer.writeAll(", "); try self.writer.writeAll(", ");
@@ -716,7 +688,6 @@ const Renderer = struct {
} else if (mem.eql(u8, suffix.text, "U")) { } else if (mem.eql(u8, suffix.text, "U")) {
try self.writer.print("@as(u32, {s})", .{tok.text}); try self.writer.print("@as(u32, {s})", .{tok.text});
} else { } else {
std.debug.print("aaa {s}\n", .{suffix.text});
return error.InvalidApiConstant; return error.InvalidApiConstant;
} }
}, },
@@ -848,10 +819,6 @@ const Renderer = struct {
} }
fn renderDecl(self: *Self, decl: reg.Declaration) !void { fn renderDecl(self: *Self, decl: reg.Declaration) !void {
if (try self.renderSpecial(decl.name)) {
return;
}
switch (decl.decl_type) { switch (decl.decl_type) {
.container => |container| try self.renderContainer(decl.name, container), .container => |container| try self.renderContainer(decl.name, container),
.enumeration => |enumeration| try self.renderEnumeration(decl.name, enumeration), .enumeration => |enumeration| try self.renderEnumeration(decl.name, enumeration),
@@ -865,27 +832,10 @@ const Renderer = struct {
} }
} }
fn renderAssign(self: *Self, name: []const u8) !void { fn renderSpecialContainer(self: *Self, name: []const u8) !bool {
try self.writer.writeAll("pub const ");
try self.renderName(name);
try self.writer.writeAll(" = ");
}
fn renderSpecial(self: *Self, name: []const u8) !bool {
const maybe_author = self.id_renderer.getAuthorTag(name); const maybe_author = self.id_renderer.getAuthorTag(name);
const basename = self.id_renderer.stripAuthorTag(name); const basename = self.id_renderer.stripAuthorTag(name);
if (std.mem.eql(u8, basename, "VkBool32")) { if (std.mem.eql(u8, basename, "VkAccelerationStructureInstance")) {
try self.renderAssign(name);
try self.writer.writeAll(
\\enum(i32) {
\\ false,
\\ true,
\\ _,
\\};
\\
);
} else if (std.mem.eql(u8, basename, "VkAccelerationStructureInstance")) {
try self.renderAssign(name);
try self.writer.print( try self.writer.print(
\\extern struct {{ \\extern struct {{
\\ transform: TransformMatrix{s}, \\ transform: TransformMatrix{s},
@@ -903,8 +853,8 @@ const Renderer = struct {
, ,
.{maybe_author orelse ""}, .{maybe_author orelse ""},
); );
return true;
} else if (std.mem.eql(u8, basename, "VkAccelerationStructureSRTMotionInstance")) { } else if (std.mem.eql(u8, basename, "VkAccelerationStructureSRTMotionInstance")) {
try self.renderAssign(name);
try self.writer.print( try self.writer.print(
\\extern struct {{ \\extern struct {{
\\ transform_t0: SRTData{0s}, \\ transform_t0: SRTData{0s},
@@ -923,8 +873,8 @@ const Renderer = struct {
, ,
.{maybe_author orelse ""}, .{maybe_author orelse ""},
); );
return true;
} else if (std.mem.eql(u8, basename, "VkAccelerationStructureMatrixMotionInstance")) { } else if (std.mem.eql(u8, basename, "VkAccelerationStructureMatrixMotionInstance")) {
try self.renderAssign(name);
try self.writer.print( try self.writer.print(
\\extern struct {{ \\extern struct {{
\\ transform_t0: TransformMatrix{0s}, \\ transform_t0: TransformMatrix{0s},
@@ -943,125 +893,10 @@ const Renderer = struct {
, ,
.{maybe_author orelse ""}, .{maybe_author orelse ""},
); );
} else if (std.mem.eql(u8, basename, "VkClusterAccelerationStructureBuildTriangleClusterInfo")) {
try self.renderAssign(name);
try self.writer.print(
\\extern struct {{
\\ cluster_id: u32,
\\ cluster_flags: ClusterAccelerationStructureClusterFlags{0s},
\\ cluster_data: packed struct(u32) {{
\\ triangle_count: u9,
\\ vertex_count: u9,
\\ position_truncate_bit_count: u6,
\\ index_type: u4,
\\ opacity_micromap_index_type: u4,
\\ }},
\\ base_geometry_index_and_geometry_flags: ClusterAccelerationStructureGeometryIndexAndGeometryFlags{0s},
\\ index_buffer_stride: u16,
\\ vertex_buffer_stride: u16,
\\ geometry_index_and_flags_buffer_stride: u16,
\\ opacity_micromap_index_buffer_stride: u16,
\\ index_buffer: DeviceAddress,
\\ vertex_buffer: DeviceAddress,
\\ geometry_index_and_flags_buffer: DeviceAddress,
\\ opacity_micromap_array: DeviceAddress,
\\ opacity_micromap_index_buffer: DeviceAddress,
\\}};
,
.{maybe_author orelse ""},
);
} else if (std.mem.eql(u8, basename, "VkClusterAccelerationStructureBuildTriangleClusterTemplateInfo")) {
try self.renderAssign(name);
try self.writer.print(
\\extern struct {{
\\ cluster_id: u32,
\\ cluster_flags: ClusterAccelerationStructureClusterFlags{0s},
\\ cluster_data: packed struct(u32) {{
\\ triangle_count: u9,
\\ vertex_count: u9,
\\ position_truncate_bit_count: u6,
\\ index_type: u4,
\\ opacity_micromap_index_type: u4,
\\ }},
\\ base_geometry_index_and_geometry_flags: ClusterAccelerationStructureGeometryIndexAndGeometryFlags{0s},
\\ index_buffer_stride: u16,
\\ vertex_buffer_stride: u16,
\\ geometry_index_and_flags_buffer_stride: u16,
\\ opacity_micromap_index_buffer_stride: u16,
\\ index_buffer: DeviceAddress,
\\ vertex_buffer: DeviceAddress,
\\ geometry_index_and_flags_buffer: DeviceAddress,
\\ opacity_micromap_array: DeviceAddress,
\\ opacity_micromap_index_buffer: DeviceAddress,
\\ instantiation_bounding_box_limit: DeviceAddress,
\\}};
,
.{maybe_author orelse ""},
);
} else if (std.mem.eql(u8, basename, "VkClusterAccelerationStructureInstantiateClusterInfo")) {
try self.renderAssign(name);
try self.writer.print(
\\extern struct {{
\\ cluster_id_offset: u32,
\\ geometry_index_offset: packed struct(u32) {{
\\ offset: u24,
\\ reserved: u8 = 0,
\\ }},
\\ cluster_template_address: DeviceAddress,
\\ vertex_buffer: StridedDeviceAddress{0s},
\\}};
,
.{maybe_author orelse ""},
);
} else {
return false;
}
return true; return true;
} }
fn renderSimpleBitContainer(self: *Self, container: reg.Container) !bool {
var total_bits: usize = 0;
var is_flags_container = true;
for (container.fields) |field| {
const bits = field.bits orelse {
// C abi type - not a packed struct.
return false; return false;
};
total_bits += bits;
if (bits != 1) {
is_flags_container = 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.
if (is_flags_container) {
try self.writer.writeAll(" bool = false,\n");
} else {
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 { fn renderContainer(self: *Self, name: []const u8, container: reg.Container) !void {
@@ -1069,7 +904,7 @@ const Renderer = struct {
try self.renderName(name); try self.renderName(name);
try self.writer.writeAll(" = "); try self.writer.writeAll(" = ");
if (try self.renderSimpleBitContainer(container)) { if (try self.renderSpecialContainer(name)) {
return; return;
} }
@@ -1127,7 +962,7 @@ const Renderer = struct {
try self.writer.writeAll(" = ."); try self.writer.writeAll(" = .");
try self.writeIdentifierWithCase(.snake, stype["VK_STRUCTURE_TYPE_".len..]); try self.writeIdentifierWithCase(.snake, stype["VK_STRUCTURE_TYPE_".len..]);
} else if (field.field_type == .name and mem.eql(u8, "VkBool32", field.field_type.name) and isFeatureStruct(name, container.extends)) { } else if (field.field_type == .name and mem.eql(u8, "VkBool32", field.field_type.name) and isFeatureStruct(name, container.extends)) {
try self.writer.writeAll(" = .false"); try self.writer.writeAll(" = FALSE");
} else if (field.is_optional) { } else if (field.is_optional) {
if (field.field_type == .name) { if (field.field_type == .name) {
const field_type_name = field.field_type.name; const field_type_name = field.field_type.name;
@@ -1138,8 +973,6 @@ const Renderer = struct {
try self.writer.writeAll(" = .{}"); try self.writer.writeAll(" = .{}");
} else if (decl_type == .typedef and decl_type.typedef == .command_ptr) { } else if (decl_type == .typedef and decl_type.typedef == .command_ptr) {
try self.writer.writeAll(" = null"); try self.writer.writeAll(" = null");
} else if (mem.eql(u8, "VkBool32", field.field_type.name)) {
try self.writer.writeAll(" = .false");
} else if ((decl_type == .typedef and builtin_types.has(decl_type.typedef.name)) or } else if ((decl_type == .typedef and builtin_types.has(decl_type.typedef.name)) or
(decl_type == .foreign and builtin_types.has(field_type_name))) (decl_type == .foreign and builtin_types.has(field_type_name)))
{ {
@@ -1317,9 +1150,7 @@ const Renderer = struct {
} }
fn renderForeign(self: *Self, name: []const u8, foreign: reg.Foreign) !void { fn renderForeign(self: *Self, name: []const u8, foreign: reg.Foreign) !void {
if (mem.eql(u8, foreign.depends, "vk_platform") or if (mem.eql(u8, foreign.depends, "vk_platform")) {
builtin_types.get(name) != null)
{
return; // Skip built-in types, they are handled differently return; // Skip built-in types, they are handled differently
} }
@@ -1350,18 +1181,18 @@ const Renderer = struct {
} }
fn renderCommandPtrs(self: *Self) !void { fn renderCommandPtrs(self: *Self) !void {
for (self.decls_by_name.keys(), self.decls_by_name.values()) |name, decl_type| { for (self.registry.decls) |decl| {
switch (decl_type) { switch (decl.decl_type) {
.command => { .command => {
try self.writer.writeAll("pub const "); try self.writer.writeAll("pub const ");
try self.renderCommandPtrName(name); try self.renderCommandPtrName(decl.name);
try self.writer.writeAll(" = "); try self.writer.writeAll(" = ");
try self.renderCommandPtr(decl_type.command, false); try self.renderCommandPtr(decl.decl_type.command, false);
try self.writer.writeAll(";\n"); try self.writer.writeAll(";\n");
}, },
.alias => |alias| if (alias.target == .other_command) { .alias => |alias| if (alias.target == .other_command) {
try self.writer.writeAll("pub const "); try self.writer.writeAll("pub const ");
try self.renderCommandPtrName(name); try self.renderCommandPtrName(decl.name);
try self.writer.writeAll(" = "); try self.writer.writeAll(" = ");
try self.renderCommandPtrName(alias.name); try self.renderCommandPtrName(alias.name);
try self.writer.writeAll(";\n"); try self.writer.writeAll(";\n");
@@ -1376,15 +1207,62 @@ const Renderer = struct {
\\pub const features = struct { \\pub const features = struct {
\\ \\
); );
// 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);
defer base_commands.deinit();
var instance_commands = std.BufSet.init(self.allocator);
defer instance_commands.deinit();
var device_commands = std.BufSet.init(self.allocator);
defer device_commands.deinit();
for (self.registry.features) |feature| { for (self.registry.features) |feature| {
try self.writer.writeAll("pub const "); try self.writer.writeAll("pub const ");
try self.writeIdentifierWithCase(.snake, trimVkNamespace(feature.name)); try self.writeIdentifierWithCase(.snake, trimVkNamespace(feature.name));
try self.writer.writeAll("= ApiInfo {\n"); try self.writer.writeAll("= ApiInfo {\n");
try self.writer.print(".name = \"{s}\", .version = makeApiVersion(0, {}, {}, 0),\n}};\n", .{ try self.writer.print(".name = \"{s}\", .version = makeApiVersion(0, {}, {}, 0),", .{
trimVkNamespace(feature.name), trimVkNamespace(feature.name),
feature.level.major, feature.level.major,
feature.level.minor, feature.level.minor,
}); });
// collect feature information
for (feature.requires) |require| {
for (require.commands) |command_name| {
const decl = self.resolveDeclaration(command_name) orelse continue;
// If the target type does not exist, it was likely an empty enum -
// assume spec is correct and that this was not a function alias.
const decl_type = self.resolveAlias(decl) catch continue;
const command = switch (decl_type) {
.command => |cmd| cmd,
else => continue,
};
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);
},
}
}
}
// 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();
try self.writer.writeAll(".instance_commands = ");
try self.renderCommandFlags(&instance_commands);
instance_commands.hash_map.clearRetainingCapacity();
try self.writer.writeAll(".device_commands = ");
try self.renderCommandFlags(&device_commands);
device_commands.hash_map.clearRetainingCapacity();
try self.writer.writeAll("};\n");
} }
try self.writer.writeAll("};\n"); try self.writer.writeAll("};\n");
@@ -1395,60 +1273,71 @@ const Renderer = struct {
\\pub const extensions = struct { \\pub const extensions = struct {
\\ \\
); );
// 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);
defer base_commands.deinit();
var instance_commands = std.BufSet.init(self.allocator);
defer instance_commands.deinit();
var device_commands = std.BufSet.init(self.allocator);
defer device_commands.deinit();
for (self.registry.extensions) |ext| { for (self.registry.extensions) |ext| {
try self.writer.writeAll("pub const "); try self.writer.writeAll("pub const ");
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.writeIdentifierWithCase(.snake, trimVkNamespace(ext.name));
}
try self.writer.writeAll("= ApiInfo {\n"); try self.writer.writeAll("= ApiInfo {\n");
try self.writer.print(".name = \"{s}\", .version = ", .{ext.name}); try self.writer.print(".name = \"{s}\", .version = {},", .{ ext.name, ext.version });
switch (ext.version) { // collect extension functions
.int => |version| try self.writer.print("makeApiVersion(0, {}, 0, 0)", .{version}), for (ext.requires) |require| {
// This should be the same as in self.renderApiConstant. for (require.commands) |command_name| {
// We assume that this is already a vk.Version type. const decl = self.resolveDeclaration(command_name) orelse continue;
.alias => |alias| try self.renderName(alias), // If the target type does not exist, it was likely an empty enum -
.unknown => try self.writer.writeAll("makeApiVersion(0, 0, 0, 0)"), // assume spec is correct and that this was not a function alias.
} const decl_type = self.resolveAlias(decl) catch continue;
try self.writer.writeAll(",};\n"); const command = switch (decl_type) {
}
try self.writer.writeAll("};\n");
}
fn renderDispatchTables(self: *Self) !void {
try self.renderDispatchTable(.base);
try self.renderDispatchTable(.instance);
try self.renderDispatchTable(.device);
}
fn renderDispatchTable(self: *Self, dispatch_type: CommandDispatchType) !void {
try self.writer.print(
"pub const {s}Dispatch = struct {{\n",
.{dispatch_type.name()},
);
for (self.decls_by_name.keys(), self.decls_by_name.values()) |name, decl_type| {
const final_decl_type = self.resolveAlias(decl_type) catch continue;
const command = switch (final_decl_type) {
.command => |cmd| cmd, .command => |cmd| cmd,
else => continue, else => continue,
}; };
const class = classifyCommandDispatch(command_name, command);
if (classifyCommandDispatch(name, command) != dispatch_type) { switch (class) {
continue; .base => {
try base_commands.insert(command_name);
},
.instance => {
try instance_commands.insert(command_name);
},
.device => {
try device_commands.insert(command_name);
},
} }
try self.writeIdentifier(name);
try self.writer.writeAll(": ?");
try self.renderCommandPtrName(name);
try self.writer.writeAll(" = null,\n");
} }
}
// and write them out
try self.writer.writeAll(".base_commands = ");
try self.renderCommandFlags(&base_commands);
base_commands.hash_map.clearRetainingCapacity();
try self.writer.writeAll(".instance_commands = ");
try self.renderCommandFlags(&instance_commands);
instance_commands.hash_map.clearRetainingCapacity();
try self.writer.writeAll(".device_commands = ");
try self.renderCommandFlags(&device_commands);
device_commands.hash_map.clearRetainingCapacity();
try self.writer.writeAll("};\n"); try self.writer.writeAll("};\n");
} }
try self.writer.writeAll("};\n");
}
fn renderCommandFlags(self: *Self, commands: *const std.BufSet) !void {
try self.writer.writeAll(".{\n");
var iterator = commands.iterator();
while (iterator.next()) |command_name| {
try self.writer.writeAll(".");
try self.writeIdentifierWithCase(.camel, trimVkNamespace(command_name.*));
try self.writer.writeAll(" = true, \n");
}
try self.writer.writeAll("},\n");
}
fn renderWrappers(self: *Self) !void { fn renderWrappers(self: *Self) !void {
try self.writer.writeAll(command_flags_mixin); try self.writer.writeAll(command_flags_mixin);
@@ -1459,17 +1348,130 @@ const Renderer = struct {
fn renderWrappersOfDispatchType(self: *Self, dispatch_type: CommandDispatchType) !void { fn renderWrappersOfDispatchType(self: *Self, dispatch_type: CommandDispatchType) !void {
const name = dispatch_type.name(); const name = dispatch_type.name();
const name_lower = dispatch_type.nameLower();
try self.writer.print( try self.writer.print(
\\pub const {0s}Wrapper = {0s}WrapperWithCustomDispatch({0s}Dispatch); \\pub const {0s}CommandFlags = packed struct {{
\\pub fn {0s}WrapperWithCustomDispatch(DispatchType: type) type {{
\\ return struct {{
\\ const Self = @This();
\\ pub const Dispatch = DispatchType;
\\
\\ dispatch: Dispatch,
\\ \\
, .{name}); , .{name});
for (self.registry.decls) |decl| {
// If the target type does not exist, it was likely an empty enum -
// assume spec is correct and that this was not a function alias.
const decl_type = self.resolveAlias(decl.decl_type) catch continue;
const command = switch (decl_type) {
.command => |cmd| cmd,
else => continue,
};
if (classifyCommandDispatch(decl.name, command) == dispatch_type) {
try self.writer.writeAll(" ");
try self.writeIdentifierWithCase(.camel, trimVkNamespace(decl.name));
try self.writer.writeAll(": bool = false,\n");
}
}
try self.writer.print(
\\pub fn CmdType(comptime tag: std.meta.FieldEnum({0s}CommandFlags)) type {{
\\ return switch (tag) {{
\\
, .{name});
for (self.registry.decls) |decl| {
// If the target type does not exist, it was likely an empty enum -
// assume spec is correct and that this was not a function alias.
const decl_type = self.resolveAlias(decl.decl_type) catch continue;
const command = switch (decl_type) {
.command => |cmd| cmd,
else => continue,
};
if (classifyCommandDispatch(decl.name, command) == dispatch_type) {
try self.writer.writeByte('.');
try self.writeIdentifierWithCase(.camel, trimVkNamespace(decl.name));
try self.writer.writeAll(" => ");
try self.renderCommandPtrName(decl.name);
try self.writer.writeAll(",\n");
}
}
try self.writer.writeAll(" };\n}");
try self.writer.print(
\\pub fn cmdName(tag: std.meta.FieldEnum({0s}CommandFlags)) [:0]const u8 {{
\\ return switch(tag) {{
\\
, .{name});
for (self.registry.decls) |decl| {
// If the target type does not exist, it was likely an empty enum -
// assume spec is correct and that this was not a function alias.
const decl_type = self.resolveAlias(decl.decl_type) catch continue;
const command = switch (decl_type) {
.command => |cmd| cmd,
else => continue,
};
if (classifyCommandDispatch(decl.name, command) == dispatch_type) {
try self.writer.writeByte('.');
try self.writeIdentifierWithCase(.camel, trimVkNamespace(decl.name));
try self.writer.print(
\\ => "{s}",
\\
, .{decl.name});
}
}
try self.writer.writeAll(" };\n}");
try self.renderFlagFunctions(name, "CommandFlagsMixin", command_flag_functions, "CommandFlags");
try self.writer.print(
\\}};
\\pub fn {0s}Wrapper(comptime apis: []const ApiInfo) type {{
\\ return struct {{
\\ dispatch: Dispatch,
\\
\\ const Self = @This();
\\ pub const commands = blk: {{
\\ var cmds: {0s}CommandFlags = .{{}};
\\ for (apis) |api| {{
\\ cmds = cmds.merge(api.{1s}_commands);
\\ }}
\\ break :blk cmds;
\\ }};
\\ pub const Dispatch = blk: {{
\\ @setEvalBranchQuota(1_000_000);
\\ const Type = std.builtin.Type;
\\ const fields_len = fields_len: {{
\\ var fields_len: u32 = 0;
\\ for (@typeInfo({0s}CommandFlags).Struct.fields) |field| {{
\\ fields_len += @intCast(@intFromBool(@field(commands, field.name)));
\\ }}
\\ break :fields_len fields_len;
\\ }};
\\ var fields: [fields_len]Type.StructField = undefined;
\\ var i: usize = 0;
\\ for (@typeInfo({0s}CommandFlags).Struct.fields) |field| {{
\\ if (@field(commands, field.name)) {{
\\ const field_tag = std.enums.nameCast(std.meta.FieldEnum({0s}CommandFlags), field.name);
\\ const PfnType = {0s}CommandFlags.CmdType(field_tag);
\\ fields[i] = .{{
\\ .name = {0s}CommandFlags.cmdName(field_tag),
\\ .type = PfnType,
\\ .default_value = null,
\\ .is_comptime = false,
\\ .alignment = @alignOf(PfnType),
\\ }};
\\ i += 1;
\\ }}
\\ }}
\\ break :blk @Type(.{{
\\ .Struct = .{{
\\ .layout = .auto,
\\ .fields = &fields,
\\ .decls = &[_]std.builtin.Type.Declaration{{}},
\\ .is_tuple = false,
\\ }},
\\ }});
\\ }};
\\
, .{ name, name_lower });
try self.renderWrapperLoader(dispatch_type); try self.renderWrapperLoader(dispatch_type);
@@ -1514,10 +1516,24 @@ const Renderer = struct {
@setEvalBranchQuota(2000); @setEvalBranchQuota(2000);
try self.writer.print( try self.writer.print(
\\pub fn load({[params]s}) Self {{ \\pub fn load({[params]s}) error{{CommandLoadFailure}}!Self {{
\\ var self: Self = .{{ .dispatch = .{{}} }}; \\ var self: Self = undefined;
\\ inline for (std.meta.fields(Dispatch)) |field| {{ \\ inline for (std.meta.fields(Dispatch)) |field| {{
\\ const cmd_ptr = loader({[first_arg]s}, field.name.ptr) orelse undefined; \\ const name: [*:0]const u8 = @ptrCast(field.name ++ "\x00");
\\ if (loader({[first_arg]s}, name)) |cmd_ptr| {{
\\ @field(self.dispatch, field.name) = @ptrCast(cmd_ptr);
\\ }} else {{
\\ std.log.err("Command loading failed for \"{{s}}\".", .{{field.name}});
\\ return error.CommandLoadFailure;
\\ }}
\\ }}
\\ return self;
\\}}
\\pub fn loadNoFail({[params]s}) Self {{
\\ var self: Self = undefined;
\\ inline for (std.meta.fields(Dispatch)) |field| {{
\\ const name: [*:0]const u8 = @ptrCast(field.name ++ "\x00");
\\ const cmd_ptr = loader({[first_arg]s}, name) orelse undefined;
\\ @field(self.dispatch, field.name) = @ptrCast(cmd_ptr); \\ @field(self.dispatch, field.name) = @ptrCast(cmd_ptr);
\\ }} \\ }}
\\ return self; \\ return self;
@@ -1541,16 +1557,13 @@ const Renderer = struct {
const loader_name = dispatch_type.name(); const loader_name = dispatch_type.name();
try self.writer.print( try self.writer.print(
\\pub const {0s}Proxy = {0s}ProxyWithCustomDispatch({1s}Dispatch); \\pub fn {0s}Proxy(comptime apis: []const ApiInfo) type {{
\\pub fn {0s}ProxyWithCustomDispatch(DispatchType: type) type {{ \\ @setEvalBranchQuota(100_000);
\\ return struct {{ \\ return struct {{
\\ const Self = @This(); \\ const Self = @This();
\\ pub const Wrapper = {1s}WrapperWithCustomDispatch(DispatchType); \\ pub const Wrapper = {1s}Wrapper(apis);
\\ \\
\\ handle: {0s}, \\ handle: {0s},
// Note: This is a pointer because in the past there were some performance
// issues with putting an object and vtable in the same structure. This also
// affected std.mem.Allocator, which is why its like that too.
\\ wrapper: *const Wrapper, \\ wrapper: *const Wrapper,
\\ \\
\\ pub fn init(handle: {0s}, wrapper: *const Wrapper) Self {{ \\ pub fn init(handle: {0s}, wrapper: *const Wrapper) Self {{
@@ -1671,13 +1684,11 @@ const Renderer = struct {
const params = command.params[0 .. command.params.len - 2]; const params = command.params[0 .. command.params.len - 2];
const data_type = try getEnumerateFunctionDataType(command); const data_type = try getEnumerateFunctionDataType(command);
if (returns_vk_result) {
try self.writer.writeAll("pub const "); try self.writer.writeAll("pub const ");
try self.renderErrorSetName(name); try self.renderErrorSetName(name);
try self.writer.writeAll(" = Wrapper."); try self.writer.writeAll(" = Wrapper.");
try self.renderErrorSetName(name); try self.renderErrorSetName(name);
try self.writer.writeAll(";\n"); try self.writer.writeAll(";\n");
}
try self.renderAllocWrapperPrototype(name, params, returns_vk_result, data_type, dispatch_handle, .proxy); try self.renderAllocWrapperPrototype(name, params, returns_vk_result, data_type, dispatch_handle, .proxy);
try self.writer.writeAll( try self.writer.writeAll(
@@ -1806,7 +1817,7 @@ const Renderer = struct {
) !void { ) !void {
try self.writer.writeAll("self.dispatch."); try self.writer.writeAll("self.dispatch.");
try self.writeIdentifier(name); try self.writeIdentifier(name);
try self.writer.writeAll(".?("); try self.writer.writeAll("(");
for (command.params) |param| { for (command.params) |param| {
switch (try self.classifyParam(param)) { switch (try self.classifyParam(param)) {
@@ -1829,13 +1840,12 @@ const Renderer = struct {
} }
fn extractReturns(self: *Self, command: reg.Command) ![]const ReturnValue { fn extractReturns(self: *Self, command: reg.Command) ![]const ReturnValue {
const allocator = self.allocator; var returns = std.ArrayList(ReturnValue).init(self.allocator);
var returns: std.ArrayList(ReturnValue) = .empty;
if (command.return_type.* == .name) { if (command.return_type.* == .name) {
const return_name = command.return_type.name; const return_name = command.return_type.name;
if (!mem.eql(u8, return_name, "void") and !mem.eql(u8, return_name, "VkResult")) { if (!mem.eql(u8, return_name, "void") and !mem.eql(u8, return_name, "VkResult")) {
try returns.append(allocator, .{ try returns.append(.{
.name = "return_value", .name = "return_value",
.return_value_type = command.return_type.*, .return_value_type = command.return_type.*,
.origin = .inner_return_value, .origin = .inner_return_value,
@@ -1848,7 +1858,7 @@ const Renderer = struct {
return error.InvalidRegistry; return error.InvalidRegistry;
} }
try returns.append(allocator, .{ try returns.append(.{
.name = "result", .name = "result",
.return_value_type = command.return_type.*, .return_value_type = command.return_type.*,
.origin = .inner_return_value, .origin = .inner_return_value,
@@ -1859,7 +1869,7 @@ const Renderer = struct {
for (command.params) |param| { for (command.params) |param| {
if ((try self.classifyParam(param)) == .out_pointer) { if ((try self.classifyParam(param)) == .out_pointer) {
try returns.append(allocator, .{ try returns.append(.{
.name = derefName(param.name), .name = derefName(param.name),
.return_value_type = param.param_type.pointer.child.*, .return_value_type = param.param_type.pointer.child.*,
.origin = .parameter, .origin = .parameter,
@@ -1867,7 +1877,7 @@ const Renderer = struct {
} }
} }
return try returns.toOwnedSlice(allocator); return try returns.toOwnedSlice();
} }
fn renderReturnStructName(self: *Self, command_name: []const u8) !void { fn renderReturnStructName(self: *Self, command_name: []const u8) !void {
@@ -2133,15 +2143,10 @@ const Renderer = struct {
} }
} }
}; };
}
pub fn render( pub fn render(writer: anytype, allocator: Allocator, registry: *const reg.Registry, id_renderer: *IdRenderer) !void {
writer: *std.Io.Writer, var renderer = try Renderer(@TypeOf(writer)).init(writer, allocator, registry, id_renderer);
allocator: Allocator,
registry: *const reg.Registry,
id_renderer: *IdRenderer,
have_video: bool,
) !void {
var renderer = try Renderer.init(writer, allocator, registry, id_renderer, have_video);
defer renderer.deinit(); defer renderer.deinit();
try renderer.render(); try renderer.render();
} }

View File

@@ -439,15 +439,15 @@ fn parseElement(parser: *Parser, alloc: Allocator, comptime kind: ElementKind) !
}, },
}; };
var attributes: std.ArrayList(Attribute) = .empty; var attributes = std.ArrayList(Attribute).init(alloc);
defer attributes.deinit(alloc); defer attributes.deinit();
var children: std.ArrayList(Content) = .empty; var children = std.ArrayList(Content).init(alloc);
defer children.deinit(alloc); defer children.deinit();
while (parser.eatWs()) { while (parser.eatWs()) {
const attr = (try parseAttr(parser, alloc)) orelse break; const attr = (try parseAttr(parser, alloc)) orelse break;
try attributes.append(alloc, attr); try attributes.append(attr);
} }
switch (kind) { switch (kind) {
@@ -464,7 +464,7 @@ fn parseElement(parser: *Parser, alloc: Allocator, comptime kind: ElementKind) !
} }
const content = try parseContent(parser, alloc); const content = try parseContent(parser, alloc);
try children.append(alloc, content); try children.append(content);
} }
const closing_tag = try parseNameNoDupe(parser); const closing_tag = try parseNameNoDupe(parser);
@@ -481,8 +481,8 @@ fn parseElement(parser: *Parser, alloc: Allocator, comptime kind: ElementKind) !
const element = try alloc.create(Element); const element = try alloc.create(Element);
element.* = .{ element.* = .{
.tag = try alloc.dupe(u8, tag), .tag = try alloc.dupe(u8, tag),
.attributes = try attributes.toOwnedSlice(alloc), .attributes = try attributes.toOwnedSlice(),
.children = try children.toOwnedSlice(alloc), .children = try children.toOwnedSlice(),
}; };
return element; return element;
} }

View File

@@ -21,6 +21,7 @@ pub const MTLTexture_id = u32;
pub const MTLSharedEvent_id = u32; pub const MTLSharedEvent_id = u32;
pub const IOSurfaceRef = u32; pub const IOSurfaceRef = u32;
// For some reason these types are exported in a different header, and not described in vk.xml.
pub const StdVideoH264ProfileIdc = u32; pub const StdVideoH264ProfileIdc = u32;
pub const StdVideoH264LevelIdc = u32; pub const StdVideoH264LevelIdc = u32;
pub const StdVideoH264ChromaFormatIdc = u32; pub const StdVideoH264ChromaFormatIdc = u32;
@@ -99,13 +100,13 @@ comptime {
fn reallyRefAllDecls(comptime T: type) void { fn reallyRefAllDecls(comptime T: type) void {
switch (@typeInfo(T)) { switch (@typeInfo(T)) {
.@"struct", .@"union" => { .Struct, .Union => {
reallyRefAllContainerDecls(T); reallyRefAllContainerDecls(T);
inline for (std.meta.fields(T)) |field| { inline for (std.meta.fields(T)) |field| {
reallyRefAllDecls(field.type); reallyRefAllDecls(field.type);
} }
}, },
.@"enum", .@"opaque" => { .Enum, .Opaque => {
reallyRefAllContainerDecls(T); reallyRefAllContainerDecls(T);
}, },
else => {}, else => {},