Skip to content

Commit

Permalink
Create Vulkan context
Browse files Browse the repository at this point in the history
  • Loading branch information
siniarskimar committed Jul 8, 2024
1 parent a874133 commit 7486dff
Show file tree
Hide file tree
Showing 5 changed files with 303 additions and 3 deletions.
14 changes: 12 additions & 2 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,30 @@ pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});

const scanner = zig_wayland.Scanner.create(b, .{});

const mod_wayland = b.createModule(.{ .root_source_file = scanner.result });
scanner.addSystemProtocol("stable/xdg-shell/xdg-shell.xml");

scanner.generate("xdg_wm_base", 3);
scanner.generate("wl_compositor", 6);

const vk_registry = b.dependency("vulkan_headers", .{}).path("registry/vk.xml");
const dep_vkzig = b.dependency("vulkan_zig", .{
.optimize = std.builtin.OptimizeMode.ReleaseSafe,
});
const vk_gen = dep_vkzig.artifact("vulkan-zig-generator");
const run_vk_gen = b.addRunArtifact(vk_gen);
run_vk_gen.addFileArg(vk_registry);

const mod_vulkan = b.createModule(.{ .root_source_file = run_vk_gen.addOutputFileArg("vk.zig") });
const mod_wayland = b.createModule(.{ .root_source_file = scanner.result });

const exe_zentura = b.addExecutable(.{
.name = "zentura",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
exe_zentura.root_module.addImport("wayland", mod_wayland);
exe_zentura.root_module.addImport("vulkan", mod_vulkan);
exe_zentura.linkLibC();
exe_zentura.linkSystemLibrary("wayland-client");
scanner.addCSource(exe_zentura);
Expand Down
8 changes: 8 additions & 0 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,13 @@
.url = "https://github.com/ifreund/zig-wayland/archive/master.zip",
.hash = "1220ab835f090bde18b74191e88c4ab3bd49364d310bd9912eccebd3c47bad7957a3",
},
.vulkan_headers = .{
.url = "https://github.com/KhronosGroup/Vulkan-Headers/archive/190d2cb24e90e5bf2bec0a75604a9b3586485b6d.tar.gz",
.hash = "1220e70b9e0d124b04778cebdecabda4cdc5caab4bbaa9a1bbb9bba5ee51245b73a1",
},
.vulkan_zig = .{
.url = "https://github.com/Snektron/vulkan-zig/archive/66b7b773bb61e2102025f2d5ff0ae8c5f53e19cc.tar.gz",
.hash = "12208958f173b8b81bfac797955f0416ab38b21d1f69d4ebf6c7ca460a828a41cd45",
},
},
}
15 changes: 15 additions & 0 deletions src/main.zig
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
const std = @import("std");
const wayland = @import("./wayland.zig");
const vulkan = @import("./vulkan.zig");
const WlContext = wayland.WlContext;
const WlWindow = wayland.WlWindow;

pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();

var vksolib = try vulkan.loadSharedLibrary();
defer vksolib.close();

var wlcontext = try WlContext.init();
defer wlcontext.deinit();

Expand All @@ -21,6 +28,14 @@ pub fn main() !void {
if (wlwindow.context.wl_display.roundtrip() != .SUCCESS) return error.RoundtripFailed;
wlwindow.wl_surface.commit();

var vkcontext = try vulkan.Context.initWayland(gpa.allocator(), wlcontext.wl_display, wlwindow.wl_surface);
defer vkcontext.deinit(gpa.allocator());

wlwindow.wl_surface.commit();
if (wlwindow.context.wl_display.roundtrip() != .SUCCESS) return error.RoundtripFailed;

std.debug.print("{s}\n", .{std.mem.sliceTo(&vkcontext.pdevprops.device_name, 0)});

while (!wlwindow.is_closed) {
std.time.sleep(std.time.ns_per_ms);
if (wlwindow.context.wl_display.roundtrip() != .SUCCESS) return error.RoundtripFailed;
Expand Down
264 changes: 264 additions & 0 deletions src/vulkan.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
const std = @import("std");
const builtin = @import("builtin");

const wayland = if (builtin.os.tag == .linux) @import("wayland").client else {};
const wl = wayland.wl;
const vk = @import("vulkan");

const apis: []const vk.ApiInfo = &.{
.{
.base_commands = .{
.createInstance = true,
},
.instance_commands = .{
.createDevice = true,
},
},
vk.features.version_1_0,
vk.extensions.khr_surface,
vk.extensions.khr_wayland_surface,
vk.extensions.khr_swapchain,
};

const required_instance_extensions = [_][*:0]const u8{
vk.extensions.khr_surface.name,
vk.extensions.khr_wayland_surface.name,
};

const required_device_extensions = [_][*:0]const u8{
vk.extensions.khr_swapchain.name,
};

const BaseDispatch = vk.BaseWrapper(apis);
const InstanceDispatch = vk.InstanceWrapper(apis);
const DeviceDispatch = vk.DeviceWrapper(apis);

const Instance = vk.InstanceProxy(apis);
const Device = vk.DeviceProxy(apis);

const vkGetInstanceProcAddressFn = *const fn (instance: vk.Instance, procname: [*:0]const u8) vk.PfnVoidFunction;

var vkGetInstanceProcAddress: ?vkGetInstanceProcAddressFn = null;

pub fn loadSharedLibrary() !std.DynLib {
// TODO: Search for shared library
if (builtin.os.tag != .linux) @compileError("Only Linux is supported");

var lib = try std.DynLib.open("/usr/lib/libvulkan.so");

vkGetInstanceProcAddress = lib.lookup(vkGetInstanceProcAddressFn, "vkGetInstanceProcAddr") orelse
return error.LookupFailed;
return lib;
}

pub const Context = struct {
instance: Instance,
base_dispatch: BaseDispatch,

surface: vk.SurfaceKHR,

pdev: vk.PhysicalDevice,
pdevprops: vk.PhysicalDeviceProperties,
pdevmemprops: vk.PhysicalDeviceMemoryProperties,

dev: Device,
graphics_queue: Queue,
present_queue: Queue,

getInstanceProcAddress: vkGetInstanceProcAddressFn,

const app_info: vk.ApplicationInfo = .{
.p_application_name = "zentura",
.application_version = 0,
.engine_version = 0,
.api_version = vk.API_VERSION_1_0,
};

pub const CommandBuffer = vk.CommandBufferProxy(apis);

fn init(allocator: std.mem.Allocator) !@This() {
var self: Context = undefined;
self.getInstanceProcAddress = vkGetInstanceProcAddress orelse return error.VkGetInstanceProcAddressNull;

self.base_dispatch = try BaseDispatch.load(self.getInstanceProcAddress);
const instance = try self.base_dispatch.createInstance(&vk.InstanceCreateInfo{
.p_application_info = &app_info,
.enabled_extension_count = @intCast(required_instance_extensions.len),
.pp_enabled_extension_names = @ptrCast(&required_instance_extensions),
.flags = .{},
}, null);

const instance_dispatch = try allocator.create(InstanceDispatch);
errdefer allocator.destroy(instance_dispatch);
instance_dispatch.* = try InstanceDispatch.load(instance, self.getInstanceProcAddress);
self.instance = Instance.init(instance, instance_dispatch);

return self;
}

pub fn initWayland(allocator: std.mem.Allocator, wl_display: *wl.Display, wl_surface: *wl.Surface) !@This() {
var self = try init(allocator);
errdefer allocator.destroy(self.instance.wrapper);
errdefer self.instance.destroyInstance(null);

self.surface = try self.instance.createWaylandSurfaceKHR(&vk.WaylandSurfaceCreateInfoKHR{
.display = @ptrCast(wl_display),
.surface = @ptrCast(wl_surface),
.flags = .{},
}, null);
errdefer self.instance.destroySurfaceKHR(self.surface, null);

const device_candidate = try findDeviceCandidate(allocator, self.instance, self.surface);
self.pdev = device_candidate.pdev;
self.pdevprops = device_candidate.props;

const device = try initializeDeviceCandidate(self.instance, device_candidate);

const device_dispatch = try allocator.create(DeviceDispatch);
errdefer allocator.destroy(device_dispatch);

device_dispatch.* = try DeviceDispatch.load(device, self.instance.wrapper.dispatch.vkGetDeviceProcAddr);
self.dev = Device.init(device, device_dispatch);
errdefer self.dev.destroyDevice(null);

self.graphics_queue = Queue.init(self.dev, device_candidate.queues.graphics_family, 0);
self.present_queue = Queue.init(self.dev, device_candidate.queues.present_family, 0);

self.pdevmemprops = self.instance.getPhysicalDeviceMemoryProperties(self.pdev);

return self;
}

pub fn deinit(self: @This(), allocator: std.mem.Allocator) void {
self.dev.destroyDevice(null);
allocator.destroy(self.dev.wrapper);

self.instance.destroySurfaceKHR(self.surface, null);

self.instance.destroyInstance(null);
allocator.destroy(self.instance.wrapper);
}

fn initializeDeviceCandidate(instance: Instance, candidate: DeviceCandidate) !vk.Device {
const priority = [_]f32{1};

return try instance.createDevice(candidate.pdev, &vk.DeviceCreateInfo{
.queue_create_info_count = if (candidate.queues.graphics_family == candidate.queues.present_family)
1
else
2,
.p_queue_create_infos = &[_]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,
} },
.enabled_extension_count = @intCast(required_device_extensions.len),
.pp_enabled_extension_names = &required_device_extensions,
}, null);
}

const Queue = struct {
handle: vk.Queue,
family: u32,

fn init(device: Device, family: u32, queue_index: u32) @This() {
return .{
.handle = device.getDeviceQueue(family, queue_index),
.family = family,
};
}
};

const DeviceQueueFamilies = struct {
graphics_family: u32,
present_family: u32,
};

const DeviceCandidate = struct {
pdev: vk.PhysicalDevice,
props: vk.PhysicalDeviceProperties,
queues: DeviceQueueFamilies,
};

fn findDeviceCandidate(allocator: std.mem.Allocator, instance: Instance, surface: vk.SurfaceKHR) !DeviceCandidate {
const physical_devices = try instance.enumeratePhysicalDevicesAlloc(allocator);
defer allocator.free(physical_devices);

if (physical_devices.len == 0) return error.NoPhysicalDevices;

return for (physical_devices) |device| {
if (!(try deviceSupportsRequiredExtensions(allocator, instance, device))) {
continue;
}
if (!(try deviceSupportsSurface(instance, device, surface))) {
continue;
}
const queues = allocateQueues(allocator, instance, device, surface) catch |err| switch (err) {
error.NoSuitableQueues => continue,
else => return err,
};
break DeviceCandidate{
.pdev = device,
.props = instance.getPhysicalDeviceProperties(device),
.queues = queues,
};
} else error.NoSuitableDevice;
}

fn allocateQueues(allocator: std.mem.Allocator, instance: Instance, dev: vk.PhysicalDevice, surface: vk.SurfaceKHR) !DeviceQueueFamilies {
const families = try instance.getPhysicalDeviceQueueFamilyPropertiesAlloc(dev, allocator);
defer allocator.free(families);

var graphics: ?u32 = null;
var present: ?u32 = null;

return for (families, 0..) |fprops, idx| {
const family: u32 = @intCast(idx);

if (graphics == null and fprops.queue_flags.graphics_bit) {
graphics = family;
}
if (present == null and (try instance.getPhysicalDeviceSurfaceSupportKHR(dev, family, surface) == vk.TRUE)) {
present = family;
}
if (graphics != null and present != null) {
break DeviceQueueFamilies{
.graphics_family = graphics.?,
.present_family = present.?,
};
}
} else error.NoSuitableQueues;
}

fn deviceSupportsRequiredExtensions(allocator: std.mem.Allocator, instance: Instance, device: vk.PhysicalDevice) !bool {
const ext_properties = try instance.enumerateDeviceExtensionPropertiesAlloc(device, null, allocator);
defer allocator.free(ext_properties);

// TODO: Explore if this is the most optimal way of doing this
for (required_device_extensions) |ext| {
for (ext_properties) |props| {
if (std.mem.eql(u8, std.mem.span(ext), std.mem.sliceTo(&props.extension_name, 0))) {
break;
}
} else {
return false;
}
}

return true;
}

fn deviceSupportsSurface(instance: Instance, device: vk.PhysicalDevice, surface: vk.SurfaceKHR) !bool {
var format_count: u32 = undefined;
var present_mode_count: u32 = undefined;

_ = try instance.getPhysicalDeviceSurfaceFormatsKHR(device, surface, &format_count, null);
_ = try instance.getPhysicalDeviceSurfacePresentModesKHR(device, surface, &present_mode_count, null);

return format_count > 0 and present_mode_count > 0;
}
};
5 changes: 4 additions & 1 deletion src/wayland.zig
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ pub const WlWindow = struct {
wl_surface: *wl.Surface,
xdg_surface: *xdg.Surface,
xdg_toplevel: *xdg.Toplevel,
frame_callback: ?*wl.Callback = null,

// Properties
width: u32 = 0,
Expand Down Expand Up @@ -132,6 +131,10 @@ pub const WlWindow = struct {
data.got_resized = true;
data.width = @intCast(ev.width);
data.height = @intCast(ev.height);
} else if (ev.width == 0 and ev.height == 0) {
data.got_resized = true;
data.width = 800;
data.height = 600;
}
},
.close => {
Expand Down

0 comments on commit 7486dff

Please sign in to comment.