diff --git a/src/nu/Render.zig b/src/nu/Render.zig index fa48ff7..0c54891 100644 --- a/src/nu/Render.zig +++ b/src/nu/Render.zig @@ -1,23 +1,383 @@ +//! Isolate vulkan code (except for ImGui) through this module. +//! +//! Requires that Window module already be initialized. + const std = @import("std"); +const builtin = @import("builtin"); +const vk = @import("vk"); const Window = @import("Window.zig"); -// isolate all the vulkan code through this path -// except for imgui code +pub const RenderOptions = struct { + app_name: []const u8, + engine_name: []const u8 = "nu-au", +}; -// const au = @import("au.zig"); +pub const use_debug_messenger = switch (builtin.mode) { + .Debug, .ReleaseSafe => true, + .ReleaseSmall, .ReleaseFast => false, +}; -pub fn init(alloc: std.mem.Allocator) !void { - _ = alloc; +pub const apis: []const vk.ApiInfo = &.{ + vk.features.version_1_0, + vk.features.version_1_1, + vk.features.version_1_2, + vk.features.version_1_3, + vk.extensions.khr_surface, + vk.extensions.khr_swapchain, + vk.extensions.khr_dynamic_rendering, + if (use_debug_messenger) vk.extensions.ext_debug_utils else .{}, +}; - std.debug.print("Init Render\n", .{}); +pub const device_extensions: []const [*:0]const u8 = &.{ + // todo somehow sync this with APIs above? + vk.extensions.khr_swapchain.name, + vk.extensions.khr_dynamic_rendering.name, +}; - // todo check vulkan supported - // todo create window surface +// // todo check vulkan supported +// pub fn frame() void { +// std.debug.print("frame\n", .{}); +// } + +pub const BaseWrapper = vk.BaseWrapper(apis); +pub const InstanceWrapper = vk.InstanceWrapper(apis); +pub const DeviceWrapper = vk.DeviceWrapper(apis); + +pub const InstanceProxy = vk.InstanceProxy(apis); +pub const DeviceProxy = vk.DeviceProxy(apis); +pub const QueueProxy = vk.QueueProxy(apis); +pub const CommandBufferProxy = vk.CommandBufferProxy(apis); + +pub const B: *const BaseWrapper = &_bw; +pub const I: *const InstanceProxy = &_ip; +pub const D: *const DeviceProxy = &_dp; +pub const Q: *const QueueProxy = &_qp; + +pub const device_config: *const CandidateDeviceInfo = &_dconfig; + +var _bw: BaseWrapper = undefined; +var _iw: InstanceWrapper = undefined; +var _dw: DeviceWrapper = undefined; + +var _ip: InstanceProxy = undefined; +var _dp: DeviceProxy = undefined; +var _qp: QueueProxy = undefined; + +var _instance: vk.Instance = undefined; +var _device: vk.Device = undefined; +var _dconfig: CandidateDeviceInfo = undefined; +var _queue: vk.Queue = undefined; +var _surface: vk.SurfaceKHR = undefined; + +pub fn init( + alloc: std.mem.Allocator, +) !void { + try init_base(); + errdefer deinit_base(); + + try init_instance(alloc); + errdefer deinit_instance(); + + try init_device(alloc); + errdefer deinit_device(); } -pub fn frame() void { - std.debug.print("frame\n", .{ }); +pub fn deinit() void { + deinit_device(); + deinit_instance(); + deinit_base(); } -pub fn deinit() void {} +fn init_base() !void { + if (use_debug_messenger) { + _bw = try BaseWrapper.load(glfwGetInstanceProcAddress); + } else { + _bw = BaseWrapper.loadNoFail(glfwGetInstanceProcAddress); + } +} + +fn deinit_base() void {} + +fn init_instance(alloc: std.mem.Allocator) !void { + var extensions = std.ArrayList([*:0]const u8).init(alloc); + defer extensions.deinit(); + + var layers = std.ArrayList([*:0]const u8).init(alloc); + defer layers.deinit(); + + if (use_debug_messenger) { + try extensions.appendSlice(&.{ + vk.extensions.ext_debug_utils.name, + }); + + try layers.appendSlice(&.{ + "VK_LAYER_KHRONOS_validation", + }); + } + + var glfw_exts_count: u32 = 0; + const glfw_exts: [*]const [*:0]const u8 = + @ptrCast(glfwGetRequiredInstanceExtensions(&glfw_exts_count)); + try extensions.appendSlice(glfw_exts[0..glfw_exts_count]); + + const mci: vk.DebugUtilsMessengerCreateInfoEXT = .{ + .message_severity = .{ + .error_bit_ext = true, + .info_bit_ext = true, + .verbose_bit_ext = true, + .warning_bit_ext = true, + }, + .message_type = .{ + .device_address_binding_bit_ext = true, + .general_bit_ext = false, + .performance_bit_ext = true, + .validation_bit_ext = true, + }, + .pfn_user_callback = &debug_callback, + .p_user_data = null, + }; + + _instance = try B.createInstance(&.{ + .p_application_info = &.{ + .p_application_name = "zig-glfw-vulkan", // todo RenderOptions + .application_version = vk.makeApiVersion(0, 0, 0, 0), + .p_engine_name = "nu-au", // todo RenderOptions + .engine_version = vk.makeApiVersion(0, 0, 0, 0), + .api_version = vk.API_VERSION_1_3, + }, + .enabled_extension_count = @intCast(extensions.items.len), + .pp_enabled_extension_names = extensions.items.ptr, + .enabled_layer_count = @intCast(layers.items.len), + .pp_enabled_layer_names = layers.items.ptr, + .p_next = if (use_debug_messenger) &mci else null, + }, null); + + if (use_debug_messenger) { + _iw = try InstanceWrapper.load(_instance, _bw.dispatch.vkGetInstanceProcAddr); + } else { + _iw = InstanceWrapper.loadNoFail(_instance, _bw.dispatch.vkGetInstanceProcAddr); + } + + _ip = InstanceProxy.init(_instance, &_iw); + + if (glfwCreateWindowSurface(_instance, Window.handle, null, &_surface) != .success) { + return error.glfwCreateWindowSurfaceFailed; + } +} + +fn deinit_instance() void { + _ip.destroySurfaceKHR(_surface, null); + _ip.destroyInstance(null); +} + +const CandidateDeviceInfo = struct { + pdev: vk.PhysicalDevice, + format: vk.SurfaceFormatKHR, + mode: vk.PresentModeKHR, + family: u32, // must support graphics and present for now + + fn init(alloc: std.mem.Allocator, pdev: vk.PhysicalDevice) !struct { i32, CandidateDeviceInfo } { + var score: i32 = 0; + var res: CandidateDeviceInfo = undefined; + + res.pdev = pdev; + + const props = I.getPhysicalDeviceProperties(pdev); + score += switch (props.device_type) { + vk.PhysicalDeviceType.discrete_gpu => 1000, + vk.PhysicalDeviceType.integrated_gpu => 500, + else => 0, + }; + + var format_count: u32 = undefined; + _ = try I.getPhysicalDeviceSurfaceFormatsKHR(pdev, _surface, &format_count, null); + if (format_count == 0) return error.NoSurfaceFormats; + const formats = try alloc.alloc(vk.SurfaceFormatKHR, format_count); + defer alloc.free(formats); + _ = try I.getPhysicalDeviceSurfaceFormatsKHR(pdev, _surface, &format_count, formats.ptr); + + for (formats) |fmt| { + if (fmt.color_space == .srgb_nonlinear_khr) { + res.format = fmt; + break; + } + } else { + res.format = formats[0]; + score -= 100; + } + + var mode_count: u32 = undefined; + _ = try I.getPhysicalDeviceSurfacePresentModesKHR(pdev, _surface, &mode_count, null); + if (mode_count == 0) return error.NoSurfacePresentModes; + const modes = try alloc.alloc(vk.PresentModeKHR, mode_count); + defer alloc.free(modes); + _ = try I.getPhysicalDeviceSurfacePresentModesKHR(pdev, _surface, &mode_count, modes.ptr); + + if (std.mem.indexOfAny(vk.PresentModeKHR, modes, &.{ + vk.PresentModeKHR.mailbox_khr, + vk.PresentModeKHR.immediate_khr, + })) |idx| { + res.mode = modes[idx]; + } else { + score -= 50; + res.mode = .fifo_khr; // this is guaranteed + } + + var ext_count: u32 = undefined; + _ = try I.enumerateDeviceExtensionProperties(pdev, null, &ext_count, null); + const exts = try alloc.alloc(vk.ExtensionProperties, ext_count); + defer alloc.free(exts); + _ = try I.enumerateDeviceExtensionProperties(pdev, null, &ext_count, exts.ptr); + + for (device_extensions) |needle| { + for (exts) |ext| { + if (std.mem.eql( + u8, + std.mem.span(needle), + std.mem.sliceTo(&ext.extension_name, 0), + )) + break; + } else { + return error.MissingDeviceExtension; + } + } + + var family_count: u32 = undefined; + I.getPhysicalDeviceQueueFamilyProperties(pdev, &family_count, null); + const families = try alloc.alloc(vk.QueueFamilyProperties, family_count); + defer alloc.free(families); + I.getPhysicalDeviceQueueFamilyProperties(pdev, &family_count, families.ptr); + + for (families, 0..) |prop, idx| { + const graphics_support = prop.queue_flags.graphics_bit; + const present_support = try I.getPhysicalDeviceSurfaceSupportKHR(pdev, @intCast(idx), _surface) == vk.TRUE; + + if (graphics_support and present_support) { + res.family = @intCast(idx); + break; + } + } else { + return error.NoSuitableFamily; + } + + return .{ score, res }; + } +}; + +fn init_device(alloc: std.mem.Allocator) !void { + var pdev_count: u32 = undefined; + _ = try I.enumeratePhysicalDevices(&pdev_count, null); + if (pdev_count == 0) return error.NoDevice; + const pdevs = try alloc.alloc(vk.PhysicalDevice, pdev_count); + defer alloc.free(pdevs); + _ = try I.enumeratePhysicalDevices(&pdev_count, pdevs.ptr); + + // const scores = std.ArrayList(i32). + var scores: std.MultiArrayList(struct { score: i32, ci: CandidateDeviceInfo }) = .{}; + defer scores.deinit(alloc); + + for (pdevs) |pdev| { + const score, const ci = CandidateDeviceInfo.init(alloc, pdev) catch continue; + try scores.append(alloc, .{ .score = score, .ci = ci }); + } + + const idx = std.sort.argMax(i32, scores.items(.score), {}, std.sort.asc(i32)) orelse + return error.NoSuitableDevice; + _dconfig = scores.get(idx).ci; + + const qci: []const vk.DeviceQueueCreateInfo = &.{ + vk.DeviceQueueCreateInfo{ + .queue_family_index = _dconfig.family, + .queue_count = 1, + .p_queue_priorities = &[_]f32{1.0}, + }, + }; + + _device = try I.createDevice(_dconfig.pdev, &.{ + .queue_create_info_count = @intCast(qci.len), + .p_queue_create_infos = qci.ptr, + .enabled_extension_count = @intCast(device_extensions.len), + .pp_enabled_extension_names = device_extensions.ptr, + .p_next = &vk.PhysicalDeviceDynamicRenderingFeaturesKHR{ + .dynamic_rendering = vk.TRUE, + }, + }, null); + + if (use_debug_messenger) { + _dw = try DeviceWrapper.load(_device, _iw.dispatch.vkGetDeviceProcAddr); + } else { + _dw = DeviceWrapper.loadNoFail(_device, _iw.dispatch.vkGetDeviceProcAddr); + } + _dp = DeviceProxy.init(_device, &_dw); + errdefer D.destroyDevice(null); + + _queue = D.getDeviceQueue(_dconfig.family, 0); + + _qp = QueueProxy.init(_queue, &_dw); + + // todo i'm thinking this needs to be a more complex pointer structure... i'm making assumptions here about how the + // command pools are meant to work. probably I am cooking too much. +} + +fn deinit_device() void { + D.destroyDevice(null); +} + +pub fn debug_callback( + msg_severity: vk.DebugUtilsMessageSeverityFlagsEXT, + msg_type: vk.DebugUtilsMessageTypeFlagsEXT, + p_data: ?*const vk.DebugUtilsMessengerCallbackDataEXT, + _: ?*anyopaque, +) callconv(vk.vulkan_call_conv) vk.Bool32 { + // ripped from std.log.defaultLog + + const data = p_data orelse return vk.FALSE; + const message = data.p_message orelse return vk.FALSE; + + const severity_prefix = if (msg_severity.verbose_bit_ext) + "verbose:" + else if (msg_severity.info_bit_ext) + "info:" + else if (msg_severity.warning_bit_ext) + "warning:" + else if (msg_severity.error_bit_ext) + "error:" + else + "?:"; + + const type_prefix = if (msg_type.general_bit_ext) + "" + else if (msg_type.validation_bit_ext) + "validation:" + else if (msg_type.performance_bit_ext) + "performance:" + else if (msg_type.device_address_binding_bit_ext) + "device_address_binding:" + else + "?:"; + + const stderr = std.io.getStdErr().writer(); + var bw = std.io.bufferedWriter(stderr); + const writer = bw.writer(); + + std.debug.lockStdErr(); + defer std.debug.unlockStdErr(); + nosuspend { + writer.print("vk-{s}{s} {s}\n", .{ severity_prefix, type_prefix, message }) catch return vk.FALSE; + bw.flush() catch return vk.FALSE; + } + + return vk.FALSE; +} + +extern fn glfwGetInstanceProcAddress(instance: vk.Instance, procname: [*:0]const u8) vk.PfnVoidFunction; + +extern fn glfwGetRequiredInstanceExtensions(count: *u32) [*]const [*:0]const u8; + +extern fn glfwCreateWindowSurface( + instance: vk.Instance, + window: *Window.c.GLFWwindow, + allocation_callbacks: ?*const vk.AllocationCallbacks, + surface: *vk.SurfaceKHR, +) vk.Result; diff --git a/src/nu/Window.zig b/src/nu/Window.zig index dd03900..1701c83 100644 --- a/src/nu/Window.zig +++ b/src/nu/Window.zig @@ -20,7 +20,7 @@ pub const Options = struct { }; var bus: Bus = undefined; -var handle: *c.GLFWwindow = undefined; +pub var handle: *c.GLFWwindow = undefined; var unfocused_rate: f32 = 1.0 / 20.0; pub fn init(alloc: std.mem.Allocator, options: Options) !void {