From 830ef09dd6d6e9393a40ca62cc601cfa06972573 Mon Sep 17 00:00:00 2001 From: IntegratedQuantum Date: Sun, 8 Jun 2025 16:07:16 +0200 Subject: [PATCH] Add a vulkan instance and prepare for starting the vulkan rewrite --- build.zig | 3 + src/graphics/Window.zig | 10 +++ src/graphics/vulkan.zig | 153 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 src/graphics/vulkan.zig diff --git a/build.zig b/build.zig index 36bde4ab..2ac8ae8d 100644 --- a/build.zig +++ b/build.zig @@ -58,10 +58,12 @@ fn linkLibraries(b: *std.Build, exe: *std.Build.Step.Compile, useLocalDeps: bool exe.linkSystemLibrary("gdi32"); exe.linkSystemLibrary("opengl32"); exe.linkSystemLibrary("ws2_32"); + exe.linkSystemLibrary("vulkan-1"); } else if(t.os.tag == .linux) { exe.linkSystemLibrary("asound"); exe.linkSystemLibrary("X11"); exe.linkSystemLibrary("GL"); + exe.linkSystemLibrary("vulkan"); } else if(t.os.tag == .macos) { exe.linkFramework("AudioUnit"); exe.linkFramework("AudioToolbox"); @@ -74,6 +76,7 @@ fn linkLibraries(b: *std.Build, exe: *std.Build.Step.Compile, useLocalDeps: bool exe.addRPath(.{.cwd_relative = "/usr/local/GL/lib"}); exe.root_module.addRPathSpecial("@executable_path/../Library"); exe.addRPath(.{.cwd_relative = "/opt/X11/lib"}); + exe.linkSystemLibrary("vulkan.1"); } else { std.log.err("Unsupported target: {}\n", .{t.os.tag}); } diff --git a/src/graphics/Window.zig b/src/graphics/Window.zig index 881dae64..ebfd11fa 100644 --- a/src/graphics/Window.zig +++ b/src/graphics/Window.zig @@ -6,8 +6,11 @@ const files = main.files; const vec = main.vec; const Vec2f = vec.Vec2f; +const vulkan = @import("vulkan.zig"); + pub const c = @cImport({ @cInclude("glad/glad.h"); + @cDefine("GLFW_INCLUDE_VULKAN", ""); @cInclude("GLFW/glfw3.h"); }); @@ -634,6 +637,12 @@ pub fn init() void { // MARK: init() @panic("Failed to initialize GLFW"); } + 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.", .{}); + } + + vulkan.Instance.init(); + c.glfwWindowHint(c.GLFW_OPENGL_DEBUG_CONTEXT, 1); c.glfwWindowHint(c.GLFW_CONTEXT_VERSION_MAJOR, 4); c.glfwWindowHint(c.GLFW_CONTEXT_VERSION_MINOR, 6); @@ -677,6 +686,7 @@ pub fn init() void { // MARK: init() pub fn deinit() void { Gamepad.deinit(); c.glfwDestroyWindow(window); + vulkan.Instance.deinit(); c.glfwTerminate(); } var cursorVisible: bool = true; diff --git a/src/graphics/vulkan.zig b/src/graphics/vulkan.zig new file mode 100644 index 00000000..cc56c5c9 --- /dev/null +++ b/src/graphics/vulkan.zig @@ -0,0 +1,153 @@ +const std = @import("std"); + +const main = @import("main"); +const c = main.Window.c; + +const Errors = struct { // MARK: Errors + pub const VK_SUCCESS: c_int = 0; + pub const VK_NOT_READY: c_int = 1; + pub const VK_TIMEOUT: c_int = 2; + pub const VK_EVENT_SET: c_int = 3; + pub const VK_EVENT_RESET: c_int = 4; + pub const VK_INCOMPLETE: c_int = 5; + pub const VK_ERROR_OUT_OF_HOST_MEMORY: c_int = -1; + pub const VK_ERROR_OUT_OF_DEVICE_MEMORY: c_int = -2; + pub const VK_ERROR_INITIALIZATION_FAILED: c_int = -3; + pub const VK_ERROR_DEVICE_LOST: c_int = -4; + pub const VK_ERROR_MEMORY_MAP_FAILED: c_int = -5; + pub const VK_ERROR_LAYER_NOT_PRESENT: c_int = -6; + pub const VK_ERROR_EXTENSION_NOT_PRESENT: c_int = -7; + pub const VK_ERROR_FEATURE_NOT_PRESENT: c_int = -8; + pub const VK_ERROR_INCOMPATIBLE_DRIVER: c_int = -9; + pub const VK_ERROR_TOO_MANY_OBJECTS: c_int = -10; + pub const VK_ERROR_FORMAT_NOT_SUPPORTED: c_int = -11; + pub const VK_ERROR_FRAGMENTED_POOL: c_int = -12; + pub const VK_ERROR_UNKNOWN: c_int = -13; + pub const VK_ERROR_OUT_OF_POOL_MEMORY: c_int = -1000069000; + pub const VK_ERROR_INVALID_EXTERNAL_HANDLE: c_int = -1000072003; + pub const VK_ERROR_FRAGMENTATION: c_int = -1000161000; + pub const VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS: c_int = -1000257000; + pub const VK_PIPELINE_COMPILE_REQUIRED: c_int = 1000297000; + pub const VK_ERROR_NOT_PERMITTED: c_int = -1000174001; + pub const VK_ERROR_SURFACE_LOST_KHR: c_int = -1000000000; + pub const VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: c_int = -1000000001; + pub const VK_SUBOPTIMAL_KHR: c_int = 1000001003; + pub const VK_ERROR_OUT_OF_DATE_KHR: c_int = -1000001004; + pub const VK_ERROR_INCOMPATIBLE_DISPLAY_KHR: c_int = -1000003001; + pub const VK_ERROR_VALIDATION_FAILED_EXT: c_int = -1000011001; + pub const VK_ERROR_INVALID_SHADER_NV: c_int = -1000012000; + pub const VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR: c_int = -1000023000; + pub const VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR: c_int = -1000023001; + pub const VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR: c_int = -1000023002; + pub const VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR: c_int = -1000023003; + pub const VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR: c_int = -1000023004; + pub const VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR: c_int = -1000023005; + pub const VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT: c_int = -1000158000; + pub const VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT: c_int = -1000255000; + pub const VK_THREAD_IDLE_KHR: c_int = 1000268000; + pub const VK_THREAD_DONE_KHR: c_int = 1000268001; + pub const VK_OPERATION_DEFERRED_KHR: c_int = 1000268002; + pub const VK_OPERATION_NOT_DEFERRED_KHR: c_int = 1000268003; + pub const VK_ERROR_INVALID_VIDEO_STD_PARAMETERS_KHR: c_int = -1000299000; + pub const VK_ERROR_COMPRESSION_EXHAUSTED_EXT: c_int = -1000338000; + pub const VK_INCOMPATIBLE_SHADER_BINARY_EXT: c_int = 1000482000; + pub const VK_PIPELINE_BINARY_MISSING_KHR: c_int = 1000483000; + pub const VK_ERROR_NOT_ENOUGH_SPACE_KHR: c_int = -1000483000; + pub const VK_ERROR_OUT_OF_POOL_MEMORY_KHR: c_int = -1000069000; + pub const VK_ERROR_INVALID_EXTERNAL_HANDLE_KHR: c_int = -1000072003; + pub const VK_ERROR_FRAGMENTATION_EXT: c_int = -1000161000; + pub const VK_ERROR_NOT_PERMITTED_EXT: c_int = -1000174001; + pub const VK_ERROR_NOT_PERMITTED_KHR: c_int = -1000174001; + pub const VK_ERROR_INVALID_DEVICE_ADDRESS_EXT: c_int = -1000257000; + pub const VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS_KHR: c_int = -1000257000; + pub const VK_PIPELINE_COMPILE_REQUIRED_EXT: c_int = 1000297000; + pub const VK_ERROR_PIPELINE_COMPILE_REQUIRED_EXT: c_int = 1000297000; + pub const VK_ERROR_INCOMPATIBLE_SHADER_BINARY_EXT: c_int = 1000482000; + pub const VK_RESULT_MAX_ENUM: c_int = 2147483647; +}; + +fn checkResult(result: c.VkResult) void { + if(result != c.VK_SUCCESS) { + inline for(@typeInfo(Errors).@"struct".decls) |decl| { + if(result == @field(c, decl.name)) { + std.log.err("Encountered a vulkan error: {s}", .{decl.name}); + return; + } + } + std.log.err("Encountered a vulkan error with unknown error code {}", .{result}); + } +} + +fn fakeCheckResult(result: anytype) void { + if(@TypeOf(result) != void) { + checkResult(result); + } +} + +fn allocEnumeration(function: anytype, allocator: main.heap.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; + fakeCheckResult(@call(.auto, function, args ++ .{&count, null})); + const list = allocator.alloc(T, count); + fakeCheckResult(@call(.auto, function, args ++ .{&count, list.ptr})); + return list; +} + +pub const Instance = struct { // MARK: Instance + var instance: c.VkInstance = undefined; + + const validationLayers: []const [*:0]const u8 = &.{ + "VK_LAYER_KHRONOS_validation", + }; + + fn checkValidationLayerSupport() bool { + const availableLayers: []c.VkLayerProperties = allocEnumeration(c.vkEnumerateInstanceLayerProperties, main.stackAllocator, .{}); + defer main.stackAllocator.free(availableLayers); + for(validationLayers) |layerName| continueOuter: { + for(availableLayers) |layerProperties| { + if(std.mem.eql(u8, std.mem.span(layerName), std.mem.span(@as([*:0]const u8, @ptrCast(&layerProperties.layerName))))) { + break :continueOuter; + } + } + std.log.err("Couldn't find validation layer {s}", .{layerName}); + return false; + } + return true; + } + + pub fn init() void { + const appInfo = c.VkApplicationInfo { + .sType = c.VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = "Cubyz", + .applicationVersion = c.VK_MAKE_VERSION(0, 0, 0), + .pEngineName = "custom", + .engineVersion = c.VK_MAKE_VERSION(0, 0, 0), + .apiVersion = c.VK_API_VERSION_1_3, + }; + var glfwExtensionCount: u32 = 0; + const glfwExtensions: [*c][*c]const u8 = c.glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + const availableExtensions: []c.VkExtensionProperties = allocEnumeration(c.vkEnumerateInstanceExtensionProperties, main.stackAllocator, .{null}); + defer main.stackAllocator.free(availableExtensions); + std.log.debug("Availabe vulkan instance extensions:", .{}); + for(availableExtensions) |ext| { + std.log.debug("\t{s}", .{ext.extensionName}); + } + + const createInfo = c.VkInstanceCreateInfo { + .sType = c.VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &appInfo, + .enabledExtensionCount = glfwExtensionCount, + .ppEnabledExtensionNames = glfwExtensions, + .ppEnabledLayerNames = validationLayers.ptr, + .enabledLayerCount = if(checkValidationLayerSupport()) validationLayers.len else 0, + }; + // TODO: Use the debug callback when validation layers are enabled to write messages into the logger. + // https://docs.vulkan.org/tutorial/latest/03_Drawing_a_triangle/00_Setup/02_Validation_layers.html#_message_callback + checkResult(c.vkCreateInstance(&createInfo, null, &instance)); + } + + pub fn deinit() void { + c.vkDestroyInstance(instance, null); + } +};