{"id":13711555,"url":"https://github.com/Snektron/vulkan-zig","last_synced_at":"2025-05-06T21:31:39.212Z","repository":{"id":38016691,"uuid":"215272170","full_name":"Snektron/vulkan-zig","owner":"Snektron","description":"Vulkan binding generator for Zig","archived":false,"fork":false,"pushed_at":"2025-05-05T19:16:34.000Z","size":1004,"stargazers_count":621,"open_issues_count":17,"forks_count":62,"subscribers_count":16,"default_branch":"master","last_synced_at":"2025-05-05T20:30:16.788Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Snektron.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-10-15T10:34:44.000Z","updated_at":"2025-05-05T19:16:38.000Z","dependencies_parsed_at":"2024-01-05T10:35:08.432Z","dependency_job_id":"f9d5f878-aa3a-4fb3-99a4-e9e5da939e3a","html_url":"https://github.com/Snektron/vulkan-zig","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Snektron%2Fvulkan-zig","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Snektron%2Fvulkan-zig/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Snektron%2Fvulkan-zig/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Snektron%2Fvulkan-zig/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Snektron","download_url":"https://codeload.github.com/Snektron/vulkan-zig/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252772025,"owners_count":21801832,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T23:01:09.312Z","updated_at":"2025-05-06T21:31:39.203Z","avatar_url":"https://github.com/Snektron.png","language":"Zig","readme":"# vulkan-zig\n\nA Vulkan binding generator for Zig.\n\n[![Actions Status](https://github.com/Snektron/vulkan-zig/workflows/Build/badge.svg)](https://github.com/Snektron/vulkan-zig/actions)\n\n## Overview\n\nvulkan-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.\n\nvulkan-zig is automatically tested daily against the latest vk.xml and zig, and supports vk.xml from version 1.x.163.\n\n## Example\n\nA partial implementation of https://vulkan-tutorial.com is implemented in [examples/triangle.zig](examples/triangle.zig). This example can be ran by executing `zig build --build-file $(pwd)/examples/build.zig run-triangle` in vulkan-zig's root. See in particular the [build file](examples/build.zig), which contains a concrete example of how to use vulkan-zig as a dependency.\n\n### Zig versions\n\nvulkan-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-\u003cversion\u003e-compat` branch.\n\n`master` is compatible and tested with the Zig self-hosted compiler. The `zig-stage1-compat` branch contains a version which is compatible with the Zig stage 1 compiler.\n\n## Features\n### CLI-interface\n\nA CLI-interface is provided to generate vk.zig from the [Vulkan XML registry](https://github.com/KhronosGroup/Vulkan-Docs/blob/main/xml), which is built by default when invoking `zig build` in the project root. To generate vk.zig, simply invoke the program as follows:\n```\n$ zig-out/bin/vulkan-zig-generator path/to/vk.xml output/path/to/vk.zig\n```\nThis reads the xml file, parses its contents, renders the Vulkan bindings, and formats file, before writing the result to the output path. While the intended usage of vulkan-zig is through direct generation from build.zig (see below), the CLI-interface can be used for one-off generation and vendoring the result.\n\n`path/to/vk.xml` can be obtained from several sources:\n- From the LunarG Vulkan SDK. This can either be obtained from [LunarG](https://www.lunarg.com/vulkan-sdk) or usually using the package manager. The registry can then be found at `$VULKAN_SDK/share/vulkan/registry/vk.xml`.\n- Directly from the [Vulkan-Headers GitHub repository](https://github.com/KhronosGroup/Vulkan-Headers/blob/main/registry/vk.xml).\n\n### Generation with the package manager from build.zig\n\nThere is also support for adding this project as a dependency through zig package manager in its current form. In order to do this, add this repo as a dependency in your build.zig.zon:\n```zig\n.{\n    // -- snip --\n    .dependencies = .{\n        // -- snip --\n        .vulkan_zig = .{\n            .url = \"https://github.com/Snektron/vulkan-zig/archive/\u003ccommit SHA\u003e.tar.gz\",\n            .hash = \"\u003cdependency hash\u003e\",\n        },\n    },\n}\n```\nAnd then in your build.zig file, you'll need to add a line like this to your build function:\n```zig\nconst vulkan = b.dependency(\"vulkan_zig\", .{\n    .registry = b.path(\"path/to/vk.xml\"),\n}).module(\"vulkan-zig\");\nexe.root_module.addImport(\"vulkan\", vulkan);\n```\nThat will allow you to `@import(\"vulkan\")` in your executable's source.\n\n#### Generating bindings directly from Vulkan-Headers\n\nBindings can be generated directly from the Vulkan-Headers repository by adding Vulkan-Headers as a dependency, and then passing the path to `vk.xml` from that dependency:\n```zig\n.{\n    // -- snip --\n    .dependencies = .{\n        // -- snip --\n        .vulkan_headers = .{\n            .url = \"https://github.com/KhronosGroup/Vulkan-Headers/archive/v1.3.283.tar.gz\",\n            .hash = \"\u003cdependency hash\u003e\",\n        },\n    },\n}\n```\n```zig\nconst vulkan = b.dependency(\"vulkan_zig\", .{\n    .registry = b.dependency(\"vulkan_headers\", .{}).path(\"registry/vk.xml\"),\n}).module(\"vulkan-zig\");\nexe.root_module.addImport(\"vulkan\", vulkan);\n```\n\n### Manual generation with the package manager from build.zig\n\nBindings can also be generated by invoking the generator directly. This may be useful is some special cases, for example, it integrates particularly well with fetching the registry via the package manager. This can be done by adding the Vulkan-Headers repository to your dependencies, and then passing the `vk.xml` inside it to vulkan-zig-generator:\n```zig\n.{\n    // -- snip --\n    .depdendencies = .{\n        // -- snip --\n        .vulkan_headers = .{\n            .url = \"https://github.com/KhronosGroup/Vulkan-Headers/archive/\u003ccommit SHA\u003e.tar.gz\",\n            .hash = \"\u003cdependency hash\u003e\",\n        },\n    },\n}\n\n```\nAnd then pass `vk.xml` to vulkan-zig-generator as follows:\n```zig\n// Get the (lazy) path to vk.xml:\nconst registry = b.dependency(\"vulkan_headers\", .{}).path(\"registry/vk.xml\");\n// Get generator executable reference\nconst vk_gen = b.dependency(\"vulkan_zig\", .{}).artifact(\"vulkan-zig-generator\");\n// Set up a run step to generate the bindings\nconst vk_generate_cmd = b.addRunArtifact(vk_gen);\n// Pass the registry to the generator\nvk_generate_cmd.addFileArg(registry);\n// Create a module from the generator's output...\nconst vulkan_zig = b.addModule(\"vulkan-zig\", .{\n    .root_source_file = vk_generate_cmd.addOutputFileArg(\"vk.zig\"),\n});\n// ... and pass it as a module to your executable's build command\nexe.root_module.addImport(\"vulkan\", vulkan_zig);\n```\n\nSee [examples/build.zig](examples/build.zig) and [examples/build.zig.zon](examples/build.zig.zon) for a concrete example.\n\n### Function \u0026 field renaming\n\nFunctions and fields are renamed to be more or less in line with [Zig's standard library style](https://ziglang.org/documentation/master/#Style-Guide):\n* The vk prefix is removed everywhere\n  * Structs like `VkInstanceCreateInfo` are renamed to `InstanceCreateInfo`.\n  * Handles like `VkSwapchainKHR` are renamed to `SwapchainKHR` (note that the tag is retained in caps).\n  * Functions like `vkCreateInstance` are generated as `createInstance` as wrapper and as `PfnCreateInstance` as function pointer.\n  * API constants like `VK_WHOLE_SIZE` retain screaming snake case, and are generates as `WHOLE_SIZE`.\n* The type name is stripped from enumeration fields and bitflags, and they are generated in (lower) snake case. For example, `VK_IMAGE_LAYOUT_GENERAL` is generated as just `general`. Note that author tags are also generated to lower case: `VK_SURFACE_TRANSFORM_FLAGS_IDENTITY_BIT_KHR` is translated to `identity_bit_khr`.\n* Container fields and function parameter names are generated in (lower) snake case in a similar manner: `ppEnabledLayerNames` becomes `pp_enabled_layer_names`.\n* Any name which is either an illegal Zig name or a reserved identifier is rendered using `@\"name\"` syntax. For example, `VK_IMAGE_TYPE_2D` is translated to `@\"2d\"`.\n\n### Dispatch Tables\n\nVulkan-zig provides no integration for statically linking libvulkan, and these symbols are not generated at all. Instead, vulkan functions are to be loaded dynamically. For each Vulkan function, a function pointer type is generated using the exact parameters and return types as defined by the Vulkan specification:\n```zig\npub const PfnCreateInstance = fn (\n    p_create_info: *const InstanceCreateInfo,\n    p_allocator: ?*const AllocationCallbacks,\n    p_instance: *Instance,\n) callconv(vulkan_call_conv) Result;\n```\n\nA set of _dispatch table_ structures is generated. A dispatch table simply contains a set of (optional) function pointers to Vulkan API functions, and not much else. Function pointers grouped by the nature of the function as follows:\n* Vulkan functions which are loaded by `vkGetInstanceProcAddr` without the need for passing an instance are placed in `BaseDispatch`.\n* Vulkan functions which are loaded by `vkGetInstanceProcAddr` but do need an instance are placed in `InstanceDispatch`.\n* Vulkan functions which are loaded by `vkGetDeviceProcAddr` are placed in `DeviceDispatch`.\n\n### Wrappers\n\nTo provide more interesting functionality, a set of _wrapper_ types is also generated, one for each dispatch table type. These contain the Zig-versions of each Vulkan API function, along with corresponding error set definitions, return type definitions, etc, where appropriate.\n\nThe wrapper struct then provides wrapper functions for each function pointer in the dispatch struct:\n```zig\npub const BaseWrapper = struct {\n    const Self = @This();\n    const Dispatch = CreateDispatchStruct(cmds);\n\n    dispatch: Dispatch,\n\n    pub const CreateInstanceError = error{\n        OutOfHostMemory,\n        OutOfDeviceMemory,\n        InitializationFailed,\n        LayerNotPresent,\n        ExtensionNotPresent,\n        IncompatibleDriver,\n        Unknown,\n    };\n    pub fn createInstance(\n        self: Self,\n        create_info: InstanceCreateInfo,\n        p_allocator: ?*const AllocationCallbacks,\n    ) CreateInstanceError!Instance {\n        var instance: Instance = undefined;\n        const result = self.dispatch.vkCreateInstance.?(\n            \u0026create_info,\n            p_allocator,\n            \u0026instance,\n        );\n        switch (result) {\n            .success =\u003e {},\n            .error_out_of_host_memory =\u003e return error.OutOfHostMemory,\n            .error_out_of_device_memory =\u003e return error.OutOfDeviceMemory,\n            .error_initialization_failed =\u003e return error.InitializationFailed,\n            .error_layer_not_present =\u003e return error.LayerNotPresent,\n            .error_extension_not_present =\u003e return error.ExtensionNotPresent,\n            .error_incompatible_driver =\u003e return error.IncompatibleDriver,\n            else =\u003e return error.Unknown,\n        }\n        return instance;\n    }\n\n    ...\n};\n```\nWrappers are generated according to the following rules:\n* The return type is determined from the original return type and the parameters.\n  * Any non-const, non-optional single-item pointer is interpreted as an out parameter.\n  * If a command returns a non-error `VkResult` other than `VK_SUCCESS` it is also returned.\n  * If there are multiple return values selected, an additional struct is generated. The original call's return value is called `return_value`, `VkResult` is named `result`, and the out parameters are called the same except `p_` is removed. They are generated in this order.\n* Any const non-optional single-item pointer is interpreted as an in-parameter. For these, one level of indirection is removed so that create info structure pointers can now be passed as values, enabling the ability to use struct literals for these parameters.\n* Error codes are translated into Zig errors.\n* As of yet, there is no specific handling of enumeration style commands or other commands which accept slices.\n\n#### Initializing Wrappers\n\nWrapper types are initialized by the `load` function, which must be passed a _loader_: A function which loads a function pointer by name.\n* 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).\n* For `InstanceWrapper`, this function has signature `fn load(instance: Instance, loader: anytype) Self`, where the type of `loader` must resemble `PfnGetInstanceProcAddr`.\n* For `DeviceWrapper`, this function has signature `fn load(device: Device, loader: anytype) Self`, where the type of `loader` must resemble `PfnGetDeviceProcAddr`.\n\nNote that these functions accepts a loader with the signature of `anytype` instead of `PfnGetInstanceProcAddr`. This is because it is valid for `vkGetInstanceProcAddr` to load itself, in which case the returned function is to be called with the vulkan calling convention. This calling convention is not required for loading vulkan-zig itself, though, and a loader to be called with any calling convention with the target architecture may be passed in. This is particularly useful when interacting with C libraries that provide `vkGetInstanceProcAddr`.\n\n```zig\n// vkGetInstanceProcAddr as provided by GLFW.\n// Note that vk.Instance and vk.PfnVoidFunction are ABI compatible with VkInstance,\n// and that `extern` implies the C calling convention.\npub extern fn glfwGetInstanceProcAddress(instance: vk.Instance, procname: [*:0]const u8) vk.PfnVoidFunction;\n\n// Or provide a custom implementation.\n// This function is called with the unspecified Zig-internal calling convention.\nfn customGetInstanceProcAddress(instance: vk.Instance, procname: [*:0]const u8) vk.PfnVoidFunction {\n    ...\n}\n\n// Both calls are valid.\nconst vkb = BaseWrapper.load(glfwGetInstanceProcAddress);\nconst vkb = BaseWrapper.load(customGetInstanceProcAddress);\n```\n\nThe `load` function tries to load all function pointers unconditionally, regardless of enabled extensions or platform. If a function pointer could not be loaded, its entry in the dispatch table is set to `null`. When invoking a function on a wrapper table, the function pointer is checked for null, and there will be a crash or undefined behavior if it was not loaded properly. That means that **it is up to the programmer to ensure that a function pointer is valid for the platform before calling it**, either by checking whether the associated extension or Vulkan version is supported or simply by checking whether the function pointer is non-null.\n\nOne can access the underlying unwrapped C functions by doing `wrapper.dispatch.vkFuncYouWant.?(..)`.\n\n#### Proxying Wrappers\n\nProxying wrappers wrap a wrapper and a pointer to the associated handle in a single struct, and automatically passes this handle to commands as appropriate. Besides the proxying wrappers for instances and devices, there are also proxying wrappers for queues and command buffers. Proxying wrapper type are constructed in the same way as a regular wrapper, by passing an api specification to them. To initialize a proxying wrapper, it must be passed a handle and a pointer to an appropriate wrapper. For queue and command buffer proxying wrappers, a pointer to a device wrapper must be passed.\n\n```zig\nconst InstanceWrapper = vk.InstanceWrapper;\nconst Instance = vk.InstanceProxy;\n\nconst instance_handle = try vkb.createInstance(...);\nconst vki = try InstanceWrapper.load(instance_handle, vkb.dispatch.vkGetInstanceProcAddr.?);\nconst instance = Instance.load(instance_handle, \u0026vki);\ndefer instance.destroyInstance(null);\n```\n\nFor queue and command buffer proxying wrappers, the `queue` and `cmd` prefix is removed for functions where appropriate. Note that the device proxying wrappers also have the queue and command buffer functions made available for convenience, but there the prefix is not stripped.\n\nNote that the proxy must be passed a _pointer_ to a wrapper. This is because there was a limitation with LLVM in the past, where a struct with an object pointer and its associated function pointers wouldn't be optimized properly. By using a separate function pointer, LLVM knows that the \"vtable\" dispatch struct can never be modified and so it can subject each call to vtable optimizations.\n\n### Bitflags\n\nPacked 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.\n\nEach bit is defaulted to `false`, and the first `bool` is aligned to guarantee the overal alignment\nof each Flags type to guarantee ABI compatibility when passing bitfields through structs:\n```zig\npub const QueueFlags = packed struct {\n    graphics_bit: bool align(@alignOf(Flags)) = false,\n    compute_bit: bool = false,\n    transfer_bit: bool = false,\n    sparse_binding_bit: bool = false,\n    protected_bit: bool = false,\n    _reserved_bit_5: bool = false,\n    _reserved_bit_6: bool = false,\n    ...\n}\n```\nNote that on function call ABI boundaries, this alignment trick is not sufficient. Instead, the flags\nare reinterpreted as an integer which is passed instead. Each flags type is augmented by a mixin which provides `IntType`, an integer which represents the flags on function ABI boundaries. This mixin also provides some common set operation on bitflags:\n```zig\npub fn FlagsMixin(comptime FlagsType: type) type {\n    return struct {\n        pub const IntType = Flags;\n\n        // Return the integer representation of these flags\n        pub fn toInt(self: FlagsType) IntType {...}\n\n        // Turn an integer representation back into a flags type\n        pub fn fromInt(flags: IntType) FlagsType { ... }\n\n        // Return the set-union of `lhs` and `rhs.\n        pub fn merge(lhs: FlagsType, rhs: FlagsType) FlagsType { ... }\n\n        // Return the set-intersection of `lhs` and `rhs`.\n        pub fn intersect(lhs: FlagsType, rhs: FlagsType) FlagsType { ... }\n\n        // Return the set-complement of `lhs` and `rhs`. Note: this also inverses reserved bits.\n        pub fn complement(self: FlagsType) FlagsType { ... }\n\n        // Return the set-subtraction of `lhs` and `rhs`: All fields set in `rhs` are cleared in `lhs`.\n        pub fn subtract(lhs: FlagsType, rhs: FlagsType) FlagsType { ... }\n\n        // Returns whether all bits set in `rhs` are also set in `lhs`.\n        pub fn contains(lhs: FlagsType, rhs: FlagsType) bool { ... }\n    };\n}\n```\n\n### Handles\n\nHandles are generated to a non-exhaustive enum, backed by a `u64` for non-dispatchable handles and `usize` for dispatchable ones:\n```zig\nconst Instance = extern enum(usize) { null_handle = 0, _ };\n```\nThis means that handles are type-safe even when compiling for a 32-bit target.\n\n### Struct defaults\n\nDefaults are generated for certain fields of structs:\n* sType is defaulted to the appropriate value.\n* pNext is defaulted to `null`.\n* No other fields have default values.\n```zig\npub const InstanceCreateInfo = extern struct {\n    s_type: StructureType = .instance_create_info,\n    p_next: ?*const anyopaque = null,\n    flags: InstanceCreateFlags,\n    ...\n};\n```\n\n### Pointer types\n\nPointer types in both commands (wrapped and function pointers) and struct fields are augmented with the following information, where available in the registry:\n* Pointer optional-ness.\n* Pointer const-ness.\n* Pointer size: Either single-item, null-terminated or many-items.\n\nNote that this information is not everywhere as useful in the registry, leading to places where optional-ness is not correct. Most notably, CreateInfo type structures which take a slice often have the item count marked as optional, but the pointer itself not. As of yet, this is not fixed in vulkan-zig. If drivers properly follow the Vulkan specification, these can be initialized to `undefined`, however, [that is not always the case](https://zeux.io/2019/07/17/serializing-pipeline-cache/).\n\n### Platform types\n\nDefaults with the same ABI layout are generated for most platform-defined types. These can either by bitcasted to, or overridden by defining them in the project root:\n```zig\npub const xcb_connection_t = if (@hasDecl(root, \"xcb_connection_t\")) root.xcb_connection_t else opaque{};\n```\nFor some times (such as those from Google Games Platform) no default is known, but an `opaque{}` will be used by default. Usage of these without providing a concrete type in the project root is likely an error.\n\n### Shader compilation\n\nShaders should be compiled by invoking a shader compiler via the build system. For example:\n```zig\npub fn build(b: *Builder) void {\n    ...\n    const vert_cmd = b.addSystemCommand(\u0026.{\n        \"glslc\",\n        \"--target-env=vulkan1.2\",\n        \"-o\"\n    });\n    const vert_spv = vert_cmd.addOutputFileArg(\"vert.spv\");\n    vert_cmd.addFileArg(b.path(\"shaders/triangle.vert\"));\n    exe.root_module.addAnonymousImport(\"vertex_shader\", .{\n        .root_source_file = vert_spv\n    });\n    ...\n}\n```\n\nNote that SPIR-V must be 32-bit aligned when fed to Vulkan. The easiest way to do this is to dereference the shader's bytecode and manually align it as follows:\n```zig\nconst vert_spv align(@alignOf(u32)) = @embedFile(\"vertex_shader\").*;\n```\n\nSee [examples/build.zig](examples/build.zig) for a working example.\n\nFor more advanced shader compiler usage, one may consider a library such as [shader_compiler](https://github.com/Games-by-Mason/shader_compiler).\n\n### Vulkan Video\n\nVulkan-zig also supports generating Vulkan Video bindings. To do this, one additionally pass `--video \u003cvideo.xml\u003e` to the generator, or pass `-Dvideo=\u003cvideo.xml\u003e` to build.zig. If using vulkan-zig via the Zig package manager, the following also works:\n```zig\nconst vulkan_headers = b.dependency(\"vulkan_headers\");\nconst vulkan = b.dependency(\"vulkan_zig\", .{\n    .registry = vulkan_headers.path(\"registry/vk.xml\"),\n    .video = vulkan_headers.path(\"registery/video.xml\"),\n}).module(\"vulkan-zig\");\n```\n\nThe Vulkan Video bindings are not generated by default. In this case, the relevant definitions must be supplied by the user. See [platform types](#platform-types) for how this is done.\n\n## Limitations\n\n* vulkan-zig has as of yet no functionality for selecting feature levels and extensions when generating bindings. This is because when an extension is promoted to Vulkan core, its fields and commands are renamed to lose the extensions author tag (for example, VkSemaphoreWaitFlagsKHR was renamed to VkSemaphoreWaitFlags when it was promoted from an extension to Vulkan 1.2 core). This leads to inconsistencies when only items from up to a certain feature level is included, as these promoted items then need to re-gain a tag.\n\n## See also\n\n* Implementation of https://vulkan-tutorial.com using `@cImport`'ed bindings: https://github.com/andrewrk/zig-vulkan-triangle.\n* Alternative binding generator: https://github.com/SpexGuy/Zig-Vulkan-Headers\n* Zig bindings for GLFW: https://github.com/hexops/mach-glfw\n  * With vulkan-zig integration example: https://github.com/hexops/mach-glfw-vulkan-example\n* Advanced shader compilation: https://github.com/Games-by-Mason/shader_compiler\n","funding_links":[],"categories":["Libraries","Bindings","Multimedia \u0026 Graphics"],"sub_categories":["GPU Computing"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSnektron%2Fvulkan-zig","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FSnektron%2Fvulkan-zig","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSnektron%2Fvulkan-zig/lists"}