48 Commits

Author SHA1 Message Date
Robin Voetter
d87813312e Make Vulkan enums always be 32-bit (fixes #26) 2021-11-08 13:51:35 +01:00
InKryption
e17c3593d1 Update graphics_context.zig 2021-11-08 13:51:24 +01:00
InKryption
9513d33bf8 Replace anytype with explicit []const {s}Command 2021-11-08 13:51:12 +01:00
Robin Voetter
59c5b88d17 Add mach-glfw and mach-glfw-vulkan-example readme links 2021-11-08 13:48:27 +01:00
Stephen Gutekanst
4588c0fcad examples: do not call glfwSwapBuffers
I am porting this example to [mach-glfw](github.com/hexops/mach-glfw), and noticed that no GLFW error handling callback is registered because in my port there are a lot of GLFW errors :)

`glfwSwapBuffers` here is emitting `GLFW_NO_WINDOW_CONTEXT` errors constantly, because calling it without a valid OpenGL context is illegal. It's not needed for Vulkan.
2021-11-08 13:48:03 +01:00
Robin Voetter
f55409f98a Make command enums lower camel case to reflect command function name style 2021-10-25 14:38:00 +02:00
Robin Voetter
cbf06a8d42 CI: Enable CI for zig-0.8.1-compat 2021-10-25 13:59:04 +02:00
Robin Voetter
c5bb254766 Make sure there are no errors after parsing generated Zig 2021-10-25 13:58:44 +02:00
Robin Voetter
5980bac303 CI: Bump vulkan sdk to 189 2021-10-25 13:55:18 +02:00
Robin Voetter
3bfacc7e16 Fix some allocation bugs, replace everything by arena (#18) 2021-10-25 13:54:48 +02:00
Marten Ringwelski
1e594c0f09 examples/swapchain: Fix typo 2021-10-25 13:54:31 +02:00
ashpil
397e663296 adds defaults for previously undetected feature struct 2021-10-25 13:54:12 +02:00
Robin Voetter
933010cfff Update readme with new build.zig usage 2021-10-25 13:53:04 +02:00
ashpil
0eccd593ce implements default for feature structs 2021-10-25 13:46:52 +02:00
Robin Voetter
6a2c379146 Update readme 2021-07-06 11:00:13 +02:00
Robin Voetter
4429151d9c CI: Also test 0.8.0 branch 2021-07-06 10:46:44 +02:00
Robin Voetter
77651872ab Render error set constant for wrappers 2021-07-06 10:37:39 +02:00
Robin Voetter
5a51d18bda CI: Upload vk.zig as artifact 2021-07-06 10:37:39 +02:00
Robin Voetter
6feeeac109 Merge pull request #12 from ashpil/master
camel -> snake for command enums + fixes
2021-07-06 10:37:39 +02:00
ashpil
8f10cec149 camel -> snake for command enums + fixes 2021-07-06 10:37:39 +02:00
ashpil
0e65efd9d6 less verbose interface via @Type 2021-07-06 10:37:39 +02:00
Robin Voetter
b3c71d69ea Fix a whole bunch of issues exposed by ziglang/zig#9191
This also includes a workaround for the fact that @"type" refers to the builtin
and not to a variable called "type". See ziglang/zig#2897.
2021-07-06 10:37:39 +02:00
Robin Voetter
b63533d95b Fix another vk.xml moment 2021-07-06 10:37:39 +02:00
Robin Voetter
419e541a16 zig fmt **.zig 2021-07-06 10:37:39 +02:00
Robin Voetter
4b4ef38c93 Fix incorrect alignment of Flags64 type packed structs 2021-06-08 00:28:03 +02:00
Robin Voetter
2af84b5212 CI: Bump Vulkan SDK version 2021-06-05 13:44:35 +02:00
Robin Voetter
beaa5bb0a5 Adapt to changed hashmap API 2021-06-05 13:42:30 +02:00
Robin Voetter
e0101accda CallingConvention and Target live in std, not in builtin 2021-05-21 12:55:14 +02:00
Robin Voetter
1ebdd7f063 Adapt to Zig changes: Handle errors of testing functions 2021-05-11 14:23:29 +02:00
Robin Voetter
0c404b3c32 Update examples xml 2021-04-18 00:25:50 +02:00
Robin Voetter
5375c873d9 Vulkan 1.2.175 compatibility 2021-04-13 19:52:06 +02:00
Robin Voetter
711bc08211 Stop fixing up bitmasks.
This seems to not be required anymore.
2021-04-08 13:02:07 +02:00
Robin Voetter
80d338e984 Stop fixing up tags.
This seems to not be needed anymore.
2021-04-08 12:48:35 +02:00
Robin Voetter
fc7c823293 Stop filtering out promoted extensions (Fixes #10)
This seems to not be needed anymore.
2021-04-08 12:47:25 +02:00
Robin Voetter
4c96d30e11 Use branch for maintaining older versions instead of tag 2021-04-07 21:42:16 +02:00
Robin Voetter
1e7b5edb10 Allow F as floating-point suffix 2021-03-30 13:08:34 +02:00
Robin Voetter
938359c6c9 Fix parse error 2021-03-30 12:53:32 +02:00
Robin Voetter
bb21cf6892 Update to new zig render API 2021-02-26 12:42:40 +01:00
Robin Voetter
5c5134269b CI: Split out build & fetch vk.xml steps 2021-02-16 15:28:58 +01:00
Robin Voetter
9f23e2e16d Vulkan 1.2.170 compatibility 2021-02-16 15:26:22 +01:00
Robin Voetter
e7d6f9f012 Use linkLibC instead of linkSystemLibrary to link libc 2021-02-12 14:42:04 +01:00
Robin Voetter
4ccb530585 Clarify on compatible zig versions (#8) 2021-02-10 00:02:54 +01:00
Robin Voetter
d8b223bb3b Remove some old code 2021-02-10 00:01:46 +01:00
Robin Voetter
a1f08ee687 Allow top level comments in xml parser 2021-02-01 02:24:57 +01:00
Robin Voetter
fe85c8975e Small styling fix 2021-01-17 03:49:55 +01:00
Robin Voetter
1c8e6b5a4b Make API-enums non-exhaustive
The Vulkan implementation is not required to
filter enums on values supported by the requested
API, and so may return values that the
implementation doesn't know about. By making
these enums non-exhaustive, the programmer is
forced to deal with these kinds of cases
appropriately.
2021-01-16 17:06:43 +01:00
Robin Voetter
4a3700dbdd Generate fully qualified alias enum variants 2021-01-16 05:14:14 +01:00
Robin Voetter
caaf915671 Replace {z} format specifiers with std.zig.fmtId 2021-01-08 19:49:06 +01:00
19 changed files with 3067 additions and 1458 deletions

View File

@@ -2,9 +2,9 @@ name: Build
on:
push:
branches: [ master ]
branches: [ zig-0.8.1-compat ]
pull_request:
branches: [ master ]
branches: [ zig-0.8.1-compat ]
schedule:
- cron: '0 6 * * *'
@@ -18,7 +18,7 @@ jobs:
- name: Setup Zig
uses: goto-bus-stop/setup-zig@v1.3.0
with:
version: master
version: 0.8.0
- name: Test
run: |
@@ -27,11 +27,20 @@ jobs:
- name: Fetch Vulkan SDK
run: |
wget -qO - https://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo apt-key add -
sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-1.2.162-focal.list https://packages.lunarg.com/vulkan/1.2.162/lunarg-vulkan-1.2.162-focal.list
sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-1.2.189-focal.list https://packages.lunarg.com/vulkan/1.2.189/lunarg-vulkan-1.2.189-focal.list
sudo apt update
sudo apt install shaderc libglfw3 libglfw3-dev
- name: Build with latest zig & vk.xml
- name: Fetch latest vk.xml
run: |
wget https://raw.githubusercontent.com/KhronosGroup/Vulkan-Docs/main/xml/vk.xml
- name: Build with latest zig & vk.xml
run: |
zig build -Dvulkan-registry=./vk.xml
- name: Archive vk.xml
uses: actions/upload-artifact@v2
with:
name: vk.zig
path: zig-cache/vk.zig

1
.gitignore vendored
View File

@@ -1 +1,2 @@
zig-cache/
zig-out/

View File

@@ -8,7 +8,11 @@ A Vulkan binding generator for Zig.
vulkan-zig attempts to provide a better experience to programming Vulkan applications in Zig, by providing features such as integration of vulkan errors with Zig's error system, function pointer loading, renaming fields to standard Zig style, better bitfield handling, turning out parameters into return values and more.
vulkan-zig is automatically tested against the latest vk.xml and zig, and supports vk.xml from version 1.x.163.
vulkan-zig is automatically tested daily against the latest vk.xml and zig, and supports vk.xml from version 1.x.163.
### Zig versions
vulkan-zig aims to be always compatible with the ever-changing Zig master branch (however, development may lag a few days behind). Sometimes, the Zig master branch breaks a bunch of functionality however, which may make the latest version vulkan-zig incompatible with older releases of Zig. This repository aims to have a version compatible for both the latest Zig master, and the latest Zig release. The `master` branch is compatible with the `master` branch of Zig, and versions for older versions of Zig are maintained in the `zig-<version>-compat` branch.
## Features
### CLI-interface
@@ -32,10 +36,10 @@ pub fn build(b: *Builder) void {
exe.step.dependOn(&gen.step);
// Add the generated file as package to the final executable
exe.addPackagePath("vulkan", gen.full_out_path);
exe.addPackage(gen.package);
}
```
This reads vk.xml, parses its contents, and renders the Vulkan bindings to "vk.zig", which is then formatted and placed in `zig-cache`. The resulting file can then be added to an executable by using `addPackagePath`.
This reads vk.xml, parses its contents, and renders the Vulkan bindings to "vk.zig", which is then formatted and placed in `zig-cache`. The resulting file can then be added to an executable by using `addPackage`, after which the bindings will be made available to the executable under the name `vulkan`.
### Function & field renaming
Functions and fields are renamed to be more or less in line with [Zig's standard library style](https://ziglang.org/documentation/master/#Style-Guide):
@@ -63,23 +67,22 @@ For each function, a wrapper is generated into one of three structs:
* InstanceWrapper. This contains wrappers for functions which are otherwise loaded by `vkGetInstanceProcAddr`.
* DeviceWrapper. This contains wrappers for functions which are loaded by `vkGetDeviceProcAddr`.
Each wrapper struct is to be used as a mixin on a struct containing **just** function pointers as members:
Each wrapper struct can be called with an array of the appropriate enums:
```zig
const vk = @import("vulkan");
const BaseDispatch = struct {
vkCreateInstance: vk.PfnCreateInstance,
usingnamespace vk.BaseWrapper(@This());
};
const BaseDispatch = vk.BaseWrapper(.{
.CreateInstance,
});
```
The wrapper struct then provides wrapper functions for each function pointer in the dispatch struct:
```zig
pub const BaseWrapper(comptime Self: type) type {
pub const BaseWrapper(comptime cmds: anytype) type {
...
const Dispatch = CreateDispatchStruct(cmds);
return struct {
pub fn createInstance(
self: Self,
create_info: InstanceCreateInfo,
p_allocator: ?*const AllocationCallbacks,
) error{
dispatch: Dispatch,
pub const CreateInstanceError = error{
OutOfHostMemory,
OutOfDeviceMemory,
InitializationFailed,
@@ -87,9 +90,14 @@ pub const BaseWrapper(comptime Self: type) type {
ExtensionNotPresent,
IncompatibleDriver,
Unknown,
}!Instance {
};
pub fn createInstance(
self: Self,
create_info: InstanceCreateInfo,
p_allocator: ?*const AllocationCallbacks,
) CreateInstanceError!Instance {
var instance: Instance = undefined;
const result = self.vkCreateInstance(
const result = self.dispatch.vkCreateInstance(
&create_info,
p_allocator,
&instance,
@@ -121,9 +129,11 @@ Wrappers are generated according to the following rules:
* As of yet, there is no specific handling of enumeration style commands or other commands which accept slices.
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: PfnGetInstanceProcAddr) !Self`.
* For `InstanceWrapper`, this function has signature `fn load(instance: Instance, loader: PfnGetInstanceProcAddr) !Self`.
* For `DeviceWrapper`, this function has signature `fn load(device: Device, loader: PfnGetDeviceProcAddr) !Self`.
* 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 `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`.
One can access the underlying unwrapped C functions by doing `wrapper.dispatch.vkFuncYouWant(..)`.
### 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.
@@ -221,7 +231,7 @@ pub fn build(b: *Builder) void {
const gen = vkgen.VkGenerateStep(b, "path/to/vk.xml", "vk.zig");
exe.step.dependOn(&gen.step);
exe.addPackagePath("vulkan", gen.full_out_path);
exe.addPackage(gen.package);
const shader_comp = vkgen.ShaderCompileStep.init(
builder,
@@ -242,5 +252,7 @@ Upon compilation, glslc is then invoked to compile each shader, and the result i
A partial implementation of https://vulkan-tutorial.org is implemented in [examples/triangle.zig](examples/triangle.zig). This example can be ran by executing `zig build run-triangle` in vulkan-zig's root.
## See also
* Implementation of https://vulkan-tutorial.org: https://github.com/andrewrk/zig-vulkan-triangle.
* Implementation of https://vulkan-tutorial.org using `@cImport`'ed bindings: https://github.com/andrewrk/zig-vulkan-triangle.
* Alternative binding generator: https://github.com/SpexGuy/Zig-Vulkan-Headers
* Zig bindings for GLFW: https://github.com/hexops/mach-glfw
* With vulkan-zig integration example: https://github.com/hexops/mach-glfw-vulkan-example

View File

@@ -34,15 +34,15 @@ pub const ResourceGenStep = struct {
return self;
}
fn renderPath(self: *ResourceGenStep, path: []const u8, writer: anytype) void {
fn renderPath(path: []const u8, writer: anytype) void {
const separators = &[_]u8{ std.fs.path.sep_windows, std.fs.path.sep_posix };
var i: usize = 0;
while (std.mem.indexOfAnyPos(u8, path, i, separators)) |j| {
writer.writeAll(path[i .. j]) catch unreachable;
writer.writeAll(path[i..j]) catch unreachable;
switch (std.fs.path.sep) {
std.fs.path.sep_windows => writer.writeAll("\\\\") catch unreachable,
std.fs.path.sep_posix => writer.writeByte(std.fs.path.sep_posix) catch unreachable,
else => unreachable
else => unreachable,
}
i = j + 1;
@@ -54,8 +54,8 @@ pub const ResourceGenStep = struct {
const shader_out_path = self.shader_step.add(source);
var writer = self.resources.writer();
writer.print("pub const {s} = @embedFile(\"", .{ name }) catch unreachable;
self.renderPath(shader_out_path, writer);
writer.print("pub const {s} = @embedFile(\"", .{name}) catch unreachable;
renderPath(shader_out_path, writer);
writer.writeAll("\");\n") catch unreachable;
}
@@ -85,7 +85,7 @@ pub fn build(b: *Builder) void {
triangle_exe.setTarget(target);
triangle_exe.setBuildMode(mode);
triangle_exe.install();
triangle_exe.linkSystemLibrary("c");
triangle_exe.linkLibC();
triangle_exe.linkSystemLibrary("glfw");
const vk_xml_path = b.option([]const u8, "vulkan-registry", "Override the to the Vulkan registry") orelse "examples/vk.xml";

View File

@@ -3,85 +3,80 @@ const vk = @import("vulkan");
const c = @import("c.zig");
const Allocator = std.mem.Allocator;
const required_device_extensions = [_][]const u8{
vk.extension_info.khr_swapchain.name
};
const required_device_extensions = [_][]const u8{vk.extension_info.khr_swapchain.name};
const BaseDispatch = struct {
vkCreateInstance: vk.PfnCreateInstance,
usingnamespace vk.BaseWrapper(@This());
};
const BaseDispatch = vk.BaseWrapper(&.{
.createInstance,
});
const InstanceDispatch = struct {
vkDestroyInstance: vk.PfnDestroyInstance,
vkCreateDevice: vk.PfnCreateDevice,
vkDestroySurfaceKHR: vk.PfnDestroySurfaceKHR,
vkEnumeratePhysicalDevices: vk.PfnEnumeratePhysicalDevices,
vkGetPhysicalDeviceProperties: vk.PfnGetPhysicalDeviceProperties,
vkEnumerateDeviceExtensionProperties: vk.PfnEnumerateDeviceExtensionProperties,
vkGetPhysicalDeviceSurfaceFormatsKHR: vk.PfnGetPhysicalDeviceSurfaceFormatsKHR,
vkGetPhysicalDeviceSurfacePresentModesKHR: vk.PfnGetPhysicalDeviceSurfacePresentModesKHR,
vkGetPhysicalDeviceSurfaceCapabilitiesKHR: vk.PfnGetPhysicalDeviceSurfaceCapabilitiesKHR,
vkGetPhysicalDeviceQueueFamilyProperties: vk.PfnGetPhysicalDeviceQueueFamilyProperties,
vkGetPhysicalDeviceSurfaceSupportKHR: vk.PfnGetPhysicalDeviceSurfaceSupportKHR,
vkGetPhysicalDeviceMemoryProperties: vk.PfnGetPhysicalDeviceMemoryProperties,
vkGetDeviceProcAddr: vk.PfnGetDeviceProcAddr,
usingnamespace vk.InstanceWrapper(@This());
};
const InstanceDispatch = vk.InstanceWrapper(&.{
.destroyInstance,
.createDevice,
.destroySurfaceKHR,
.enumeratePhysicalDevices,
.getPhysicalDeviceProperties,
.enumerateDeviceExtensionProperties,
.getPhysicalDeviceSurfaceFormatsKHR,
.getPhysicalDeviceSurfacePresentModesKHR,
.getPhysicalDeviceSurfaceCapabilitiesKHR,
.getPhysicalDeviceQueueFamilyProperties,
.getPhysicalDeviceSurfaceSupportKHR,
.getPhysicalDeviceMemoryProperties,
.getDeviceProcAddr,
});
const DeviceDispatch = struct {
vkDestroyDevice: vk.PfnDestroyDevice,
vkGetDeviceQueue: vk.PfnGetDeviceQueue,
vkCreateSemaphore: vk.PfnCreateSemaphore,
vkCreateFence: vk.PfnCreateFence,
vkCreateImageView: vk.PfnCreateImageView,
vkDestroyImageView: vk.PfnDestroyImageView,
vkDestroySemaphore: vk.PfnDestroySemaphore,
vkDestroyFence: vk.PfnDestroyFence,
vkGetSwapchainImagesKHR: vk.PfnGetSwapchainImagesKHR,
vkCreateSwapchainKHR: vk.PfnCreateSwapchainKHR,
vkDestroySwapchainKHR: vk.PfnDestroySwapchainKHR,
vkAcquireNextImageKHR: vk.PfnAcquireNextImageKHR,
vkDeviceWaitIdle: vk.PfnDeviceWaitIdle,
vkWaitForFences: vk.PfnWaitForFences,
vkResetFences: vk.PfnResetFences,
vkQueueSubmit: vk.PfnQueueSubmit,
vkQueuePresentKHR: vk.PfnQueuePresentKHR,
vkCreateCommandPool: vk.PfnCreateCommandPool,
vkDestroyCommandPool: vk.PfnDestroyCommandPool,
vkAllocateCommandBuffers: vk.PfnAllocateCommandBuffers,
vkFreeCommandBuffers: vk.PfnFreeCommandBuffers,
vkQueueWaitIdle: vk.PfnQueueWaitIdle,
vkCreateShaderModule: vk.PfnCreateShaderModule,
vkDestroyShaderModule: vk.PfnDestroyShaderModule,
vkCreatePipelineLayout: vk.PfnCreatePipelineLayout,
vkDestroyPipelineLayout: vk.PfnDestroyPipelineLayout,
vkCreateRenderPass: vk.PfnCreateRenderPass,
vkDestroyRenderPass: vk.PfnDestroyRenderPass,
vkCreateGraphicsPipelines: vk.PfnCreateGraphicsPipelines,
vkDestroyPipeline: vk.PfnDestroyPipeline,
vkCreateFramebuffer: vk.PfnCreateFramebuffer,
vkDestroyFramebuffer: vk.PfnDestroyFramebuffer,
vkBeginCommandBuffer: vk.PfnBeginCommandBuffer,
vkEndCommandBuffer: vk.PfnEndCommandBuffer,
vkAllocateMemory: vk.PfnAllocateMemory,
vkFreeMemory: vk.PfnFreeMemory,
vkCreateBuffer: vk.PfnCreateBuffer,
vkDestroyBuffer: vk.PfnDestroyBuffer,
vkGetBufferMemoryRequirements: vk.PfnGetBufferMemoryRequirements,
vkMapMemory: vk.PfnMapMemory,
vkUnmapMemory: vk.PfnUnmapMemory,
vkBindBufferMemory: vk.PfnBindBufferMemory,
vkCmdBeginRenderPass: vk.PfnCmdBeginRenderPass,
vkCmdEndRenderPass: vk.PfnCmdEndRenderPass,
vkCmdBindPipeline: vk.PfnCmdBindPipeline,
vkCmdDraw: vk.PfnCmdDraw,
vkCmdSetViewport: vk.PfnCmdSetViewport,
vkCmdSetScissor: vk.PfnCmdSetScissor,
vkCmdBindVertexBuffers: vk.PfnCmdBindVertexBuffers,
vkCmdCopyBuffer: vk.PfnCmdCopyBuffer,
usingnamespace vk.DeviceWrapper(@This());
};
const DeviceDispatch = vk.DeviceWrapper(&.{
.destroyDevice,
.getDeviceQueue,
.createSemaphore,
.createFence,
.createImageView,
.destroyImageView,
.destroySemaphore,
.destroyFence,
.getSwapchainImagesKHR,
.createSwapchainKHR,
.destroySwapchainKHR,
.acquireNextImageKHR,
.deviceWaitIdle,
.waitForFences,
.resetFences,
.queueSubmit,
.queuePresentKHR,
.createCommandPool,
.destroyCommandPool,
.allocateCommandBuffers,
.freeCommandBuffers,
.queueWaitIdle,
.createShaderModule,
.destroyShaderModule,
.createPipelineLayout,
.destroyPipelineLayout,
.createRenderPass,
.destroyRenderPass,
.createGraphicsPipelines,
.destroyPipeline,
.createFramebuffer,
.destroyFramebuffer,
.beginCommandBuffer,
.endCommandBuffer,
.allocateMemory,
.freeMemory,
.createBuffer,
.destroyBuffer,
.getBufferMemoryRequirements,
.mapMemory,
.unmapMemory,
.bindBufferMemory,
.cmdBeginRenderPass,
.cmdEndRenderPass,
.cmdBindPipeline,
.cmdDraw,
.cmdSetViewport,
.cmdSetScissor,
.cmdBindVertexBuffers,
.cmdCopyBuffer,
});
pub const GraphicsContext = struct {
vkb: BaseDispatch,
@@ -107,9 +102,9 @@ pub const GraphicsContext = struct {
const app_info = vk.ApplicationInfo{
.p_application_name = app_name,
.application_version = vk.makeVersion(0, 0, 0),
.application_version = vk.makeApiVersion(0, 0, 0, 0),
.p_engine_name = app_name,
.engine_version = vk.makeVersion(0, 0, 0),
.engine_version = vk.makeApiVersion(0, 0, 0, 0),
.api_version = vk.API_VERSION_1_2,
};
@@ -125,14 +120,14 @@ pub const GraphicsContext = struct {
self.vki = try InstanceDispatch.load(self.instance, c.glfwGetInstanceProcAddress);
errdefer self.vki.destroyInstance(self.instance, null);
self.surface = try createSurface(self.vki, self.instance, window);
self.surface = try createSurface(self.instance, window);
errdefer self.vki.destroySurfaceKHR(self.instance, self.surface, null);
const candidate = try pickPhysicalDevice(self.vki, self.instance, allocator, self.surface);
self.pdev = candidate.pdev;
self.props = candidate.props;
self.dev = try initializeCandidate(self.vki, candidate);
self.vkd = try DeviceDispatch.load(self.dev, self.vki.vkGetDeviceProcAddr);
self.vkd = try DeviceDispatch.load(self.dev, self.vki.dispatch.vkGetDeviceProcAddr);
errdefer self.vkd.destroyDevice(self.dev, null);
self.graphics_queue = Queue.init(self.vkd, self.dev, candidate.queues.graphics_family);
@@ -151,11 +146,11 @@ pub const GraphicsContext = struct {
pub fn deviceName(self: GraphicsContext) []const u8 {
const len = std.mem.indexOfScalar(u8, &self.props.device_name, 0).?;
return self.props.device_name[0 .. len];
return self.props.device_name[0..len];
}
pub fn findMemoryTypeIndex(self: GraphicsContext, memory_type_bits: u32, flags: vk.MemoryPropertyFlags) !u32 {
for (self.mem_props.memory_types[0 .. self.mem_props.memory_type_count]) |mem_type, i| {
for (self.mem_props.memory_types[0..self.mem_props.memory_type_count]) |mem_type, i| {
if (memory_type_bits & (@as(u32, 1) << @truncate(u5, i)) != 0 and mem_type.property_flags.contains(flags)) {
return @truncate(u32, i);
}
@@ -184,7 +179,7 @@ pub const Queue = struct {
}
};
fn createSurface(vki: InstanceDispatch, instance: vk.Instance, window: *c.GLFWwindow) !vk.SurfaceKHR {
fn createSurface(instance: vk.Instance, window: *c.GLFWwindow) !vk.SurfaceKHR {
var surface: vk.SurfaceKHR = undefined;
if (c.glfwCreateWindowSurface(instance, window, null, &surface) != .success) {
return error.SurfaceInitFailed;
@@ -207,7 +202,7 @@ fn initializeCandidate(vki: InstanceDispatch, candidate: DeviceCandidate) !vk.De
.queue_family_index = candidate.queues.present_family,
.queue_count = 1,
.p_queue_priorities = &priority,
}
},
};
const queue_count: u32 = if (candidate.queues.graphics_family == candidate.queues.present_family)
@@ -281,19 +276,14 @@ fn checkSuitable(
return DeviceCandidate{
.pdev = pdev,
.props = props,
.queues = allocation
.queues = allocation,
};
}
return null;
}
fn allocateQueues(
vki: InstanceDispatch,
pdev: vk.PhysicalDevice,
allocator: *Allocator,
surface: vk.SurfaceKHR
) !?QueueAllocation {
fn allocateQueues(vki: InstanceDispatch, pdev: vk.PhysicalDevice, allocator: *Allocator, surface: vk.SurfaceKHR) !?QueueAllocation {
var family_count: u32 = undefined;
vki.getPhysicalDeviceQueueFamilyProperties(pdev, &family_count, null);
@@ -307,7 +297,7 @@ fn allocateQueues(
for (families) |properties, i| {
const family = @intCast(u32, i);
if (graphics_family == null and properties.queue_flags.contains(.{.graphics_bit = true})) {
if (graphics_family == null and properties.queue_flags.graphics_bit) {
graphics_family = family;
}
@@ -319,7 +309,7 @@ fn allocateQueues(
if (graphics_family != null and present_family != null) {
return QueueAllocation{
.graphics_family = graphics_family.?,
.present_family = present_family.?
.present_family = present_family.?,
};
}
@@ -352,7 +342,7 @@ fn checkExtensionSupport(
for (required_device_extensions) |ext| {
for (propsv) |props| {
const len = std.mem.indexOfScalar(u8, &props.extension_name, 0).?;
const prop_ext_name = props.extension_name[0 .. len];
const prop_ext_name = props.extension_name[0..len];
if (std.mem.eql(u8, ext, prop_ext_name)) {
break;
}

View File

@@ -41,7 +41,7 @@ pub const Swapchain = struct {
}
const concurrent = gc.graphics_queue.family != gc.present_queue.family;
const qfi = [_]u32{gc.graphics_queue.family, gc.present_queue.family};
const qfi = [_]u32{ gc.graphics_queue.family, gc.present_queue.family };
const handle = try gc.vkd.createSwapchainKHR(gc.dev, .{
.flags = .{},
@@ -51,12 +51,12 @@ pub const Swapchain = struct {
.image_color_space = surface_format.color_space,
.image_extent = actual_extent,
.image_array_layers = 1,
.image_usage = .{.color_attachment_bit = true, .transfer_dst_bit = true},
.image_usage = .{ .color_attachment_bit = true, .transfer_dst_bit = true },
.image_sharing_mode = if (concurrent) .concurrent else .exclusive,
.queue_family_index_count = qfi.len,
.p_queue_family_indices = &qfi,
.pre_transform = caps.current_transform,
.composite_alpha = .{.opaque_bit_khr = true},
.composite_alpha = .{ .opaque_bit_khr = true },
.present_mode = present_mode,
.clipped = vk.TRUE,
.old_swapchain = old_handle,
@@ -71,7 +71,7 @@ pub const Swapchain = struct {
const swap_images = try initSwapchainImages(gc, handle, surface_format.format, allocator);
errdefer for (swap_images) |si| si.deinit(gc);
var next_image_acquired = try gc.vkd.createSemaphore(gc.dev, .{.flags = .{}}, null);
var next_image_acquired = try gc.vkd.createSemaphore(gc.dev, .{ .flags = .{} }, null);
errdefer gc.vkd.destroySemaphore(gc.dev, next_image_acquired, null);
const result = try gc.vkd.acquireNextImageKHR(gc.dev, handle, std.math.maxInt(u64), next_image_acquired, .null_handle);
@@ -147,7 +147,7 @@ pub const Swapchain = struct {
try self.gc.vkd.resetFences(self.gc.dev, 1, @ptrCast([*]const vk.Fence, &current.frame_fence));
// Step 2: Submit the command buffer
const wait_stage = [_]vk.PipelineStageFlags{.{.top_of_pipe_bit = true}};
const wait_stage = [_]vk.PipelineStageFlags{.{ .top_of_pipe_bit = true }};
try self.gc.vkd.queueSubmit(self.gc.graphics_queue.handle, 1, &[_]vk.SubmitInfo{.{
.wait_semaphore_count = 1,
.p_wait_semaphores = @ptrCast([*]const vk.Semaphore, &current.image_acquired),
@@ -201,9 +201,9 @@ const SwapImage = struct {
.image = image,
.view_type = .@"2d",
.format = format,
.components = .{.r = .identity, .g = .identity, .b = .identity, .a = .identity},
.components = .{ .r = .identity, .g = .identity, .b = .identity, .a = .identity },
.subresource_range = .{
.aspect_mask = .{.color_bit = true},
.aspect_mask = .{ .color_bit = true },
.base_mip_level = 0,
.level_count = 1,
.base_array_layer = 0,
@@ -212,13 +212,13 @@ const SwapImage = struct {
}, null);
errdefer gc.vkd.destroyImageView(gc.dev, view, null);
const image_acquired = try gc.vkd.createSemaphore(gc.dev, .{.flags = .{}}, null);
const image_acquired = try gc.vkd.createSemaphore(gc.dev, .{ .flags = .{} }, null);
errdefer gc.vkd.destroySemaphore(gc.dev, image_acquired, null);
const render_finished = try gc.vkd.createSemaphore(gc.dev, .{.flags = .{}}, null);
const render_finished = try gc.vkd.createSemaphore(gc.dev, .{ .flags = .{} }, null);
errdefer gc.vkd.destroySemaphore(gc.dev, image_acquired, null);
const frame_fence = try gc.vkd.createFence(gc.dev, .{.flags = .{.signaled_bit = true}}, null);
const frame_fence = try gc.vkd.createFence(gc.dev, .{ .flags = .{ .signaled_bit = true } }, null);
errdefer gc.vkd.destroyFence(gc.dev, frame_fence, null);
return SwapImage{
@@ -251,10 +251,10 @@ fn initSwapchainImages(gc: *const GraphicsContext, swapchain: vk.SwapchainKHR, f
_ = try gc.vkd.getSwapchainImagesKHR(gc.dev, swapchain, &count, images.ptr);
const swap_images = try allocator.alloc(SwapImage, count);
errdefer allocator.free(images);
errdefer allocator.free(swap_images);
var i: usize = 0;
errdefer for (swap_images[0 .. i]) |si| si.deinit(gc);
errdefer for (swap_images[0..i]) |si| si.deinit(gc);
for (images) |image| {
swap_images[i] = try SwapImage.init(gc, image, format);

View File

@@ -35,16 +35,16 @@ const Vertex = struct {
};
const vertices = [_]Vertex{
.{.pos = .{0, -0.5}, .color = .{1, 0, 0}},
.{.pos = .{0.5, 0.5}, .color = .{0, 1, 0}},
.{.pos = .{-0.5, 0.5}, .color = .{0, 0, 1}},
.{ .pos = .{ 0, -0.5 }, .color = .{ 1, 0, 0 } },
.{ .pos = .{ 0.5, 0.5 }, .color = .{ 0, 1, 0 } },
.{ .pos = .{ -0.5, 0.5 }, .color = .{ 0, 0, 1 } },
};
pub fn main() !void {
if (c.glfwInit() != c.GLFW_TRUE) return error.GlfwInitFailed;
defer c.glfwTerminate();
var extent = vk.Extent2D{.width = 800, .height = 600};
var extent = vk.Extent2D{ .width = 800, .height = 600 };
c.glfwWindowHint(c.GLFW_CLIENT_API, c.GLFW_NO_API);
const window = c.glfwCreateWindow(
@@ -52,7 +52,7 @@ pub fn main() !void {
@intCast(c_int, extent.height),
app_name,
null,
null
null,
) orelse return error.WindowInitFailed;
defer c.glfwDestroyWindow(window);
@@ -61,7 +61,7 @@ pub fn main() !void {
const gc = try GraphicsContext.init(allocator, app_name, window);
defer gc.deinit();
std.debug.print("Using device: {s}\n", .{ gc.deviceName() });
std.debug.print("Using device: {s}\n", .{gc.deviceName()});
var swapchain = try Swapchain.init(&gc, allocator, extent);
defer swapchain.deinit();
@@ -78,7 +78,7 @@ pub fn main() !void {
const render_pass = try createRenderPass(&gc, swapchain);
defer gc.vkd.destroyRenderPass(gc.dev, render_pass, null);
var pipeline = try createPipeline(&gc, extent, pipeline_layout, render_pass);
var pipeline = try createPipeline(&gc, pipeline_layout, render_pass);
defer gc.vkd.destroyPipeline(gc.dev, pipeline, null);
var framebuffers = try createFramebuffers(&gc, allocator, render_pass, swapchain);
@@ -93,18 +93,18 @@ pub fn main() !void {
const buffer = try gc.vkd.createBuffer(gc.dev, .{
.flags = .{},
.size = @sizeOf(@TypeOf(vertices)),
.usage = .{.transfer_dst_bit = true, .vertex_buffer_bit = true},
.usage = .{ .transfer_dst_bit = true, .vertex_buffer_bit = true },
.sharing_mode = .exclusive,
.queue_family_index_count = 0,
.p_queue_family_indices = undefined,
}, null);
defer gc.vkd.destroyBuffer(gc.dev, buffer, null);
const mem_reqs = gc.vkd.getBufferMemoryRequirements(gc.dev, buffer);
const memory = try gc.allocate(mem_reqs, .{.device_local_bit = true});
const memory = try gc.allocate(mem_reqs, .{ .device_local_bit = true });
defer gc.vkd.freeMemory(gc.dev, memory, null);
try gc.vkd.bindBufferMemory(gc.dev, buffer, memory, 0);
try uploadVertices(&gc, pool, buffer, memory);
try uploadVertices(&gc, pool, buffer);
var cmdbufs = try createCommandBuffers(
&gc,
@@ -114,7 +114,7 @@ pub fn main() !void {
swapchain.extent,
render_pass,
pipeline,
framebuffers
framebuffers,
);
defer destroyCommandBuffers(&gc, pool, allocator, cmdbufs);
@@ -146,30 +146,28 @@ pub fn main() !void {
swapchain.extent,
render_pass,
pipeline,
framebuffers
framebuffers,
);
}
c.glfwSwapBuffers(window);
c.glfwPollEvents();
}
try swapchain.waitForAllFences();
}
fn uploadVertices(gc: *const GraphicsContext, pool: vk.CommandPool, buffer: vk.Buffer, memory: vk.DeviceMemory) !void {
fn uploadVertices(gc: *const GraphicsContext, pool: vk.CommandPool, buffer: vk.Buffer) !void {
const staging_buffer = try gc.vkd.createBuffer(gc.dev, .{
.flags = .{},
.size = @sizeOf(@TypeOf(vertices)),
.usage = .{.transfer_src_bit = true},
.usage = .{ .transfer_src_bit = true },
.sharing_mode = .exclusive,
.queue_family_index_count = 0,
.p_queue_family_indices = undefined,
}, null);
defer gc.vkd.destroyBuffer(gc.dev, staging_buffer, null);
const mem_reqs = gc.vkd.getBufferMemoryRequirements(gc.dev, staging_buffer);
const staging_memory = try gc.allocate(mem_reqs, .{.host_visible_bit = true, .host_coherent_bit = true});
const staging_memory = try gc.allocate(mem_reqs, .{ .host_visible_bit = true, .host_coherent_bit = true });
defer gc.vkd.freeMemory(gc.dev, staging_memory, null);
try gc.vkd.bindBufferMemory(gc.dev, staging_buffer, staging_memory, 0);
@@ -196,7 +194,7 @@ fn copyBuffer(gc: *const GraphicsContext, pool: vk.CommandPool, dst: vk.Buffer,
defer gc.vkd.freeCommandBuffers(gc.dev, pool, 1, @ptrCast([*]const vk.CommandBuffer, &cmdbuf));
try gc.vkd.beginCommandBuffer(cmdbuf, .{
.flags = .{.one_time_submit_bit = true},
.flags = .{ .one_time_submit_bit = true },
.p_inheritance_info = null,
});
@@ -243,7 +241,7 @@ fn createCommandBuffers(
errdefer gc.vkd.freeCommandBuffers(gc.dev, pool, @truncate(u32, cmdbufs.len), cmdbufs.ptr);
const clear = vk.ClearValue{
.color = .{.float_32 = .{0, 0, 0, 1}},
.color = .{ .float_32 = .{ 0, 0, 0, 1 } },
};
const viewport = vk.Viewport{
@@ -256,7 +254,7 @@ fn createCommandBuffers(
};
const scissor = vk.Rect2D{
.offset = .{.x = 0, .y = 0},
.offset = .{ .x = 0, .y = 0 },
.extent = extent,
};
@@ -273,7 +271,7 @@ fn createCommandBuffers(
.render_pass = render_pass,
.framebuffer = framebuffers[i],
.render_area = .{
.offset = .{.x = 0, .y = 0},
.offset = .{ .x = 0, .y = 0 },
.extent = extent,
},
.clear_value_count = 1,
@@ -297,17 +295,12 @@ fn destroyCommandBuffers(gc: *const GraphicsContext, pool: vk.CommandPool, alloc
allocator.free(cmdbufs);
}
fn createFramebuffers(
gc: *const GraphicsContext,
allocator: *Allocator,
render_pass: vk.RenderPass,
swapchain: Swapchain
) ![]vk.Framebuffer {
fn createFramebuffers(gc: *const GraphicsContext, allocator: *Allocator, render_pass: vk.RenderPass, swapchain: Swapchain) ![]vk.Framebuffer {
const framebuffers = try allocator.alloc(vk.Framebuffer, swapchain.swap_images.len);
errdefer allocator.free(framebuffers);
var i: usize = 0;
errdefer for (framebuffers[0 .. i]) |fb| gc.vkd.destroyFramebuffer(gc.dev, fb, null);
errdefer for (framebuffers[0..i]) |fb| gc.vkd.destroyFramebuffer(gc.dev, fb, null);
for (framebuffers) |*fb| {
fb.* = try gc.vkd.createFramebuffer(gc.dev, .{
@@ -334,7 +327,7 @@ fn createRenderPass(gc: *const GraphicsContext, swapchain: Swapchain) !vk.Render
const color_attachment = vk.AttachmentDescription{
.flags = .{},
.format = swapchain.surface_format.format,
.samples = .{.@"1_bit" = true},
.samples = .{ .@"1_bit" = true },
.load_op = .clear,
.store_op = .store,
.stencil_load_op = .dont_care,
@@ -374,7 +367,6 @@ fn createRenderPass(gc: *const GraphicsContext, swapchain: Swapchain) !vk.Render
fn createPipeline(
gc: *const GraphicsContext,
extent: vk.Extent2D,
layout: vk.PipelineLayout,
render_pass: vk.RenderPass,
) !vk.Pipeline {
@@ -395,14 +387,14 @@ fn createPipeline(
const pssci = [_]vk.PipelineShaderStageCreateInfo{
.{
.flags = .{},
.stage = .{.vertex_bit = true},
.stage = .{ .vertex_bit = true },
.module = vert,
.p_name = "main",
.p_specialization_info = null,
},
.{
.flags = .{},
.stage = .{.fragment_bit = true},
.stage = .{ .fragment_bit = true },
.module = frag,
.p_name = "main",
.p_specialization_info = null,
@@ -436,7 +428,7 @@ fn createPipeline(
.depth_clamp_enable = vk.FALSE,
.rasterizer_discard_enable = vk.FALSE,
.polygon_mode = .fill,
.cull_mode = .{.back_bit = true},
.cull_mode = .{ .back_bit = true },
.front_face = .clockwise,
.depth_bias_enable = vk.FALSE,
.depth_bias_constant_factor = 0,
@@ -447,7 +439,7 @@ fn createPipeline(
const pmsci = vk.PipelineMultisampleStateCreateInfo{
.flags = .{},
.rasterization_samples = .{.@"1_bit" = true},
.rasterization_samples = .{ .@"1_bit" = true },
.sample_shading_enable = vk.FALSE,
.min_sample_shading = 1,
.p_sample_mask = null,
@@ -463,7 +455,7 @@ fn createPipeline(
.src_alpha_blend_factor = .one,
.dst_alpha_blend_factor = .zero,
.alpha_blend_op = .add,
.color_write_mask = .{.r_bit = true, .g_bit = true, .b_bit = true, .a_bit = true},
.color_write_mask = .{ .r_bit = true, .g_bit = true, .b_bit = true, .a_bit = true },
};
const pcbsci = vk.PipelineColorBlendStateCreateInfo{
@@ -472,10 +464,10 @@ fn createPipeline(
.logic_op = .copy,
.attachment_count = 1,
.p_attachments = @ptrCast([*]const vk.PipelineColorBlendAttachmentState, &pcbas),
.blend_constants = [_]f32{0, 0, 0, 0},
.blend_constants = [_]f32{ 0, 0, 0, 0 },
};
const dynstate = [_]vk.DynamicState{.viewport, .scissor};
const dynstate = [_]vk.DynamicState{ .viewport, .scissor };
const pdsci = vk.PipelineDynamicStateCreateInfo{
.flags = .{},
.dynamic_state_count = dynstate.len,
@@ -506,7 +498,8 @@ fn createPipeline(
_ = try gc.vkd.createGraphicsPipelines(
gc.dev,
.null_handle,
1, @ptrCast([*]const vk.GraphicsPipelineCreateInfo, &gpci),
1,
@ptrCast([*]const vk.GraphicsPipelineCreateInfo, &gpci),
null,
@ptrCast([*]vk.Pipeline, &pipeline),
);

File diff suppressed because it is too large Load Diff

View File

@@ -49,7 +49,7 @@ pub const ShaderCompileStep = struct {
"shaders",
src,
}) catch unreachable;
self.shaders.append(.{.source_path = src, .full_out_path = full_out_path}) catch unreachable;
self.shaders.append(.{ .source_path = src, .full_out_path = full_out_path }) catch unreachable;
return full_out_path;
}

View File

@@ -2,6 +2,64 @@ const std = @import("std");
const mem = std.mem;
const Allocator = mem.Allocator;
pub fn isZigPrimitiveType(name: []const u8) bool {
if (name.len > 1 and (name[0] == 'u' or name[0] == 'i')) {
for (name[1..]) |c| {
switch (c) {
'0'...'9' => {},
else => return false,
}
}
return true;
}
const primitives = [_][]const u8{
"void",
"comptime_float",
"comptime_int",
"bool",
"isize",
"usize",
"f16",
"f32",
"f64",
"f128",
"c_longdouble",
"noreturn",
"type",
"anyerror",
"c_short",
"c_ushort",
"c_int",
"c_uint",
"c_long",
"c_ulong",
"c_longlong",
"c_ulonglong",
};
for (primitives) |reserved| {
if (mem.eql(u8, reserved, name)) {
return true;
}
}
return false;
}
fn needZigEscape(name: []const u8) bool {
return !std.zig.fmt.isValidId(name) or isZigPrimitiveType(name);
}
pub fn writeIdentifier(out: anytype, id: []const u8) !void {
// https://github.com/ziglang/zig/issues/2897
if (isZigPrimitiveType(id)) {
try out.print("{s}_", .{id});
} else {
try out.print("{}", .{std.zig.fmtId(id)});
}
}
pub const CaseStyle = enum {
snake,
screaming_snake,
@@ -51,7 +109,7 @@ pub const SegmentIterator = struct {
}
const end = self.nextBoundary();
const word = self.text[self.offset .. end];
const word = self.text[self.offset..end];
self.offset = end;
return word;
}
@@ -128,7 +186,7 @@ pub const IdRenderer = struct {
}
lower_first = false;
for (segment[i + 1..]) |c| {
for (segment[i + 1 ..]) |c| {
try self.text_cache.append(std.ascii.toLower(c));
}
}
@@ -138,14 +196,10 @@ pub const IdRenderer = struct {
}
}
pub fn render(self: IdRenderer, out: anytype, id: []const u8) !void {
try out.print("{z}", .{ id });
}
pub fn renderFmt(self: *IdRenderer, out: anytype, comptime fmt: []const u8, args: anytype) !void {
self.text_cache.items.len = 0;
try std.fmt.format(self.text_cache.writer(), fmt, args);
try out.print("{z}", .{ self.text_cache.items });
try writeIdentifier(out, self.text_cache.items);
}
pub fn renderWithCase(self: *IdRenderer, out: anytype, case_style: CaseStyle, id: []const u8) !void {
@@ -162,7 +216,7 @@ pub const IdRenderer = struct {
.camel => try self.renderCamel(false, adjusted_id, tag),
}
try out.print("{z}", .{ self.text_cache.items });
try writeIdentifier(out, self.text_cache.items);
}
pub fn getAuthorTag(self: IdRenderer, id: []const u8) ?[]const u8 {

View File

@@ -1,6 +1,5 @@
pub const generateVk = @import("vulkan/generator.zig").generate;
pub const VkGenerateStep = @import("vulkan/build_integration.zig").GenerateStep;
pub const generateSpirv = @import("spirv/generator.zig").generate;
pub const ShaderCompileStep = @import("build_integration.zig").ShaderCompileStep;
test "main" {

View File

@@ -5,7 +5,6 @@ const usage = "Usage: {s} [-h|--help] <spec xml path> <output zig source>\n";
pub fn main() !void {
const stderr = std.io.getStdErr();
const stdout = std.io.getStdOut();
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
@@ -32,7 +31,7 @@ pub fn main() !void {
\\
\\
++ usage,
.{ prog_name },
.{prog_name},
);
return;
} else if (maybe_xml_path == null) {
@@ -40,17 +39,17 @@ pub fn main() !void {
} else if (maybe_out_path == null) {
maybe_out_path = arg;
} else {
try stderr.writer().print("Error: Superficial argument '{s}'\n", .{ arg });
try stderr.writer().print("Error: Superficial argument '{s}'\n", .{arg});
}
}
const xml_path = maybe_xml_path orelse {
try stderr.writer().print("Error: Missing required argument <spec xml path>\n" ++ usage, .{ prog_name });
try stderr.writer().print("Error: Missing required argument <spec xml path>\n" ++ usage, .{prog_name});
return;
};
const out_path = maybe_out_path orelse {
try stderr.writer().print("Error: Missing required argument <output zig source>\n" ++ usage, .{ prog_name });
try stderr.writer().print("Error: Missing required argument <output zig source>\n" ++ usage, .{prog_name});
return;
};
@@ -60,15 +59,22 @@ pub fn main() !void {
return;
};
const out_file = cwd.createFile(out_path, .{}) catch |err| {
try stderr.writer().print("Error: Failed to create output file '{s}' ({s})\n", .{ out_path, @errorName(err) });
return;
};
defer out_file.close();
var out_buffer = std.ArrayList(u8).init(allocator);
try generate(allocator, xml_src, out_buffer.writer());
const tree = try std.zig.parse(allocator, out_buffer.items);
_ = try std.zig.render(allocator, out_file.writer(), tree);
const tree = try std.zig.parse(allocator, out_buffer.items);
const formatted = try tree.render(allocator);
defer allocator.free(formatted);
if (std.fs.path.dirname(out_path)) |dir| {
cwd.makePath(dir) catch |err| {
try stderr.writer().print("Error: Failed to create output directory '{s}' ({s})\n", .{ dir, @errorName(err) });
return;
};
}
cwd.writeFile(out_path, formatted) catch |err| {
try stderr.writer().print("Error: Failed to write to output file '{s}' ({s})\n", .{ out_path, @errorName(err) });
return;
};
}

View File

@@ -52,7 +52,7 @@ pub const GenerateStep = struct {
pub fn initFromSdk(builder: *Builder, sdk_path: []const u8, out_path: []const u8) *GenerateStep {
const spec_path = std.fs.path.join(
builder.allocator,
&[_][]const u8{sdk_path, "share/vulkan/registry/vk.xml"},
&[_][]const u8{ sdk_path, "share/vulkan/registry/vk.xml" },
) catch unreachable;
return init(builder, spec_path, out_path);
@@ -65,16 +65,19 @@ pub const GenerateStep = struct {
fn make(step: *Step) !void {
const self = @fieldParentPtr(GenerateStep, "step", step);
const cwd = std.fs.cwd();
var out_buffer = std.ArrayList(u8).init(self.builder.allocator);
const spec = try cwd.readFileAlloc(self.builder.allocator, self.spec_path, std.math.maxInt(usize));
var out_buffer = std.ArrayList(u8).init(self.builder.allocator);
try generate(self.builder.allocator, spec, out_buffer.writer());
const tree = try std.zig.parse(self.builder.allocator, out_buffer.items);
std.debug.assert(tree.errors.len == 0); // If this triggers, vulkan-zig produced invalid code.
var formatted = try tree.render(self.builder.allocator);
const dir = path.dirname(self.package.path).?;
try cwd.makePath(dir);
const output_file = cwd.createFile(self.package.path, .{}) catch unreachable;
defer output_file.close();
_ = try std.zig.render(self.builder.allocator, output_file.writer(), tree);
try cwd.writeFile(self.package.path, formatted);
}
};

View File

@@ -70,7 +70,7 @@ pub const CTokenizer = struct {
}
}
const token_text = self.source[start .. self.offset];
const token_text = self.source[start..self.offset];
const kind = if (mem.eql(u8, token_text, "typedef"))
Token.Kind.kw_typedef
@@ -83,7 +83,7 @@ pub const CTokenizer = struct {
else
Token.Kind.id;
return .{.kind = kind, .text = token_text};
return .{ .kind = kind, .text = token_text };
}
fn int(self: *CTokenizer) Token {
@@ -100,7 +100,7 @@ pub const CTokenizer = struct {
return .{
.kind = .int,
.text = self.source[start .. self.offset],
.text = self.source[start..self.offset],
};
}
@@ -115,7 +115,7 @@ pub const CTokenizer = struct {
pub fn next(self: *CTokenizer) !?Token {
self.skipws();
if (mem.startsWith(u8, self.source[self.offset ..], "//") or self.in_comment) {
if (mem.startsWith(u8, self.source[self.offset..], "//") or self.in_comment) {
const end = mem.indexOfScalarPos(u8, self.source, self.offset, '\n') orelse {
self.offset = self.source.len;
self.in_comment = true;
@@ -143,15 +143,12 @@ pub const CTokenizer = struct {
']' => kind = .rbracket,
'(' => kind = .lparen,
')' => kind = .rparen,
else => return error.UnexpectedCharacter
else => return error.UnexpectedCharacter,
}
const start = self.offset;
_ = self.consumeNoEof();
return Token{
.kind = kind,
.text = self.source[start .. self.offset]
};
return Token{ .kind = kind, .text = self.source[start..self.offset] };
}
};
@@ -173,11 +170,11 @@ pub const XmlCTokenizer = struct {
const text = elem.children.items[0].CharData;
if (mem.eql(u8, elem.tag, "type")) {
return Token{.kind = .type_name, .text = text};
return Token{ .kind = .type_name, .text = text };
} else if (mem.eql(u8, elem.tag, "enum")) {
return Token{.kind = .enum_name, .text = text};
return Token{ .kind = .enum_name, .text = text };
} 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 {
@@ -206,7 +203,7 @@ pub const XmlCTokenizer = struct {
if (self.it.next()) |child| {
switch (child.*) {
.CharData => |cdata| self.ctok = CTokenizer{.source = cdata, .in_comment = in_comment},
.CharData => |cdata| self.ctok = CTokenizer{ .source = cdata, .in_comment = in_comment },
.Comment => {}, // xml comment
.Element => |elem| if (!in_comment) if (try elemToToken(elem)) |tok| return tok,
}
@@ -254,14 +251,14 @@ pub fn parseTypedef(allocator: *Allocator, xctok: *XmlCTokenizer) !registry.Decl
return registry.Declaration{
.name = decl.name orelse return error.MissingTypeIdentifier,
.decl_type = .{.typedef = decl.decl_type},
.decl_type = .{ .typedef = decl.decl_type },
};
}
// MEMBER = DECLARATION (':' int)?
pub fn parseMember(allocator: *Allocator, xctok: *XmlCTokenizer) !registry.Container.Field {
const decl = try parseDeclaration(allocator, xctok);
var field = registry.Container.Field {
var field = registry.Container.Field{
.name = decl.name orelse return error.MissingTypeIdentifier,
.field_type = decl.decl_type,
.bits = null,
@@ -294,7 +291,7 @@ pub fn parseParamOrProto(allocator: *Allocator, xctok: *XmlCTokenizer) !registry
}
return registry.Declaration{
.name = decl.name orelse return error.MissingTypeIdentifier,
.decl_type = .{.typedef = decl.decl_type},
.decl_type = .{ .typedef = decl.decl_type },
};
}
@@ -333,7 +330,7 @@ fn parseDeclaration(allocator: *Allocator, xctok: *XmlCTokenizer) ParseError!Dec
if (tok.kind != .type_name and tok.kind != .id) return error.InvalidSyntax;
const type_name = tok.text;
var type_info = TypeInfo{.name = type_name};
var type_info = TypeInfo{ .name = type_name };
// Parse pointers
type_info = try parsePointers(allocator, xctok, inner_is_const, type_info);
@@ -365,7 +362,7 @@ fn parseDeclaration(allocator: *Allocator, xctok: *XmlCTokenizer) ParseError!Dec
.array = .{
.size = array_size,
.child = child,
}
},
};
// update the inner_type pointer so it points to the proper
@@ -403,8 +400,8 @@ fn parseFnPtrSuffix(allocator: *Allocator, xctok: *XmlCTokenizer, return_type: T
.return_type = return_type_heap,
.success_codes = &[_][]const u8{},
.error_codes = &[_][]const u8{},
}
}
},
},
};
const first_param = try parseDeclaration(allocator, xctok);
@@ -500,31 +497,31 @@ fn parseArrayDeclarator(xctok: *XmlCTokenizer) !?ArraySize {
.int = std.fmt.parseInt(usize, size_tok.text, 10) catch |err| switch (err) {
error.Overflow => return error.Overflow,
error.InvalidCharacter => unreachable,
}
},
.enum_name => .{.alias = size_tok.text},
else => return error.InvalidSyntax
},
.enum_name => .{ .alias = size_tok.text },
else => return error.InvalidSyntax,
};
_ = try xctok.expect(.rbracket);
return size;
}
pub fn parseVersion(xctok: *XmlCTokenizer) ![3][]const u8 {
pub fn parseVersion(xctok: *XmlCTokenizer) ![4][]const u8 {
_ = try xctok.expect(.hash);
const define = try xctok.expect(.id);
if (!mem.eql(u8, define.text, "define")) {
return error.InvalidVersion;
}
const name = try xctok.expect(.name);
_ = try xctok.expect(.name);
const vk_make_version = try xctok.expect(.type_name);
if (!mem.eql(u8, vk_make_version.text, "VK_MAKE_VERSION")) {
if (!mem.eql(u8, vk_make_version.text, "VK_MAKE_API_VERSION")) {
return error.NotVersion;
}
_ = try xctok.expect(.lparen);
var version: [3][]const u8 = undefined;
var version: [4][]const u8 = undefined;
for (version) |*part, i| {
if (i != 0) {
_ = try xctok.expect(.comma);
@@ -540,44 +537,38 @@ pub fn parseVersion(xctok: *XmlCTokenizer) ![3][]const u8 {
return version;
}
fn testTokenizer(tokenizer: anytype, expected_tokens: []const Token) void {
fn testTokenizer(tokenizer: anytype, expected_tokens: []const Token) !void {
for (expected_tokens) |expected| {
const tok = (tokenizer.next() catch unreachable).?;
testing.expectEqual(expected.kind, tok.kind);
testing.expectEqualSlices(u8, expected.text, tok.text);
try testing.expectEqual(expected.kind, tok.kind);
try testing.expectEqualSlices(u8, expected.text, tok.text);
}
if (tokenizer.next() catch unreachable) |_| unreachable;
}
test "CTokenizer" {
var ctok = CTokenizer {
.source = \\typedef ([const)]** VKAPI_PTR 123,;aaaa
};
var ctok = CTokenizer{ .source = "typedef ([const)]** VKAPI_PTR 123,;aaaa" };
testTokenizer(
&ctok,
&[_]Token{
.{.kind = .kw_typedef, .text = "typedef"},
.{.kind = .lparen, .text = "("},
.{.kind = .lbracket, .text = "["},
.{.kind = .kw_const, .text = "const"},
.{.kind = .rparen, .text = ")"},
.{.kind = .rbracket, .text = "]"},
.{.kind = .star, .text = "*"},
.{.kind = .star, .text = "*"},
.{.kind = .kw_vkapi_ptr, .text = "VKAPI_PTR"},
.{.kind = .int, .text = "123"},
.{.kind = .comma, .text = ","},
.{.kind = .semicolon, .text = ";"},
.{.kind = .id, .text = "aaaa"},
}
);
try testTokenizer(&ctok, &[_]Token{
.{ .kind = .kw_typedef, .text = "typedef" },
.{ .kind = .lparen, .text = "(" },
.{ .kind = .lbracket, .text = "[" },
.{ .kind = .kw_const, .text = "const" },
.{ .kind = .rparen, .text = ")" },
.{ .kind = .rbracket, .text = "]" },
.{ .kind = .star, .text = "*" },
.{ .kind = .star, .text = "*" },
.{ .kind = .kw_vkapi_ptr, .text = "VKAPI_PTR" },
.{ .kind = .int, .text = "123" },
.{ .kind = .comma, .text = "," },
.{ .kind = .semicolon, .text = ";" },
.{ .kind = .id, .text = "aaaa" },
});
}
test "XmlCTokenizer" {
const document = try xml.parse(
testing.allocator,
const document = try xml.parse(testing.allocator,
\\<root>// comment <name>commented name</name> <type>commented type</type> trailing
\\ typedef void (VKAPI_PTR *<name>PFN_vkVoidFunction</name>)(void);
\\</root>
@@ -586,27 +577,23 @@ test "XmlCTokenizer" {
var xctok = XmlCTokenizer.init(document.root);
testTokenizer(
&xctok,
&[_]Token{
.{.kind = .kw_typedef, .text = "typedef"},
.{.kind = .id, .text = "void"},
.{.kind = .lparen, .text = "("},
.{.kind = .kw_vkapi_ptr, .text = "VKAPI_PTR"},
.{.kind = .star, .text = "*"},
.{.kind = .name, .text = "PFN_vkVoidFunction"},
.{.kind = .rparen, .text = ")"},
.{.kind = .lparen, .text = "("},
.{.kind = .id, .text = "void"},
.{.kind = .rparen, .text = ")"},
.{.kind = .semicolon, .text = ";"},
}
);
try testTokenizer(&xctok, &[_]Token{
.{ .kind = .kw_typedef, .text = "typedef" },
.{ .kind = .id, .text = "void" },
.{ .kind = .lparen, .text = "(" },
.{ .kind = .kw_vkapi_ptr, .text = "VKAPI_PTR" },
.{ .kind = .star, .text = "*" },
.{ .kind = .name, .text = "PFN_vkVoidFunction" },
.{ .kind = .rparen, .text = ")" },
.{ .kind = .lparen, .text = "(" },
.{ .kind = .id, .text = "void" },
.{ .kind = .rparen, .text = ")" },
.{ .kind = .semicolon, .text = ";" },
});
}
test "parseTypedef" {
const document = try xml.parse(
testing.allocator,
const document = try xml.parse(testing.allocator,
\\<root> // comment <name>commented name</name> trailing
\\ typedef const struct <type>Python</type>* pythons[4];
\\ // more comments
@@ -621,10 +608,10 @@ test "parseTypedef" {
var xctok = XmlCTokenizer.init(document.root);
const decl = try parseTypedef(&arena.allocator, &xctok);
testing.expectEqualSlices(u8, "pythons", decl.name);
try testing.expectEqualSlices(u8, "pythons", decl.name);
const array = decl.decl_type.typedef.array;
testing.expectEqual(ArraySize{.int = 4}, array.size);
try testing.expectEqual(ArraySize{ .int = 4 }, array.size);
const ptr = array.child.pointer;
testing.expectEqual(true, ptr.is_const);
testing.expectEqualSlices(u8, "Python", ptr.child.name);
try testing.expectEqual(true, ptr.is_const);
try testing.expectEqualSlices(u8, "Python", ptr.child.name);
}

View File

@@ -9,41 +9,30 @@ const Allocator = mem.Allocator;
const FeatureLevel = reg.FeatureLevel;
const EnumFieldMerger = struct {
const EnumExtensionMap = std.StringArrayHashMap(std.ArrayListUnmanaged(reg.Enum.Field));
const FieldSet = std.StringArrayHashMap(void);
const EnumExtensionMap = std.StringArrayHashMapUnmanaged(std.ArrayListUnmanaged(reg.Enum.Field));
const FieldSet = std.StringArrayHashMapUnmanaged(void);
gpa: *Allocator,
reg_arena: *Allocator,
arena: *Allocator,
registry: *reg.Registry,
enum_extensions: EnumExtensionMap,
field_set: FieldSet,
fn init(gpa: *Allocator, reg_arena: *Allocator, registry: *reg.Registry) EnumFieldMerger {
fn init(arena: *Allocator, registry: *reg.Registry) EnumFieldMerger {
return .{
.gpa = gpa,
.reg_arena = reg_arena,
.arena = arena,
.registry = registry,
.enum_extensions = EnumExtensionMap.init(gpa),
.field_set = FieldSet.init(gpa),
.enum_extensions = .{},
.field_set = .{},
};
}
fn deinit(self: *EnumFieldMerger) void {
for (self.enum_extensions.items()) |*entry| {
entry.value.deinit(self.gpa);
}
self.field_set.deinit();
self.enum_extensions.deinit();
}
fn putEnumExtension(self: *EnumFieldMerger, enum_name: []const u8, field: reg.Enum.Field) !void {
const res = try self.enum_extensions.getOrPut(enum_name);
const res = try self.enum_extensions.getOrPut(self.arena, enum_name);
if (!res.found_existing) {
res.entry.value = std.ArrayListUnmanaged(reg.Enum.Field){};
res.value_ptr.* = std.ArrayListUnmanaged(reg.Enum.Field){};
}
try res.entry.value.append(self.gpa, field);
try res.value_ptr.append(self.arena, field);
}
fn addRequires(self: *EnumFieldMerger, reqs: []const reg.Require) !void {
@@ -61,11 +50,11 @@ const EnumFieldMerger = struct {
self.field_set.clearRetainingCapacity();
const n_fields_upper_bound = base_enum.fields.len + extensions.items.len;
const new_fields = try self.reg_arena.alloc(reg.Enum.Field, n_fields_upper_bound);
const new_fields = try self.arena.alloc(reg.Enum.Field, n_fields_upper_bound);
var i: usize = 0;
for (base_enum.fields) |field| {
const res = try self.field_set.getOrPut(field.name);
const res = try self.field_set.getOrPut(self.arena, field.name);
if (!res.found_existing) {
new_fields[i] = field;
i += 1;
@@ -74,16 +63,16 @@ const EnumFieldMerger = struct {
// Assume that if a field name clobbers, the value is the same
for (extensions.items) |field| {
const res = try self.field_set.getOrPut(field.name);
const res = try self.field_set.getOrPut(self.arena, field.name);
if (!res.found_existing) {
new_fields[i] = field;
i += 1;
}
}
// Existing base_enum.fields was allocatued by `self.reg_arena`, so
// Existing base_enum.fields was allocated by `self.arena`, so
// it gets cleaned up whenever that is deinited.
base_enum.fields = self.reg_arena.shrink(new_fields, i);
base_enum.fields = self.arena.shrink(new_fields, i);
}
fn merge(self: *EnumFieldMerger) !void {
@@ -105,131 +94,8 @@ const EnumFieldMerger = struct {
}
};
const TagFixerUpper = struct {
allocator: *Allocator,
registry: *reg.Registry,
names: std.StringHashMap(void),
id_renderer: *const IdRenderer,
fn init(allocator: *Allocator, registry: *reg.Registry, id_renderer: *const IdRenderer) TagFixerUpper {
return .{
.allocator = allocator,
.registry = registry,
.names = std.StringHashMap(void).init(allocator),
.id_renderer = id_renderer,
};
}
fn deinit(self: *TagFixerUpper) void {
self.names.deinit();
}
fn insertName(self: *TagFixerUpper, name: []const u8) !void {
const tagless = self.id_renderer.stripAuthorTag(name);
const result = try self.names.getOrPut(name);
if (result.found_existing) {
return error.DuplicateDefinition;
}
}
fn extractNames(self: *TagFixerUpper) !void {
for (self.registry.decls) |decl| {
try self.insertName(decl.name);
switch (decl.decl_type) {
.enumeration => |enumeration| {
for (enumeration.fields) |field| {
try self.insertName(field.name);
}
},
else => {},
}
}
}
fn fixAlias(self: *TagFixerUpper, name: *[]const u8) !void {
if (self.names.contains(name.*)) {
// The alias exists, everything is fine
return;
}
// The alias does not exist, check if the tagless version exists
const tagless = self.id_renderer.stripAuthorTag(name.*);
if (self.names.contains(tagless)) {
// Fix up the name to the tagless version
name.* = tagless;
return;
}
// Neither original nor tagless version exists
return error.InvalidRegistry;
}
fn fixCommand(self: *TagFixerUpper, command: *reg.Command) !void {
for (command.params) |*param| {
try self.fixTypeInfo(&param.param_type);
}
try self.fixTypeInfo(command.return_type);
for (command.success_codes) |*code| {
try self.fixAlias(code);
}
for (command.error_codes) |*code| {
try self.fixAlias(code);
}
}
fn fixTypeInfo(self: *TagFixerUpper, type_info: *reg.TypeInfo) error{InvalidRegistry}!void {
switch (type_info.*) {
.name => |*name| try self.fixAlias(name),
.command_ptr => |*command| try self.fixCommand(command),
.pointer => |ptr| try self.fixTypeInfo(ptr.child),
.array => |arr| try self.fixTypeInfo(arr.child),
}
}
fn fixNames(self: *TagFixerUpper) !void {
for (self.registry.decls) |*decl| {
switch (decl.decl_type) {
.container => |*container| {
for (container.fields) |*field| {
try self.fixTypeInfo(&field.field_type);
}
},
.enumeration => |*enumeration| {
for (enumeration.fields) |*field| {
if (field.value == .alias) {
try self.fixAlias(&field.value.alias.name);
}
}
},
.bitmask => |*bitmask| {
if (bitmask.bits_enum) |*bits| {
try self.fixAlias(bits);
}
},
.command => |*command| try self.fixCommand(command),
.alias => |*alias| try self.fixAlias(&alias.name),
.typedef => |*type_info| try self.fixTypeInfo(type_info),
else => {},
}
}
}
fn fixup(self: *TagFixerUpper) !void {
// Extract all non-aliases
try self.extractNames();
// Fix aliases
try self.fixNames();
}
};
pub const Generator = struct {
gpa: *Allocator,
reg_arena: std.heap.ArenaAllocator,
arena: std.heap.ArenaAllocator,
registry: reg.Registry,
id_renderer: IdRenderer,
@@ -240,27 +106,14 @@ pub const Generator = struct {
for (tags) |*tag, i| tag.* = result.registry.tags[i].name;
return Generator{
.gpa = allocator,
.reg_arena = result.arena,
.arena = result.arena,
.registry = result.registry,
.id_renderer = IdRenderer.init(allocator, tags),
};
}
fn deinit(self: Generator) void {
self.gpa.free(self.id_renderer.tags);
self.reg_arena.deinit();
}
fn removePromotedExtensions(self: *Generator) void {
var write_index: usize = 0;
for (self.registry.extensions) |ext| {
if (ext.promoted_to == .none) {
self.registry.extensions[write_index] = ext;
write_index += 1;
}
}
self.registry.extensions.len = write_index;
self.arena.deinit();
}
fn stripFlagBits(self: Generator, name: []const u8) []const u8 {
@@ -273,49 +126,47 @@ pub const Generator = struct {
return tagless[0 .. tagless.len - "Flags".len];
}
fn fixupBitmasks(self: *Generator) !void {
var bits = std.StringHashMap([]const u8).init(self.gpa);
defer bits.deinit();
for (self.registry.decls) |decl| {
if (decl.decl_type == .enumeration and decl.decl_type.enumeration.is_bitmask) {
try bits.put(self.stripFlagBits(decl.name), decl.name);
}
}
for (self.registry.decls) |*decl| {
switch (decl.decl_type) {
.bitmask => |*bitmask| {
const base_name = self.stripFlags(decl.name);
if (bitmask.bits_enum) |bits_enum| {
if (bits.get(base_name) == null) {
bitmask.bits_enum = null;
}
} else if (bits.get(base_name)) |bits_enum| {
bitmask.bits_enum = bits_enum;
}
},
else => {}
}
}
}
// Solve `registry.declarations` according to `registry.extensions` and `registry.features`.
fn mergeEnumFields(self: *Generator) !void {
var merger = EnumFieldMerger.init(self.gpa, &self.reg_arena.allocator, &self.registry);
defer merger.deinit();
var merger = EnumFieldMerger.init(&self.arena.allocator, &self.registry);
try merger.merge();
}
fn fixupTags(self: *Generator) !void {
var fixer_upper = TagFixerUpper.init(self.gpa, &self.registry, &self.id_renderer);
defer fixer_upper.deinit();
try fixer_upper.fixup();
// https://github.com/KhronosGroup/Vulkan-Docs/pull/1556
fn fixupBitFlags(self: *Generator) !void {
var seen_bits = std.StringArrayHashMap(void).init(&self.arena.allocator);
defer seen_bits.deinit();
for (self.registry.decls) |decl| {
const bitmask = switch (decl.decl_type) {
.bitmask => |bm| bm,
else => continue,
};
if (bitmask.bits_enum) |bits_enum| {
try seen_bits.put(bits_enum, {});
}
}
var i: usize = 0;
for (self.registry.decls) |decl| {
switch (decl.decl_type) {
.enumeration => |e| {
if (e.is_bitmask and seen_bits.get(decl.name) == null)
continue;
},
else => {},
}
self.registry.decls[i] = decl;
i += 1;
}
self.registry.decls.len = i;
}
fn render(self: *Generator, writer: anytype) !void {
try renderRegistry(writer, &self.reg_arena.allocator, &self.registry, &self.id_renderer);
try renderRegistry(writer, &self.arena.allocator, &self.registry, &self.id_renderer);
}
};
@@ -330,9 +181,7 @@ pub fn generate(allocator: *Allocator, spec_xml: []const u8, writer: anytype) !v
var gen = try Generator.init(allocator, spec.root);
defer gen.deinit();
gen.removePromotedExtensions();
try gen.mergeEnumFields();
try gen.fixupBitmasks();
try gen.fixupTags();
try gen.fixupBitFlags();
try gen.render(writer);
}

View File

@@ -74,7 +74,7 @@ fn parseTypes(allocator: *Allocator, out: []registry.Declaration, types_elem: *x
} else if (mem.eql(u8, category, "funcpointer")) {
break :blk try parseFuncPointer(allocator, ty);
} else if (mem.eql(u8, category, "enum")) {
break :blk (try parseEnumAlias(allocator, ty)) orelse continue;
break :blk (try parseEnumAlias(ty)) orelse continue;
}
continue;
@@ -95,7 +95,7 @@ fn parseForeigntype(ty: *xml.Element) !registry.Declaration {
return registry.Declaration{
.name = name,
.decl_type = .{.foreign = .{.depends = depends}},
.decl_type = .{ .foreign = .{ .depends = depends } },
};
}
@@ -104,12 +104,27 @@ fn parseBitmaskType(ty: *xml.Element) !registry.Declaration {
const alias = ty.getAttribute("alias") orelse return error.InvalidRegistry;
return registry.Declaration{
.name = name,
.decl_type = .{.alias = .{.name = alias, .target = .other_type}},
.decl_type = .{ .alias = .{ .name = alias, .target = .other_type } },
};
} else {
const flags_type = ty.getCharData("type") orelse return error.InvalidRegistry;
const bitwidth: u8 = if (mem.eql(u8, flags_type, "VkFlags"))
32
else if (mem.eql(u8, flags_type, "VkFlags64"))
64
else
return error.InvalidRegistry;
return registry.Declaration{
.name = ty.getCharData("name") orelse return error.InvalidRegistry,
.decl_type = .{.bitmask = .{.bits_enum = ty.getAttribute("requires")}},
.decl_type = .{
.bitmask = .{
// Who knows why these are different fields
.bits_enum = ty.getAttribute("requires") orelse ty.getAttribute("bitvalues"),
.bitwidth = bitwidth,
},
},
};
}
}
@@ -120,7 +135,9 @@ fn parseHandleType(ty: *xml.Element) !registry.Declaration {
const alias = ty.getAttribute("alias") orelse return error.InvalidRegistry;
return registry.Declaration{
.name = name,
.decl_type = .{.alias = .{.name = alias, .target = .other_type}},
.decl_type = .{
.alias = .{ .name = alias, .target = .other_type },
},
};
} else {
const name = ty.getCharData("name") orelse return error.InvalidRegistry;
@@ -136,7 +153,7 @@ fn parseHandleType(ty: *xml.Element) !registry.Declaration {
.handle = .{
.parent = ty.getAttribute("parent"),
.is_dispatchable = dispatchable,
}
},
},
};
}
@@ -152,7 +169,7 @@ fn parseBaseType(allocator: *Allocator, ty: *xml.Element) !registry.Declaration
// macros, which is why this part is not built into the xml/c parser.
return registry.Declaration{
.name = name,
.decl_type = .{.external = {}},
.decl_type = .{ .external = {} },
};
}
}
@@ -163,7 +180,9 @@ fn parseContainer(allocator: *Allocator, ty: *xml.Element, is_union: bool) !regi
if (ty.getAttribute("alias")) |alias| {
return registry.Declaration{
.name = name,
.decl_type = .{.alias = .{.name = alias, .target = .other_type}},
.decl_type = .{
.alias = .{ .name = alias, .target = .other_type },
},
};
}
@@ -186,21 +205,34 @@ fn parseContainer(allocator: *Allocator, ty: *xml.Element, is_union: bool) !regi
members = allocator.shrink(members, i);
var maybe_extends: ?[][]const u8 = null;
if (ty.getAttribute("structextends")) |extends| {
const n_structs = std.mem.count(u8, extends, ",") + 1;
maybe_extends = try allocator.alloc([]const u8, n_structs);
var struct_extends = std.mem.split(extends, ",");
var j: usize = 0;
while (struct_extends.next()) |struct_extend| {
maybe_extends.?[j] = struct_extend;
j += 1;
}
}
it = ty.findChildrenByTag("member");
for (members) |*member| {
const member_elem = it.next().?;
try parsePointerMeta(.{.container = members}, &member.field_type, member_elem);
try parsePointerMeta(.{ .container = members }, &member.field_type, member_elem);
}
return registry.Declaration {
return registry.Declaration{
.name = name,
.decl_type = .{
.container = .{
.stype = maybe_stype,
.fields = members,
.is_union = is_union,
}
}
.extends = maybe_extends,
},
},
};
}
@@ -223,7 +255,7 @@ fn lenToPointerSize(fields: Fields, len: []const u8) registry.Pointer.PointerSiz
for (params) |*param| {
if (mem.eql(u8, param.name, len)) {
param.is_buffer_len = true;
return .{.other_field = param.name};
return .{ .other_field = param.name };
}
}
},
@@ -231,7 +263,7 @@ fn lenToPointerSize(fields: Fields, len: []const u8) registry.Pointer.PointerSiz
for (members) |*member| {
if (mem.eql(u8, member.name, len)) {
member.is_buffer_len = true;
return .{.other_field = member.name};
return .{ .other_field = member.name };
}
}
},
@@ -278,12 +310,14 @@ fn parsePointerMeta(fields: Fields, type_info: *registry.TypeInfo, elem: *xml.El
}
}
fn parseEnumAlias(allocator: *Allocator, elem: *xml.Element) !?registry.Declaration {
fn parseEnumAlias(elem: *xml.Element) !?registry.Declaration {
if (elem.getAttribute("alias")) |alias| {
const name = elem.getAttribute("name") orelse return error.InvalidRegistry;
return registry.Declaration{
.name = name,
.decl_type = .{.alias = .{.name = alias, .target = .other_type}},
.decl_type = .{
.alias = .{ .name = alias, .target = .other_type },
},
};
}
@@ -301,7 +335,7 @@ fn parseEnums(allocator: *Allocator, out: []registry.Declaration, root: *xml.Ele
out[i] = .{
.name = name,
.decl_type = .{.enumeration = try parseEnumFields(allocator, enums)},
.decl_type = .{ .enumeration = try parseEnumFields(allocator, enums) },
};
i += 1;
}
@@ -317,6 +351,11 @@ fn parseEnumFields(allocator: *Allocator, elem: *xml.Element) !registry.Enum {
return error.InvalidRegistry;
}
const bitwidth = if (elem.getAttribute("bitwidth")) |bitwidth|
try std.fmt.parseInt(u8, bitwidth, 10)
else
32;
const fields = try allocator.alloc(registry.Enum.Field, elem.children.items.len);
var i: usize = 0;
@@ -328,6 +367,7 @@ fn parseEnumFields(allocator: *Allocator, elem: *xml.Element) !registry.Enum {
return registry.Enum{
.fields = allocator.shrink(fields, i),
.bitwidth = bitwidth,
.is_bitmask = is_bitmask,
};
}
@@ -351,14 +391,14 @@ fn parseEnumField(field: *xml.Element) !registry.Enum.Field {
// tag. In the latter case its passed via the `ext_nr` parameter.
if (field.getAttribute("value")) |value| {
if (mem.startsWith(u8, value, "0x")) {
break :blk .{.bit_vector = try std.fmt.parseInt(i32, value[2..], 16)};
break :blk .{ .bit_vector = try std.fmt.parseInt(i32, value[2..], 16) };
} else {
break :blk .{.int = try std.fmt.parseInt(i32, value, 10)};
break :blk .{ .int = try std.fmt.parseInt(i32, value, 10) };
}
} else if (field.getAttribute("bitpos")) |bitpos| {
break :blk .{.bitpos = try std.fmt.parseInt(u5, bitpos, 10)};
break :blk .{ .bitpos = try std.fmt.parseInt(u6, bitpos, 10) };
} else if (field.getAttribute("alias")) |alias| {
break :blk .{.alias = .{.name = alias, .is_compat_alias = is_compat_alias}};
break :blk .{ .alias = .{ .name = alias, .is_compat_alias = is_compat_alias } };
} else {
return error.InvalidRegistry;
}
@@ -401,7 +441,9 @@ fn parseCommand(allocator: *Allocator, elem: *xml.Element) !registry.Declaration
const name = elem.getAttribute("name") orelse return error.InvalidRegistry;
return registry.Declaration{
.name = name,
.decl_type = .{.alias = .{.name = alias, .target = .other_command}}
.decl_type = .{
.alias = .{ .name = alias, .target = .other_command },
},
};
}
@@ -442,10 +484,10 @@ fn parseCommand(allocator: *Allocator, elem: *xml.Element) !registry.Declaration
it = elem.findChildrenByTag("param");
for (params) |*param| {
const param_elem = it.next().?;
try parsePointerMeta(.{.command = params}, &param.param_type, param_elem);
try parsePointerMeta(.{ .command = params }, &param.param_type, param_elem);
}
return registry.Declaration {
return registry.Declaration{
.name = command_decl.name,
.decl_type = .{
.command = .{
@@ -453,8 +495,8 @@ fn parseCommand(allocator: *Allocator, elem: *xml.Element) !registry.Declaration
.return_type = return_type,
.success_codes = success_codes,
.error_codes = error_codes,
}
}
},
},
};
}
@@ -499,7 +541,7 @@ fn parseApiConstants(allocator: *Allocator, root: *xml.Element) ![]registry.ApiC
constants[i] = .{
.name = constant.getAttribute("name") orelse return error.InvalidRegistry,
.value = .{.expr = expr},
.value = .{ .expr = expr },
};
i += 1;
@@ -522,15 +564,13 @@ fn parseDefines(types: *xml.Element, out: []registry.ApiConstant) !usize {
if (mem.eql(u8, name, "VK_HEADER_VERSION")) {
out[i] = .{
.name = name,
.value = .{.expr = mem.trim(u8, ty.children.items[2].CharData, " ")},
.value = .{ .expr = mem.trim(u8, ty.children.items[2].CharData, " ") },
};
} else {
var xctok = cparse.XmlCTokenizer.init(ty);
out[i] = .{
.name = name,
.value = .{
.version = cparse.parseVersion(&xctok) catch continue
},
.value = .{ .version = cparse.parseVersion(&xctok) catch continue },
};
}
i += 1;
@@ -591,7 +631,7 @@ fn parseFeature(allocator: *Allocator, feature: *xml.Element) !registry.Feature
return registry.Feature{
.name = name,
.level = feature_level,
.requires = allocator.shrink(requires, i)
.requires = allocator.shrink(requires, i),
};
}
@@ -624,7 +664,10 @@ fn parseEnumExtension(elem: *xml.Element, parent_extnumber: ?u31) !?registry.Req
return registry.Require.EnumExtension{
.extends = extends,
.extnumber = actual_extnumber,
.field = .{.name = name, .value = .{.int = value}},
.field = .{
.name = name,
.value = .{ .int = value },
},
};
}
@@ -687,7 +730,7 @@ fn parseRequire(allocator: *Allocator, require: *xml.Element, extnumber: ?u31) !
return error.InvalidRegistry;
}
break :blk try splitFeatureLevel(feature_level["VK_VERSION_".len ..], "_");
break :blk try splitFeatureLevel(feature_level["VK_VERSION_".len..], "_");
};
return registry.Require{
@@ -752,12 +795,11 @@ fn parseExtension(allocator: *Allocator, extension: *xml.Element) !registry.Exte
const promoted_to: registry.Extension.Promotion = blk: {
const promotedto = extension.getAttribute("promotedto") orelse break :blk .none;
if (mem.startsWith(u8, promotedto, "VK_VERSION_")) {
const feature_level = try splitFeatureLevel(promotedto["VK_VERSION_".len ..], "_");
break :blk .{.feature = feature_level};
const feature_level = try splitFeatureLevel(promotedto["VK_VERSION_".len..], "_");
break :blk .{ .feature = feature_level };
}
break :blk .{.extension = promotedto};
break :blk .{ .extension = promotedto };
};
const number = blk: {
@@ -798,7 +840,7 @@ fn parseExtension(allocator: *Allocator, extension: *xml.Element) !registry.Exte
.promoted_to = promoted_to,
.platform = platform,
.required_feature_level = requires_core,
.requires = allocator.shrink(requires, i)
.requires = allocator.shrink(requires, i),
};
}

View File

@@ -37,7 +37,7 @@ pub const Alias = struct {
pub const ApiConstant = struct {
pub const Value = union(enum) {
expr: []const u8,
version: [3][]const u8,
version: [4][]const u8,
};
name: []const u8,
@@ -65,19 +65,20 @@ pub const Container = struct {
};
stype: ?[]const u8,
extends: ?[]const []const u8,
fields: []Field,
is_union: bool,
};
pub const Enum = struct {
pub const Value = union(enum) {
bitpos: u5, // 1 << bitpos
bitpos: u6, // 1 << bitpos
bit_vector: i32, // Combined flags & some vendor IDs
int: i32,
alias: struct {
name: []const u8,
is_compat_alias: bool,
}
},
};
pub const Field = struct {
@@ -86,11 +87,13 @@ pub const Enum = struct {
};
fields: []Field,
bitwidth: u8,
is_bitmask: bool,
};
pub const Bitmask = struct {
bits_enum: ?[]const u8,
bitwidth: u8,
};
pub const Handle = struct {
@@ -116,7 +119,7 @@ pub const Pointer = struct {
one,
many, // The length is given by some complex expression, possibly involving another field
other_field: []const u8, // The length is given by some other field or parameter
zero_terminated
zero_terminated,
};
is_const: bool,

View File

@@ -9,14 +9,17 @@ const IdRenderer = id_render.IdRenderer;
const preamble =
\\
\\// This file is generated from the Khronos Vulkan XML API registry
\\// This file is generated from the Khronos Vulkan XML API registry by vulkan-zig.
\\
\\const std = @import("std");
\\const builtin = @import("builtin");
\\const root = @import("root");
\\pub const vulkan_call_conv: builtin.CallingConvention = if (builtin.os.tag == .windows and builtin.cpu.arch == .i386)
\\
\\const GlobalScope = @This();
\\
\\pub const vulkan_call_conv: std.builtin.CallingConvention = if (builtin.os.tag == .windows and builtin.cpu.arch == .i386)
\\ .Stdcall
\\ else if (builtin.abi == .android and (builtin.cpu.arch.isARM() or builtin.cpu.arch.isThumb()) and builtin.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"
\\ // 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,
@@ -24,9 +27,9 @@ const preamble =
\\ .AAPCSVFP
\\ else
\\ .C;
\\pub fn FlagsMixin(comptime FlagsType: type) type {
\\pub fn FlagsMixin(comptime FlagsType: type, comptime Int: type) type {
\\ return struct {
\\ pub const IntType = Flags;
\\ pub const IntType = Int;
\\ pub fn toInt(self: FlagsType) IntType {
\\ return @bitCast(IntType, self);
\\ }
@@ -40,7 +43,7 @@ const preamble =
\\ return fromInt(toInt(lhs) & toInt(rhs));
\\ }
\\ pub fn complement(self: FlagsType) FlagsType {
\\ return fromInt(~toInt(lhs));
\\ return fromInt(~toInt(self));
\\ }
\\ pub fn subtract(lhs: FlagsType, rhs: FlagsType) FlagsType {
\\ return fromInt(toInt(lhs) & toInt(rhs.complement()));
@@ -50,54 +53,61 @@ const preamble =
\\ }
\\ };
\\}
\\pub fn makeVersion(major: u10, minor: u10, patch: u12) u32 {
\\ return (@as(u32, major) << 22) | (@as(u32, minor) << 12) | patch;
\\pub fn makeApiVersion(variant: u3, major: u7, minor: u10, patch: u12) u32 {
\\ return (@as(u32, variant) << 29) | (@as(u32, major) << 22) | (@as(u32, minor) << 12) | patch;
\\}
\\pub fn versionMajor(version: u32) u10 {
\\ return @truncate(u10, version >> 22);
\\pub fn apiVersionVariant(version: u32) u3 {
\\ return @truncate(u3, version >> 29);
\\}
\\pub fn versionMinor(version: u32) u10 {
\\pub fn apiVersionMajor(version: u32) u7 {
\\ return @truncate(u7, version >> 22);
\\}
\\pub fn apiVersionMinor(version: u32) u10 {
\\ return @truncate(u10, version >> 12);
\\}
\\pub fn versionPatch(version: u32) u12 {
\\pub fn apiVersionPatch(version: u32) u12 {
\\ return @truncate(u12, version);
\\}
\\
;
;
const builtin_types = std.ComptimeStringMap([]const u8, .{
.{"void", @typeName(void)},
.{"char", @typeName(u8)},
.{"float", @typeName(f32)},
.{"double", @typeName(f64)},
.{"uint8_t", @typeName(u8)},
.{"uint16_t", @typeName(u16)},
.{"uint32_t", @typeName(u32)},
.{"uint64_t", @typeName(u64)},
.{"int32_t", @typeName(i32)},
.{"int64_t", @typeName(i64)},
.{"size_t", @typeName(usize)},
.{"int", @typeName(c_int)},
.{ "void", @typeName(void) },
.{ "char", @typeName(u8) },
.{ "float", @typeName(f32) },
.{ "double", @typeName(f64) },
.{ "uint8_t", @typeName(u8) },
.{ "uint16_t", @typeName(u16) },
.{ "uint32_t", @typeName(u32) },
.{ "uint64_t", @typeName(u64) },
.{ "int8_t", @typeName(i8) },
.{ "int16_t", @typeName(i16) },
.{ "int32_t", @typeName(i32) },
.{ "int64_t", @typeName(i64) },
.{ "size_t", @typeName(usize) },
.{ "int", @typeName(c_int) },
});
const foreign_types = std.ComptimeStringMap([]const u8, .{
.{"Display", "opaque {}"},
.{"VisualID", @typeName(c_uint)},
.{"Window", @typeName(c_ulong)},
.{"RROutput", @typeName(c_ulong)},
.{"wl_display", "opaque {}"},
.{"wl_surface", "opaque {}"},
.{"HINSTANCE", "std.os.HINSTANCE"},
.{"HWND", "*opaque {}"},
.{"HMONITOR", "*opaque {}"},
.{"HANDLE", "std.os.HANDLE"},
.{"SECURITY_ATTRIBUTES", "std.os.SECURITY_ATTRIBUTES"},
.{"DWORD", "std.os.DWORD"},
.{"LPCWSTR", "std.os.LPCWSTR"},
.{"xcb_connection_t", "opaque {}"},
.{"xcb_visualid_t", @typeName(u32)},
.{"xcb_window_t", @typeName(u32)},
.{"zx_handle_t", @typeName(u32)},
.{ "Display", "opaque {}" },
.{ "VisualID", @typeName(c_uint) },
.{ "Window", @typeName(c_ulong) },
.{ "RROutput", @typeName(c_ulong) },
.{ "wl_display", "opaque {}" },
.{ "wl_surface", "opaque {}" },
.{ "HINSTANCE", "std.os.HINSTANCE" },
.{ "HWND", "*opaque {}" },
.{ "HMONITOR", "*opaque {}" },
.{ "HANDLE", "std.os.HANDLE" },
.{ "SECURITY_ATTRIBUTES", "std.os.SECURITY_ATTRIBUTES" },
.{ "DWORD", "std.os.DWORD" },
.{ "LPCWSTR", "std.os.LPCWSTR" },
.{ "xcb_connection_t", "opaque {}" },
.{ "xcb_visualid_t", @typeName(u32) },
.{ "xcb_window_t", @typeName(u32) },
.{ "zx_handle_t", @typeName(u32) },
.{ "_screen_context", "opaque {}" },
.{ "_screen_window", "opaque {}" },
});
fn eqlIgnoreCase(lhs: []const u8, rhs: []const u8) bool {
@@ -115,7 +125,7 @@ fn eqlIgnoreCase(lhs: []const u8, rhs: []const u8) bool {
}
pub fn trimVkNamespace(id: []const u8) []const u8 {
const prefixes = [_][]const u8{"VK_", "vk", "Vk", "PFN_vk"};
const prefixes = [_][]const u8{ "VK_", "vk", "Vk", "PFN_vk" };
for (prefixes) |prefix| {
if (mem.startsWith(u8, id, prefix)) {
return id[prefix.len..];
@@ -129,15 +139,16 @@ fn Renderer(comptime WriterType: type) type {
return struct {
const Self = @This();
const WriteError = WriterType.Error;
const RenderTypeInfoError = WriteError || error {
OutOfMemory,
};
const RenderTypeInfoError = WriteError || std.fmt.ParseIntError || error{ OutOfMemory, InvalidRegistry };
const BitflagName = struct {
/// Name without FlagBits, so VkSurfaceTransformFlagBitsKHR
/// becomes VkSurfaceTransform
base_name: []const u8,
/// Optional flag bits revision, used in places like VkAccessFlagBits2KHR
revision: ?[]const u8,
/// Optional tag of the flag
tag: ?[]const u8,
};
@@ -174,10 +185,6 @@ fn Renderer(comptime WriterType: type) type {
declarations_by_name: std.StringHashMap(*const reg.DeclarationType),
fn init(writer: WriterType, allocator: *Allocator, registry: *const reg.Registry, id_renderer: *IdRenderer) !Self {
const tags = try allocator.alloc([]const u8, registry.tags.len);
errdefer allocator.free(tags);
for (tags) |*tag, i| tag.* = registry.tags[i].name;
var declarations_by_name = std.StringHashMap(*const reg.DeclarationType).init(allocator);
errdefer declarations_by_name.deinit();
@@ -187,7 +194,7 @@ fn Renderer(comptime WriterType: type) type {
return error.InvalidRegistry;
}
result.entry.value = &decl.decl_type;
result.value_ptr.* = &decl.decl_type;
}
return Self{
@@ -201,12 +208,10 @@ fn Renderer(comptime WriterType: type) type {
fn deinit(self: *Self) void {
self.declarations_by_name.deinit();
self.allocator.free(self.id_renderer.tags);
self.id_renderer.deinit();
}
fn writeIdentifier(self: Self, id: []const u8) !void {
try self.id_renderer.render(self.writer, id);
try id_render.writeIdentifier(self.writer, id);
}
fn writeIdentifierWithCase(self: *Self, case: CaseStyle, id: []const u8) !void {
@@ -225,8 +230,8 @@ fn Renderer(comptime WriterType: type) type {
while (true) {
const rest = field_it.rest();
const field_segment = field_it.next() orelse return error.InvalidRegistry;
const enum_segment = enum_it.next() orelse return rest;
const field_segment = field_it.next() orelse return error.FieldNameEqualsEnumName;
if (!eqlIgnoreCase(enum_segment, field_segment)) {
return rest;
@@ -234,16 +239,48 @@ fn Renderer(comptime WriterType: type) type {
}
}
fn extractBitflagName(self: Self, name: []const u8) ?BitflagName {
const tag = self.id_renderer.getAuthorTag(name);
const base_name = if (tag) |tag_name| name[0 .. name.len - tag_name.len] else name;
fn extractBitflagFieldName(bitflag_name: BitflagName, field_name: []const u8) ![]const u8 {
var flag_it = id_render.SegmentIterator.init(bitflag_name.base_name);
var field_it = id_render.SegmentIterator.init(field_name);
if (!mem.endsWith(u8, base_name, "FlagBits")) {
while (true) {
const rest = field_it.rest();
const field_segment = field_it.next() orelse return error.InvalidRegistry;
const flag_segment = flag_it.next() orelse {
if (bitflag_name.revision) |revision| {
if (mem.eql(u8, revision, field_segment))
return field_it.rest();
}
return rest;
};
if (!eqlIgnoreCase(flag_segment, field_segment)) {
return rest;
}
}
}
fn extractBitflagName(self: Self, name: []const u8) !?BitflagName {
const tag = self.id_renderer.getAuthorTag(name);
const tagless_name = if (tag) |tag_name| name[0 .. name.len - tag_name.len] else name;
// Strip out the "version" number of a bitflag, like VkAccessFlagBits2KHR.
const base_name = std.mem.trimRight(u8, tagless_name, "0123456789");
const maybe_flag_bits_index = mem.lastIndexOf(u8, base_name, "FlagBits");
if (maybe_flag_bits_index == null) {
return null;
} else if (maybe_flag_bits_index != base_name.len - "FlagBits".len) {
// It is unlikely that a type that is not a flag bit would contain FlagBits,
// and more likely that we have missed something if FlagBits isn't the last
// part of base_name
return error.InvalidRegistry;
}
return BitflagName{
.base_name = base_name[0 .. base_name.len - "FlagBits".len],
.revision = if (base_name.len != tagless_name.len) tagless_name[base_name.len..] else null,
.tag = tag,
};
}
@@ -255,16 +292,6 @@ fn Renderer(comptime WriterType: type) type {
return mem.endsWith(u8, base_name, "Flags");
}
fn containerHasField(self: Self, container: *const reg.Container, field_name: []const u8) bool {
for (container.fields) |field| {
if (mem.eql(u8, field, field_name)) {
return true;
}
}
return false;
}
fn isInOutPointer(self: Self, ptr: reg.Pointer) !bool {
if (ptr.child.* != .name) {
return false;
@@ -333,7 +360,7 @@ fn Renderer(comptime WriterType: type) type {
}
},
.name => |name| {
if (self.extractBitflagName(param.param_type.name) != null or self.isFlags(param.param_type.name)) {
if ((try self.extractBitflagName(name)) != null or self.isFlags(name)) {
return .bitflags;
}
},
@@ -347,20 +374,20 @@ fn Renderer(comptime WriterType: type) type {
return .other;
}
fn classifyCommandDispatch(self: Self, name: []const u8, command: reg.Command) CommandDispatchType {
fn classifyCommandDispatch(name: []const u8, command: reg.Command) CommandDispatchType {
const device_handles = std.ComptimeStringMap(void, .{
.{"VkDevice", {}},
.{"VkCommandBuffer", {}},
.{"VkQueue", {}},
.{ "VkDevice", {} },
.{ "VkCommandBuffer", {} },
.{ "VkQueue", {} },
});
const override_functions = std.ComptimeStringMap(CommandDispatchType, .{
.{"vkGetInstanceProcAddr", .base},
.{"vkCreateInstance", .base},
.{"vkEnumerateInstanceLayerProperties", .base},
.{"vkEnumerateInstanceExtensionProperties", .base},
.{"vkEnumerateInstanceVersion", .base},
.{"vkGetDeviceProcAddr", .instance},
.{ "vkGetInstanceProcAddr", .base },
.{ "vkCreateInstance", .base },
.{ "vkEnumerateInstanceLayerProperties", .base },
.{ "vkEnumerateInstanceExtensionProperties", .base },
.{ "vkEnumerateInstanceVersion", .base },
.{ "vkGetDeviceProcAddr", .instance },
});
if (override_functions.get(name)) |dispatch_type| {
@@ -382,6 +409,7 @@ fn Renderer(comptime WriterType: type) type {
fn render(self: *Self) !void {
try self.renderCopyright();
try self.writer.writeAll(preamble);
try self.renderCommandEnums();
for (self.registry.api_constants) |api_constant| {
try self.renderApiConstant(api_constant);
@@ -396,6 +424,88 @@ fn Renderer(comptime WriterType: type) type {
try self.renderWrappers();
}
fn renderCommandEnums(self: *Self) !void {
try self.renderCommandEnumOfDispatchType(.base);
try self.renderCommandEnumOfDispatchType(.instance);
try self.renderCommandEnumOfDispatchType(.device);
try self.writer.writeAll("\n");
}
fn renderCommandEnumOfDispatchType(self: *Self, dispatch_type: CommandDispatchType) !void {
const dispatch_type_name = switch (dispatch_type) {
.base => "Base",
.instance => "Instance",
.device => "Device",
};
try self.writer.print("pub const {s}Command = enum {{\n", .{dispatch_type_name});
for (self.registry.decls) |decl| {
const command = switch (decl.decl_type) {
.command => |cmd| cmd,
else => continue,
};
if (classifyCommandDispatch(decl.name, command) == dispatch_type) {
try self.writeIdentifierWithCase(.camel, trimVkNamespace(decl.name));
try self.writer.writeAll(",\n");
}
}
{
try self.writer.print(
\\
\\pub fn symbol(self: {s}Command) [:0]const u8 {{
\\ return switch (self) {{
\\
, .{dispatch_type_name}
);
for (self.registry.decls) |decl| {
const command = switch (decl.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.print(" => \"{s}\",\n", .{ decl.name });
}
}
try self.writer.writeAll("};\n}\n");
}
{
try self.writer.print(
\\
\\pub fn PfnType(comptime self: {s}Command) type {{
\\ return switch (self) {{
\\
, .{dispatch_type_name}
);
for (self.registry.decls) |decl| {
const command = switch (decl.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(" => ");
try self.renderCommandPtrName(decl.name);
try self.writer.writeAll(",\n");
}
}
try self.writer.writeAll("};\n}\n");
}
try self.writer.writeAll("};\n");
}
fn renderCopyright(self: *Self) !void {
var it = mem.split(self.registry.copyright, "\n");
while (it.next()) |line| {
@@ -411,7 +521,7 @@ fn Renderer(comptime WriterType: type) type {
switch (api_constant.value) {
.expr => |expr| try self.renderApiConstantExpr(expr),
.version => |version| {
try self.writer.writeAll("makeVersion(");
try self.writer.writeAll("makeApiVersion(");
for (version) |part, i| {
if (i != 0) {
try self.writer.writeAll(", ");
@@ -431,7 +541,7 @@ fn Renderer(comptime WriterType: type) type {
else
expr;
var tokenizer = cparse.CTokenizer{.source = adjusted_expr};
var tokenizer = cparse.CTokenizer{ .source = adjusted_expr };
var peeked: ?cparse.Token = null;
while (true) {
const tok = peeked orelse (try tokenizer.next()) orelse break;
@@ -467,10 +577,10 @@ fn Renderer(comptime WriterType: type) type {
},
.dot => {
const decimal = (try tokenizer.next()) orelse return error.InvalidConstantExpr;
try self.writer.print("@as(f32, {s}.{s})", .{tok.text, decimal.text});
try self.writer.print("@as(f32, {s}.{s})", .{ tok.text, decimal.text });
const f = (try tokenizer.next()) orelse return error.InvalidConstantExpr;
if (f.kind != .id or !mem.eql(u8, f.text, "f")) {
if (f.kind != .id or f.text.len != 1 or (f.text[0] != 'f' and f.text[0] != 'F')) {
return error.InvalidApiConstant;
}
},
@@ -495,10 +605,11 @@ fn Renderer(comptime WriterType: type) type {
if (builtin_types.get(name)) |zig_name| {
try self.writer.writeAll(zig_name);
return;
} else if (self.extractBitflagName(name)) |bitflag_name| {
try self.writeIdentifierFmt("{s}Flags{s}", .{
} else if (try self.extractBitflagName(name)) |bitflag_name| {
try self.writeIdentifierFmt("{s}Flags{s}{s}", .{
trimVkNamespace(bitflag_name.base_name),
@as([]const u8, if (bitflag_name.tag) |tag| tag else "")
@as([]const u8, if (bitflag_name.revision) |revision| revision else ""),
@as([]const u8, if (bitflag_name.tag) |tag| tag else ""),
});
return;
} else if (mem.startsWith(u8, name, "vk")) {
@@ -534,10 +645,11 @@ fn Renderer(comptime WriterType: type) type {
blk: {
if (param.param_type == .name) {
if (self.extractBitflagName(param.param_type.name)) |bitflag_name| {
try self.writeIdentifierFmt("{s}Flags{s}", .{
if (try self.extractBitflagName(param.param_type.name)) |bitflag_name| {
try self.writeIdentifierFmt("{s}Flags{s}{s}", .{
trimVkNamespace(bitflag_name.base_name),
@as([]const u8, if (bitflag_name.tag) |tag| tag else "")
@as([]const u8, if (bitflag_name.revision) |revision| revision else ""),
@as([]const u8, if (bitflag_name.tag) |tag| tag else ""),
});
try self.writer.writeAll(".IntType");
break :blk;
@@ -638,7 +750,7 @@ fn Renderer(comptime WriterType: type) type {
}
} else {
try self.renderTypeInfo(field.field_type);
try self.renderContainerDefaultField(container, field);
try self.renderContainerDefaultField(name, container, field);
try self.writer.writeAll(", ");
}
}
@@ -646,7 +758,7 @@ fn Renderer(comptime WriterType: type) type {
try self.writer.writeAll("};\n");
}
fn renderContainerDefaultField(self: *Self, container: reg.Container, field: reg.Container.Field) !void {
fn renderContainerDefaultField(self: *Self, name: []const u8, container: reg.Container, field: reg.Container.Field) !void {
if (mem.eql(u8, field.name, "pNext")) {
try self.writer.writeAll(" = null");
} else if (mem.eql(u8, field.name, "sType")) {
@@ -660,10 +772,22 @@ fn Renderer(comptime WriterType: type) type {
}
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 !container.is_union and mem.eql(u8, "VkBool32", field.field_type.name) and isFeatureStruct(name, container.extends)) {
try self.writer.writeAll(" = FALSE");
}
}
fn isFeatureStruct(name: []const u8, maybe_extends: ?[]const []const u8) bool {
if (std.mem.eql(u8, name, "VkPhysicalDeviceFeatures")) return true;
if (maybe_extends) |extends| {
return for (extends) |extend| {
if (mem.eql(u8, extend, "VkDeviceCreateInfo")) break true;
} else false;
}
return false;
}
fn renderEnumFieldName(self: *Self, name: []const u8, field_name: []const u8) !void {
try self.writeIdentifierWithCase(.snake, try self.extractEnumFieldName(name, field_name));
}
@@ -676,70 +800,90 @@ fn Renderer(comptime WriterType: type) type {
try self.writer.writeAll("pub const ");
try self.renderName(name);
try self.writer.writeAll(" = extern enum {");
try self.writer.writeAll(" = enum(i32) {");
for (enumeration.fields) |field| {
if (field.value == .alias) {
if (field.value == .alias)
continue;
}
try self.renderEnumFieldName(name, field.name);
switch (field.value) {
.int => |int| try self.writer.print(" = {}, ", .{int}),
.bitpos => |pos| try self.writer.print(" = 1 << {}, ", .{pos}),
.bit_vector => |value| try self.writer.print(" = 0x{X}, ", .{value}),
.bit_vector => |bv| try self.writer.print("= 0x{X}, ", .{bv}),
.alias => unreachable,
}
}
try self.writer.writeAll("_,");
for (enumeration.fields) |field| {
if (field.value != .alias or field.value.alias.is_compat_alias) {
if (field.value != .alias or field.value.alias.is_compat_alias)
continue;
}
try self.writer.writeAll("pub const ");
try self.renderEnumFieldName(name, field.name);
try self.writer.writeAll(" = .");
try self.writer.writeAll(" = ");
try self.renderName(name);
try self.writer.writeByte('.');
try self.renderEnumFieldName(name, field.value.alias.name);
try self.writer.writeAll(";");
try self.writer.writeAll(";\n");
}
try self.writer.writeAll("};\n");
}
fn bitmaskFlagsType(bitwidth: u8) ![]const u8 {
return switch (bitwidth) {
32 => "Flags",
64 => "Flags64",
else => return error.InvalidRegistry,
};
}
fn renderUsingFlagsMixin(self: *Self, name: []const u8, bitwidth: u8) !void {
const flags_type = try bitmaskFlagsType(bitwidth);
try self.writer.writeAll("pub usingnamespace FlagsMixin(");
try self.renderName(name);
try self.writer.print(", {s});\n", .{flags_type});
}
fn renderBitmaskBits(self: *Self, name: []const u8, bits: reg.Enum) !void {
try self.writer.writeAll("pub const ");
try self.renderName(name);
try self.writer.writeAll(" = packed struct {");
const bitflag_name = (try self.extractBitflagName(name)) orelse return error.InvalidRegistry;
const flags_type = try bitmaskFlagsType(bits.bitwidth);
if (bits.fields.len == 0) {
try self.writer.writeAll("_reserved_bits: Flags = 0,");
try self.writer.print("_reserved_bits: {s} = 0,", .{flags_type});
} else {
var flags_by_bitpos = [_]?[]const u8{null} ** 32;
var flags_by_bitpos = [_]?[]const u8{null} ** 64;
for (bits.fields) |field| {
if (field.value == .bitpos) {
flags_by_bitpos[field.value.bitpos] = field.name;
}
}
for (flags_by_bitpos) |opt_flag_name, bitpos| {
if (opt_flag_name) |flag_name| {
try self.renderEnumFieldName(name, flag_name);
for (flags_by_bitpos[0..bits.bitwidth]) |maybe_flag_name, bitpos| {
if (maybe_flag_name) |flag_name| {
const field_name = try extractBitflagFieldName(bitflag_name, flag_name);
try self.writeIdentifierWithCase(.snake, field_name);
} else {
try self.writer.print("_reserved_bit_{}", .{bitpos});
}
try self.writer.writeAll(": bool ");
if (bitpos == 0) { // Force alignment to integer boundaries
try self.writer.writeAll("align(@alignOf(Flags)) ");
try self.writer.print("align(@alignOf({s})) ", .{flags_type});
}
try self.writer.writeAll("= false, ");
}
}
try self.writer.writeAll("pub usingnamespace FlagsMixin(");
try self.renderName(name);
try self.writer.writeAll(");\n};\n");
try self.writer.print(", {s});\n}};\n", .{flags_type});
}
fn renderBitmask(self: *Self, name: []const u8, bitmask: reg.Bitmask) !void {
@@ -747,19 +891,21 @@ fn Renderer(comptime WriterType: type) type {
// The bits structure is generated by renderBitmaskBits, but that wont
// output flags with no associated bits type.
const flags_type = try bitmaskFlagsType(bitmask.bitwidth);
try self.writer.writeAll("pub const ");
try self.renderName(name);
try self.writer.writeAll(
\\ = packed struct {
\\_reserved_bits: Flags = 0,
try self.writer.print(
\\ = packed struct {{
\\_reserved_bits: {s} = 0,
\\pub usingnamespace FlagsMixin(
);
, .{flags_type});
try self.renderName(name);
try self.writer.writeAll(
\\);
\\};
try self.writer.print(
\\, {s});
\\}};
\\
);
, .{flags_type});
}
}
@@ -768,13 +914,13 @@ fn Renderer(comptime WriterType: type) type {
try self.writer.writeAll("pub const ");
try self.renderName(name);
try self.writer.print(" = extern enum({s}) {{null_handle = 0, _}};\n", .{backing_type});
try self.writer.print(" = enum({s}) {{null_handle = 0, _}};\n", .{backing_type});
}
fn renderAlias(self: *Self, name: []const u8, alias: reg.Alias) !void {
if (alias.target == .other_command) {
return;
} else if (self.extractBitflagName(name) != null) {
} else if ((try self.extractBitflagName(name)) != null) {
// Don't make aliases of the bitflag names, as those are replaced by just the flags type
return;
}
@@ -849,32 +995,58 @@ fn Renderer(comptime WriterType: type) type {
try self.writer.writeAll("pub const ");
try self.writeIdentifierWithCase(.snake, trimVkNamespace(ext.name));
try self.writer.writeAll("= Info {\n");
try self.writer.print(".name = \"{s}\", .version = {},", .{ext.name, ext.version});
try self.writer.print(".name = \"{s}\", .version = {},", .{ ext.name, ext.version });
try self.writer.writeAll("};\n");
}
try self.writer.writeAll("};\n");
}
fn renderWrappers(self: *Self) !void {
try self.renderWrappersOfDispatchType("BaseWrapper", .base);
try self.renderWrappersOfDispatchType("InstanceWrapper", .instance);
try self.renderWrappersOfDispatchType("DeviceWrapper", .device);
try self.renderWrappersOfDispatchType(.base);
try self.renderWrappersOfDispatchType(.instance);
try self.renderWrappersOfDispatchType(.device);
}
fn renderWrappersOfDispatchType(self: *Self, name: []const u8, dispatch_type: CommandDispatchType) !void {
fn renderWrappersOfDispatchType(self: *Self, dispatch_type: CommandDispatchType) !void {
const name = switch (dispatch_type) {
.base => "Base",
.instance => "Instance",
.device => "Device",
};
try self.writer.print(
\\pub fn {s}(comptime Self: type) type {{
\\pub fn {s}Wrapper(comptime cmds: []const {s}Command) type {{
\\ comptime var fields: [cmds.len]std.builtin.TypeInfo.StructField = undefined;
\\ inline for (cmds) |cmd, i| {{
\\ const PfnType = cmd.PfnType();
\\ fields[i] = .{{
\\ .name = cmd.symbol(),
\\ .field_type = PfnType,
\\ .default_value = null,
\\ .is_comptime = false,
\\ .alignment = @alignOf(PfnType),
\\ }};
\\ }}
\\ const Dispatch = @Type(.{{
\\ .Struct = .{{
\\ .layout = .Auto,
\\ .fields = &fields,
\\ .decls = &[_]std.builtin.TypeInfo.Declaration{{}},
\\ .is_tuple = false,
\\ }},
\\ }});
\\ return struct {{
\\ dispatch: Dispatch,
\\
, .{name}
);
\\ const Self = @This();
, .{ name, name });
try self.renderWrapperLoader(dispatch_type);
for (self.registry.decls) |decl| {
if (decl.decl_type == .command) {
const command = decl.decl_type.command;
if (self.classifyCommandDispatch(decl.name, command) == dispatch_type) {
if (classifyCommandDispatch(decl.name, command) == dispatch_type) {
try self.renderWrapper(decl.name, decl.decl_type.command);
}
}
@@ -901,16 +1073,15 @@ fn Renderer(comptime WriterType: type) type {
try self.writer.print(
\\pub fn load({s}) !Self {{
\\ var self: Self = undefined;
\\ inline for (std.meta.fields(Self)) |field| {{
\\ inline for (std.meta.fields(Dispatch)) |field| {{
\\ const name = @ptrCast([*:0]const u8, field.name ++ "\x00");
\\ const cmd_ptr = loader({s}name) orelse return error.InvalidCommand;
\\ @field(self, field.name) = @ptrCast(field.field_type, cmd_ptr);
\\ const cmd_ptr = loader({s}name) orelse return error.CommandLoadFailure;
\\ @field(self.dispatch, field.name) = @ptrCast(field.field_type, cmd_ptr);
\\ }}
\\ return self;
\\}}
\\
, .{params, loader_first_param}
);
, .{ params, loader_first_param });
}
fn derefName(name: []const u8) []const u8 {
@@ -939,7 +1110,8 @@ fn Renderer(comptime WriterType: type) type {
.bitflags, // Special stuff handled in renderWrapperCall
.buffer_len,
.mut_buffer_len,
.other => {
.other,
=> {
try self.writeIdentifierWithCase(.snake, param.name);
try self.writer.writeAll(": ");
try self.renderTypeInfo(param.param_type);
@@ -951,8 +1123,8 @@ fn Renderer(comptime WriterType: type) type {
try self.writer.writeAll(") ");
if (command.return_type.* == .name and mem.eql(u8, command.return_type.name, "VkResult")) {
try self.renderErrorSet(command.error_codes);
if (command.error_codes.len > 0) {
try self.renderErrorSetName(name);
try self.writer.writeByte('!');
}
@@ -966,7 +1138,7 @@ fn Renderer(comptime WriterType: type) type {
}
fn renderWrapperCall(self: *Self, name: []const u8, command: reg.Command, returns: []const ReturnValue) !void {
try self.writer.writeAll("self.");
try self.writer.writeAll("self.dispatch.");
try self.writeIdentifier(name);
try self.writer.writeAll("(");
@@ -987,10 +1159,7 @@ fn Renderer(comptime WriterType: type) type {
try self.writeIdentifierWithCase(.snake, param.name);
try self.writer.writeAll(".toInt()");
},
.in_out_pointer,
.buffer_len,
.mut_buffer_len,
.other => {
.in_out_pointer, .buffer_len, .mut_buffer_len, .other => {
try self.writeIdentifierWithCase(.snake, param.name);
},
}
@@ -1045,6 +1214,11 @@ fn Renderer(comptime WriterType: type) type {
try self.writeIdentifierFmt("{s}Result", .{trimVkNamespace(command_name)});
}
fn renderErrorSetName(self: *Self, name: []const u8) !void {
try self.writeIdentifierWithCase(.title, trimVkNamespace(name));
try self.writer.writeAll("Error");
}
fn renderReturnStruct(self: *Self, command_name: []const u8, returns: []const ReturnValue) !void {
try self.writer.writeAll("pub const ");
try self.renderReturnStructName(command_name);
@@ -1054,16 +1228,13 @@ fn Renderer(comptime WriterType: type) type {
try self.writer.writeAll(": ");
try self.renderTypeInfo(ret.return_value_type);
try self.writer.writeAll(", ");
}
try self.writer.writeAll("};\n");
}
fn renderWrapper(self: *Self, name: []const u8, command: reg.Command) !void {
const returns_vk_result = command.return_type.* == .name
and mem.eql(u8, command.return_type.name, "VkResult");
const returns_void = command.return_type.* == .name
and mem.eql(u8, command.return_type.name, "void");
const returns_vk_result = command.return_type.* == .name and mem.eql(u8, command.return_type.name, "VkResult");
const returns_void = command.return_type.* == .name and mem.eql(u8, command.return_type.name, "void");
const returns = try self.extractReturns(command);
@@ -1071,6 +1242,14 @@ fn Renderer(comptime WriterType: type) type {
try self.renderReturnStruct(name, returns);
}
if (command.error_codes.len > 0) {
try self.writer.writeAll("pub const ");
try self.renderErrorSetName(name);
try self.writer.writeAll(" = ");
try self.renderErrorSet(command.error_codes);
try self.writer.writeAll(";\n");
}
try self.renderWrapperPrototype(name, command, returns);
if (returns.len == 1 and returns[0].origin == .inner_return_value) {
@@ -1140,13 +1319,13 @@ fn Renderer(comptime WriterType: type) type {
try self.writer.writeAll(") {\n");
for (command.success_codes) |success| {
try self.writer.writeByte('.');
try self.writer.writeAll("Result.");
try self.renderEnumFieldName("VkResult", success);
try self.writer.writeAll(" => {},");
}
for (command.error_codes) |err| {
try self.writer.writeByte('.');
try self.writer.writeAll("Result.");
try self.renderEnumFieldName("VkResult", err);
try self.writer.writeAll(" => return error.");
try self.renderResultAsErrorName(err);
@@ -1168,7 +1347,7 @@ fn Renderer(comptime WriterType: type) type {
fn renderResultAsErrorName(self: *Self, name: []const u8) !void {
const error_prefix = "VK_ERROR_";
if (mem.startsWith(u8, name, error_prefix)) {
try self.writeIdentifierWithCase(.title, name[error_prefix.len ..]);
try self.writeIdentifierWithCase(.title, name[error_prefix.len..]);
} else {
// Apparently some commands (VkAcquireProfilingLockInfoKHR) return
// success codes as error...

View File

@@ -7,13 +7,13 @@ const ArrayList = std.ArrayList;
pub const Attribute = struct {
name: []const u8,
value: []const u8
value: []const u8,
};
pub const Content = union(enum) {
CharData: []const u8,
Comment: []const u8,
Element: *Element
Element: *Element,
};
pub const Element = struct {
@@ -50,7 +50,7 @@ pub const Element = struct {
return switch (child.children.items[0]) {
.CharData => |char_data| char_data,
else => null
else => null,
};
}
@@ -74,7 +74,7 @@ pub const Element = struct {
pub fn findChildrenByTag(self: *Element, tag: []const u8) FindChildrenByTagIterator {
return .{
.inner = self.elements(),
.tag = tag
.tag = tag,
};
}
@@ -129,7 +129,7 @@ pub const Element = struct {
pub const XmlDecl = struct {
version: []const u8,
encoding: ?[]const u8,
standalone: ?bool
standalone: ?bool,
};
pub const Document = struct {
@@ -154,7 +154,7 @@ const ParseContext = struct {
.source = source,
.offset = 0,
.line = 0,
.column = 0
.column = 0,
};
}
@@ -211,7 +211,7 @@ const ParseContext = struct {
fn expectStr(self: *ParseContext, text: []const u8) !void {
if (self.source.len < self.offset + text.len) {
return error.UnexpectedEof;
} else if (std.mem.startsWith(u8, self.source[self.offset ..], text)) {
} else if (std.mem.startsWith(u8, self.source[self.offset..], text)) {
var i: usize = 0;
while (i < text.len) : (i += 1) {
_ = self.consumeNoEof();
@@ -232,7 +232,7 @@ const ParseContext = struct {
ws = true;
_ = self.consumeNoEof();
},
else => break
else => break,
}
}
@@ -245,62 +245,62 @@ const ParseContext = struct {
fn currentLine(self: ParseContext) []const u8 {
var begin: usize = 0;
if (mem.indexOfScalarPos(u8, self.source[0 .. self.offset], '\n')) |prev_nl| {
if (mem.lastIndexOfScalar(u8, self.source[0..self.offset], '\n')) |prev_nl| {
begin = prev_nl + 1;
}
var end = mem.indexOfScalarPos(u8, self.source, self.offset, '\n') orelse self.source.len;
return self.source[begin .. end];
return self.source[begin..end];
}
};
test "ParseContext" {
{
var ctx = ParseContext.init("I like pythons");
testing.expectEqual(@as(?u8, 'I'), ctx.peek());
testing.expectEqual(@as(u8, 'I'), ctx.consumeNoEof());
testing.expectEqual(@as(?u8, ' '), ctx.peek());
testing.expectEqual(@as(u8, ' '), try ctx.consume());
try testing.expectEqual(@as(?u8, 'I'), ctx.peek());
try testing.expectEqual(@as(u8, 'I'), ctx.consumeNoEof());
try testing.expectEqual(@as(?u8, ' '), ctx.peek());
try testing.expectEqual(@as(u8, ' '), try ctx.consume());
testing.expect(ctx.eat('l'));
testing.expectEqual(@as(?u8, 'i'), ctx.peek());
testing.expectEqual(false, ctx.eat('a'));
testing.expectEqual(@as(?u8, 'i'), ctx.peek());
try testing.expect(ctx.eat('l'));
try testing.expectEqual(@as(?u8, 'i'), ctx.peek());
try testing.expectEqual(false, ctx.eat('a'));
try testing.expectEqual(@as(?u8, 'i'), ctx.peek());
try ctx.expect('i');
testing.expectEqual(@as(?u8, 'k'), ctx.peek());
testing.expectError(error.UnexpectedCharacter, ctx.expect('a'));
testing.expectEqual(@as(?u8, 'k'), ctx.peek());
try testing.expectEqual(@as(?u8, 'k'), ctx.peek());
try testing.expectError(error.UnexpectedCharacter, ctx.expect('a'));
try testing.expectEqual(@as(?u8, 'k'), ctx.peek());
testing.expect(ctx.eatStr("ke"));
testing.expectEqual(@as(?u8, ' '), ctx.peek());
try testing.expect(ctx.eatStr("ke"));
try testing.expectEqual(@as(?u8, ' '), ctx.peek());
testing.expect(ctx.eatWs());
testing.expectEqual(@as(?u8, 'p'), ctx.peek());
testing.expectEqual(false, ctx.eatWs());
testing.expectEqual(@as(?u8, 'p'), ctx.peek());
try testing.expect(ctx.eatWs());
try testing.expectEqual(@as(?u8, 'p'), ctx.peek());
try testing.expectEqual(false, ctx.eatWs());
try testing.expectEqual(@as(?u8, 'p'), ctx.peek());
testing.expectEqual(false, ctx.eatStr("aaaaaaaaa"));
testing.expectEqual(@as(?u8, 'p'), ctx.peek());
try testing.expectEqual(false, ctx.eatStr("aaaaaaaaa"));
try testing.expectEqual(@as(?u8, 'p'), ctx.peek());
testing.expectError(error.UnexpectedEof, ctx.expectStr("aaaaaaaaa"));
testing.expectEqual(@as(?u8, 'p'), ctx.peek());
testing.expectError(error.UnexpectedCharacter, ctx.expectStr("pytn"));
testing.expectEqual(@as(?u8, 'p'), ctx.peek());
try testing.expectError(error.UnexpectedEof, ctx.expectStr("aaaaaaaaa"));
try testing.expectEqual(@as(?u8, 'p'), ctx.peek());
try testing.expectError(error.UnexpectedCharacter, ctx.expectStr("pytn"));
try testing.expectEqual(@as(?u8, 'p'), ctx.peek());
try ctx.expectStr("python");
testing.expectEqual(@as(?u8, 's'), ctx.peek());
try testing.expectEqual(@as(?u8, 's'), ctx.peek());
}
{
var ctx = ParseContext.init("");
testing.expectEqual(ctx.peek(), null);
testing.expectError(error.UnexpectedEof, ctx.consume());
testing.expectEqual(ctx.eat('p'), false);
testing.expectError(error.UnexpectedEof, ctx.expect('p'));
try testing.expectEqual(ctx.peek(), null);
try testing.expectError(error.UnexpectedEof, ctx.consume());
try testing.expectEqual(ctx.eat('p'), false);
try testing.expectError(error.UnexpectedEof, ctx.expect('p'));
}
}
pub const ParseError = error {
pub const ParseError = error{
IllegalCharacter,
UnexpectedEof,
UnexpectedCharacter,
@@ -311,7 +311,7 @@ pub const ParseError = error {
InvalidStandaloneValue,
NonMatchingClosingTag,
InvalidDocument,
OutOfMemory
OutOfMemory,
};
pub fn parse(backing_allocator: *Allocator, source: []const u8) !Document {
@@ -323,15 +323,20 @@ fn parseDocument(ctx: *ParseContext, backing_allocator: *Allocator) !Document {
var doc = Document{
.arena = ArenaAllocator.init(backing_allocator),
.xml_decl = null,
.root = undefined
.root = undefined,
};
errdefer doc.deinit();
try trySkipComments(ctx, &doc.arena.allocator);
doc.xml_decl = try tryParseProlog(ctx, &doc.arena.allocator);
_ = ctx.eatWs();
try trySkipComments(ctx, &doc.arena.allocator);
doc.root = (try tryParseElement(ctx, &doc.arena.allocator)) orelse return error.InvalidDocument;
_ = ctx.eatWs();
try trySkipComments(ctx, &doc.arena.allocator);
if (ctx.peek() != null) return error.InvalidDocument;
@@ -351,7 +356,7 @@ fn parseAttrValue(ctx: *ParseContext, alloc: *Allocator) ![]const u8 {
const end = ctx.offset - 1;
return try dupeAndUnescape(alloc, ctx.source[begin .. end]);
return try dupeAndUnescape(alloc, ctx.source[begin..end]);
}
fn parseEqAttrValue(ctx: *ParseContext, alloc: *Allocator) ![]const u8 {
@@ -371,14 +376,14 @@ fn parseNameNoDupe(ctx: *ParseContext) ![]const u8 {
switch (ch) {
' ', '\t', '\n', '\r' => break,
'&', '"', '\'', '<', '>', '?', '=', '/' => break,
else => _ = ctx.consumeNoEof()
else => _ = ctx.consumeNoEof(),
}
}
const end = ctx.offset;
if (begin == end) return error.InvalidName;
return ctx.source[begin .. end];
return ctx.source[begin..end];
}
fn tryParseCharData(ctx: *ParseContext, alloc: *Allocator) !?[]const u8 {
@@ -386,24 +391,24 @@ fn tryParseCharData(ctx: *ParseContext, alloc: *Allocator) !?[]const u8 {
while (ctx.peek()) |ch| {
switch (ch) {
'<', '>' => break,
else => _ = ctx.consumeNoEof()
'<' => break,
else => _ = ctx.consumeNoEof(),
}
}
const end = ctx.offset;
if (begin == end) return null;
return try dupeAndUnescape(alloc, ctx.source[begin .. end]);
return try dupeAndUnescape(alloc, ctx.source[begin..end]);
}
fn parseContent(ctx: *ParseContext, alloc: *Allocator) ParseError!Content {
if (try tryParseCharData(ctx, alloc)) |cd| {
return Content{.CharData = cd};
return Content{ .CharData = cd };
} else if (try tryParseComment(ctx, alloc)) |comment| {
return Content{.Comment = comment};
return Content{ .Comment = comment };
} else if (try tryParseElement(ctx, alloc)) |elem| {
return Content{.Element = elem};
return Content{ .Element = elem };
} else {
return error.UnexpectedCharacter;
}
@@ -472,41 +477,41 @@ test "tryParseElement" {
{
var ctx = ParseContext.init("<= a='b'/>");
testing.expectEqual(@as(?*Element, null), try tryParseElement(&ctx, alloc));
testing.expectEqual(@as(?u8, '<'), ctx.peek());
try testing.expectEqual(@as(?*Element, null), try tryParseElement(&ctx, alloc));
try testing.expectEqual(@as(?u8, '<'), ctx.peek());
}
{
var ctx = ParseContext.init("<python size='15' color = \"green\"/>");
const elem = try tryParseElement(&ctx, alloc);
testing.expectEqualSlices(u8, elem.?.tag, "python");
try testing.expectEqualSlices(u8, elem.?.tag, "python");
const size_attr = elem.?.attributes.items[0];
testing.expectEqualSlices(u8, size_attr.name, "size");
testing.expectEqualSlices(u8, size_attr.value, "15");
try testing.expectEqualSlices(u8, size_attr.name, "size");
try testing.expectEqualSlices(u8, size_attr.value, "15");
const color_attr = elem.?.attributes.items[1];
testing.expectEqualSlices(u8, color_attr.name, "color");
testing.expectEqualSlices(u8, color_attr.value, "green");
try testing.expectEqualSlices(u8, color_attr.name, "color");
try testing.expectEqualSlices(u8, color_attr.value, "green");
}
{
var ctx = ParseContext.init("<python>test</python>");
const elem = try tryParseElement(&ctx, alloc);
testing.expectEqualSlices(u8, elem.?.tag, "python");
testing.expectEqualSlices(u8, elem.?.children.items[0].CharData, "test");
try testing.expectEqualSlices(u8, elem.?.tag, "python");
try testing.expectEqualSlices(u8, elem.?.children.items[0].CharData, "test");
}
{
var ctx = ParseContext.init("<a>b<c/>d<e/>f<!--g--></a>");
const elem = try tryParseElement(&ctx, alloc);
testing.expectEqualSlices(u8, elem.?.tag, "a");
testing.expectEqualSlices(u8, elem.?.children.items[0].CharData, "b");
testing.expectEqualSlices(u8, elem.?.children.items[1].Element.tag, "c");
testing.expectEqualSlices(u8, elem.?.children.items[2].CharData, "d");
testing.expectEqualSlices(u8, elem.?.children.items[3].Element.tag, "e");
testing.expectEqualSlices(u8, elem.?.children.items[4].CharData, "f");
testing.expectEqualSlices(u8, elem.?.children.items[5].Comment, "g");
try testing.expectEqualSlices(u8, elem.?.tag, "a");
try testing.expectEqualSlices(u8, elem.?.children.items[0].CharData, "b");
try testing.expectEqualSlices(u8, elem.?.children.items[1].Element.tag, "c");
try testing.expectEqualSlices(u8, elem.?.children.items[2].CharData, "d");
try testing.expectEqualSlices(u8, elem.?.children.items[3].Element.tag, "e");
try testing.expectEqualSlices(u8, elem.?.children.items[4].CharData, "f");
try testing.expectEqualSlices(u8, elem.?.children.items[5].Comment, "g");
}
}
@@ -560,24 +565,30 @@ test "tryParseProlog" {
{
var ctx = ParseContext.init("<?xmla version='aa'?>");
testing.expectEqual(@as(?*XmlDecl, null), try tryParseProlog(&ctx, alloc));
testing.expectEqual(@as(?u8, '<'), ctx.peek());
try testing.expectEqual(@as(?*XmlDecl, null), try tryParseProlog(&ctx, alloc));
try testing.expectEqual(@as(?u8, '<'), ctx.peek());
}
{
var ctx = ParseContext.init("<?xml version='aa'?>");
const decl = try tryParseProlog(&ctx, alloc);
testing.expectEqualSlices(u8, "aa", decl.?.version);
testing.expectEqual(@as(?[]const u8, null), decl.?.encoding);
testing.expectEqual(@as(?bool, null), decl.?.standalone);
try testing.expectEqualSlices(u8, "aa", decl.?.version);
try testing.expectEqual(@as(?[]const u8, null), decl.?.encoding);
try testing.expectEqual(@as(?bool, null), decl.?.standalone);
}
{
var ctx = ParseContext.init("<?xml version=\"aa\" encoding = 'bbb' standalone \t = 'yes'?>");
const decl = try tryParseProlog(&ctx, alloc);
testing.expectEqualSlices(u8, "aa", decl.?.version);
testing.expectEqualSlices(u8, "bbb", decl.?.encoding.?);
testing.expectEqual(@as(?bool, true), decl.?.standalone.?);
try testing.expectEqualSlices(u8, "aa", decl.?.version);
try testing.expectEqualSlices(u8, "bbb", decl.?.encoding.?);
try testing.expectEqual(@as(?bool, true), decl.?.standalone.?);
}
}
fn trySkipComments(ctx: *ParseContext, alloc: *Allocator) !void {
while (try tryParseComment(ctx, alloc)) |_| {
_ = ctx.eatWs();
}
}
@@ -590,21 +601,18 @@ fn tryParseComment(ctx: *ParseContext, alloc: *Allocator) !?[]const u8 {
}
const end = ctx.offset - "-->".len;
return try mem.dupe(alloc, u8, ctx.source[begin .. end]);
return try mem.dupe(alloc, u8, ctx.source[begin..end]);
}
fn unescapeEntity(text: []const u8) !u8 {
const EntitySubstition = struct {
text: []const u8,
replacement: u8
};
const EntitySubstition = struct { text: []const u8, replacement: u8 };
const entities = [_]EntitySubstition{
.{.text = "&lt;", .replacement = '<'},
.{.text = "&gt;", .replacement = '>'},
.{.text = "&amp;", .replacement = '&'},
.{.text = "&apos;", .replacement = '\''},
.{.text = "&quot;", .replacement = '"'}
.{ .text = "&lt;", .replacement = '<' },
.{ .text = "&gt;", .replacement = '>' },
.{ .text = "&amp;", .replacement = '&' },
.{ .text = "&apos;", .replacement = '\'' },
.{ .text = "&quot;", .replacement = '"' },
};
for (entities) |entity| {
@@ -622,7 +630,7 @@ fn dupeAndUnescape(alloc: *Allocator, text: []const u8) ![]const u8 {
while (i < text.len) : (j += 1) {
if (text[i] == '&') {
const entity_end = 1 + (mem.indexOfScalarPos(u8, text, i, ';') orelse return error.InvalidEntity);
str[j] = try unescapeEntity(text[i .. entity_end]);
str[j] = try unescapeEntity(text[i..entity_end]);
i = entity_end;
} else {
str[j] = text[i];
@@ -638,10 +646,19 @@ test "dupeAndUnescape" {
defer arena.deinit();
var alloc = &arena.allocator;
testing.expectEqualSlices(u8, "test", try dupeAndUnescape(alloc, "test"));
testing.expectEqualSlices(u8, "a<b&c>d\"e'f<", try dupeAndUnescape(alloc, "a&lt;b&amp;c&gt;d&quot;e&apos;f&lt;"));
testing.expectError(error.InvalidEntity, dupeAndUnescape(alloc, "python&"));
testing.expectError(error.InvalidEntity, dupeAndUnescape(alloc, "python&&"));
testing.expectError(error.InvalidEntity, dupeAndUnescape(alloc, "python&test;"));
testing.expectError(error.InvalidEntity, dupeAndUnescape(alloc, "python&boa"));
try testing.expectEqualSlices(u8, "test", try dupeAndUnescape(alloc, "test"));
try testing.expectEqualSlices(u8, "a<b&c>d\"e'f<", try dupeAndUnescape(alloc, "a&lt;b&amp;c&gt;d&quot;e&apos;f&lt;"));
try testing.expectError(error.InvalidEntity, dupeAndUnescape(alloc, "python&"));
try testing.expectError(error.InvalidEntity, dupeAndUnescape(alloc, "python&&"));
try testing.expectError(error.InvalidEntity, dupeAndUnescape(alloc, "python&test;"));
try testing.expectError(error.InvalidEntity, dupeAndUnescape(alloc, "python&boa"));
}
test "Top level comments" {
var arena = std.heap.ArenaAllocator.init(testing.allocator);
defer arena.deinit();
var alloc = &arena.allocator;
const doc = try parse(alloc, "<?xml version='aa'?><!--comment--><python color='green'/><!--another comment-->");
try testing.expectEqualSlices(u8, "python", doc.root.tag);
}