forked from mirror/vulkan-zig
305 lines
10 KiB
Zig
305 lines
10 KiB
Zig
const std = @import("std");
|
|
const vk = @import("vulkan");
|
|
const c = @import("c.zig");
|
|
const Allocator = std.mem.Allocator;
|
|
|
|
const required_device_extensions = [_][*:0]const u8{vk.extensions.khr_swapchain.name};
|
|
|
|
/// There are 3 levels of bindings in vulkan-zig:
|
|
/// - The Dispatch types (vk.BaseDispatch, vk.InstanceDispatch, vk.DeviceDispatch)
|
|
/// are "plain" structs which just contain the function pointers for a particular
|
|
/// object.
|
|
/// - The Wrapper types (vk.Basewrapper, vk.InstanceWrapper, vk.DeviceWrapper) contains
|
|
/// the Dispatch type, as well as Ziggified Vulkan functions - these return Zig errors,
|
|
/// etc.
|
|
/// - The Proxy types (vk.InstanceProxy, vk.DeviceProxy, vk.CommandBufferProxy,
|
|
/// vk.QueueProxy) contain a pointer to a Wrapper and also contain the object's handle.
|
|
/// Calling Ziggified functions on these types automatically passes the handle as
|
|
/// the first parameter of each function. Note that this type accepts a pointer to
|
|
/// a wrapper struct as there is a problem with LLVM where embedding function pointers
|
|
/// and object pointer in the same struct leads to missed optimizations. If the wrapper
|
|
/// member is a pointer, LLVM will try to optimize it as any other vtable.
|
|
/// The wrappers contain
|
|
const BaseWrapper = vk.BaseWrapper;
|
|
const InstanceWrapper = vk.InstanceWrapper;
|
|
const DeviceWrapper = vk.DeviceWrapper;
|
|
|
|
const Instance = vk.InstanceProxy;
|
|
const Device = vk.DeviceProxy;
|
|
|
|
pub const GraphicsContext = struct {
|
|
pub const CommandBuffer = vk.CommandBufferProxy;
|
|
|
|
allocator: Allocator,
|
|
|
|
vkb: BaseWrapper,
|
|
|
|
instance: Instance,
|
|
surface: vk.SurfaceKHR,
|
|
pdev: vk.PhysicalDevice,
|
|
props: vk.PhysicalDeviceProperties,
|
|
mem_props: vk.PhysicalDeviceMemoryProperties,
|
|
|
|
dev: Device,
|
|
graphics_queue: Queue,
|
|
present_queue: Queue,
|
|
|
|
pub fn init(allocator: Allocator, app_name: [*:0]const u8, window: *c.GLFWwindow) !GraphicsContext {
|
|
var self: GraphicsContext = undefined;
|
|
self.allocator = allocator;
|
|
self.vkb = BaseWrapper.load(c.glfwGetInstanceProcAddress);
|
|
|
|
var extension_names = std.ArrayList([*:0]const u8).init(allocator);
|
|
defer extension_names.deinit();
|
|
// these extensions are to support vulkan in mac os
|
|
// see https://github.com/glfw/glfw/issues/2335
|
|
try extension_names.append("VK_KHR_portability_enumeration");
|
|
try extension_names.append("VK_KHR_get_physical_device_properties2");
|
|
|
|
var glfw_exts_count: u32 = 0;
|
|
const glfw_exts = c.glfwGetRequiredInstanceExtensions(&glfw_exts_count);
|
|
|
|
var i: usize = 0;
|
|
while (i < glfw_exts_count) : (i += 1) {
|
|
try extension_names.append(glfw_exts[i]);
|
|
}
|
|
|
|
const instance = try self.vkb.createInstance(&.{
|
|
.p_application_info = &.{
|
|
.p_application_name = app_name,
|
|
.application_version = @bitCast(vk.makeApiVersion(0, 0, 0, 0)),
|
|
.p_engine_name = app_name,
|
|
.engine_version = @bitCast(vk.makeApiVersion(0, 0, 0, 0)),
|
|
.api_version = @bitCast(vk.API_VERSION_1_2),
|
|
},
|
|
.enabled_extension_count = @intCast(extension_names.items.len),
|
|
.pp_enabled_extension_names = extension_names.items.ptr,
|
|
// enumerate_portability_bit_khr to support vulkan in mac os
|
|
// see https://github.com/glfw/glfw/issues/2335
|
|
.flags = .{ .enumerate_portability_bit_khr = true },
|
|
}, null);
|
|
|
|
const vki = try allocator.create(InstanceWrapper);
|
|
errdefer allocator.destroy(vki);
|
|
vki.* = InstanceWrapper.load(instance, self.vkb.dispatch.vkGetInstanceProcAddr.?);
|
|
self.instance = Instance.init(instance, vki);
|
|
errdefer self.instance.destroyInstance(null);
|
|
|
|
self.surface = try createSurface(self.instance, window);
|
|
errdefer self.instance.destroySurfaceKHR(self.surface, null);
|
|
|
|
const candidate = try pickPhysicalDevice(self.instance, allocator, self.surface);
|
|
self.pdev = candidate.pdev;
|
|
self.props = candidate.props;
|
|
|
|
const dev = try initializeCandidate(self.instance, candidate);
|
|
|
|
const vkd = try allocator.create(DeviceWrapper);
|
|
errdefer allocator.destroy(vkd);
|
|
vkd.* = DeviceWrapper.load(dev, self.instance.wrapper.dispatch.vkGetDeviceProcAddr.?);
|
|
self.dev = Device.init(dev, vkd);
|
|
errdefer self.dev.destroyDevice(null);
|
|
|
|
self.graphics_queue = Queue.init(self.dev, candidate.queues.graphics_family);
|
|
self.present_queue = Queue.init(self.dev, candidate.queues.present_family);
|
|
|
|
self.mem_props = self.instance.getPhysicalDeviceMemoryProperties(self.pdev);
|
|
|
|
return self;
|
|
}
|
|
|
|
pub fn deinit(self: GraphicsContext) void {
|
|
self.dev.destroyDevice(null);
|
|
self.instance.destroySurfaceKHR(self.surface, null);
|
|
self.instance.destroyInstance(null);
|
|
|
|
// Don't forget to free the tables to prevent a memory leak.
|
|
self.allocator.destroy(self.dev.wrapper);
|
|
self.allocator.destroy(self.instance.wrapper);
|
|
}
|
|
|
|
pub fn deviceName(self: *const GraphicsContext) []const u8 {
|
|
return std.mem.sliceTo(&self.props.device_name, 0);
|
|
}
|
|
|
|
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], 0..) |mem_type, i| {
|
|
if (memory_type_bits & (@as(u32, 1) << @truncate(i)) != 0 and mem_type.property_flags.contains(flags)) {
|
|
return @truncate(i);
|
|
}
|
|
}
|
|
|
|
return error.NoSuitableMemoryType;
|
|
}
|
|
|
|
pub fn allocate(self: GraphicsContext, requirements: vk.MemoryRequirements, flags: vk.MemoryPropertyFlags) !vk.DeviceMemory {
|
|
return try self.dev.allocateMemory(&.{
|
|
.allocation_size = requirements.size,
|
|
.memory_type_index = try self.findMemoryTypeIndex(requirements.memory_type_bits, flags),
|
|
}, null);
|
|
}
|
|
};
|
|
|
|
pub const Queue = struct {
|
|
handle: vk.Queue,
|
|
family: u32,
|
|
|
|
fn init(device: Device, family: u32) Queue {
|
|
return .{
|
|
.handle = device.getDeviceQueue(family, 0),
|
|
.family = family,
|
|
};
|
|
}
|
|
};
|
|
|
|
fn createSurface(instance: Instance, window: *c.GLFWwindow) !vk.SurfaceKHR {
|
|
var surface: vk.SurfaceKHR = undefined;
|
|
if (c.glfwCreateWindowSurface(instance.handle, window, null, &surface) != .success) {
|
|
return error.SurfaceInitFailed;
|
|
}
|
|
|
|
return surface;
|
|
}
|
|
|
|
fn initializeCandidate(instance: Instance, candidate: DeviceCandidate) !vk.Device {
|
|
const priority = [_]f32{1};
|
|
const qci = [_]vk.DeviceQueueCreateInfo{
|
|
.{
|
|
.queue_family_index = candidate.queues.graphics_family,
|
|
.queue_count = 1,
|
|
.p_queue_priorities = &priority,
|
|
},
|
|
.{
|
|
.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)
|
|
1
|
|
else
|
|
2;
|
|
|
|
return try instance.createDevice(candidate.pdev, &.{
|
|
.queue_create_info_count = queue_count,
|
|
.p_queue_create_infos = &qci,
|
|
.enabled_extension_count = required_device_extensions.len,
|
|
.pp_enabled_extension_names = @ptrCast(&required_device_extensions),
|
|
}, null);
|
|
}
|
|
|
|
const DeviceCandidate = struct {
|
|
pdev: vk.PhysicalDevice,
|
|
props: vk.PhysicalDeviceProperties,
|
|
queues: QueueAllocation,
|
|
};
|
|
|
|
const QueueAllocation = struct {
|
|
graphics_family: u32,
|
|
present_family: u32,
|
|
};
|
|
|
|
fn pickPhysicalDevice(
|
|
instance: Instance,
|
|
allocator: Allocator,
|
|
surface: vk.SurfaceKHR,
|
|
) !DeviceCandidate {
|
|
const pdevs = try instance.enumeratePhysicalDevicesAlloc(allocator);
|
|
defer allocator.free(pdevs);
|
|
|
|
for (pdevs) |pdev| {
|
|
if (try checkSuitable(instance, pdev, allocator, surface)) |candidate| {
|
|
return candidate;
|
|
}
|
|
}
|
|
|
|
return error.NoSuitableDevice;
|
|
}
|
|
|
|
fn checkSuitable(
|
|
instance: Instance,
|
|
pdev: vk.PhysicalDevice,
|
|
allocator: Allocator,
|
|
surface: vk.SurfaceKHR,
|
|
) !?DeviceCandidate {
|
|
if (!try checkExtensionSupport(instance, pdev, allocator)) {
|
|
return null;
|
|
}
|
|
|
|
if (!try checkSurfaceSupport(instance, pdev, surface)) {
|
|
return null;
|
|
}
|
|
|
|
if (try allocateQueues(instance, pdev, allocator, surface)) |allocation| {
|
|
const props = instance.getPhysicalDeviceProperties(pdev);
|
|
return DeviceCandidate{
|
|
.pdev = pdev,
|
|
.props = props,
|
|
.queues = allocation,
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
fn allocateQueues(instance: Instance, pdev: vk.PhysicalDevice, allocator: Allocator, surface: vk.SurfaceKHR) !?QueueAllocation {
|
|
const families = try instance.getPhysicalDeviceQueueFamilyPropertiesAlloc(pdev, allocator);
|
|
defer allocator.free(families);
|
|
|
|
var graphics_family: ?u32 = null;
|
|
var present_family: ?u32 = null;
|
|
|
|
for (families, 0..) |properties, i| {
|
|
const family: u32 = @intCast(i);
|
|
|
|
if (graphics_family == null and properties.queue_flags.graphics_bit) {
|
|
graphics_family = family;
|
|
}
|
|
|
|
if (present_family == null and (try instance.getPhysicalDeviceSurfaceSupportKHR(pdev, family, surface)) == vk.TRUE) {
|
|
present_family = family;
|
|
}
|
|
}
|
|
|
|
if (graphics_family != null and present_family != null) {
|
|
return QueueAllocation{
|
|
.graphics_family = graphics_family.?,
|
|
.present_family = present_family.?,
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
fn checkSurfaceSupport(instance: Instance, pdev: vk.PhysicalDevice, surface: vk.SurfaceKHR) !bool {
|
|
var format_count: u32 = undefined;
|
|
_ = try instance.getPhysicalDeviceSurfaceFormatsKHR(pdev, surface, &format_count, null);
|
|
|
|
var present_mode_count: u32 = undefined;
|
|
_ = try instance.getPhysicalDeviceSurfacePresentModesKHR(pdev, surface, &present_mode_count, null);
|
|
|
|
return format_count > 0 and present_mode_count > 0;
|
|
}
|
|
|
|
fn checkExtensionSupport(
|
|
instance: Instance,
|
|
pdev: vk.PhysicalDevice,
|
|
allocator: Allocator,
|
|
) !bool {
|
|
const propsv = try instance.enumerateDeviceExtensionPropertiesAlloc(pdev, null, allocator);
|
|
defer allocator.free(propsv);
|
|
|
|
for (required_device_extensions) |ext| {
|
|
for (propsv) |props| {
|
|
if (std.mem.eql(u8, std.mem.span(ext), std.mem.sliceTo(&props.extension_name, 0))) {
|
|
break;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|