14 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
10 changed files with 193 additions and 137 deletions

View File

@@ -2,9 +2,9 @@ name: Build
on:
push:
branches: [ zig-0.8.0-compat ]
branches: [ zig-0.8.1-compat ]
pull_request:
branches: [ zig-0.8.0-compat ]
branches: [ zig-0.8.1-compat ]
schedule:
- cron: '0 6 * * *'
@@ -27,7 +27,7 @@ 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.176-focal.list https://packages.lunarg.com/vulkan/1.2.176/lunarg-vulkan-1.2.176-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

View File

@@ -36,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):
@@ -231,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,
@@ -252,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

@@ -5,77 +5,77 @@ const Allocator = std.mem.Allocator;
const required_device_extensions = [_][]const u8{vk.extension_info.khr_swapchain.name};
const BaseDispatch = vk.BaseWrapper(.{
.CreateInstance,
const BaseDispatch = vk.BaseWrapper(&.{
.createInstance,
});
const InstanceDispatch = vk.InstanceWrapper(.{
.DestroyInstance,
.CreateDevice,
.DestroySurfaceKHR,
.EnumeratePhysicalDevices,
.GetPhysicalDeviceProperties,
.EnumerateDeviceExtensionProperties,
.GetPhysicalDeviceSurfaceFormatsKHR,
.GetPhysicalDeviceSurfacePresentModesKHR,
.GetPhysicalDeviceSurfaceCapabilitiesKHR,
.GetPhysicalDeviceQueueFamilyProperties,
.GetPhysicalDeviceSurfaceSupportKHR,
.GetPhysicalDeviceMemoryProperties,
.GetDeviceProcAddr,
const InstanceDispatch = vk.InstanceWrapper(&.{
.destroyInstance,
.createDevice,
.destroySurfaceKHR,
.enumeratePhysicalDevices,
.getPhysicalDeviceProperties,
.enumerateDeviceExtensionProperties,
.getPhysicalDeviceSurfaceFormatsKHR,
.getPhysicalDeviceSurfacePresentModesKHR,
.getPhysicalDeviceSurfaceCapabilitiesKHR,
.getPhysicalDeviceQueueFamilyProperties,
.getPhysicalDeviceSurfaceSupportKHR,
.getPhysicalDeviceMemoryProperties,
.getDeviceProcAddr,
});
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,
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 {

View File

@@ -251,7 +251,7 @@ 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);

View File

@@ -150,7 +150,6 @@ pub fn main() !void {
);
}
c.glfwSwapBuffers(window);
c.glfwPollEvents();
}

View File

@@ -72,6 +72,7 @@ pub const GenerateStep = struct {
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);

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.values()) |*value| {
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.value_ptr.* = std.ArrayListUnmanaged(reg.Enum.Field){};
}
try res.value_ptr.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 {
@@ -106,8 +95,7 @@ const EnumFieldMerger = struct {
};
pub const Generator = struct {
gpa: *Allocator,
reg_arena: std.heap.ArenaAllocator,
arena: std.heap.ArenaAllocator,
registry: reg.Registry,
id_renderer: IdRenderer,
@@ -118,16 +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();
self.arena.deinit();
}
fn stripFlagBits(self: Generator, name: []const u8) []const u8 {
@@ -142,14 +128,13 @@ pub const Generator = struct {
// 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();
}
// https://github.com/KhronosGroup/Vulkan-Docs/pull/1556
fn fixupBitFlags(self: *Generator) !void {
var seen_bits = std.StringArrayHashMap(void).init(&self.reg_arena.allocator);
var seen_bits = std.StringArrayHashMap(void).init(&self.arena.allocator);
defer seen_bits.deinit();
for (self.registry.decls) |decl| {
@@ -181,7 +166,7 @@ pub const Generator = struct {
}
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);
}
};

View File

@@ -205,6 +205,18 @@ 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().?;
@@ -218,6 +230,7 @@ fn parseContainer(allocator: *Allocator, ty: *xml.Element, is_union: bool) !regi
.stype = maybe_stype,
.fields = members,
.is_union = is_union,
.extends = maybe_extends,
},
},
};

View File

@@ -65,6 +65,7 @@ pub const Container = struct {
};
stype: ?[]const u8,
extends: ?[]const []const u8,
fields: []Field,
is_union: bool,
};

View File

@@ -185,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();
@@ -212,8 +208,6 @@ 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 {
@@ -446,19 +440,70 @@ fn Renderer(comptime WriterType: type) type {
try self.writer.print("pub const {s}Command = enum {{\n", .{dispatch_type_name});
for (self.registry.decls) |decl| {
if (decl.decl_type == .command) {
const command = decl.decl_type.command;
if (classifyCommandDispatch(decl.name, command) == dispatch_type) {
try self.writer.print("{s},\n", .{trimVkNamespace(decl.name)});
}
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");
try self.writer.print(
\\fn {s}CommandToString(cmd: {s}Command) []const u8 {{
\\ return std.meta.tagName(cmd);
\\}}
, .{ dispatch_type_name, dispatch_type_name });
}
fn renderCopyright(self: *Self) !void {
@@ -705,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(", ");
}
}
@@ -713,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")) {
@@ -728,9 +773,21 @@ fn Renderer(comptime WriterType: type) type {
try self.writer.writeAll(" = .");
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));
}
@@ -743,7 +800,7 @@ fn Renderer(comptime WriterType: type) type {
try self.writer.writeAll("pub const ");
try self.renderName(name);
try self.writer.writeAll(" = enum(c_int) {");
try self.writer.writeAll(" = enum(i32) {");
for (enumeration.fields) |field| {
if (field.value == .alias)
@@ -958,18 +1015,16 @@ fn Renderer(comptime WriterType: type) type {
};
try self.writer.print(
\\pub fn {s}Wrapper(comptime cmds: anytype) 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 cmd_name = {s}CommandToString(cmd);
\\ const cmd_type_name = "Pfn" ++ cmd_name;
\\ const cmd_type = @field(GlobalScope, cmd_type_name);
\\ const PfnType = cmd.PfnType();
\\ fields[i] = .{{
\\ .name = "vk" ++ cmd_name,
\\ .field_type = cmd_type,
\\ .name = cmd.symbol(),
\\ .field_type = PfnType,
\\ .default_value = null,
\\ .is_comptime = false,
\\ .alignment = @alignOf(*cmd_type),
\\ .alignment = @alignOf(PfnType),
\\ }};
\\ }}
\\ const Dispatch = @Type(.{{