Skip to content

Mikastiv/vk-kickstart

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

vk-kickstart

A Zig library to help with Vulkan initialization inspired by vk-bootstrap

The minimum required version is Vulkan 1.1

This library helps with:

  • Instance creation
  • Setting up debug environment (validation layers and debug messenger)
  • Physical device selection based on a set of criteria
  • Enabling physical device extensions
  • Device creation
  • Swapchain creation
  • Getting queues

Setting up

Add vulkan-zig as a dependency to your build.zig.zon:

zig fetch --save=vulkan_zig "https://github.com/Snektron/vulkan-zig/archive/<COMMIT_HASH>.tar.gz"

Use the same version as vk-kickstart for the commit hash. See build.zig.zon

Then add vk-kickstart:

zig fetch --save https://github.com/Mikastiv/vk-kickstart/archive/<COMMIT_HASH>.tar.gz

Then update your build file with the following:

// Provide the path to the Vulkan registry (requires version >= 1.3.277)
const xml_path: []const u8 = b.pathFromRoot("vk.xml");

// Add the vulkan-zig module
const vkzig_dep = b.dependency("vulkan_zig", .{
    .registry = xml_path,
});
exe.root_module.addImport("vulkan", vkzig_dep.module("vulkan-zig"));

// Add vk-kickstart
const kickstart_dep = b.dependency("vk_kickstart", .{
    .registry = xml_path,
    // Optional, default is false
    .enable_validation = if (optimize == .Debug) true else false,
    // Optional, default is false
    .verbose = true,
});
exe.root_module.addImport("vk-kickstart", kickstart_dep.module("vk-kickstart"));

You can then import vk-kickstart as a module and vulkan-zig

const vkk = @import("vk-kickstart");
const vk = @import("vulkan-zig");

See build.zig for an example

How to use

For a code example, see main.zig

Instance creation

Using the Instance.CreateOptions struct's fields, you can you can choose how you want the instance to be configured like the required api version.

Note: VK_KHR_surface and the platform specific surface extension are automatically enabled. Only works for Windows, MacOS and Linux (xcb, xlib or wayland) for now

const vk = @import("vulkan-zig");

pub const CreateOptions = struct {
    /// Application name
    app_name: [*:0]const u8 = "",
    /// Application version
    app_version: u32 = 0,
    /// Engine name
    engine_name: [*:0]const u8 = "",
    /// Engine version
    engine_version: u32 = 0,
    /// Required Vulkan version (minimum 1.1)
    required_api_version: u32 = vk.API_VERSION_1_1,
    /// Array of required extensions to enable
    /// Note: VK_KHR_surface and the platform specific surface extension are automatically enabled
    required_extensions: []const [*:0]const u8 = &.{},
    /// Array of required layers to enable
    required_layers: []const [*:0]const u8 = &.{},
    /// Vulkan allocation callbacks
    allocation_callbacks: ?*const vk.AllocationCallbacks = null,
    /// Custom debug callback function (or use default)
    debug_callback: vk.PfnDebugUtilsMessengerCallbackEXT = defaultDebugMessageCallback,
    /// Debug message severity filter
    debug_message_severity: vk.DebugUtilsMessageSeverityFlagsEXT = default_message_severity,
    /// Debug message type filter
    debug_message_type: vk.DebugUtilsMessageTypeFlagsEXT = default_message_type,
    /// Debug user data pointer
    debug_user_data: ?*anyopaque = null,
    /// pNext chain
    p_next_chain: ?*anyopaque = null,
};

Pass these options to Instance.create() to create an instance

Physical device selection

You can set criterias to select an appropriate physical device for your application using PhysicalDevice.SelectOptions

Note: VK_KHR_subset (if available) and VK_KHR_swapchain are automatically enabled, no need to add them to the list

const vk = @import("vulkan-zig");

pub const SelectOptions = struct {
    /// Vulkan render surface
    surface: vk.SurfaceKHR,
    /// Name of the device to select
    name: ?[*:0]const u8 = null,
    /// Required Vulkan version (minimum 1.1)
    required_api_version: u32 = vk.API_VERSION_1_1,
    /// Prefered physical device type
    preferred_type: vk.PhysicalDeviceType = .discrete_gpu,
    /// Transfer queue preference
    transfer_queue: QueuePreference = .none,
    /// Compute queue preference
    compute_queue: QueuePreference = .none,
    /// Required local memory size
    required_mem_size: vk.DeviceSize = 0,
    /// Required physical device features
    required_features: vk.PhysicalDeviceFeatures = .{},
    /// Required physical device feature version 1.1
    required_features_11: vk.PhysicalDeviceVulkan11Features = .{},
    /// Required physical device feature version 1.2
    required_features_12: ?vk.PhysicalDeviceVulkan12Features = null,
    /// Required physical device feature version 1.3
    required_features_13: ?vk.PhysicalDeviceVulkan13Features = null,
    /// Array of required physical device extensions to enable
    /// Note: VK_KHR_swapchain and VK_KHR_subset (if available) are automatically enabled
    required_extensions: []const [*:0]const u8 = &.{},
};

Pass these options to PhysicalDevice.select() to select a device

Device creation

For this, you only need to call Device.create() with the previously selected physical device

Swapchain creation

Finally to create a swapchain, use Swapchain.CreateOptions

const vk = @import("vulkan-zig");

pub const CreateOptions = struct {
    /// Desired size (in pixels) of the swapchain image(s)
    /// These values will be clamped within the capabilities of the device
    desired_extent: vk.Extent2D,
    /// Swapchain create flags
    create_flags: vk.SwapchainCreateFlagsKHR = .{},
    /// Desired minimum number of presentable images that the application needs
    /// If left on default, will try to use the minimum of the device + 1
    /// This value will be clamped between the device's minimum and maximum (if there is a max)
    desired_min_image_count: ?u32 = null,
    /// Array of desired image formats, in order of priority
    /// Will fallback to the first found if none match
    desired_formats: []const vk.SurfaceFormatKHR = &.{
        .{ .format = .b8g8r8a8_srgb, .color_space = .srgb_nonlinear_khr },
    },
    /// Array of desired present modes, in order of priority
    /// Will fallback to fifo_khr if none match
    desired_present_modes: []const vk.PresentModeKHR = &.{
        .mailbox_khr,
    },
    /// Desired number of views in a multiview/stereo surface
    /// Will be clamped down if higher than device's max
    desired_array_layer_count: u32 = 1,
    /// Intended usage of the (acquired) swapchain images
    image_usage_flags: vk.ImageUsageFlags = .{ .color_attachment_bit = true },
    /// Value describing the transform, relative to the presentation engine’s natural orientation, applied to the image content prior to presentation
    pre_transform: ?vk.SurfaceTransformFlagsKHR = null,
    /// Value indicating the alpha compositing mode to use when this surface is composited together with other surfaces on certain window systems
    composite_alpha: vk.CompositeAlphaFlagsKHR = .{ .opaque_bit_khr = true },
    /// Discard rendering operation that are not visible
    clipped: vk.Bool32 = vk.TRUE,
    /// Existing non-retired swapchain currently associated with surface
    old_swapchain: ?vk.SwapchainKHR = null,
    /// pNext chain
    p_next_chain: ?*anyopaque = null,
    /// Vulkan allocation callbacks
    allocation_callbacks: ?*const vk.AllocationCallbacks = null,
};

Pass these options and a the logical device to Swapchain.create() to create the swapchain

Vulkan dispatchers

vk-kickstart uses vulkan-zig and you can access vulkan functions using it's dispatcher api with the functions vkb(), vki() and vkd()

const vkk = @import("vk-kickstart");
const vkb = vkk.dispatch.vkb; // Base dispatch
const vki = vkk.dispatch.vki; // Instance dispatch
const vkd = vkk.dispatch.vkd; // Device dispatch

Not all functions are loaded by default. If you need other functions you will need to overwrite them in the root module like you would do for std.log.

In your main.zig:

pub const vulkan_dispatch = struct {
    pub const base = dispatch.base;
    pub const instance = dispatch.instance;
    pub const device = dispatch.device;
};

See dispatch.zig for their definition

You can then use in your code any loaded vulkan functions:

vkd().createRenderPass();
vkd().cmdDrawIndexed();
vkd().allocateCommandBuffers();

Todo list

  • Headless mode