From a4a79603ba44e762db4fdb34d4e4ccbe22449adf Mon Sep 17 00:00:00 2001 From: IntegratedQuantum <43880493+IntegratedQuantum@users.noreply.github.com> Date: Sun, 10 Aug 2025 13:21:13 +0200 Subject: [PATCH] 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. --- build.zig.zon | 28 ++++---- src/graphics/Window.zig | 11 ++- src/graphics/vulkan.zig | 152 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 170 insertions(+), 21 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index ed8410d7c..3d11c80fa 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -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 = .{ diff --git a/src/graphics/Window.zig b/src/graphics/Window.zig index 967d11853..5cea42bea 100644 --- a/src/graphics/Window.zig +++ b/src/graphics/Window.zig @@ -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(); } diff --git a/src/graphics/vulkan.zig b/src/graphics/vulkan.zig index 6f4beee1b..e31a48ee0 100644 --- a/src/graphics/vulkan.zig +++ b/src/graphics/vulkan.zig @@ -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))}); +}