diff --git a/src/utilities.zig b/src/utilities.zig index dfdb98c..a8c9969 100644 --- a/src/utilities.zig +++ b/src/utilities.zig @@ -12,7 +12,7 @@ pub const QueueFamilyIndices = struct { } }; -pub const SwapChainDetails = struct { +pub const SwapchainDetails = struct { surface_capabilities: vk.SurfaceCapabilitiesKHR, formats: []vk.SurfaceFormatKHR, presentation_modes: []vk.PresentModeKHR, diff --git a/src/vulkan_renderer.zig b/src/vulkan_renderer.zig index 349485f..74c07ae 100644 --- a/src/vulkan_renderer.zig +++ b/src/vulkan_renderer.zig @@ -3,7 +3,7 @@ const sdl = @import("sdl2"); const vk = @import("vulkan"); const Utilities = @import("utilities.zig"); const QueueFamilyIndices = Utilities.QueueFamilyIndices; -const SwapChainDetails = Utilities.SwapChainDetails; +const SwapchainDetails = Utilities.SwapchainDetails; const enable_validation_layers = true; const validation_layers = [_][*:0]const u8{"VK_LAYER_KHRONOS_validation"}; @@ -44,6 +44,8 @@ pub const VulkanRenderer = struct { surface: vk.SurfaceKHR, + swapchain: vk.SwapchainKHR, + debug_utils: ?vk.DebugUtilsMessengerEXT, pub fn init(window: sdl.Window, allocator: std.mem.Allocator) !Self { @@ -79,6 +81,8 @@ pub const VulkanRenderer = struct { self.graphics_queue = Queue.init(queues[0], vkd); self.presentation_queue = Queue.init(queues[1], vkd); + self.swapchain = try self.createSwapChain(); + return self; } @@ -157,6 +161,63 @@ pub const VulkanRenderer = struct { return try self.instance.createDevice(self.physical_device, &device_create_info, null); } + fn createSwapChain(self: *Self) !vk.SwapchainKHR { + const swapchain_details = try self.getSwapchainDetails(self.physical_device); + defer self.allocator.free(swapchain_details.formats); + defer self.allocator.free(swapchain_details.presentation_modes); + + // 1. Choose best surface format + const surface_format = chooseBestSurfaceFormat(swapchain_details.formats); + // 2. Choose best presentation mode + const present_mode = chooseBestPresentationMode(swapchain_details.presentation_modes); + // 3. Choose swapchain image resolution + const extent = chooseSwapExtent(&self.window, swapchain_details.surface_capabilities); + + // How many images are in the swapchain? Get 1 more than the minimum to allow triple buffering + var image_count: u32 = swapchain_details.surface_capabilities.min_image_count + 1; + const max_image_count = swapchain_details.surface_capabilities.max_image_count; + + // Clamp down if higher + // If 0, it means it's limitless + if (max_image_count != 0 and image_count > max_image_count) { + image_count = max_image_count; + } + + var swapchain_create_info: vk.SwapchainCreateInfoKHR = .{ + .image_format = surface_format.format, + .image_color_space = surface_format.color_space, + .present_mode = present_mode, + .image_extent = extent, + .min_image_count = image_count, + .image_array_layers = 1, // Number of layers for each image + .image_usage = .{ .color_attachment_bit = true }, // What attachment will images be used as + .pre_transform = swapchain_details.surface_capabilities.current_transform, // Transform to perform on swapchain images + .composite_alpha = .{ .opaque_bit_khr = true }, // How to handle blending images with external graphics (e.g.: other windows) + .clipped = vk.TRUE, // Whether to clip parts of images not in view (e.g.: behind another window, off-screen, etc...) + .old_swapchain = .null_handle, // Links old one to quickly share responsibilities in case it's been destroyed and replaced + .surface = self.surface, + .image_sharing_mode = .exclusive, + }; + + // Get queue family indices + const family_indices = try self.getQueueFamilies(self.physical_device); + + // If graphic and presentation families are different, then swapchain must let images be shared between families + + if (family_indices.graphics_family.? != family_indices.presentation_family.?) { + const qfi = [_]u32{ + family_indices.graphics_family.?, + family_indices.presentation_family.?, + }; + + swapchain_create_info.image_sharing_mode = .concurrent; + swapchain_create_info.queue_family_index_count = 2; // Number of queues to share images between + swapchain_create_info.p_queue_family_indices = &qfi; + } + + return try self.device.createSwapchainKHR(&swapchain_create_info, null); + } + fn getPhysicalDevice(self: Self) !vk.PhysicalDevice { var pdev_count: u32 = 0; _ = try self.instance.enumeratePhysicalDevices(&pdev_count, null); @@ -289,7 +350,7 @@ pub const VulkanRenderer = struct { const extension_support = self.checkDeviceExtensions(pdev) catch return false; - const swapchain_details = self.getSwapChainDetails(pdev) catch return false; + const swapchain_details = self.getSwapchainDetails(pdev) catch return false; defer self.allocator.free(swapchain_details.formats); defer self.allocator.free(swapchain_details.presentation_modes); @@ -322,7 +383,7 @@ pub const VulkanRenderer = struct { return false; } - fn getSwapChainDetails(self: Self, pdev: vk.PhysicalDevice) !SwapChainDetails { + fn getSwapchainDetails(self: Self, pdev: vk.PhysicalDevice) !SwapchainDetails { // Capabilities const surface_capabilities = try self.instance.getPhysicalDeviceSurfaceCapabilitiesKHR(pdev, self.surface); @@ -351,6 +412,7 @@ pub const VulkanRenderer = struct { if (enable_validation_layers) { self.instance.destroyDebugUtilsMessengerEXT(self.debug_utils.?, null); } + self.device.destroySwapchainKHR(self.swapchain, null); self.device.destroyDevice(null); self.instance.destroySurfaceKHR(self.surface, null); self.instance.destroyInstance(null); @@ -360,6 +422,59 @@ pub const VulkanRenderer = struct { } }; +// Format: VK_FORMAT_R8G8B8A8_UNORM (VK_FORMAT_B8G8R8A8_UNORM as backup) +// Color space: VK_COLOR_SPACE_SRGB_NONLINEAR_KHR +fn chooseBestSurfaceFormat(formats: []vk.SurfaceFormatKHR) vk.SurfaceFormatKHR { + // If only one format available and is undefined, then this means all formats are available + if (formats.len == 1 and formats[0].format == vk.Format.undefined) { + return .{ + .format = vk.Format.r8g8b8a8_unorm, + .color_space = vk.ColorSpaceKHR.srgb_nonlinear_khr, + }; + } + + for (formats) |format| { + if ((format.format == vk.Format.r8g8b8a8_unorm or format.format == vk.Format.b8g8r8a8_unorm) and format.color_space == vk.ColorSpaceKHR.srgb_nonlinear_khr) { + return format; + } + } + + return formats[0]; +} + +fn chooseBestPresentationMode(presentation_modes: []vk.PresentModeKHR) vk.PresentModeKHR { + for (presentation_modes) |presentation_mode| { + if (presentation_mode == vk.PresentModeKHR.mailbox_khr) { + return presentation_mode; + } + } + + // Use FIFO as Vulkan spec says it must be present + return vk.PresentModeKHR.fifo_khr; +} + +fn chooseSwapExtent(window: *sdl.Window, surface_capabilities: vk.SurfaceCapabilitiesKHR) vk.Extent2D { + // If the current extent is at max value, the extent can vary. Otherwise it's the size of the window + if (surface_capabilities.current_extent.width != std.math.maxInt(u32)) { + return surface_capabilities.current_extent; + } + + // If value can very, need to set the extent manually + const framebuffer_size = sdl.vulkan.getDrawableSize(window); + + var extent: vk.Extent2D = .{ + .width = @intCast(framebuffer_size.width), + .height = @intCast(framebuffer_size.height), + }; + + // Surface also defines max and min, so make sure it's within boundaries by clamping values + extent.width = @max(surface_capabilities.min_image_extent.width, @min(surface_capabilities.max_image_extent.width, extent.width)); + extent.height = @max(surface_capabilities.min_image_extent.height, @min(surface_capabilities.max_image_extent.height, extent.height)); + + return extent; +} + +// Validation layers stuff fn createDebugMessenger(instance: Instance) !vk.DebugUtilsMessengerEXT { const debug_create_info = getDebugUtilsCreateInfo();