Vulkan: Select the physical device (#1749)

progress towards #102

I also made a new Window, which is hidden by default, but we can use it
to start drawing things in Vulkan parallel to the rest of the game in
the future, to ease the transition process.
This commit is contained in:
IntegratedQuantum 2025-08-10 13:21:13 +02:00 committed by GitHub
parent 2ef1e1860f
commit a4a79603ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 170 additions and 21 deletions

View File

@ -9,37 +9,37 @@
.lazy = true,
},
.cubyz_deps_headers = .{
.url = "https://github.com/PixelGuys/Cubyz-Libs/releases/download/7/cubyz_deps_headers.tar.gz",
.hash = "N-V-__8AAKJ9OwA8jY0yp1Lokn0e8tdmOaz1MLUCFh-azTZq",
.url = "https://github.com/PixelGuys/Cubyz-Libs/releases/download/8/cubyz_deps_headers.tar.gz",
.hash = "N-V-__8AAI-aOwAGCfJiF1xWZSQ0yxGSyyuj-VO5P_UqqyJ0",
},
.cubyz_deps_aarch64_macos = .{
.url = "https://github.com/PixelGuys/Cubyz-Libs/releases/download/7/cubyz_deps_aarch64-macos-none.tar.gz",
.hash = "N-V-__8AACiCRALoq18Einwt-YUa-hM441UctNoLBgclCjS8",
.url = "https://github.com/PixelGuys/Cubyz-Libs/releases/download/8/cubyz_deps_aarch64-macos-none.tar.gz",
.hash = "N-V-__8AAI-aOwAGCfJiF1xWZSQ0yxGSyyuj-VO5P_UqqyJ0",
.lazy = true,
},
.cubyz_deps_aarch64_linux = .{
.url = "https://github.com/PixelGuys/Cubyz-Libs/releases/download/7/cubyz_deps_aarch64-linux-musl.tar.gz",
.hash = "N-V-__8AAHg2lgJB3mcjhodFwzlJJ8_Nm1SmGackQ5268vg-",
.url = "https://github.com/PixelGuys/Cubyz-Libs/releases/download/8/cubyz_deps_aarch64-linux-musl.tar.gz",
.hash = "N-V-__8AAI-aOwAGCfJiF1xWZSQ0yxGSyyuj-VO5P_UqqyJ0",
.lazy = true,
},
.cubyz_deps_aarch64_windows = .{
.url = "https://github.com/PixelGuys/Cubyz-Libs/releases/download/7/cubyz_deps_aarch64-windows-gnu.tar.gz",
.hash = "N-V-__8AAPYXtQJNrOeyHuVk3vNvOh8XMQ3p23ZXhlzf-yRK",
.url = "https://github.com/PixelGuys/Cubyz-Libs/releases/download/8/cubyz_deps_aarch64-windows-gnu.tar.gz",
.hash = "N-V-__8AAAI8tQKULcx4VW98BqluDNYJhHtN2OBlFw2Cm19f",
.lazy = true,
},
.cubyz_deps_x86_64_macos = .{
.url = "https://github.com/PixelGuys/Cubyz-Libs/releases/download/7/cubyz_deps_x86_64-macos-none.tar.gz",
.hash = "N-V-__8AAIydPQJgYAFNXcw3ytC6-hFSUf-cJPUmcD26cPWd",
.url = "https://github.com/PixelGuys/Cubyz-Libs/releases/download/8/cubyz_deps_x86_64-macos-none.tar.gz",
.hash = "N-V-__8AANi9PQLVH2WpYTmNnlcdBHDkNZI9yJz6fAznklHu",
.lazy = true,
},
.cubyz_deps_x86_64_linux = .{
.url = "https://github.com/PixelGuys/Cubyz-Libs/releases/download/7/cubyz_deps_x86_64-linux-musl.tar.gz",
.hash = "N-V-__8AANZnlAJC50QAqnYfjyySyZkE0iiuvozhG4-ZSIiY",
.url = "https://github.com/PixelGuys/Cubyz-Libs/releases/download/8/cubyz_deps_x86_64-linux-musl.tar.gz",
.hash = "N-V-__8AAIKQlALN_67_ilCxZcxIGddSBBi7A4lVVa0jFeW9",
.lazy = true,
},
.cubyz_deps_x86_64_windows = .{
.url = "https://github.com/PixelGuys/Cubyz-Libs/releases/download/7/cubyz_deps_x86_64-windows-gnu.tar.gz",
.hash = "N-V-__8AAKwH1wLR7xuDyeH7WPnmJKSQWpj4LAcg_EV-wAX7",
.url = "https://github.com/PixelGuys/Cubyz-Libs/releases/download/8/cubyz_deps_x86_64-windows-gnu.tar.gz",
.hash = "N-V-__8AAM4p1wKrLLOhfB8egk7fpA7WnEGIU46h_pKk8Xou",
.lazy = true,
},
.cubyz_large_assets = .{

View File

@ -20,6 +20,7 @@ pub var lastUsedMouse: bool = true;
pub var width: u31 = 1280;
pub var height: u31 = 720;
pub var window: *c.GLFWwindow = undefined;
pub var vulkanWindow: *c.GLFWwindow = undefined;
pub var grabbed: bool = false;
pub var scrollOffset: f32 = 0;
@ -646,9 +647,16 @@ pub fn init() void { // MARK: init()
if(c.glfwVulkanSupported() == c.GLFW_FALSE) {
std.log.err("Vulkan is not supported. Please update your drivers if you want to keep playing Cubyz in the future.", .{});
} else {
vulkan.init();
c.glfwWindowHint(c.GLFW_CLIENT_API, c.GLFW_NO_API);
c.glfwWindowHint(c.GLFW_VISIBLE, c.GLFW_FALSE);
vulkanWindow = c.glfwCreateWindow(width, height, "Cubyz", null, null) orelse @panic("Failed to create GLFW window");
vulkan.init(vulkanWindow) catch |err| {
std.log.err("Error while initializing Vulkan: {s}", .{@errorName(err)});
};
}
c.glfwWindowHint(c.GLFW_CLIENT_API, c.GLFW_OPENGL_API);
c.glfwWindowHint(c.GLFW_VISIBLE, c.GLFW_TRUE);
c.glfwWindowHint(c.GLFW_OPENGL_DEBUG_CONTEXT, 1);
c.glfwWindowHint(c.GLFW_CONTEXT_VERSION_MAJOR, 4);
c.glfwWindowHint(c.GLFW_CONTEXT_VERSION_MINOR, 6);
@ -692,6 +700,7 @@ pub fn init() void { // MARK: init()
pub fn deinit() void {
Gamepad.deinit();
c.glfwDestroyWindow(window);
c.glfwDestroyWindow(vulkanWindow);
vulkan.deinit();
c.glfwTerminate();
}

View File

@ -75,31 +75,63 @@ fn checkResultIfAvailable(result: anytype) void {
fn allocEnumerationGeneric(function: anytype, allocator: NeverFailingAllocator, args: anytype) []@typeInfo(@typeInfo(@TypeOf(function)).@"fn".params[@typeInfo(@TypeOf(function)).@"fn".params.len - 1].type.?).pointer.child {
const T = @typeInfo(@typeInfo(@TypeOf(function)).@"fn".params[@typeInfo(@TypeOf(function)).@"fn".params.len - 1].type.?).pointer.child;
var count: u32 = 0;
checkResultIfAvailable(@call(.auto, function, args ++ .{&count, null}));
const list = allocator.alloc(T, count);
checkResultIfAvailable(@call(.auto, function, args ++ .{&count, list.ptr}));
return list;
while(true) {
checkResultIfAvailable(@call(.auto, function, args ++ .{&count, null}));
const list = allocator.alloc(T, count);
const result = @call(.auto, function, args ++ .{&count, list.ptr});
if(@TypeOf(result) != void and result == c.VK_INCOMPLETE) {
allocator.free(list);
continue;
}
checkResultIfAvailable(result);
if(count < list.len) return allocator.realloc(list, count);
return list;
}
}
// MARK: Enumerators
pub fn enumerateInstanceLayerProperties(allocator: NeverFailingAllocator) []c.VkLayerProperties {
return allocEnumerationGeneric(c.vkEnumerateInstanceLayerProperties, allocator, .{});
}
pub fn enumerateInstanceExtensionProperties(allocator: NeverFailingAllocator, layerName: ?[*:0]u8) []c.VkExtensionProperties {
pub fn enumerateInstanceExtensionProperties(allocator: NeverFailingAllocator, layerName: ?[*:0]const u8) []c.VkExtensionProperties {
return allocEnumerationGeneric(c.vkEnumerateInstanceExtensionProperties, allocator, .{layerName});
}
pub fn enumeratePhysicalDevices(allocator: NeverFailingAllocator) []c.VkPhysicalDevice {
return allocEnumerationGeneric(c.vkEnumeratePhysicalDevices, allocator, .{instance});
}
pub fn enumerateDeviceExtensionProperties(allocator: NeverFailingAllocator, dev: c.VkPhysicalDevice, layerName: ?[*:0]const u8) []c.VkExtensionProperties {
return allocEnumerationGeneric(c.vkEnumerateDeviceExtensionProperties, allocator, .{dev, layerName});
}
pub fn getPhysicalDeviceQueueFamilyProperties(allocator: NeverFailingAllocator, dev: c.VkPhysicalDevice) []c.VkQueueFamilyProperties {
return allocEnumerationGeneric(c.vkGetPhysicalDeviceQueueFamilyProperties, allocator, .{dev});
}
pub fn getPhysicalDeviceSurfaceFormatsKHR(allocator: NeverFailingAllocator, dev: c.VkPhysicalDevice) []c.VkSurfaceFormatKHR {
return allocEnumerationGeneric(c.vkGetPhysicalDeviceSurfaceFormatsKHR, allocator, .{dev, surface});
}
// MARK: globals
var instance: c.VkInstance = undefined;
var surface: c.VkSurfaceKHR = undefined;
var physicalDevice: c.VkPhysicalDevice = undefined;
// MARK: init
pub fn init() void {
pub fn init(window: ?*c.GLFWwindow) !void {
createInstance();
checkResult(c.glfwCreateWindowSurface(instance, window, null, &surface));
try pickPhysicalDevice();
}
pub fn deinit() void {
c.vkDestroySurfaceKHR(instance, surface, null);
c.vkDestroyInstance(instance, null);
}
@ -156,3 +188,111 @@ pub fn createInstance() void {
};
checkResult(c.vkCreateInstance(&createInfo, null, &instance));
}
// MARK: Physical Device
const deviceExtensions = [_][*:0]const u8{
c.VK_KHR_SWAPCHAIN_EXTENSION_NAME,
};
const QueueFamilyIndidices = struct {
graphicsFamily: ?u32 = null,
presentFamily: ?u32 = null,
fn isComplete(self: QueueFamilyIndidices) bool {
return self.graphicsFamily != null and self.presentFamily != null;
}
};
fn findQueueFamilies(dev: c.VkPhysicalDevice) QueueFamilyIndidices {
var result: QueueFamilyIndidices = .{};
const queueFamilies = getPhysicalDeviceQueueFamilyProperties(main.stackAllocator, dev);
defer main.stackAllocator.free(queueFamilies);
for(queueFamilies, 0..) |family, i| {
if(family.queueFlags & c.VK_QUEUE_GRAPHICS_BIT != 0) {
result.graphicsFamily = @intCast(i);
}
var presentSupport: u32 = 0;
checkResult(c.vkGetPhysicalDeviceSurfaceSupportKHR(dev, @intCast(i), surface, &presentSupport));
if(presentSupport != 0) {
result.presentFamily = @intCast(i);
}
}
return result;
}
fn checkDeviceExtensionSupport(dev: c.VkPhysicalDevice) bool {
const availableExtension = enumerateDeviceExtensionProperties(main.stackAllocator, dev, null);
defer main.stackAllocator.free(availableExtension);
for(deviceExtensions) |requiredName| continueOuter: {
for(availableExtension) |available| {
if(std.mem.eql(u8, std.mem.span(requiredName), std.mem.span(@as([*:0]const u8, @ptrCast(&available.extensionName))))) {
break :continueOuter;
}
}
std.log.warn("Rejecting device because extension {s} was not found", .{requiredName});
return false;
}
return true;
}
fn getDeviceScore(dev: c.VkPhysicalDevice) f32 {
var properties: c.VkPhysicalDeviceProperties = undefined;
c.vkGetPhysicalDeviceProperties(dev, &properties);
var features: c.VkPhysicalDeviceFeatures = undefined;
c.vkGetPhysicalDeviceFeatures(dev, &features);
std.log.debug("Device: {s}", .{@as([*:0]const u8, @ptrCast(&properties.deviceName))});
std.log.debug("Properties: {}", .{properties});
std.log.debug("Features: {}", .{features});
const baseScore: f32 = switch(properties.deviceType) {
c.VK_PHYSICAL_DEVICE_TYPE_CPU => 1e-9,
c.VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU => 1e9,
c.VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU => 1,
else => 0.1,
};
const availableExtension = enumerateDeviceExtensionProperties(main.stackAllocator, dev, null);
defer main.stackAllocator.free(availableExtension);
std.log.debug("Device extensions:", .{});
for(availableExtension) |ext| {
std.log.debug("\t{s}", .{ext.extensionName});
}
if(!findQueueFamilies(dev).isComplete() or !checkDeviceExtensionSupport(dev)) return 0;
if(features.multiDrawIndirect != c.VK_TRUE) {
std.log.warn("Rejecting device: multDrawIndirect is not supported", .{});
return 0;
}
if(features.dualSrcBlend != c.VK_TRUE) {
std.log.warn("Rejecting device: dual source blending is not supported", .{});
return 0;
}
return baseScore;
}
fn pickPhysicalDevice() !void {
const devices = enumeratePhysicalDevices(main.stackAllocator);
defer main.stackAllocator.free(devices);
if(devices.len == 0) {
return error.NoDevicesFound;
}
var bestScore: f32 = 0;
for(devices) |dev| {
const score = getDeviceScore(dev);
if(score > bestScore) {
bestScore = score;
physicalDevice = dev;
}
}
if(bestScore == 0) {
return error.NoCapableDeviceFound;
}
var properties: c.VkPhysicalDeviceProperties = undefined;
c.vkGetPhysicalDeviceProperties(physicalDevice, &properties);
std.log.info("Selected device {s}", .{@as([*:0]const u8, @ptrCast(&properties.deviceName))});
}