Implement a basic swapchain

This commit is contained in:
Przemyslaw Gasinski 2024-06-26 16:25:41 +02:00
parent e03d4c59ac
commit b40005c7c7
2 changed files with 119 additions and 4 deletions

View file

@ -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,

View file

@ -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();